diff --git a/src/main/java/me/braydon/mc/common/PlayerUtils.java b/src/main/java/me/braydon/mc/common/PlayerUtils.java new file mode 100644 index 0000000..98ed819 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/PlayerUtils.java @@ -0,0 +1,67 @@ +package me.braydon.mc.common; + +import lombok.NonNull; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import me.braydon.mc.model.Skin; + +import javax.imageio.ImageIO; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.net.URL; + +/** + * @author Braydon + */ +@UtilityClass +public final class PlayerUtils { + private static final int SKIN_TEXTURE_SIZE = 64; // The skin of a skin texture + + /** + * Get the head texture of a skin. + * + * @param skin the skin to get the head texture from + * @param size the size to scale the head texture to + * @return the head texture of the skin + */ + @SneakyThrows + public static byte[] getHeadTexture(@NonNull Skin skin, int size) { + return getSkinPartTexture(skin, 8, 8, SKIN_TEXTURE_SIZE / 8, SKIN_TEXTURE_SIZE / 8, size); + } + + /** + * Get the texture of a specific part of a skin. + * + * @param skin the skin to get the part from + * @param x the x position of the part + * @param y the y position of the part + * @param width the width of the part + * @param height the height of the part + * @param size the size to scale the part to + * @return the texture of the skin part + */ + @SneakyThrows + public static byte[] getSkinPartTexture(@NonNull Skin skin, int x, int y, int width, int height, int size) { + BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture + + // Create a new BufferedImage for the part of the skin texture + BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + // Crop just the part we want based on our x, y, width, and height + headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null); + + // Scale the skin part texture + double scale = (double) size / width; + AffineTransform transform = AffineTransform.getScaleInstance(scale, scale); + headTexture = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(headTexture, null); + + // Convert BufferedImage to byte array + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ImageIO.write(headTexture, "png", outputStream); + outputStream.flush(); + return outputStream.toByteArray(); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/controller/PlayerController.java b/src/main/java/me/braydon/mc/controller/PlayerController.java index 1d44849..e24dd08 100644 --- a/src/main/java/me/braydon/mc/controller/PlayerController.java +++ b/src/main/java/me/braydon/mc/controller/PlayerController.java @@ -10,7 +10,6 @@ import me.braydon.mc.model.Skin; import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.service.MojangService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ByteArrayResource; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -25,6 +24,9 @@ import org.springframework.web.bind.annotation.*; @RequestMapping(value = "/player", produces = MediaType.APPLICATION_JSON_VALUE) @Log4j2(topic = "Player Controller") public final class PlayerController { + private static final int DEFAULT_HEAD_SIZE = 128; + private static final int MAX_HEAD_SIZE = 512; + /** * The Mojang service to use for player information. */ @@ -60,15 +62,23 @@ public final class PlayerController { * * @param query the query to search for the player by * @param extension the head image extension + * @param size the size of the head image * @return the head texture * @throws BadRequestException if the extension is invalid */ @GetMapping("/head/{query}.{extension}") @ResponseBody - public ResponseEntity getHead(@PathVariable @NonNull String query, @PathVariable @NonNull String extension) throws BadRequestException { + public ResponseEntity getHead(@PathVariable @NonNull String query, @PathVariable @NonNull String extension, + @RequestParam(required = false) Integer size + ) throws BadRequestException { if ((extension = extension.trim()).isBlank()) { // Invalid extension throw new BadRequestException("Invalid extension"); } + if (size == null || size <= 0) { // Invalid size + size = DEFAULT_HEAD_SIZE; + } + size = Math.min(size, MAX_HEAD_SIZE); // Limit the size to 512 + Skin target = null; // The target skin to get the head of try { CachedPlayer player = mojangService.getPlayer(query, false); // Retrieve the player @@ -82,6 +92,6 @@ public final class PlayerController { // Return the head texture return ResponseEntity.ok() .contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG) - .body(new ByteArrayResource(PlayerUtils.getSkinPartTexture(target))); + .body(PlayerUtils.getHeadTexture(target, size)); } } \ No newline at end of file