package games.strategy.triplea.image; import java.awt.Color; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; /** * Class used to transform images including colorizing and flipping. Colorizing is based on finding * the luminance from the original image then applying hue, saturation, and brightness to colorize * it. More information about luminance can be found here: * https://en.wikipedia.org/wiki/Relative_luminance. */ public class ImageTransformer { private static final int MAX_COLOR = 256; private static final float LUMINANCE_RED = 0.2126f; private static final float LUMINANCE_GREEN = 0.7152f; private static final float LUMINANCE_BLUE = 0.0722f; private ImageTransformer() {} /** * Apply color and brightness to the given image. This uses the hue and saturation of the given * color. It ignores the brightness of the color and uses the given brightness value. */ public static void colorize(final Color color, final int brightness, final BufferedImage image) { final float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); colorize((int) (hsb[0] * 360), (int) (hsb[1] * 100), brightness, image); } /** * Apply HSB values to the given image. * * @param hue 0 to 360 * @param saturation 0 to 100 * @param brightness -100 to 100, which adjusts the given image's brightness * @param image the image to transform */ private static void colorize( final int hue, final int saturation, final int brightness, final BufferedImage image) { // Create color lookup table for luminance values final int[] redLookup = new int[MAX_COLOR]; final int[] greenLookup = new int[MAX_COLOR]; final int[] blueLookup = new int[MAX_COLOR]; final float hueFloat = hue / 360f; final float saturationFloat = saturation / 100f; for (int i = 0; i < MAX_COLOR; i++) { final float brightnessFloat = (float) i / (MAX_COLOR - 1); final Color color = Color.getHSBColor(hueFloat, saturationFloat, brightnessFloat); redLookup[i] = color.getRed(); greenLookup[i] = color.getGreen(); blueLookup[i] = color.getBlue(); } // Loop through image to colorize each pixel for (int x = 0; x < image.getWidth(); x++) { for (int y = 0; y < image.getHeight(); y++) { final Color color = new Color(image.getRGB(x, y), true); // Don't worry about colorization if pixel is transparent if (color.getAlpha() == 0) { continue; } // Avoid colorization if pixel is close to black so image stays sharp final float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); if (hsb[2] < 0.1) { continue; } // Find luminance and use lookup table to set resulting color final int lum = findLuminance(color, brightness); final Color finalColor = new Color(redLookup[lum], greenLookup[lum], blueLookup[lum], color.getAlpha()); image.setRGB(x, y, finalColor.getRGB()); } } } private static int findLuminance(final Color color, final int brightness) { int lum = (int) (color.getRed() * LUMINANCE_RED + color.getGreen() * LUMINANCE_GREEN + color.getBlue() * LUMINANCE_BLUE); if (brightness > 0) { lum = (int) (lum * (100f - brightness) / 100f); lum = (int) (lum + (255f - (100f - brightness) * 255f / 100f)); } else if (brightness < 0) { lum = (int) ((lum * (brightness + 100f)) / 100f); } return lum; } public static BufferedImage flipHorizontally(final BufferedImage image) { final AffineTransform tx = AffineTransform.getScaleInstance(-1, 1); tx.translate(-image.getWidth(null), 0); final AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR); return op.filter(image, null); } }