add isometric head renderer
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 30s
Some checks failed
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Failing after 30s
This commit is contained in:
@ -3,6 +3,7 @@ package cc.fascinated.service;
|
||||
import cc.fascinated.common.PlayerUtils;
|
||||
import cc.fascinated.common.Tuple;
|
||||
import cc.fascinated.common.UUIDUtils;
|
||||
import cc.fascinated.config.Config;
|
||||
import cc.fascinated.exception.impl.MojangAPIRateLimitException;
|
||||
import cc.fascinated.exception.impl.RateLimitException;
|
||||
import cc.fascinated.exception.impl.ResourceNotFoundException;
|
||||
@ -58,7 +59,7 @@ public class PlayerService {
|
||||
}
|
||||
|
||||
Optional<CachedPlayer> cachedPlayer = playerCacheRepository.findById(uuid);
|
||||
if (cachedPlayer.isPresent()) { // Return the cached player if it exists
|
||||
if (cachedPlayer.isPresent() && Config.INSTANCE.isProduction()) { // Return the cached player if it exists
|
||||
log.info("Player {} is cached", originalId);
|
||||
return cachedPlayer.get();
|
||||
}
|
||||
@ -94,7 +95,7 @@ public class PlayerService {
|
||||
public CachedPlayerName usernameToUuid(String username) {
|
||||
log.info("Getting UUID from username: {}", username);
|
||||
Optional<CachedPlayerName> cachedPlayerName = playerNameCacheRepository.findById(username.toUpperCase());
|
||||
if (cachedPlayerName.isPresent()) {
|
||||
if (cachedPlayerName.isPresent() && Config.INSTANCE.isProduction()) {
|
||||
return cachedPlayerName.get();
|
||||
}
|
||||
try {
|
||||
@ -118,20 +119,21 @@ public class PlayerService {
|
||||
*
|
||||
* @param player the player
|
||||
* @param part the part of the skin
|
||||
* @param renderOverlay whether to render the overlay
|
||||
* @return the skin part
|
||||
*/
|
||||
public CachedPlayerSkinPart getSkinPart(Player player, Skin.Parts part, int size) {
|
||||
public CachedPlayerSkinPart getSkinPart(Player player, Skin.Parts part, boolean renderOverlay, int size) {
|
||||
log.info("Getting skin part: {} for player: {}", part.getName(), player.getUniqueId());
|
||||
String key = "%s-%s-%s".formatted(player.getUniqueId(), part.getName(), size);
|
||||
Optional<CachedPlayerSkinPart> cache = playerSkinPartCacheRepository.findById(key);
|
||||
|
||||
// The skin part is cached
|
||||
if (cache.isPresent()) {
|
||||
if (cache.isPresent() && Config.INSTANCE.isProduction()) {
|
||||
log.info("Skin part {} for player {} is cached", part.getName(), player.getUniqueId());
|
||||
return cache.get();
|
||||
}
|
||||
|
||||
byte[] skinPartBytes = PlayerUtils.getSkinPartBytes(player.getSkin(), part, size);
|
||||
byte[] skinPartBytes = part.getSkinPartParser().getPart(player.getSkin(), part.getName(), renderOverlay, size);
|
||||
CachedPlayerSkinPart skinPart = new CachedPlayerSkinPart(
|
||||
key,
|
||||
skinPartBytes
|
||||
|
@ -2,6 +2,7 @@ package cc.fascinated.service;
|
||||
|
||||
import cc.fascinated.common.DNSUtils;
|
||||
import cc.fascinated.common.EnumUtils;
|
||||
import cc.fascinated.config.Config;
|
||||
import cc.fascinated.exception.impl.BadRequestException;
|
||||
import cc.fascinated.exception.impl.ResourceNotFoundException;
|
||||
import cc.fascinated.model.cache.CachedMinecraftServer;
|
||||
@ -63,7 +64,7 @@ public class ServerService {
|
||||
|
||||
// Check if the server is cached
|
||||
Optional<CachedMinecraftServer> cached = serverCacheRepository.findById(key);
|
||||
if (cached.isPresent()) {
|
||||
if (cached.isPresent() && Config.INSTANCE.isProduction()) {
|
||||
log.info("Server {}:{} is cached", hostname, port);
|
||||
return cached.get();
|
||||
}
|
||||
|
48
src/main/java/cc.fascinated/service/skin/SkinPartParser.java
Normal file
48
src/main/java/cc.fascinated/service/skin/SkinPartParser.java
Normal file
@ -0,0 +1,48 @@
|
||||
package cc.fascinated.service.skin;
|
||||
|
||||
import cc.fascinated.common.ImageUtils;
|
||||
import cc.fascinated.model.player.Skin;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
@AllArgsConstructor @Getter
|
||||
public abstract class SkinPartParser {
|
||||
|
||||
/**
|
||||
* 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 scale the scale
|
||||
* @return the skin part image
|
||||
*/
|
||||
public BufferedImage getSkinPart(Skin skin, int x, int y, int width, int height, double scale) {
|
||||
try {
|
||||
BufferedImage skinImage = ImageIO.read(new ByteArrayInputStream(skin.getSkinImage()));
|
||||
BufferedImage part = skinImage.getSubimage(x, y, width, height);
|
||||
return ImageUtils.resize(part, scale);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the skin part image.
|
||||
*
|
||||
* @param skin the skin
|
||||
* @param partName the skin part name
|
||||
* @param renderOverlay whether to render the overlay
|
||||
* @param size the output size
|
||||
* @return the skin part image
|
||||
*/
|
||||
public abstract byte[] getPart(Skin skin, String partName, boolean renderOverlay, int size);
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package cc.fascinated.service.skin.impl;
|
||||
|
||||
import cc.fascinated.common.ImageUtils;
|
||||
import cc.fascinated.model.player.Skin;
|
||||
import cc.fascinated.service.skin.SkinPartParser;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
@Getter @Log4j2
|
||||
public class FlatParser extends SkinPartParser {
|
||||
|
||||
/**
|
||||
* 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 FlatParser}.
|
||||
*
|
||||
* @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 FlatParser(int x, int y, int widthAndHeight) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.widthAndHeight = widthAndHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getPart(Skin skin, String partName, boolean renderOverlay, int size) {
|
||||
double scale = (double) size / this.widthAndHeight;
|
||||
log.info("Getting {} part bytes for {} with size {} and scale {}", partName, skin.getUrl(), size, scale);
|
||||
|
||||
try {
|
||||
BufferedImage outputImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
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);
|
||||
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(outputImage, "png", outputStream);
|
||||
// Cleanup
|
||||
outputStream.flush();
|
||||
graphics.dispose();
|
||||
log.info("Successfully got {} part bytes for {}", partName, skin.getUrl());
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to get {} part bytes for {}", partName, skin.getUrl(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package cc.fascinated.service.skin.impl;
|
||||
|
||||
import cc.fascinated.common.ImageUtils;
|
||||
import cc.fascinated.model.player.Skin;
|
||||
import cc.fascinated.service.skin.SkinPartParser;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
@Getter @Log4j2
|
||||
public class IsometricHeadParser extends SkinPartParser {
|
||||
|
||||
private static final double SKEW_A = 26d / 45d; // 0.57777777
|
||||
private static final double SKEW_B = SKEW_A * 2d; // 1.15555555
|
||||
|
||||
@Override
|
||||
public byte[] getPart(Skin skin, String partName, boolean renderOverlay, int size) {
|
||||
double scale = (size / 8d) / 2.5;
|
||||
log.info("Getting {} part bytes for {} with size {} and scale {}", partName, skin.getUrl(), size, scale);
|
||||
|
||||
try {
|
||||
final BufferedImage outputImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
// Get all the required head parts
|
||||
final BufferedImage headTop = ImageUtils.resize(this.getSkinPart(skin, 8, 0, 8, 8, 1), scale);
|
||||
final BufferedImage headFront = ImageUtils.resize(this.getSkinPart(skin, 8, 8, 8, 8, 1), scale);
|
||||
final BufferedImage headRight = ImageUtils.resize(this.getSkinPart(skin, 0, 8, 8, 8, 1), scale);
|
||||
|
||||
if (renderOverlay) {
|
||||
// Draw the overlay on top of the gathered skin parts
|
||||
|
||||
// Top overlay
|
||||
Graphics2D g = headTop.createGraphics();
|
||||
g.drawImage(this.getSkinPart(skin, 40, 0, 8, 8, 1), 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
// Front overlay
|
||||
g = headFront.createGraphics();
|
||||
g.drawImage(this.getSkinPart(skin, 16, 8, 8, 8, 1), 0, 0, null);
|
||||
g.dispose();
|
||||
|
||||
// Right side overlay
|
||||
g = headRight.createGraphics();
|
||||
g.drawImage(this.getSkinPart(skin, 32, 8, 8, 8, 1), 0, 0, null);
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
// Declare pos
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
|
||||
// Declare offsets
|
||||
final double z_offset = scale * 3.5d;
|
||||
final double x_offset = scale * 2d;
|
||||
|
||||
// Create graphics
|
||||
final Graphics2D outGraphics = outputImage.createGraphics();
|
||||
|
||||
// head top
|
||||
x = x_offset;
|
||||
y = -0.5;
|
||||
z = z_offset;
|
||||
outGraphics.setTransform(new AffineTransform(1d, -SKEW_A, 1, SKEW_A, 0, 0));
|
||||
outGraphics.drawImage(headTop, (int) (y - z), (int) (x + z), headTop.getWidth(), headTop.getHeight() + 1, null);
|
||||
|
||||
// head front
|
||||
x = x_offset + 8 * scale;
|
||||
y = 0;
|
||||
z = z_offset - 0.5;
|
||||
outGraphics.setTransform(new AffineTransform(1d, -SKEW_A, 0d, SKEW_B, 0d, SKEW_A));
|
||||
outGraphics.drawImage(headFront, (int) (y + x), (int) (x + z), headFront.getWidth(), headFront.getHeight(), null);
|
||||
|
||||
// head right
|
||||
x = x_offset;
|
||||
y = 0;
|
||||
z = z_offset;
|
||||
outGraphics.setTransform(new AffineTransform(1d, SKEW_A, 0d, SKEW_B, 0d, 0d));
|
||||
outGraphics.drawImage(headRight, (int) (x + y + 1), (int) (z - y - 0.5), headRight.getWidth(), headRight.getHeight() + 1, null);
|
||||
|
||||
// Cleanup and return
|
||||
outGraphics.dispose();
|
||||
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
ImageIO.write(outputImage, "png", outputStream);
|
||||
// Cleanup
|
||||
outputStream.flush();
|
||||
outGraphics.dispose();
|
||||
log.info("Successfully got {} part bytes for {}", partName, skin.getUrl());
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to get {} part bytes for {}", partName, skin.getUrl(), ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user