diff --git a/src/main/java/me/braydon/mc/common/renderer/SkinPartRenderer.java b/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java similarity index 75% rename from src/main/java/me/braydon/mc/common/renderer/SkinPartRenderer.java rename to src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java index d0eea81..4493671 100644 --- a/src/main/java/me/braydon/mc/common/renderer/SkinPartRenderer.java +++ b/src/main/java/me/braydon/mc/common/renderer/SkinRenderer.java @@ -26,19 +26,21 @@ package me.braydon.mc.common.renderer; import lombok.NonNull; import lombok.SneakyThrows; import me.braydon.mc.common.ImageUtils; -import me.braydon.mc.model.Skin; +import me.braydon.mc.model.skin.ISkinPart; +import me.braydon.mc.model.skin.Skin; import javax.imageio.ImageIO; +import java.awt.*; import java.awt.image.BufferedImage; import java.net.URL; /** - * A renderer for a {@link Skin.Part}. + * A renderer for a {@link ISkinPart}. * * @author Braydon * @param the type of part to render */ -public abstract class SkinPartRenderer { +public abstract class SkinRenderer { /** * Invoke this render to render the * given skin part for the provided skin. @@ -60,7 +62,7 @@ public abstract class SkinPartRenderer { * @return the texture of the skin part */ @SneakyThrows - protected BufferedImage getSkinPart(@NonNull Skin skin, @NonNull Skin.Part part, double size) { + protected final BufferedImage getVanillaSkinPart(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, double size) { return getSkinPartTexture(skin, part.getCoordinates().getX(), part.getCoordinates().getY(), part.getWidth(), part.getHeight(), size); } @@ -90,4 +92,29 @@ public abstract class SkinPartRenderer { return headTexture; } + + /** + * Apply an overlay to a texture. + * + * @param overlayImage the part to overlay + */ + protected final void applyOverlay(@NonNull BufferedImage overlayImage) { + applyOverlay(overlayImage.createGraphics(), overlayImage); + } + + /** + * Apply an overlay to a texture. + * + * @param graphics the graphics to overlay on + * @param overlayImage the part to overlay + */ + protected final void applyOverlay(@NonNull Graphics2D graphics, @NonNull BufferedImage overlayImage) { + try { + graphics.drawImage(overlayImage, 0, 0, null); + graphics.dispose(); + } catch (Exception ignored) { + // We can safely ignore this, legacy + // skins don't have overlays + } + } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/renderer/impl/BasicSkinPartRenderer.java b/src/main/java/me/braydon/mc/common/renderer/impl/BodySkinPartRenderer.java similarity index 75% rename from src/main/java/me/braydon/mc/common/renderer/impl/BasicSkinPartRenderer.java rename to src/main/java/me/braydon/mc/common/renderer/impl/BodySkinPartRenderer.java index bc38e09..ec3bfb3 100644 --- a/src/main/java/me/braydon/mc/common/renderer/impl/BasicSkinPartRenderer.java +++ b/src/main/java/me/braydon/mc/common/renderer/impl/BodySkinPartRenderer.java @@ -24,18 +24,19 @@ package me.braydon.mc.common.renderer.impl; import lombok.NonNull; -import me.braydon.mc.common.renderer.SkinPartRenderer; -import me.braydon.mc.model.Skin; +import me.braydon.mc.common.renderer.SkinRenderer; +import me.braydon.mc.model.skin.ISkinPart; +import me.braydon.mc.model.skin.Skin; import java.awt.image.BufferedImage; /** - * A basic 2D renderer for a {@link Skin.Part}. + * A basic 2D renderer for a {@link ISkinPart.Basic#BODY}. * * @author Braydon */ -public final class BasicSkinPartRenderer extends SkinPartRenderer { - public static final BasicSkinPartRenderer INSTANCE = new BasicSkinPartRenderer(); +public final class BodySkinPartRenderer extends SkinRenderer { + public static final BodySkinPartRenderer INSTANCE = new BodySkinPartRenderer(); /** * Invoke this render to render the @@ -48,7 +49,7 @@ public final class BasicSkinPartRenderer extends SkinPartRenderer { * @return the rendered skin part */ @Override @NonNull - public BufferedImage render(@NonNull Skin skin, @NonNull Skin.Part part, boolean overlays, int size) { - return getSkinPart(skin, part, size / 8D); + public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Basic part, boolean overlays, int size) { + return getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, size); } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/renderer/impl/IsometricSkinPartRenderer.java b/src/main/java/me/braydon/mc/common/renderer/impl/IsometricSkinPartRenderer.java index eb206a1..60963d9 100644 --- a/src/main/java/me/braydon/mc/common/renderer/impl/IsometricSkinPartRenderer.java +++ b/src/main/java/me/braydon/mc/common/renderer/impl/IsometricSkinPartRenderer.java @@ -24,19 +24,20 @@ package me.braydon.mc.common.renderer.impl; import lombok.NonNull; -import me.braydon.mc.common.renderer.SkinPartRenderer; -import me.braydon.mc.model.Skin; +import me.braydon.mc.common.renderer.SkinRenderer; +import me.braydon.mc.model.skin.ISkinPart; +import me.braydon.mc.model.skin.Skin; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; /** - * A isometric 3D renderer for a {@link Skin.Part}. + * A isometric 3D renderer for a {@link ISkinPart.Isometric}. * * @author Braydon */ -public final class IsometricSkinPartRenderer extends SkinPartRenderer { +public final class IsometricSkinPartRenderer extends SkinRenderer { public static final IsometricSkinPartRenderer INSTANCE = new IsometricSkinPartRenderer(); private static final double SKEW_A = 26D / 45D; // 0.57777777 @@ -57,7 +58,7 @@ public final class IsometricSkinPartRenderer extends SkinPartRenderer { + public static final VanillaSkinPartRenderer INSTANCE = new VanillaSkinPartRenderer(); + + /** + * Invoke this render to render the + * given skin part for the provided skin. + * + * @param skin the skin to render the part for + * @param part the part to render + * @param overlays whether to render overlays + * @param size the size to scale the skin part to + * @return the rendered skin part + */ + @Override @NonNull + public BufferedImage render(@NonNull Skin skin, @NonNull ISkinPart.Vanilla part, boolean overlays, int size) { + double scale = size / 8D; + BufferedImage partImage = getVanillaSkinPart(skin, part, scale); // Get the part image + if (!overlays) { // Not rendering overlays + return partImage; + } + // Create a new image, draw our skin part texture, and then apply overlays + BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = texture.createGraphics(); + graphics.drawImage(partImage, 0, 0, null); + + // Draw part overlays + ISkinPart.Vanilla[] overlayParts = part.getOverlays(); + if (overlayParts != null) { + for (ISkinPart.Vanilla overlay : overlayParts) { + applyOverlay(graphics, getVanillaSkinPart(skin, overlay, scale)); + } + } + return texture; + } +} \ 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 f21b910..0fcf4e2 100644 --- a/src/main/java/me/braydon/mc/controller/PlayerController.java +++ b/src/main/java/me/braydon/mc/controller/PlayerController.java @@ -98,10 +98,11 @@ public final class PlayerController { @Parameter(description = "The skin part to get the texture of", example = "head") @PathVariable @NonNull String partName, @Parameter(description = "The player username or UUID to get", example = "Rainnny") @PathVariable @NonNull String query, @Parameter(description = "The image extension", example = "png") @PathVariable @NonNull String extension, + @Parameter(description = "Whether to render skin overlays") @RequestParam(required = false, defaultValue = "true") boolean overlays, @Parameter(description = "The size to scale the skin part texture to", example = "256") @RequestParam(required = false) String size ) throws BadRequestException { return ResponseEntity.ok() .contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG) - .body(mojangService.getSkinPartTexture(partName, query, extension, size)); + .body(mojangService.getSkinPartTexture(partName, query, extension, overlays, size)); } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/Player.java b/src/main/java/me/braydon/mc/model/Player.java index 6524ccb..84f9cea 100644 --- a/src/main/java/me/braydon/mc/model/Player.java +++ b/src/main/java/me/braydon/mc/model/Player.java @@ -24,6 +24,7 @@ package me.braydon.mc.model; import lombok.*; +import me.braydon.mc.model.skin.Skin; import me.braydon.mc.model.token.MojangProfileToken; import org.springframework.data.annotation.Id; diff --git a/src/main/java/me/braydon/mc/model/cache/CachedPlayer.java b/src/main/java/me/braydon/mc/model/cache/CachedPlayer.java index 784e225..714e9f9 100644 --- a/src/main/java/me/braydon/mc/model/cache/CachedPlayer.java +++ b/src/main/java/me/braydon/mc/model/cache/CachedPlayer.java @@ -30,7 +30,7 @@ import lombok.ToString; import me.braydon.mc.model.Cape; import me.braydon.mc.model.Player; import me.braydon.mc.model.ProfileAction; -import me.braydon.mc.model.Skin; +import me.braydon.mc.model.skin.Skin; import me.braydon.mc.model.token.MojangProfileToken; import org.springframework.data.redis.core.RedisHash; diff --git a/src/main/java/me/braydon/mc/model/skin/ISkinPart.java b/src/main/java/me/braydon/mc/model/skin/ISkinPart.java new file mode 100644 index 0000000..c1accef --- /dev/null +++ b/src/main/java/me/braydon/mc/model/skin/ISkinPart.java @@ -0,0 +1,220 @@ +/* + * MIT License + * + * Copyright (c) 2024 Braydon (Rainnny). + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package me.braydon.mc.model.skin; + +import lombok.*; +import me.braydon.mc.common.renderer.SkinRenderer; +import me.braydon.mc.common.renderer.impl.BodySkinPartRenderer; +import me.braydon.mc.common.renderer.impl.IsometricSkinPartRenderer; +import me.braydon.mc.common.renderer.impl.VanillaSkinPartRenderer; + +import java.awt.image.BufferedImage; + +/** + * A part of a {@link Skin}. + * + * @author Braydon + */ +public interface ISkinPart { + Enum[][] TYPES = { Vanilla.values(), Basic.values(), Isometric.values() }; + + /** + * Get the name of this part. + * + * @return the part name + */ + @NonNull String name(); + + /** + * Render a part of a skin. + * + * @param skin the skin to render the part for + * @param overlays whether to render overlays + * @param size the size to scale the skin part to + * @return the rendered skin part + */ + @NonNull BufferedImage render(@NonNull Skin skin, boolean overlays, int size); + + /** + * Get a skin part by the given name. + * + * @param name the name of the part + * @return the part, null if none + */ + static ISkinPart getByName(@NonNull String name) { + name = name.toUpperCase(); + for (Enum[] type : TYPES) { + for (Enum part : type) { + if (!part.name().equals(name)) { + continue; + } + return (ISkinPart) part; + } + } + return null; + } + + /** + * A part of a Vanilla skin texture. + */ + @AllArgsConstructor @RequiredArgsConstructor @Getter @ToString + enum Vanilla implements ISkinPart { + // Overlays + HEAD_OVERLAY_FACE(new Coordinates(40, 8), 8, 8), + + // Head + HEAD_TOP(new Coordinates(8, 0), 8, 8), + FACE(new Coordinates(8, 8), 8, 8, HEAD_OVERLAY_FACE), + HEAD_LEFT(new Coordinates(0, 8), 8, 8), + HEAD_RIGHT(new Coordinates(16, 8), 8, 8), + HEAD_BOTTOM(new Coordinates(16, 0), 8, 8), + HEAD_BACK(new Coordinates(24, 8), 8, 8); + + /** + * The coordinates of this part. + */ + @NonNull private final Coordinates coordinates; + + /** + * The legacy coordinates of this part. + *

+ * This is for older skin textures + * that use different positions. + *

+ */ + private LegacyCoordinates legacyCoordinates; + + /** + * The size of this part. + */ + private final int width, height; + + /** + * The overlay parts this part has. + */ + private Vanilla[] overlays; + + Vanilla(@NonNull Coordinates coordinates, int width, int height, Vanilla... overlays) { + this(coordinates, null, width, height, overlays); + } + + /** + * Render a part of a skin. + * + * @param skin the skin to render the part for + * @param overlays whether to render overlays + * @param size the size to scale the skin part to + * @return the rendered skin part + */ + @Override @NonNull + public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) { + return VanillaSkinPartRenderer.INSTANCE.render(skin, this, overlays, size); + } + + /** + * Coordinates of a part of a skin. + */ + @AllArgsConstructor @Getter @ToString + public static class Coordinates { + /** + * The X coordinate. + */ + private final int x; + + /** + * The Y coordinate. + */ + private final int y; + } + + /** + * Legacy coordinates of a part of a skin. + */ + @Getter @ToString + public static class LegacyCoordinates extends Coordinates { + /** + * Whether the part at these coordinates is flipped. + */ + private final boolean flipped; + + public LegacyCoordinates(int x, int y) { + this(x, y, false); + } + + public LegacyCoordinates(int x, int y, boolean flipped) { + super(x, y); + this.flipped = flipped; + } + } + } + + /** + * A basic part of a skin. + *

+ * These parts have custom renderers! + *

+ */ + @AllArgsConstructor @Getter + enum Basic implements ISkinPart { + BODY(BodySkinPartRenderer.INSTANCE); + + /** + * The custom renderer to use for this part. + */ + @NonNull private final SkinRenderer renderer; + + /** + * Render a part of a skin. + * + * @param skin the skin to render the part for + * @param overlays whether to render overlays + * @param size the size to scale the skin part to + * @return the rendered skin part + */ + @Override @NonNull + public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) { + return renderer.render(skin, this, overlays, size); + } + } + + /** + * A isometric part of a skin. + */ + enum Isometric implements ISkinPart { + HEAD; + + /** + * Render a part of a skin. + * + * @param skin the skin to render the part for + * @param overlays whether to render overlays + * @param size the size to scale the skin part to + * @return the rendered skin part + */ + @Override @NonNull + public BufferedImage render(@NonNull Skin skin, boolean overlays, int size) { + return IsometricSkinPartRenderer.INSTANCE.render(skin, this, overlays, size); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/Skin.java b/src/main/java/me/braydon/mc/model/skin/Skin.java similarity index 58% rename from src/main/java/me/braydon/mc/model/Skin.java rename to src/main/java/me/braydon/mc/model/skin/Skin.java index 85b5b0e..a62058a 100644 --- a/src/main/java/me/braydon/mc/model/Skin.java +++ b/src/main/java/me/braydon/mc/model/skin/Skin.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -package me.braydon.mc.model; +package me.braydon.mc.model.skin; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.gson.JsonObject; import lombok.*; import me.braydon.mc.config.AppConfig; +import me.braydon.mc.model.Player; import java.util.HashMap; import java.util.Map; @@ -64,13 +65,13 @@ public final class Skin { */ @NonNull public Skin populatePartUrls(@NonNull String playerUuid) { - Consumer addPart = part -> { + Consumer addPart = part -> { partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png"); }; - for (Part part : Part.values()) { + for (ISkinPart part : ISkinPart.Vanilla.values()) { addPart.accept(part); } - for (IsometricPart part : IsometricPart.values()) { + for (ISkinPart part : ISkinPart.Isometric.values()) { addPart.accept(part); } return this; @@ -101,89 +102,4 @@ public final class Skin { public enum Model { DEFAULT, SLIM } - - /** - * Represents a part of a skin. - */ - public interface IPart { - /** - * Get the name of this part. - * - * @return the part name - */ - @NonNull String name(); - } - - /** - * The part of a skin. - */ - @AllArgsConstructor @RequiredArgsConstructor @Getter @ToString - public enum Part implements IPart { - // Head - HEAD_TOP(new Coordinates(8, 0), 8, 8), - FACE(new Coordinates(8, 8), 8, 8), - HEAD_LEFT(new Coordinates(0, 8), 8, 8), - HEAD_RIGHT(new Coordinates(16, 8), 8, 8), - HEAD_BOTTOM(new Coordinates(16, 0), 8, 8), - HEAD_BACK(new Coordinates(24, 8), 8, 8); - - /** - * The coordinates of this part. - */ - @NonNull private final Coordinates coordinates; - - /** - * The legacy coordinates of this part. - *

- * This is for older skin textures - * that use different positions. - *

- */ - private LegacyCoordinates legacyCoordinates; - - /** - * The size of this part. - */ - private final int width, height; - - /** - * Coordinates of a part of a skin. - */ - @AllArgsConstructor @Getter @ToString - public static class Coordinates { - /** - * The X coordinate. - */ - private final int x; - - /** - * The Y coordinate. - */ - private final int y; - } - - /** - * Legacy coordinates of a part of a skin. - */ - @Getter @ToString - public static class LegacyCoordinates extends Coordinates { - /** - * Whether the part at these coordinates is flipped. - */ - private final boolean flipped; - - public LegacyCoordinates(int x, int y) { - this(x, y, false); - } - - public LegacyCoordinates(int x, int y, boolean flipped) { - super(x, y); - this.flipped = flipped; - } - } - } - - public enum IsometricPart implements IPart { - HEAD - } } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/model/token/MojangProfileToken.java b/src/main/java/me/braydon/mc/model/token/MojangProfileToken.java index d7b3011..1ea12dd 100644 --- a/src/main/java/me/braydon/mc/model/token/MojangProfileToken.java +++ b/src/main/java/me/braydon/mc/model/token/MojangProfileToken.java @@ -29,7 +29,7 @@ import lombok.*; import me.braydon.mc.config.AppConfig; import me.braydon.mc.model.Cape; import me.braydon.mc.model.ProfileAction; -import me.braydon.mc.model.Skin; +import me.braydon.mc.model.skin.Skin; import java.util.Base64; diff --git a/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java b/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java index b61d5f7..504b2d3 100644 --- a/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java +++ b/src/main/java/me/braydon/mc/repository/SkinPartTextureCacheRepository.java @@ -23,14 +23,14 @@ */ package me.braydon.mc.repository; -import me.braydon.mc.model.Skin.Part; import me.braydon.mc.model.cache.CachedSkinPartTexture; +import me.braydon.mc.model.skin.ISkinPart; import org.springframework.data.repository.CrudRepository; /** * A cache repository for skin texture parts. * * @author Braydon - * @see Part for skin parts + * @see ISkinPart for skin parts */ public interface SkinPartTextureCacheRepository extends CrudRepository { } \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index cf8a225..28d5bc0 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -32,8 +32,6 @@ import lombok.NonNull; import lombok.SneakyThrows; import lombok.extern.log4j.Log4j2; import me.braydon.mc.common.*; -import me.braydon.mc.common.renderer.impl.BasicSkinPartRenderer; -import me.braydon.mc.common.renderer.impl.IsometricSkinPartRenderer; import me.braydon.mc.common.web.JsonWebException; import me.braydon.mc.common.web.JsonWebRequest; import me.braydon.mc.exception.impl.BadRequestException; @@ -42,7 +40,6 @@ import me.braydon.mc.exception.impl.ResourceNotFoundException; import me.braydon.mc.model.MinecraftServer; import me.braydon.mc.model.Player; import me.braydon.mc.model.ProfileAction; -import me.braydon.mc.model.Skin; import me.braydon.mc.model.cache.CachedMinecraftServer; import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.model.cache.CachedPlayerName; @@ -51,6 +48,8 @@ import me.braydon.mc.model.dns.DNSRecord; import me.braydon.mc.model.dns.impl.ARecord; import me.braydon.mc.model.dns.impl.SRVRecord; import me.braydon.mc.model.server.JavaMinecraftServer; +import me.braydon.mc.model.skin.ISkinPart; +import me.braydon.mc.model.skin.Skin; import me.braydon.mc.model.token.MojangProfileToken; import me.braydon.mc.model.token.MojangUsernameToUUIDToken; import me.braydon.mc.repository.MinecraftServerCacheRepository; @@ -141,9 +140,6 @@ public final class MojangService { this.minecraftServerCache = minecraftServerCache; } - @Autowired - - @PostConstruct public void onInitialize() { // Schedule a task to fetch blocked @@ -163,23 +159,19 @@ public final class MojangService { * @param partName the part of the player's skin texture to get * @param query the query to search for the player by * @param extension the skin part image extension + * @param overlays whether to render overlays * @param sizeString the size of the skin part image * @return the skin part texture * @throws BadRequestException if the extension is invalid * @throws MojangRateLimitException if the Mojang API rate limit is reached */ @SneakyThrows - public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension, String sizeString) - throws BadRequestException, MojangRateLimitException { - partName = partName.toUpperCase(); // The part name to get - + public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension, + boolean overlays, String sizeString) throws BadRequestException, MojangRateLimitException { // Get the part from the given name - Skin.IPart part = EnumUtils.getEnumConstant(Skin.Part.class, partName); // The skin part to get - if (part == null) { // The given part is invalid, try a isometric part - part = EnumUtils.getEnumConstant(Skin.IsometricPart.class, partName);; - } + ISkinPart part = ISkinPart.getByName(partName); // The skin part to get if (part == null) { // Default to the face - part = Skin.Part.FACE; + part = ISkinPart.Vanilla.FACE; } // Ensure the extension is valid @@ -217,9 +209,7 @@ public final class MojangService { if (target == null) { // Fallback to the default skin target = Skin.DEFAULT_STEVE; } - BufferedImage texture = part instanceof Skin.IsometricPart isometricPart ? - IsometricSkinPartRenderer.INSTANCE.render(target, isometricPart, true, size) - : BasicSkinPartRenderer.INSTANCE.render(target, (Skin.Part) part, true, size); // Render the skin part + BufferedImage texture = part.render(target, overlays, size); // Render the skin part // Convert BufferedImage to byte array try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {