add head endpoint and finish the body renderer
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m28s

This commit is contained in:
Lee
2024-04-11 06:10:37 +01:00
parent 1a74b0099b
commit f63d1cc3ec
8 changed files with 230 additions and 64 deletions

View File

@ -71,6 +71,7 @@ public class PlayerService {
Tuple<Skin, Cape> skinAndCape = mojangProfile.getSkinAndCape();
CachedPlayer player = new CachedPlayer(
uuid, // Player UUID
UUIDUtils.removeDashes(uuid), // Trimmed UUID
mojangProfile.getName(), // Player Name
skinAndCape.getLeft(), // Skin
skinAndCape.getRight(), // Cape
@ -134,7 +135,7 @@ public class PlayerService {
}
long before = System.currentTimeMillis();
byte[] skinPartBytes = part.getSkinPartParser().renderPart(player.getSkin(), part.getName(), renderOverlay, size);
byte[] skinPartBytes = part.getSkinRenderer().renderPart(player.getSkin(), part.getName(), renderOverlay, size);
log.info("Took {}ms to render skin part {} for player: {}", System.currentTimeMillis() - before, part.getName(), player.getUniqueId());
CachedPlayerSkinPart skinPart = new CachedPlayerSkinPart(
key,

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -15,21 +16,47 @@ import java.io.ByteArrayOutputStream;
@AllArgsConstructor @Getter @Log4j2
public abstract class SkinRenderer {
/**
* Gets the skin image.
*
* @param skin the skin
* @return the skin image
*/
public BufferedImage getSkinImage(Skin skin) {
try {
return ImageIO.read(new ByteArrayInputStream(skin.getSkinImage()));
} catch (Exception ex) {
return null;
}
}
/**
* Gets the skin part image.
*
* @param skin the skin
* @param x the x position
* @param y the y position
* @param width the width
* @param height the height
* @param position the part position information
* @param scale the scale
* @return the skin part image
*/
public BufferedImage getSkinPart(Skin skin, int x, int y, int width, int height, double scale) {
public BufferedImage getSkinPart(Skin skin, Skin.PartPosition position, double scale) {
try {
BufferedImage skinImage = ImageIO.read(new ByteArrayInputStream(skin.getSkinImage()));
BufferedImage part = skinImage.getSubimage(x, y, width, height);
BufferedImage skinImage = this.getSkinImage(skin);
if (skinImage == null) {
return null;
}
BufferedImage part;
Skin.LegacyPartPositionData legacyData = position.getLegacyData();
if (skin.isLegacy() && legacyData != null) {
part = skinImage.getSubimage(legacyData.getX(), legacyData.getY(), position.getWidth(), position.getHeight());
if (legacyData.isFlipped()) {
part = ImageUtils.flip(part);
}
} else {
part = skinImage.getSubimage(position.getX(), position.getY(), position.getWidth(), position.getHeight());
}
if (part == null) {
return null;
}
return ImageUtils.resize(part, scale);
} catch (Exception ex) {
return null;
@ -56,6 +83,20 @@ public abstract class SkinRenderer {
}
}
/**
* Applies an overlay (skin layer) to the head part.
*
* @param graphics the graphics
* @param part the part
*/
public void applyOverlay(Graphics2D graphics, BufferedImage part) {
if (part == null) {
return;
}
graphics.drawImage(part, 0, 0, null);
graphics.dispose();
}
/**
* Renders a skin part.
*

View File

@ -0,0 +1,55 @@
package cc.fascinated.service.skin.impl;
import cc.fascinated.common.ImageUtils;
import cc.fascinated.model.player.Skin;
import cc.fascinated.service.skin.SkinRenderer;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
@Getter @Log4j2
public class BodyRenderer extends SkinRenderer {
private static final int WIDTH = 16;
private static final int HEIGHT = 32;
@Override
public byte[] renderPart(Skin skin, String partName, boolean renderOverlay, int size) {
log.info("Getting {} part bytes for {} with size {}", partName, skin.getUrl(), size);
try {
BufferedImage outputImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = outputImage.createGraphics();
// Get all the required body parts
BufferedImage head = this.getSkinPart(skin, Skin.PartPosition.HEAD, 1);
BufferedImage body = this.getSkinPart(skin, Skin.PartPosition.BODY, 1);
BufferedImage rightArm = this.getSkinPart(skin, Skin.PartPosition.RIGHT_ARM, 1);
BufferedImage leftArm = this.getSkinPart(skin, Skin.PartPosition.LEFT_ARM, 1);
BufferedImage rightLeg = this.getSkinPart(skin, Skin.PartPosition.RIGHT_LEG, 1);
BufferedImage leftLeg = this.getSkinPart(skin, Skin.PartPosition.LEFT_LEG, 1);
if (renderOverlay) { // Render the skin layers
Graphics2D overlayGraphics = head.createGraphics();
applyOverlay(overlayGraphics, this.getSkinPart(skin, Skin.PartPosition.HEAD_OVERLAY, 1));
}
// Draw the body
graphics.drawImage(head, 4, 0, null);
graphics.drawImage(body, 4, 8, null);
graphics.drawImage(rightArm, 0, 8, null);
graphics.drawImage(leftArm, 12, 8, null);
graphics.drawImage(rightLeg, 4, 20, null);
graphics.drawImage(leftLeg, 8, 20, null);
graphics.dispose(); // Clean up
return super.getBytes(ImageUtils.resize(outputImage, (double) size / HEIGHT), skin, partName);
} catch (Exception ex) {
log.error("Failed to get {} part bytes for {}", partName, skin.getUrl(), ex);
throw new RuntimeException("Failed to get " + partName + " part for " + skin.getUrl());
}
}
}

View File

@ -10,34 +10,11 @@ import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
@Getter @Log4j2
public class SquareRenderer extends SkinRenderer {
/**
* The x and y position of the part.
*/
private final int x, y;
/**
* The width and height of the part.
*/
private final int widthAndHeight;
/**
* Constructs a new {@link SquareRenderer}.
*
* @param x the x position of the part
* @param y the y position of the part
* @param widthAndHeight the width and height of the part
*/
public SquareRenderer(int x, int y, int widthAndHeight) {
this.x = x;
this.y = y;
this.widthAndHeight = widthAndHeight;
}
public class HeadRenderer extends SkinRenderer {
@Override
public byte[] renderPart(Skin skin, String partName, boolean renderOverlay, int size) {
double scale = (double) size / this.widthAndHeight;
double scale = (double) size / 8d;
log.info("Getting {} part bytes for {} with size {} and scale {}", partName, skin.getUrl(), size, scale);
try {
@ -45,7 +22,11 @@ public class SquareRenderer extends SkinRenderer {
Graphics2D graphics = outputImage.createGraphics();
graphics.setTransform(AffineTransform.getScaleInstance(scale, scale));
graphics.drawImage(this.getSkinPart(skin, this.x, this.y, this.widthAndHeight, this.widthAndHeight, 1), 0, 0, null);
graphics.drawImage(this.getSkinPart(skin, Skin.PartPosition.HEAD, 1), 0, 0, null);
if (renderOverlay) { // Render the skin layers
applyOverlay(outputImage.createGraphics(), this.getSkinPart(skin, Skin.PartPosition.HEAD_OVERLAY, 1));
}
return super.getBytes(outputImage, skin, partName);
} catch (Exception ex) {

View File

@ -13,7 +13,7 @@ import java.awt.image.BufferedImage;
@Getter @Log4j2
public class IsometricHeadRenderer extends SkinRenderer {
private static final double SKEW_A = 26d / 45d; // 0.57777777
private static final double SKEW_A = 26d / 45d; // 0.57777777
private static final double SKEW_B = SKEW_A * 2d; // 1.15555555
/**
@ -36,19 +36,19 @@ public class IsometricHeadRenderer extends SkinRenderer {
Graphics2D graphics = outputImage.createGraphics();
// Get all the required head parts
BufferedImage headTop = ImageUtils.resize(this.getSkinPart(skin, 8, 0, 8, 8, 1), scale);
BufferedImage headFront = ImageUtils.resize(this.getSkinPart(skin, 8, 8, 8, 8, 1), scale);
BufferedImage headRight = ImageUtils.resize(this.getSkinPart(skin, 0, 8, 8, 8, 1), scale);
BufferedImage headTop = ImageUtils.resize(this.getSkinPart(skin, Skin.PartPosition.HEAD_TOP, 1), scale);
BufferedImage headFront = ImageUtils.resize(this.getSkinPart(skin, Skin.PartPosition.HEAD_FRONT, 1), scale);
BufferedImage headRight = ImageUtils.resize(this.getSkinPart(skin, Skin.PartPosition.HEAD_RIGHT, 1), scale);
if (renderOverlay) {
if (renderOverlay) { // Render the skin layers
Graphics2D headGraphics = headTop.createGraphics();
applyOverlay(headGraphics,this.getSkinPart(skin, 40, 0, 8, 8, 1));
applyOverlay(headGraphics, this.getSkinPart(skin, Skin.PartPosition.HEAD_OVERLAY, 1));
headGraphics = headFront.createGraphics();
applyOverlay(headGraphics, this.getSkinPart(skin, 16, 8, 8, 8, 1));
applyOverlay(headGraphics, this.getSkinPart(skin, Skin.PartPosition.HEAD_OVERLAY_FRONT, 1));
headGraphics = headRight.createGraphics();
applyOverlay(headGraphics, this.getSkinPart(skin, 32, 8, 8, 8, 1));
applyOverlay(headGraphics, this.getSkinPart(skin, Skin.PartPosition.HEAD_OVERLAY_RIGHT, 1));
}
// Draw the head
@ -77,17 +77,6 @@ public class IsometricHeadRenderer extends SkinRenderer {
}
}
/**
* Applies an overlay (skin layer) to the head part.
*
* @param graphics the graphics
* @param part the part
*/
private void applyOverlay(Graphics2D graphics, BufferedImage part) {
graphics.drawImage(part, 0, 0, null);
graphics.dispose();
}
/**
* Draws a part of the head.
*