From 655ee50a21fdcec233b844d830c6420b718cd4fd Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Thu, 11 Apr 2024 08:56:42 -0400 Subject: [PATCH] Cache skin image data --- .../java/me/braydon/mc/common/ImageUtils.java | 18 +++++++++ .../mc/common/renderer/SkinRenderer.java | 6 +-- .../java/me/braydon/mc/model/skin/Skin.java | 38 +++++++++++++++++-- .../me/braydon/mc/service/MojangService.java | 16 ++------ 4 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/main/java/me/braydon/mc/common/ImageUtils.java b/src/main/java/me/braydon/mc/common/ImageUtils.java index ba541a7..a2919bb 100644 --- a/src/main/java/me/braydon/mc/common/ImageUtils.java +++ b/src/main/java/me/braydon/mc/common/ImageUtils.java @@ -24,11 +24,14 @@ package me.braydon.mc.common; import lombok.NonNull; +import lombok.SneakyThrows; import lombok.experimental.UtilityClass; +import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; /** * @author Braydon @@ -65,4 +68,19 @@ public final class ImageUtils { graphics.dispose(); return flipped; } + + /** + * Get the byte array from the given image. + * + * @param image the image to extract from + * @return the byte array of the image + */ + @SneakyThrows + public static byte[] toByteArray(@NonNull BufferedImage image) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ImageIO.write(image, "png", outputStream); + outputStream.flush(); + return outputStream.toByteArray(); + } + } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java b/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java index f209d6c..e8ba2fb 100644 --- a/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java +++ b/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java @@ -32,7 +32,7 @@ import me.braydon.mc.model.skin.Skin; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.net.URL; +import java.io.ByteArrayInputStream; /** * A renderer for a {@link ISkinPart}. @@ -63,17 +63,17 @@ public abstract class SkinRenderer { */ @SneakyThrows protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) { - BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture ISkinPart.Vanilla.Coordinates coordinates = part.getCoordinates(); // The coordinates of the part // The skin texture is legacy, use legacy coordinates - if (skinImage.getHeight() == 32 && part.hasLegacyCoordinates()) { + if (skin.isLegacy() && part.hasLegacyCoordinates()) { coordinates = part.getLegacyCoordinates(); } int width = part.getWidth(); // The width of the part if (skin.getModel() == Skin.Model.SLIM && part.isFrontArm()) { width--; } + BufferedImage skinImage = ImageIO.read(new ByteArrayInputStream(skin.getSkinImage())); // The skin texture BufferedImage partTexture = getSkinPartTexture(skinImage, coordinates.getX(), coordinates.getY(), width, part.getHeight(), size); if (coordinates instanceof ISkinPart.Vanilla.LegacyCoordinates legacyCoordinates && legacyCoordinates.isFlipped()) { partTexture = ImageUtils.flip(partTexture); diff --git a/src/main/java/me/braydon/mc/model/skin/Skin.java b/src/main/java/me/braydon/mc/model/skin/Skin.java index 39c9320..6281ce8 100644 --- a/src/main/java/me/braydon/mc/model/skin/Skin.java +++ b/src/main/java/me/braydon/mc/model/skin/Skin.java @@ -23,12 +23,17 @@ */ package me.braydon.mc.model.skin; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.JsonObject; import lombok.*; +import me.braydon.mc.common.ImageUtils; import me.braydon.mc.config.AppConfig; import me.braydon.mc.model.Player; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.net.URL; import java.util.HashMap; import java.util.Map; @@ -37,9 +42,9 @@ import java.util.Map; * * @author Braydon */ -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Setter @Getter @ToString +@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString public final class Skin { - public static final Skin DEFAULT_STEVE = new Skin("http://textures.minecraft.net/texture/60a5bd016b3c9a1b9272e4929e30827a67be4ebb219017adbbc4a4d22ebd5b1", Model.DEFAULT); + public static final Skin DEFAULT_STEVE = create("http://textures.minecraft.net/texture/60a5bd016b3c9a1b9272e4929e30827a67be4ebb219017adbbc4a4d22ebd5b1", Model.DEFAULT); /** * The texture URL of this skin. @@ -51,10 +56,20 @@ public final class Skin { */ @NonNull private final Model model; + /** + * The image data of this skin. + */ + @JsonIgnore private final byte[] skinImage; + + /** + * Is this skin legacy? + */ + private final boolean legacy; + /** * URLs to the parts of this skin. */ - @NonNull @JsonProperty("parts") private Map partUrls = new HashMap<>(); + @NonNull @JsonProperty("parts") private final Map partUrls; /** * Populate the part URLs for this skin. @@ -88,7 +103,22 @@ public final class Skin { if (metadataJsonObject != null) { // Parse the skin model model = Model.valueOf(metadataJsonObject.get("model").getAsString().toUpperCase()); } - return new Skin(jsonObject.get("url").getAsString(), model); + return create(jsonObject.get("url").getAsString(), model); + } + + /** + * Create a skin from the given URL and model. + * + * @param url the skin url + * @param model the skin model + * @return the constructed skin + */ + @NonNull @SneakyThrows + private static Skin create(@NonNull String url, @NonNull Model model) { + BufferedImage image = ImageIO.read(new URL(url)); // Get the skin image + byte[] bytes = ImageUtils.toByteArray(image); // Convert the image into bytes + boolean legacy = image.getWidth() == 64 && image.getHeight() == 32; // Is the skin legacy? + return new Skin(url, model, bytes, legacy, new HashMap<>()); } /** diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index e975838..85ba393 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -61,9 +61,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.stereotype.Service; -import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.InetSocketAddress; import java.net.URL; @@ -231,16 +229,10 @@ public final class MojangService { BufferedImage texture = part.render(skin, overlays, size); // Render the skin part log.info("Render of skin part took {}ms: {}", System.currentTimeMillis() - before, id); - // Convert BufferedImage to byte array - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - ImageIO.write(texture, "png", outputStream); - outputStream.flush(); - - byte[] bytes = outputStream.toByteArray(); - skinPartTextureCache.save(new CachedSkinPartTexture(id, bytes)); // Cache the texture - log.info("Cached skin part texture: {}", id); - return bytes; - } + byte[] bytes = ImageUtils.toByteArray(texture); // Convert the image into a byte array + skinPartTextureCache.save(new CachedSkinPartTexture(id, bytes)); // Cache the texture + log.info("Cached skin part texture: {}", id); + return bytes; } /**