add skin overlays to all images if it's enabled
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m35s
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 1m35s
This commit is contained in:
parent
4e08955ab9
commit
55c1ca4139
@ -4,24 +4,29 @@ import cc.fascinated.common.ImageUtils;
|
|||||||
import cc.fascinated.model.skin.ISkinPart;
|
import cc.fascinated.model.skin.ISkinPart;
|
||||||
import cc.fascinated.model.skin.Skin;
|
import cc.fascinated.model.skin.Skin;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
|
||||||
|
@Log4j2
|
||||||
public abstract class SkinRenderer<T extends ISkinPart> {
|
public abstract class SkinRenderer<T extends ISkinPart> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the texture of a part of a skin.
|
* Get the texture of a part of the skin.
|
||||||
*
|
*
|
||||||
* @param skin the skin to get the part texture from
|
* @param skin the skin to get the part texture from
|
||||||
* @param part the part of the skin to get
|
* @param part the part of the skin to get
|
||||||
* @param size the size to scale the texture to
|
* @param size the size to scale the texture to
|
||||||
|
* @param renderOverlays should the overlays be rendered
|
||||||
* @return the texture of the skin part
|
* @return the texture of the skin part
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public BufferedImage getVanillaSkinPart(Skin skin, ISkinPart.Vanilla part, double size) {
|
public BufferedImage getVanillaSkinPart(Skin skin, ISkinPart.Vanilla part, double size, boolean renderOverlays) {
|
||||||
ISkinPart.Vanilla.Coordinates coordinates = part.getCoordinates(); // The coordinates of the part
|
ISkinPart.Vanilla.Coordinates coordinates = part.getCoordinates(); // The coordinates of the part
|
||||||
|
|
||||||
// The skin texture is legacy, use legacy coordinates
|
// The skin texture is legacy, use legacy coordinates
|
||||||
@ -37,6 +42,16 @@ public abstract class SkinRenderer<T extends ISkinPart> {
|
|||||||
if (coordinates instanceof ISkinPart.Vanilla.LegacyCoordinates legacyCoordinates && legacyCoordinates.isFlipped()) {
|
if (coordinates instanceof ISkinPart.Vanilla.LegacyCoordinates legacyCoordinates && legacyCoordinates.isFlipped()) {
|
||||||
partTexture = ImageUtils.flip(partTexture);
|
partTexture = ImageUtils.flip(partTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw part overlays
|
||||||
|
ISkinPart.Vanilla[] overlayParts = part.getOverlays();
|
||||||
|
if (overlayParts != null && renderOverlays) {
|
||||||
|
log.info("Applying overlays to part: {}", part.name());
|
||||||
|
for (ISkinPart.Vanilla overlay : overlayParts) {
|
||||||
|
applyOverlay(partTexture.createGraphics(), getVanillaSkinPart(skin, overlay, size, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return partTexture;
|
return partTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ public class BodyRenderer extends SkinRenderer<ISkinPart.Custom> {
|
|||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||||
|
|
||||||
// Get the Vanilla skin parts to draw
|
// Get the Vanilla skin parts to draw
|
||||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, -1);
|
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, -1, renderOverlays);
|
||||||
BufferedImage body = getVanillaSkinPart(skin, ISkinPart.Vanilla.BODY_FRONT, -1);
|
BufferedImage body = getVanillaSkinPart(skin, ISkinPart.Vanilla.BODY_FRONT, -1, renderOverlays);
|
||||||
BufferedImage leftArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_ARM_FRONT, -1);
|
BufferedImage leftArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_ARM_FRONT, -1, renderOverlays);
|
||||||
BufferedImage rightArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_ARM_FRONT, -1);
|
BufferedImage rightArm = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_ARM_FRONT, -1, renderOverlays);
|
||||||
BufferedImage leftLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_LEG_FRONT, -1);
|
BufferedImage leftLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.LEFT_LEG_FRONT, -1, renderOverlays);
|
||||||
BufferedImage rightLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_LEG_FRONT, -1);
|
BufferedImage rightLeg = getVanillaSkinPart(skin, ISkinPart.Vanilla.RIGHT_LEG_FRONT, -1, renderOverlays);
|
||||||
|
|
||||||
// Draw the body parts
|
// Draw the body parts
|
||||||
graphics.drawImage(face, 4, 0, null);
|
graphics.drawImage(face, 4, 0, null);
|
||||||
|
@ -28,9 +28,9 @@ public class IsometricHeadRenderer extends IsometricSkinRenderer<ISkinPart.Custo
|
|||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||||
|
|
||||||
// Get the Vanilla skin parts to draw
|
// Get the Vanilla skin parts to draw
|
||||||
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale);
|
BufferedImage headTop = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_TOP, scale, renderOverlays);
|
||||||
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale);
|
BufferedImage face = getVanillaSkinPart(skin, ISkinPart.Vanilla.FACE, scale, renderOverlays);
|
||||||
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale);
|
BufferedImage headLeft = getVanillaSkinPart(skin, ISkinPart.Vanilla.HEAD_LEFT, scale, renderOverlays);
|
||||||
|
|
||||||
// Draw the top head part
|
// Draw the top head part
|
||||||
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
||||||
|
@ -17,7 +17,7 @@ public class SquareRenderer extends SkinRenderer<ISkinPart.Vanilla> {
|
|||||||
@Override
|
@Override
|
||||||
public BufferedImage render(Skin skin, ISkinPart.Vanilla part, boolean renderOverlays, int size) {
|
public BufferedImage render(Skin skin, ISkinPart.Vanilla part, boolean renderOverlays, int size) {
|
||||||
double scale = size / 8D;
|
double scale = size / 8D;
|
||||||
BufferedImage partImage = getVanillaSkinPart(skin, part, scale); // Get the part image
|
BufferedImage partImage = getVanillaSkinPart(skin, part, scale, renderOverlays); // Get the part image
|
||||||
if (!renderOverlays) { // Not rendering overlays
|
if (!renderOverlays) { // Not rendering overlays
|
||||||
return partImage;
|
return partImage;
|
||||||
}
|
}
|
||||||
@ -26,13 +26,6 @@ public class SquareRenderer extends SkinRenderer<ISkinPart.Vanilla> {
|
|||||||
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
Graphics2D graphics = texture.createGraphics(); // Create the graphics for drawing
|
||||||
graphics.drawImage(partImage, 0, 0, null);
|
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graphics.dispose();
|
graphics.dispose();
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class PlayerController {
|
|||||||
@Parameter(description = "The part of the skin", example = "head") @PathVariable String part,
|
@Parameter(description = "The part of the skin", example = "head") @PathVariable String part,
|
||||||
@Parameter(description = "The UUID or Username of the player", example = "ImFascinated") @PathVariable String id,
|
@Parameter(description = "The UUID or Username of the player", example = "ImFascinated") @PathVariable String id,
|
||||||
@Parameter(description = "The size of the image", example = "256") @RequestParam(required = false, defaultValue = "256") int size,
|
@Parameter(description = "The size of the image", example = "256") @RequestParam(required = false, defaultValue = "256") int size,
|
||||||
@Parameter(description = "Whether to render the skin overlay (skin layers)", example = "false") @RequestParam(required = false, defaultValue = "false") boolean overlay,
|
@Parameter(description = "Whether to render the skin overlay (skin layers)", example = "false") @RequestParam(required = false, defaultValue = "false") boolean overlays,
|
||||||
@Parameter(description = "Whether to download the image") @RequestParam(required = false, defaultValue = "false") boolean download) {
|
@Parameter(description = "Whether to download the image") @RequestParam(required = false, defaultValue = "false") boolean download) {
|
||||||
CachedPlayer player = playerService.getPlayer(id);
|
CachedPlayer player = playerService.getPlayer(id);
|
||||||
String dispositionHeader = download ? "attachment; filename=%s.png" : "inline; filename=%s.png";
|
String dispositionHeader = download ? "attachment; filename=%s.png" : "inline; filename=%s.png";
|
||||||
@ -58,6 +58,6 @@ public class PlayerController {
|
|||||||
.cacheControl(cacheControl)
|
.cacheControl(cacheControl)
|
||||||
.contentType(MediaType.IMAGE_PNG)
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION, dispositionHeader.formatted(player.getUsername()))
|
.header(HttpHeaders.CONTENT_DISPOSITION, dispositionHeader.formatted(player.getUsername()))
|
||||||
.body(playerService.getSkinPart(player, part, overlay, size).getBytes());
|
.body(playerService.getSkinPart(player, part, overlays, size).getBytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,12 +59,14 @@ public interface ISkinPart {
|
|||||||
@Getter
|
@Getter
|
||||||
enum Vanilla implements ISkinPart {
|
enum Vanilla implements ISkinPart {
|
||||||
// Overlays
|
// Overlays
|
||||||
|
HEAD_OVERLAY_TOP(true, new Coordinates(40, 0), 8, 8),
|
||||||
HEAD_OVERLAY_FACE(true, new Coordinates(40, 8), 8, 8),
|
HEAD_OVERLAY_FACE(true, new Coordinates(40, 8), 8, 8),
|
||||||
|
HEAD_OVERLAY_LEFT(true, new Coordinates(48, 8), 8, 8),
|
||||||
|
|
||||||
// Head
|
// Head
|
||||||
HEAD_TOP(true, new Coordinates(8, 0), 8, 8),
|
HEAD_TOP(true, new Coordinates(8, 0), 8, 8, HEAD_OVERLAY_TOP),
|
||||||
FACE(false, new Coordinates(8, 8), 8, 8, HEAD_OVERLAY_FACE),
|
FACE(false, new Coordinates(8, 8), 8, 8, HEAD_OVERLAY_FACE),
|
||||||
HEAD_LEFT(true, new Coordinates(0, 8), 8, 8),
|
HEAD_LEFT(true, new Coordinates(0, 8), 8, 8, HEAD_OVERLAY_LEFT),
|
||||||
HEAD_RIGHT(true, new Coordinates(16, 8), 8, 8),
|
HEAD_RIGHT(true, new Coordinates(16, 8), 8, 8),
|
||||||
HEAD_BOTTOM(true, new Coordinates(16, 0), 8, 8),
|
HEAD_BOTTOM(true, new Coordinates(16, 0), 8, 8),
|
||||||
HEAD_BACK(true, new Coordinates(24, 8), 8, 8),
|
HEAD_BACK(true, new Coordinates(24, 8), 8, 8),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cc.fascinated.model.skin;
|
package cc.fascinated.model.skin;
|
||||||
|
|
||||||
|
import cc.fascinated.common.EnumUtils;
|
||||||
import cc.fascinated.common.PlayerUtils;
|
import cc.fascinated.common.PlayerUtils;
|
||||||
import cc.fascinated.config.Config;
|
import cc.fascinated.config.Config;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
@ -77,9 +78,10 @@ public class Skin {
|
|||||||
}
|
}
|
||||||
String url = json.get("url").getAsString();
|
String url = json.get("url").getAsString();
|
||||||
JsonObject metadata = json.getAsJsonObject("metadata");
|
JsonObject metadata = json.getAsJsonObject("metadata");
|
||||||
Model model = Model.fromName(metadata == null ? "default" : // Fall back to slim if the model is not found
|
return new Skin(
|
||||||
metadata.get("model").getAsString());
|
url,
|
||||||
return new Skin(url, model);
|
EnumUtils.getEnumConstant(Model.class, metadata != null ? metadata.get("model").getAsString() : "DEFAULT")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -106,21 +108,6 @@ public class Skin {
|
|||||||
*/
|
*/
|
||||||
public enum Model {
|
public enum Model {
|
||||||
DEFAULT,
|
DEFAULT,
|
||||||
SLIM;
|
SLIM
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the model from its name.
|
|
||||||
*
|
|
||||||
* @param name the name of the model
|
|
||||||
* @return the model
|
|
||||||
*/
|
|
||||||
public static Model fromName(String name) {
|
|
||||||
for (Model model : values()) {
|
|
||||||
if (model.name().equalsIgnoreCase(name)) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ public class PlayerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String name = part.name();
|
String name = part.name();
|
||||||
log.info("Getting skin part {} for player: {}", name, player.getUniqueId());
|
log.info("Getting skin part {} for player: {} (size: {}, renderOverlays: {})", name, player.getUniqueId(), size, renderOverlay);
|
||||||
String key = "%s-%s-%s-%s".formatted(player.getUniqueId(), name, size, renderOverlay);
|
String key = "%s-%s-%s-%s".formatted(player.getUniqueId(), name, size, renderOverlay);
|
||||||
Optional<CachedPlayerSkinPart> cache = playerSkinPartCacheRepository.findById(key);
|
Optional<CachedPlayerSkinPart> cache = playerSkinPartCacheRepository.findById(key);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user