Add base for 3D skin heads!!!
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
16a42d1af3
commit
2f7b9f6b10
14
pom.xml
14
pom.xml
@ -116,6 +116,20 @@
|
|||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 3D Rendering 🧑🏼🔫 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>java3d</groupId>
|
||||||
|
<artifactId>j3d-core</artifactId>
|
||||||
|
<version>1.3.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>java3d</groupId>
|
||||||
|
<artifactId>j3d-core-utils</artifactId>
|
||||||
|
<version>1.3.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- SwaggerUI -->
|
<!-- SwaggerUI -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
|
@ -24,68 +24,30 @@
|
|||||||
package me.braydon.mc.common;
|
package me.braydon.mc.common;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import me.braydon.mc.model.Skin;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import java.awt.*;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.AffineTransformOp;
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Braydon
|
* @author Braydon
|
||||||
*/
|
*/
|
||||||
@UtilityClass
|
@UtilityClass
|
||||||
public final class ImageUtils {
|
public final class ImageUtils {
|
||||||
public static final int SKIN_TEXTURE_SIZE = 64; // The skin of a skin texture
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the texture of a part of a skin.
|
* Scale the given image to the provided size.
|
||||||
*
|
*
|
||||||
* @param skin the skin to get the part texture from
|
* @param image the image to scale
|
||||||
* @param part the part of the skin to get
|
* @param size the size to scale the image to
|
||||||
* @param size the size to scale the texture to
|
* @return the scaled image
|
||||||
* @return the texture of the skin part
|
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@NonNull
|
||||||
public static byte[] getSkinPart(@NonNull Skin skin, @NonNull Skin.Part part, int size) {
|
public static BufferedImage resize(@NonNull BufferedImage image, double size) {
|
||||||
return getSkinPartTexture(skin, part.getX(), part.getY(), part.getWidth(), part.getHeight(), size);
|
BufferedImage scaled = new BufferedImage((int) (image.getWidth() * size), (int) (image.getHeight() * size), BufferedImage.TYPE_INT_ARGB);
|
||||||
}
|
Graphics2D graphics = scaled.createGraphics();
|
||||||
|
graphics.drawImage(image, AffineTransform.getScaleInstance(size, size), null);
|
||||||
/**
|
graphics.dispose();
|
||||||
* Get the texture of a specific part of a skin.
|
return scaled;
|
||||||
*
|
|
||||||
* @param skin the skin to get the part from
|
|
||||||
* @param x the x position of the part
|
|
||||||
* @param y the y position of the part
|
|
||||||
* @param width the width of the part
|
|
||||||
* @param height the height of the part
|
|
||||||
* @param size the size to scale the part to
|
|
||||||
* @return the texture of the skin part
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public static byte[] getSkinPartTexture(@NonNull Skin skin, int x, int y, int width, int height, int size) {
|
|
||||||
BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture
|
|
||||||
|
|
||||||
// Create a new BufferedImage for the part of the skin texture
|
|
||||||
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
|
||||||
|
|
||||||
// Crop just the part we want based on our x, y, width, and height
|
|
||||||
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
|
|
||||||
|
|
||||||
// Scale the skin part texture
|
|
||||||
double scale = (double) size / width;
|
|
||||||
AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
|
|
||||||
headTexture = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(headTexture, null);
|
|
||||||
|
|
||||||
// Convert BufferedImage to byte array
|
|
||||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
|
||||||
ImageIO.write(headTexture, "png", outputStream);
|
|
||||||
outputStream.flush();
|
|
||||||
return outputStream.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.renderer;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import me.braydon.mc.common.ImageUtils;
|
||||||
|
import me.braydon.mc.model.Skin;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A renderer for a {@link Skin.Part}.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
* @param <T> the type of part to render
|
||||||
|
*/
|
||||||
|
public abstract class SkinPartRenderer<T extends Skin.IPart> {
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
@NonNull public abstract BufferedImage render(@NonNull Skin skin, @NonNull T part, boolean overlays, int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the texture of a part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to get the part texture from
|
||||||
|
* @param part the part of the skin to get
|
||||||
|
* @param size the size to scale the texture to
|
||||||
|
* @return the texture of the skin part
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
protected BufferedImage getSkinPart(@NonNull Skin skin, @NonNull Skin.Part part, double size) {
|
||||||
|
return getSkinPartTexture(skin, part.getCoordinates().getX(), part.getCoordinates().getY(), part.getWidth(), part.getHeight(), size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the texture of a specific part of a skin.
|
||||||
|
*
|
||||||
|
* @param skin the skin to get the part from
|
||||||
|
* @param x the x position of the part
|
||||||
|
* @param y the y position of the part
|
||||||
|
* @param width the width of the part
|
||||||
|
* @param height the height of the part
|
||||||
|
* @param size the size to scale the part to
|
||||||
|
* @return the texture of the skin part
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
private BufferedImage getSkinPartTexture(@NonNull Skin skin, int x, int y, int width, int height, double size) {
|
||||||
|
BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture
|
||||||
|
|
||||||
|
// Create a new BufferedImage for the part of the skin texture
|
||||||
|
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
// Crop just the part we want based on our x, y, width, and height
|
||||||
|
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
|
||||||
|
|
||||||
|
// Scale the skin part texture
|
||||||
|
headTexture = ImageUtils.resize(headTexture, size);
|
||||||
|
|
||||||
|
return headTexture;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.renderer.impl;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.mc.common.renderer.SkinPartRenderer;
|
||||||
|
import me.braydon.mc.model.Skin;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic 2D renderer for a {@link Skin.Part}.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class BasicSkinPartRenderer extends SkinPartRenderer<Skin.Part> {
|
||||||
|
public static final BasicSkinPartRenderer INSTANCE = new BasicSkinPartRenderer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Skin.Part part, boolean overlays, int size) {
|
||||||
|
return getSkinPart(skin, part, size / 8D);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.renderer.impl;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.braydon.mc.common.renderer.SkinPartRenderer;
|
||||||
|
import me.braydon.mc.model.Skin;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A isometric 3D renderer for a {@link Skin.Part}.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
public final class IsometricSkinPartRenderer extends SkinPartRenderer<Skin.IsometricPart> {
|
||||||
|
public static final IsometricSkinPartRenderer INSTANCE = new IsometricSkinPartRenderer();
|
||||||
|
|
||||||
|
private static final double SKEW_A = 26D / 45D; // 0.57777777
|
||||||
|
private static final double SKEW_B = SKEW_A * 2D; // 1.15555555
|
||||||
|
|
||||||
|
private static final AffineTransform HEAD_TOP_TRANSFORM = new AffineTransform(1D, -SKEW_A, 1, SKEW_A, 0, 0);
|
||||||
|
private static final AffineTransform FACE_TRANSFORM = new AffineTransform(1D, -SKEW_A, 0D, SKEW_B, 0d, SKEW_A);
|
||||||
|
private static final AffineTransform HEAD_LEFT_TRANSFORM = new AffineTransform(1D, SKEW_A, 0D, SKEW_B, 0D, 0D);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 Skin.IsometricPart part, boolean overlays, int size) {
|
||||||
|
double scale = (size / 8D) / 2.5;
|
||||||
|
double zOffset = scale * 3.5D;
|
||||||
|
double xOffset = scale * 2D;
|
||||||
|
|
||||||
|
BufferedImage texture = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D graphics = texture.createGraphics();
|
||||||
|
|
||||||
|
BufferedImage headTop = getSkinPart(skin, Skin.Part.HEAD_TOP, scale);
|
||||||
|
BufferedImage face = getSkinPart(skin, Skin.Part.FACE, scale);
|
||||||
|
BufferedImage headLeft = getSkinPart(skin, Skin.Part.HEAD_LEFT, scale);
|
||||||
|
|
||||||
|
// Draw the top of the left
|
||||||
|
drawPart(graphics, headTop, HEAD_TOP_TRANSFORM, -0.5 - zOffset, xOffset + zOffset, headTop.getWidth(), headTop.getHeight() + 2);
|
||||||
|
|
||||||
|
// Draw the face of the head
|
||||||
|
double x = xOffset + 8 * scale;
|
||||||
|
drawPart(graphics, face, FACE_TRANSFORM, x, x + zOffset - 0.5, face.getWidth(), face.getHeight());
|
||||||
|
|
||||||
|
// Draw the left side of the head
|
||||||
|
drawPart(graphics, headLeft, HEAD_LEFT_TRANSFORM, xOffset + 1, zOffset - 0.5, headLeft.getWidth(), headLeft.getHeight());
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a part onto the texture.
|
||||||
|
*
|
||||||
|
* @param graphics the graphics to draw to
|
||||||
|
* @param partImage the part image to draw
|
||||||
|
* @param transform the transform to apply
|
||||||
|
* @param x the x position to draw at
|
||||||
|
* @param y the y position to draw at
|
||||||
|
* @param width the part image width
|
||||||
|
* @param height the part image height
|
||||||
|
*/
|
||||||
|
private void drawPart(@NonNull Graphics2D graphics, @NonNull BufferedImage partImage, @NonNull AffineTransform transform,
|
||||||
|
double x, double y, int width, int height) {
|
||||||
|
graphics.setTransform(transform);
|
||||||
|
graphics.drawImage(partImage, (int) x, (int) y, width, height, null);
|
||||||
|
}
|
||||||
|
}
|
@ -26,11 +26,11 @@ package me.braydon.mc.model;
|
|||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import me.braydon.mc.common.ImageUtils;
|
|
||||||
import me.braydon.mc.config.AppConfig;
|
import me.braydon.mc.config.AppConfig;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A skin for a {@link Player}.
|
* A skin for a {@link Player}.
|
||||||
@ -64,8 +64,14 @@ public final class Skin {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public Skin populatePartUrls(@NonNull String playerUuid) {
|
public Skin populatePartUrls(@NonNull String playerUuid) {
|
||||||
for (Part part : Part.values()) {
|
Consumer<IPart> addPart = part -> {
|
||||||
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
|
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
|
||||||
|
};
|
||||||
|
for (Part part : Part.values()) {
|
||||||
|
addPart.accept(part);
|
||||||
|
}
|
||||||
|
for (IsometricPart part : IsometricPart.values()) {
|
||||||
|
addPart.accept(part);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -93,28 +99,91 @@ public final class Skin {
|
|||||||
* Possible models for a skin.
|
* Possible models for a skin.
|
||||||
*/
|
*/
|
||||||
public enum Model {
|
public enum Model {
|
||||||
SLIM, DEFAULT
|
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.
|
* The part of a skin.
|
||||||
*/
|
*/
|
||||||
@AllArgsConstructor @Getter @ToString
|
@AllArgsConstructor @RequiredArgsConstructor @Getter @ToString
|
||||||
public enum Part {
|
public enum Part implements IPart {
|
||||||
HEAD_TOP(8, 0, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 8),
|
// Head
|
||||||
FACE(8, 8, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 8),
|
HEAD_TOP(new Coordinates(8, 0), 8, 8),
|
||||||
HEAD_LEFT(0, 8, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 8),
|
FACE(new Coordinates(8, 8), 8, 8),
|
||||||
HEAD_RIGHT(16, 8, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 8),
|
HEAD_LEFT(new Coordinates(0, 8), 8, 8),
|
||||||
HEAD_BOTTOM(8, 8, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 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.
|
* The coordinates of this part.
|
||||||
*/
|
*/
|
||||||
private final int x, y;
|
@NonNull private final Coordinates coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The legacy coordinates of this part.
|
||||||
|
* <p>
|
||||||
|
* This is for older skin textures
|
||||||
|
* that use different positions.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
private LegacyCoordinates legacyCoordinates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of this part.
|
* The size of this part.
|
||||||
*/
|
*/
|
||||||
private final int width, height;
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -32,6 +32,8 @@ import lombok.NonNull;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
import me.braydon.mc.common.*;
|
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.JsonWebException;
|
||||||
import me.braydon.mc.common.web.JsonWebRequest;
|
import me.braydon.mc.common.web.JsonWebRequest;
|
||||||
import me.braydon.mc.exception.impl.BadRequestException;
|
import me.braydon.mc.exception.impl.BadRequestException;
|
||||||
@ -60,6 +62,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -163,8 +168,26 @@ public final class MojangService {
|
|||||||
* @throws BadRequestException if the extension is invalid
|
* @throws BadRequestException if the extension is invalid
|
||||||
* @throws MojangRateLimitException if the Mojang API rate limit is reached
|
* @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)
|
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension, String sizeString)
|
||||||
throws BadRequestException, MojangRateLimitException {
|
throws BadRequestException, MojangRateLimitException {
|
||||||
|
partName = partName.toUpperCase(); // The part name to get
|
||||||
|
|
||||||
|
// 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);;
|
||||||
|
}
|
||||||
|
if (part == null) { // Default to the face
|
||||||
|
part = Skin.Part.FACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the extension is valid
|
||||||
|
if (extension.isBlank()) {
|
||||||
|
throw new BadRequestException("Invalid extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the size of the part
|
||||||
Integer size = null;
|
Integer size = null;
|
||||||
if (sizeString != null) { // Attempt to parse the size
|
if (sizeString != null) { // Attempt to parse the size
|
||||||
try {
|
try {
|
||||||
@ -173,13 +196,6 @@ public final class MojangService {
|
|||||||
// Safely ignore, invalid number provided
|
// Safely ignore, invalid number provided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Skin.Part part = EnumUtils.getEnumConstant(Skin.Part.class, partName.toUpperCase()); // The skin part to get
|
|
||||||
if (part == null) { // Default to the head part
|
|
||||||
part = Skin.Part.FACE;
|
|
||||||
}
|
|
||||||
if (extension.isBlank()) { // Invalid extension
|
|
||||||
throw new BadRequestException("Invalid extension");
|
|
||||||
}
|
|
||||||
if (size == null || size <= 0) { // Invalid size
|
if (size == null || size <= 0) { // Invalid size
|
||||||
size = DEFAULT_PART_TEXTURE_SIZE;
|
size = DEFAULT_PART_TEXTURE_SIZE;
|
||||||
}
|
}
|
||||||
@ -201,9 +217,19 @@ public final class MojangService {
|
|||||||
if (target == null) { // Fallback to the default skin
|
if (target == null) { // Fallback to the default skin
|
||||||
target = Skin.DEFAULT_STEVE;
|
target = Skin.DEFAULT_STEVE;
|
||||||
}
|
}
|
||||||
byte[] texture = ImageUtils.getSkinPart(target, part, size);
|
BufferedImage texture = part instanceof Skin.IsometricPart isometricPart ?
|
||||||
skinPartTextureCache.save(new CachedSkinPartTexture(id, texture)); // Cache the texture
|
IsometricSkinPartRenderer.INSTANCE.render(target, isometricPart, true, size)
|
||||||
return texture;
|
: BasicSkinPartRenderer.INSTANCE.render(target, (Skin.Part) part, true, size); // Render the skin part
|
||||||
|
|
||||||
|
// 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
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -268,6 +294,7 @@ public final class MojangService {
|
|||||||
uuid, token.getName(),
|
uuid, token.getName(),
|
||||||
skinProperties.getSkin() == null ? Skin.DEFAULT_STEVE : skinProperties.getSkin(),
|
skinProperties.getSkin() == null ? Skin.DEFAULT_STEVE : skinProperties.getSkin(),
|
||||||
skinProperties.getCape(),
|
skinProperties.getCape(),
|
||||||
|
token.getProperties(),
|
||||||
profileActions.length == 0 ? null : profileActions,
|
profileActions.length == 0 ? null : profileActions,
|
||||||
System.currentTimeMillis()
|
System.currentTimeMillis()
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user