add server preview renderer
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 38s
All checks were successful
Deploy App / docker (ubuntu-latest, 2.44.0, 17, 3.8.5) (push) Successful in 38s
This commit is contained in:
@ -3,6 +3,7 @@ package xyz.mcutils.backend.common;
|
||||
import lombok.NonNull;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
@ -93,4 +94,20 @@ public final class ColorUtils {
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a {@link Color} from a Minecraft color code.
|
||||
*
|
||||
* @param colorCode the color code to get the color from
|
||||
* @return the color
|
||||
*/
|
||||
public static Color getMinecraftColor(char colorCode) {
|
||||
String color = COLOR_MAP.getOrDefault(colorCode, null);
|
||||
if (color == null) {
|
||||
System.out.println("Unknown color code: " + colorCode);
|
||||
return Color.WHITE;
|
||||
}
|
||||
return Color.decode(color);
|
||||
}
|
||||
|
||||
}
|
23
src/main/java/xyz/mcutils/backend/common/Fonts.java
Normal file
23
src/main/java/xyz/mcutils/backend/common/Fonts.java
Normal file
@ -0,0 +1,23 @@
|
||||
package xyz.mcutils.backend.common;
|
||||
|
||||
import xyz.mcutils.backend.Main;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Fonts {
|
||||
|
||||
public static final Font MINECRAFT;
|
||||
public static final Font MINECRAFT_BOLD;
|
||||
|
||||
static {
|
||||
InputStream stream = Main.class.getResourceAsStream("/fonts/minecraft-font.ttf");
|
||||
try {
|
||||
MINECRAFT = Font.createFont(Font.TRUETYPE_FONT, stream).deriveFont(18f);
|
||||
MINECRAFT_BOLD = MINECRAFT.deriveFont(Font.BOLD);
|
||||
} catch (FontFormatException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,21 +8,23 @@ import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
@Log4j2(topic = "Image Utils")
|
||||
public class ImageUtils {
|
||||
/**
|
||||
* Scale the given image to the provided size.
|
||||
* Scale the given image to the provided scale.
|
||||
*
|
||||
* @param image the image to scale
|
||||
* @param size the size to scale the image to
|
||||
* @param scale the scale to scale the image to
|
||||
* @return the scaled image
|
||||
*/
|
||||
public static BufferedImage resize(BufferedImage image, double size) {
|
||||
BufferedImage scaled = new BufferedImage((int) (image.getWidth() * size), (int) (image.getHeight() * size), BufferedImage.TYPE_INT_ARGB);
|
||||
public static BufferedImage resize(BufferedImage image, double scale) {
|
||||
BufferedImage scaled = new BufferedImage((int) (image.getWidth() * scale), (int) (image.getHeight() * scale), BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics = scaled.createGraphics();
|
||||
graphics.drawImage(image, AffineTransform.getScaleInstance(size, size), null);
|
||||
graphics.drawImage(image, AffineTransform.getScaleInstance(scale, scale), null);
|
||||
graphics.dispose();
|
||||
return scaled;
|
||||
}
|
||||
@ -56,4 +58,21 @@ public class ImageUtils {
|
||||
throw new Exception("Failed to convert image to bytes", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a base64 string to an image.
|
||||
*
|
||||
* @param base64 the base64 string to convert
|
||||
* @return the image
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static BufferedImage base64ToImage(String base64) {
|
||||
String favicon = base64.contains("data:image/png;base64,") ? base64.split(",")[1] : base64;
|
||||
|
||||
try {
|
||||
return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(favicon)));
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Failed to convert base64 to image", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package xyz.mcutils.backend.common.renderer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public abstract class Renderer<T> {
|
||||
|
||||
/**
|
||||
* Renders the object to the specified size.
|
||||
*
|
||||
* @param input The object to render.
|
||||
* @param size The size to render the object to.
|
||||
*/
|
||||
public abstract BufferedImage render(T input, int size);
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
package xyz.mcutils.backend.common.renderer.impl.misc;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import xyz.mcutils.backend.Main;
|
||||
import xyz.mcutils.backend.common.ColorUtils;
|
||||
import xyz.mcutils.backend.common.Fonts;
|
||||
import xyz.mcutils.backend.common.ImageUtils;
|
||||
import xyz.mcutils.backend.common.renderer.Renderer;
|
||||
import xyz.mcutils.backend.model.server.JavaMinecraftServer;
|
||||
import xyz.mcutils.backend.model.server.MinecraftServer;
|
||||
import xyz.mcutils.backend.service.ServerService;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
@Log4j2
|
||||
public class ServerPreviewRenderer extends Renderer<MinecraftServer> {
|
||||
public static final ServerPreviewRenderer INSTANCE = new ServerPreviewRenderer();
|
||||
private static BufferedImage SERVER_BACKGROUND;
|
||||
private static BufferedImage PING_ICON;
|
||||
static {
|
||||
try {
|
||||
SERVER_BACKGROUND = ImageIO.read(new ByteArrayInputStream(Main.class.getResourceAsStream("/icons/server_background.png").readAllBytes()));
|
||||
PING_ICON = ImageIO.read(new ByteArrayInputStream(Main.class.getResourceAsStream("/icons/ping.png").readAllBytes()));
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to load server preview assets", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private final int fontSize = Fonts.MINECRAFT.getSize();
|
||||
private final int width = 560;
|
||||
private final int height = 64 + 3 + 3;
|
||||
private final int padding = 3;
|
||||
|
||||
@Override
|
||||
public BufferedImage render(MinecraftServer server, int size) {
|
||||
BufferedImage texture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // The texture to return
|
||||
BufferedImage favicon = getServerFavicon(server);
|
||||
BufferedImage background = SERVER_BACKGROUND;
|
||||
|
||||
// Create the graphics for drawing
|
||||
Graphics2D graphics = texture.createGraphics();
|
||||
|
||||
// Set up the font
|
||||
graphics.setFont(Fonts.MINECRAFT);
|
||||
|
||||
// Draw the background
|
||||
for (int backgroundX = 0; backgroundX < width + background.getWidth(); backgroundX += background.getWidth()) {
|
||||
for (int backgroundY = 0; backgroundY < height + background.getHeight(); backgroundY += background.getHeight()) {
|
||||
graphics.drawImage(background, backgroundX, backgroundY, null);
|
||||
}
|
||||
}
|
||||
|
||||
int y = fontSize + 1;
|
||||
int x = 64 + 8;
|
||||
int initialX = x; // Store the initial value of x
|
||||
|
||||
// Draw the favicon
|
||||
graphics.drawImage(favicon, padding, padding, null);
|
||||
|
||||
// Draw the server hostname
|
||||
graphics.setColor(Color.WHITE);
|
||||
graphics.drawString(server.getHostname(), x, y);
|
||||
|
||||
// Draw the server motd
|
||||
y += fontSize + (padding * 2);
|
||||
for (String line : server.getMotd().getRaw()) {
|
||||
int index = 0;
|
||||
int colorIndex = line.indexOf("§");
|
||||
while (colorIndex != -1) {
|
||||
// Draw text before color code
|
||||
String textBeforeColor = line.substring(index, colorIndex);
|
||||
graphics.drawString(textBeforeColor, x, y);
|
||||
// Calculate width of text before color code
|
||||
int textWidth = graphics.getFontMetrics().stringWidth(textBeforeColor);
|
||||
// Move x position to after the drawn text
|
||||
x += textWidth;
|
||||
// Set color based on color code
|
||||
char colorCode = line.charAt(colorIndex + 1);
|
||||
|
||||
if (colorCode == 'l') {
|
||||
graphics.setFont(Fonts.MINECRAFT_BOLD);
|
||||
} else {
|
||||
Color color = ColorUtils.getMinecraftColor(colorCode);
|
||||
graphics.setColor(color);
|
||||
graphics.setFont(Fonts.MINECRAFT);
|
||||
}
|
||||
|
||||
// Move index to after the color code
|
||||
index = colorIndex + 2;
|
||||
// Find next color code
|
||||
colorIndex = line.indexOf("§", index);
|
||||
}
|
||||
// Draw remaining text
|
||||
String remainingText = line.substring(index);
|
||||
graphics.drawString(remainingText, x, y);
|
||||
// Move to the next line
|
||||
y += fontSize + padding;
|
||||
// Reset x position for the next line
|
||||
x = initialX; // Reset x to its initial value
|
||||
}
|
||||
|
||||
// Ensure the font is reset
|
||||
graphics.setFont(Fonts.MINECRAFT);
|
||||
|
||||
// Render the ping
|
||||
BufferedImage pingIcon = ImageUtils.resize(PING_ICON, 2);
|
||||
x = width - pingIcon.getWidth() - padding;
|
||||
graphics.drawImage(pingIcon, x, padding, null);
|
||||
|
||||
// Reset the y position
|
||||
y = fontSize + 1;
|
||||
|
||||
// Render the player count
|
||||
MinecraftServer.Players players = server.getPlayers();
|
||||
String playersOnline = players.getOnline() + "";
|
||||
String playersMax = players.getMax() + "";
|
||||
|
||||
// Calculate the width of each player count element
|
||||
int maxWidth = graphics.getFontMetrics().stringWidth(playersMax);
|
||||
int slashWidth = graphics.getFontMetrics().stringWidth("/");
|
||||
int onlineWidth = graphics.getFontMetrics().stringWidth(playersOnline);
|
||||
|
||||
// Calculate the total width of the player count string
|
||||
int totalWidth = maxWidth + slashWidth + onlineWidth;
|
||||
|
||||
// Calculate the starting x position
|
||||
int startX = (width - totalWidth) - pingIcon.getWidth() - 6;
|
||||
|
||||
// Render the player count elements
|
||||
graphics.setColor(Color.LIGHT_GRAY);
|
||||
graphics.drawString(playersOnline, startX, y);
|
||||
startX += onlineWidth;
|
||||
graphics.setColor(Color.DARK_GRAY);
|
||||
graphics.drawString("/", startX, y);
|
||||
startX += slashWidth;
|
||||
graphics.setColor(Color.LIGHT_GRAY);
|
||||
graphics.drawString(playersMax, startX, y);
|
||||
|
||||
return ImageUtils.resize(texture, (double) size / width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the favicon of a server.
|
||||
*
|
||||
* @param server the server to get the favicon of
|
||||
* @return the server favicon
|
||||
*/
|
||||
public BufferedImage getServerFavicon(MinecraftServer server) {
|
||||
String favicon = null;
|
||||
|
||||
// Get the server favicon
|
||||
if (server instanceof JavaMinecraftServer javaServer) {
|
||||
if (javaServer.getFavicon() != null) {
|
||||
favicon = javaServer.getFavicon().getBase64();
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to the default server icon
|
||||
if (favicon == null) {
|
||||
favicon = ServerService.DEFAULT_SERVER_ICON;
|
||||
}
|
||||
return ImageUtils.base64ToImage(favicon);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package xyz.mcutils.backend.common.renderer.impl;
|
||||
package xyz.mcutils.backend.common.renderer.impl.skin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
@ -1,4 +1,4 @@
|
||||
package xyz.mcutils.backend.common.renderer.impl;
|
||||
package xyz.mcutils.backend.common.renderer.impl.skin;
|
||||
|
||||
import xyz.mcutils.backend.common.renderer.IsometricSkinRenderer;
|
||||
import xyz.mcutils.backend.model.skin.ISkinPart;
|
@ -1,4 +1,4 @@
|
||||
package xyz.mcutils.backend.common.renderer.impl;
|
||||
package xyz.mcutils.backend.common.renderer.impl.skin;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
Reference in New Issue
Block a user