From d797f1d7b69f8d200f321c546a4d59e401968add Mon Sep 17 00:00:00 2001 From: Cerus Date: Sun, 12 Jun 2022 17:20:50 +0200 Subject: [PATCH 1/4] Implement filters --- .../java/dev/cerus/mcheadrender/Launcher.java | 5 +++ .../dev/cerus/mcheadrender/filter/Filter.java | 32 +++++++++++++++++++ .../mcheadrender/filter/GrayscaleFilter.java | 28 ++++++++++++++++ .../mcheadrender/filter/InvertFilter.java | 32 +++++++++++++++++++ .../dev/cerus/mcheadrender/web/WebServer.java | 12 ++++++- .../web/route/v1/RenderRouteV1.java | 18 ++++++++++- 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/cerus/mcheadrender/filter/Filter.java create mode 100644 src/main/java/dev/cerus/mcheadrender/filter/GrayscaleFilter.java create mode 100644 src/main/java/dev/cerus/mcheadrender/filter/InvertFilter.java diff --git a/src/main/java/dev/cerus/mcheadrender/Launcher.java b/src/main/java/dev/cerus/mcheadrender/Launcher.java index 9f46796..47472a7 100644 --- a/src/main/java/dev/cerus/mcheadrender/Launcher.java +++ b/src/main/java/dev/cerus/mcheadrender/Launcher.java @@ -2,6 +2,9 @@ import dev.cerus.mcheadrender.config.JsonFileSkinProviderConfig; import dev.cerus.mcheadrender.config.SkinProviderConfig; +import dev.cerus.mcheadrender.filter.Filter; +import dev.cerus.mcheadrender.filter.GrayscaleFilter; +import dev.cerus.mcheadrender.filter.InvertFilter; import dev.cerus.mcheadrender.image.DefaultImageCache; import dev.cerus.mcheadrender.renderer.FlatRenderer; import dev.cerus.mcheadrender.renderer.IsometricRenderer; @@ -22,6 +25,7 @@ public static void main(final String[] args) throws IOException { // Init registries final Registry rendererRegistry = new Registry<>(Renderer::id, "flat", new FlatRenderer(), new IsometricRenderer()); final Registry skinProviderRegistry = new Registry<>(SkinProvider::id, "mojang", new MojangSkinProvider()); + final Registry filterRegistry = new Registry<>(Filter::id, null, new GrayscaleFilter(), new InvertFilter()); // Init skin provider config final SkinProviderConfig skinProviderConfig = new JsonFileSkinProviderConfig(new File("skin_provider.json")); @@ -31,6 +35,7 @@ public static void main(final String[] args) throws IOException { final WebServer webServer = new WebServer( rendererRegistry, skinProviderRegistry, + filterRegistry, new DefaultImageCache() ); webServer.start(Globals.API_HOST, Globals.API_PORT); diff --git a/src/main/java/dev/cerus/mcheadrender/filter/Filter.java b/src/main/java/dev/cerus/mcheadrender/filter/Filter.java new file mode 100644 index 0000000..381627e --- /dev/null +++ b/src/main/java/dev/cerus/mcheadrender/filter/Filter.java @@ -0,0 +1,32 @@ +package dev.cerus.mcheadrender.filter; + +import java.awt.image.BufferedImage; + +/** + * Applies some sort of visual effect onto the final image + */ +public abstract class Filter { + + /** + * Apply the filter + * + * @param img The image that should be filtered + */ + public abstract void apply(BufferedImage img); + + public abstract String id(); + + @Override + public int hashCode() { + return this.id().hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if (!(obj instanceof Filter)) { + return false; + } + return obj == this || ((Filter) obj).id().equals(this.id()); + } + +} diff --git a/src/main/java/dev/cerus/mcheadrender/filter/GrayscaleFilter.java b/src/main/java/dev/cerus/mcheadrender/filter/GrayscaleFilter.java new file mode 100644 index 0000000..a1feef6 --- /dev/null +++ b/src/main/java/dev/cerus/mcheadrender/filter/GrayscaleFilter.java @@ -0,0 +1,28 @@ +package dev.cerus.mcheadrender.filter; + +import java.awt.Color; +import java.awt.image.BufferedImage; + +/** + * Grayscales an image + */ +public class GrayscaleFilter extends Filter { + + @Override + public void apply(final BufferedImage img) { + for (int x = 0; x < img.getWidth(); x++) { + for (int y = 0; y < img.getHeight(); y++) { + final int pixel = img.getRGB(x, y); + final Color color = new Color(pixel, true); + final int avg = (color.getRed() + color.getGreen() + color.getBlue()) / 3; + img.setRGB(x, y, new Color(avg, avg, avg, color.getAlpha()).getRGB()); + } + } + } + + @Override + public String id() { + return "grayscale"; + } + +} diff --git a/src/main/java/dev/cerus/mcheadrender/filter/InvertFilter.java b/src/main/java/dev/cerus/mcheadrender/filter/InvertFilter.java new file mode 100644 index 0000000..041994b --- /dev/null +++ b/src/main/java/dev/cerus/mcheadrender/filter/InvertFilter.java @@ -0,0 +1,32 @@ +package dev.cerus.mcheadrender.filter; + +import java.awt.Color; +import java.awt.image.BufferedImage; + +/** + * Inverts an image + */ +public class InvertFilter extends Filter { + + @Override + public void apply(final BufferedImage img) { + for (int x = 0; x < img.getWidth(); x++) { + for (int y = 0; y < img.getHeight(); y++) { + final int pixel = img.getRGB(x, y); + final Color color = new Color(pixel, true); + img.setRGB(x, y, new Color( + 255 - color.getRed(), + 255 - color.getGreen(), + 255 - color.getBlue(), + color.getAlpha() + ).getRGB()); + } + } + } + + @Override + public String id() { + return "invert"; + } + +} diff --git a/src/main/java/dev/cerus/mcheadrender/web/WebServer.java b/src/main/java/dev/cerus/mcheadrender/web/WebServer.java index f1d4992..6d27051 100644 --- a/src/main/java/dev/cerus/mcheadrender/web/WebServer.java +++ b/src/main/java/dev/cerus/mcheadrender/web/WebServer.java @@ -1,5 +1,6 @@ package dev.cerus.mcheadrender.web; +import dev.cerus.mcheadrender.filter.Filter; import dev.cerus.mcheadrender.image.ImageCache; import dev.cerus.mcheadrender.renderer.Renderer; import dev.cerus.mcheadrender.skin.SkinProvider; @@ -17,11 +18,16 @@ public class WebServer { private final Registry rendererRegistry; private final Registry skinProviderRegistry; private final ImageCache imageCache; + private final Registry filterRegistry; private Javalin app; - public WebServer(final Registry rendererRegistry, final Registry skinProviderRegistry, final ImageCache imageCache) { + public WebServer(final Registry rendererRegistry, + final Registry skinProviderRegistry, + final Registry filterRegistry, + final ImageCache imageCache) { this.rendererRegistry = rendererRegistry; this.skinProviderRegistry = skinProviderRegistry; + this.filterRegistry = filterRegistry; this.imageCache = imageCache; } @@ -66,4 +72,8 @@ public Registry getSkinProviderRegistry() { return this.skinProviderRegistry; } + public Registry getFilterRegistry() { + return this.filterRegistry; + } + } diff --git a/src/main/java/dev/cerus/mcheadrender/web/route/v1/RenderRouteV1.java b/src/main/java/dev/cerus/mcheadrender/web/route/v1/RenderRouteV1.java index ae32b15..1cb0ee5 100644 --- a/src/main/java/dev/cerus/mcheadrender/web/route/v1/RenderRouteV1.java +++ b/src/main/java/dev/cerus/mcheadrender/web/route/v1/RenderRouteV1.java @@ -2,6 +2,7 @@ import dev.cerus.mcheadrender.Globals; import static dev.cerus.mcheadrender.Globals.LOGGER; +import dev.cerus.mcheadrender.filter.Filter; import dev.cerus.mcheadrender.image.ImageCache; import dev.cerus.mcheadrender.renderer.Renderer; import dev.cerus.mcheadrender.skin.SkinProvider; @@ -15,8 +16,10 @@ import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Objects; import javax.imageio.ImageIO; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -88,6 +91,17 @@ public void handle(@NotNull final Context ctx) throws Exception { } final BufferedImage renderedHead = renderer.render(image, size, overlay); + + final List filters = ctx.queryParams("filter").stream() + .flatMap(s -> Arrays.stream(s.split(","))) + .map(s -> this.webServer.getFilterRegistry().get(s)) + .filter(Objects::nonNull) + .distinct() + .toList(); + for (final Filter filter : filters) { + filter.apply(renderedHead); + } + this.img(ctx, renderedHead); } @@ -112,7 +126,9 @@ public void handle(@NotNull final Context ctx) throws Exception { new QueryParam("skin", "The skin provider", String.class, true, List.copyOf(this.webServer.getSkinProviderRegistry().ids()), null, "mojang"), new QueryParam("renderer", "The renderer", String.class, true, - List.of("flat", "isometric"), null, "flat"), + List.copyOf(this.webServer.getRendererRegistry().ids()), null, "flat"), + new QueryParam("filter", "The filters (concatenate filters with ,)", String.class, true, + List.copyOf(this.webServer.getFilterRegistry().ids()), null, null), new QueryParam("size", "The desired width and height", int.class, true, null, List.of("128", "512", "16"), "8"), new QueryParam("overlay", "Whether the hat overlay should be rendered or not", From a9b7159ad9ee8380afc5eafa296181240b0ee933 Mon Sep 17 00:00:00 2001 From: Cerus Date: Sun, 12 Jun 2022 17:21:12 +0200 Subject: [PATCH 2/4] Bump version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 854d033..9775369 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ dev.cerus mc-head-render - 1.0.1 + 1.0.2 17 From 522c0d097e3c561df0cf3f881cb16269f1f2ed94 Mon Sep 17 00:00:00 2001 From: Cerus Date: Sun, 12 Jun 2022 17:35:43 +0200 Subject: [PATCH 3/4] Adjust README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4e13d40..712a970 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,12 @@ Example usage: Rendered image +
+ Render a flat head using a Minotar skin of size 128x128 with a grayscale filter + https://mchr.cerus.dev/v1/render/Cerus_?skin=minotar&size=128&filter=grayscale
+ Rendered image +
+ ## Installation ### Using docker-compose From b6857387b4f7654300b985fbe532cb0cfc4ad3d5 Mon Sep 17 00:00:00 2001 From: Cerus Date: Sun, 12 Jun 2022 17:37:47 +0200 Subject: [PATCH 4/4] Adjust README --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 712a970..1b6638c 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,9 @@ Query params: - Whether the hat overlay should be rendered or not - Allowed values: `true`, `false` - Default: `true` +- (Optional) `filter` + - The filters (concatenate filters with ,) + - Allowed values: id of a registered filter **GET /skins**\ List all available skin providers