From b594f79a2b9188e2939e37565f93e6d1c2a5edf2 Mon Sep 17 00:00:00 2001 From: filoghost Date: Sun, 27 Jun 2021 23:51:08 +0200 Subject: [PATCH] Refactor ImageMessage --- .../commands/subs/ReadimageCommand.java | 6 +- .../plugin/image/ImageMessage.java | 265 +++++++++--------- .../plugin/image/ImageTooWideException.java | 10 - 3 files changed, 127 insertions(+), 154 deletions(-) delete mode 100644 plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageTooWideException.java diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/commands/subs/ReadimageCommand.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/commands/subs/ReadimageCommand.java index 95fc3a18..3a291648 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/commands/subs/ReadimageCommand.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/commands/subs/ReadimageCommand.java @@ -10,10 +10,10 @@ import me.filoghost.fcommons.command.sub.SubCommandContext; import me.filoghost.fcommons.command.validation.CommandException; import me.filoghost.fcommons.command.validation.CommandValidate; import me.filoghost.fcommons.logging.Log; -import me.filoghost.holographicdisplays.plugin.format.ColorScheme; import me.filoghost.holographicdisplays.plugin.commands.HologramCommandValidate; import me.filoghost.holographicdisplays.plugin.disk.ConfigManager; import me.filoghost.holographicdisplays.plugin.event.InternalHologramEditEvent; +import me.filoghost.holographicdisplays.plugin.format.ColorScheme; import me.filoghost.holographicdisplays.plugin.format.DisplayFormat; import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalHologram; import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalHologramManager; @@ -21,7 +21,6 @@ import me.filoghost.holographicdisplays.plugin.hologram.internal.InternalTextLin import me.filoghost.holographicdisplays.plugin.image.ImageMessage; import me.filoghost.holographicdisplays.plugin.image.ImageReadException; import me.filoghost.holographicdisplays.plugin.image.ImageReader; -import me.filoghost.holographicdisplays.plugin.image.ImageTooWideException; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @@ -87,6 +86,7 @@ public class ReadimageCommand extends LineEditingCommand { int width = CommandValidate.parseInteger(args[2]); CommandValidate.check(width >= 2, "The width of the image must be 2 or greater."); + CommandValidate.check(width <= 150, "The width of the image must be 150 or lower."); boolean isUrl = false; @@ -134,8 +134,6 @@ public class ReadimageCommand extends LineEditingCommand { } catch (MalformedURLException e) { throw new CommandException("The provided URL was not valid."); - } catch (ImageTooWideException e) { - throw new CommandException("The image is too large. Max width allowed is " + ImageMessage.MAX_WIDTH + " pixels."); } catch (ImageReadException e) { throw new CommandException("The plugin was unable to read the image. Be sure that the format is supported."); } catch (IOException e) { diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageMessage.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageMessage.java index 9e4b5c02..8a8095ca 100644 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageMessage.java +++ b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageMessage.java @@ -7,194 +7,179 @@ package me.filoghost.holographicdisplays.plugin.image; import me.filoghost.holographicdisplays.plugin.disk.Settings; import org.bukkit.ChatColor; +import org.jetbrains.annotations.Nullable; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; /* * Credits: https://forums.bukkit.org/threads/lib-imagemessage-v2-1-send-images-to-players-via-the-chat.204902 */ public class ImageMessage { - public static final int MAX_WIDTH = 150; + private static final List COLORS = Arrays.asList( + new ColorMapping(new Color(0, 0, 170), ChatColor.DARK_BLUE), + new ColorMapping(new Color(0, 170, 0), ChatColor.DARK_GREEN), + new ColorMapping(new Color(0, 170, 170), ChatColor.DARK_AQUA), + new ColorMapping(new Color(170, 0, 0), ChatColor.DARK_RED), + new ColorMapping(new Color(170, 0, 170), ChatColor.DARK_PURPLE), + new ColorMapping(new Color(255, 170, 0), ChatColor.GOLD), + new ColorMapping(new Color(85, 85, 255), ChatColor.BLUE), + new ColorMapping(new Color(85, 255, 85), ChatColor.GREEN), + new ColorMapping(new Color(85, 255, 255), ChatColor.AQUA), + new ColorMapping(new Color(255, 85, 85), ChatColor.RED), + new ColorMapping(new Color(255, 85, 255), ChatColor.LIGHT_PURPLE), + new ColorMapping(new Color(255, 255, 85), ChatColor.YELLOW) + ); - private static final Map colorsMap = new HashMap<>(); - private static final Map graysMap = new HashMap<>(); + private static final List GRAYS = Arrays.asList( + new ColorMapping(new Color(0, 0, 0), ChatColor.BLACK), + new ColorMapping(new Color(85, 85, 85), ChatColor.DARK_GRAY), + new ColorMapping(new Color(170, 170, 170), ChatColor.GRAY), + new ColorMapping(new Color(255, 255, 255), ChatColor.WHITE) + ); - static { - colorsMap.put(ChatColor.DARK_BLUE, new Color(0, 0, 170)); - colorsMap.put(ChatColor.DARK_GREEN, new Color(0, 170, 0)); - colorsMap.put(ChatColor.DARK_AQUA, new Color(0, 170, 170)); - colorsMap.put(ChatColor.DARK_RED, new Color(170, 0, 0)); - colorsMap.put(ChatColor.DARK_PURPLE, new Color(170, 0, 170)); - colorsMap.put(ChatColor.GOLD, new Color(255, 170, 0)); - colorsMap.put(ChatColor.BLUE, new Color(85, 85, 255)); - colorsMap.put(ChatColor.GREEN, new Color(85, 255, 85)); - colorsMap.put(ChatColor.AQUA, new Color(85, 255, 255)); - colorsMap.put(ChatColor.RED, new Color(255, 85, 85)); - colorsMap.put(ChatColor.LIGHT_PURPLE, new Color(255, 85, 255)); - colorsMap.put(ChatColor.YELLOW, new Color(255, 255, 85)); + private final List lines; - graysMap.put(ChatColor.BLACK, new Color(0, 0, 0)); - graysMap.put(ChatColor.DARK_GRAY, new Color(85, 85, 85)); - graysMap.put(ChatColor.GRAY, new Color(170, 170, 170)); - graysMap.put(ChatColor.WHITE, new Color(255, 255, 255)); + public ImageMessage(BufferedImage image, int width) { + this.lines = toChatLines(resizeImage(image, width)); } + private List toChatLines(BufferedImage image) { + List lines = new ArrayList<>(image.getHeight()); + ChatColor transparencyColor = Settings.transparencyColor; + String transparencySymbol = Settings.transparencySymbol; + String imageSymbol = Settings.imageSymbol; - private final String[] lines; + for (int y = 0; y < image.getHeight(); y++) { + StringBuilder line = new StringBuilder(); + ChatColor previousColor = null; - public ImageMessage(BufferedImage image, int width) throws ImageTooWideException { - ChatColor[][] chatColors = toChatColorArray(image, width); - this.lines = toImgMessage(chatColors); + for (int x = 0; x < image.getWidth(); x++) { + ChatColor currentColor = getClosestChatColor(image, x, y); + String symbol; + + if (currentColor == null) { + // Use the transparent char + currentColor = transparencyColor; + symbol = transparencySymbol; + } else { + symbol = imageSymbol; + } + + if (currentColor != previousColor) { + // Append the different color and save it + line.append(currentColor); + previousColor = currentColor; + } + line.append(symbol); + } + + lines.add(line.toString()); + } + + return lines; } - private ChatColor[][] toChatColorArray(BufferedImage image, int width) throws ImageTooWideException { + private BufferedImage resizeImage(BufferedImage image, int width) { double ratio = (double) image.getHeight() / image.getWidth(); int height = (int) (width * ratio); if (height == 0) { height = 1; } - - if (width > MAX_WIDTH) { - throw new ImageTooWideException(); - } - - BufferedImage resized = resizeImage(image, width, height); - - ChatColor[][] chatImg = new ChatColor[resized.getWidth()][resized.getHeight()]; - for (int x = 0; x < resized.getWidth(); x++) { - for (int y = 0; y < resized.getHeight(); y++) { - int rgb = resized.getRGB(x, y); - chatImg[x][y] = getClosestChatColor(new Color(rgb, true)); - } - } - return chatImg; - } - - private String[] toImgMessage(ChatColor[][] colors) { - String[] lines = new String[colors[0].length]; - ChatColor transparencyColor = Settings.transparencyColor; - String transparencySymbol = Settings.transparencySymbol; - String imageSymbol = Settings.imageSymbol; - - for (int y = 0; y < colors[0].length; y++) { - StringBuilder line = new StringBuilder(); - ChatColor previous = ChatColor.RESET; - - for (int x = 0; x < colors.length; x++) { - ChatColor currentColor = colors[x][y]; - - if (currentColor == null) { - // Use the transparent char - if (previous != transparencyColor) { - // Change the previous chat color and append the newer - line.append(transparencyColor); - previous = transparencyColor; - } - line.append(transparencySymbol); - - } else { - if (previous != currentColor) { - line.append(currentColor.toString()); - previous = currentColor; - } - - line.append(imageSymbol); - } - } - - lines[y] = line.toString(); - } - - return lines; - } - - private BufferedImage resizeImage(BufferedImage originalImage, int width, int height) { - return toBufferedImage(originalImage.getScaledInstance(width, height, Image.SCALE_DEFAULT)); + return toBufferedImage(image.getScaledInstance(width, height, Image.SCALE_DEFAULT)); } private BufferedImage toBufferedImage(Image img) { // Creates a buffered image with transparency - BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB); + BufferedImage bufferedImage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB); // Draws the image on to the buffered image - Graphics2D graphics = bimage.createGraphics(); + Graphics2D graphics = bufferedImage.createGraphics(); graphics.drawImage(img, 0, 0, null); graphics.dispose(); - // Returns the buffered image - return bimage; + return bufferedImage; } - private double getDistance(Color c1, Color c2) { - double rmean = (c1.getRed() + c2.getRed()) / 2.0; - double r = c1.getRed() - c2.getRed(); - double g = c1.getGreen() - c2.getGreen(); - int b = c1.getBlue() - c2.getBlue(); - double weightR = 2 + rmean / 256.0; - double weightG = 4.0; - double weightB = 2 + (255 - rmean) / 256.0; - return weightR * r * r + weightG * g * g + weightB * b * b; - } - - private boolean areIdentical(Color c1, Color c2) { - return Math.abs(c1.getRed() - c2.getRed()) <= 5 - && Math.abs(c1.getGreen() - c2.getGreen()) <= 5 - && Math.abs(c1.getBlue() - c2.getBlue()) <= 5; - - } - - private ChatColor getClosestChatColor(Color color) { + private @Nullable ChatColor getClosestChatColor(BufferedImage image, int x, int y) { + Color color = new Color(image.getRGB(x, y), true); if (color.getAlpha() < 80) { return null; } - for (Entry entry : colorsMap.entrySet()) { - if (areIdentical(entry.getValue(), color)) { - return entry.getKey(); + for (ColorMapping colorMapping : COLORS) { + if (colorMapping.isIdenticalTo(color)) { + return colorMapping.chatColor; } } - double bestGrayDistance = -1; - ChatColor bestGrayMatch = null; + ColorMapping bestGrayMatch = getClosestColorMapping(GRAYS, color); - for (Entry entry : graysMap.entrySet()) { - double distance = getDistance(color, entry.getValue()); - - if (distance < bestGrayDistance || bestGrayDistance == -1) { - bestGrayDistance = distance; - bestGrayMatch = entry.getKey(); - } + if (bestGrayMatch.getDistance(color) < 17500) { + return bestGrayMatch.chatColor; + } else { + return getClosestColorMapping(COLORS, color).chatColor; } - - if (bestGrayDistance < 17500) { - return bestGrayMatch; - } - - double bestColorDistance = -1; - ChatColor bestColorMatch = null; - - for (Entry entry : colorsMap.entrySet()) { - double distance = getDistance(color, entry.getValue()); - - if (distance < bestColorDistance || bestColorDistance == -1) { - bestColorDistance = distance; - bestColorMatch = entry.getKey(); - } - } - - // Minecraft has 15 colors - return bestColorMatch; } - public String[] getLines() { + private ColorMapping getClosestColorMapping(Collection colorMappings, Color color) { + double bestDistance = 0; + ColorMapping bestMatch = null; + + for (ColorMapping colorMapping : colorMappings) { + double distance = colorMapping.getDistance(color); + + if (bestMatch == null || distance < bestDistance) { + bestMatch = colorMapping; + bestDistance = distance; + } + } + + return bestMatch; + } + + public List getLines() { return lines; } + + private static class ColorMapping { + + private final Color color; + private final ChatColor chatColor; + + ColorMapping(Color color, ChatColor chatColor) { + this.chatColor = chatColor; + this.color = color; + } + + boolean isIdenticalTo(Color otherColor) { + return Math.abs(color.getRed() - otherColor.getRed()) <= 5 + && Math.abs(color.getGreen() - otherColor.getGreen()) <= 5 + && Math.abs(color.getBlue() - otherColor.getBlue()) <= 5; + } + + double getDistance(Color otherColor) { + int redDiff = color.getRed() - otherColor.getRed(); + int greenDiff = color.getGreen() - otherColor.getGreen(); + int blueDiff = color.getBlue() - otherColor.getBlue(); + double redMean = (color.getRed() + otherColor.getRed()) / 2.0; + double redWeight = 2 + redMean / 256.0; + double greenWeight = 4.0; + double blueWeight = 2 + (255 - redMean) / 256.0; + + return redWeight * redDiff * redDiff + + greenWeight * greenDiff * greenDiff + + blueWeight * blueDiff * blueDiff; + } + + } + } diff --git a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageTooWideException.java b/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageTooWideException.java deleted file mode 100644 index 11719a29..00000000 --- a/plugin/src/main/java/me/filoghost/holographicdisplays/plugin/image/ImageTooWideException.java +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (C) filoghost and contributors - * - * SPDX-License-Identifier: GPL-3.0-or-later - */ -package me.filoghost.holographicdisplays.plugin.image; - -public class ImageTooWideException extends Exception { - -}