|
| 1 | +package com.labelzoom.api.util; |
| 2 | + |
| 3 | +import java.awt.*; |
| 4 | +import java.awt.image.BufferedImage; |
| 5 | +import java.awt.image.ColorModel; |
| 6 | +import java.awt.image.WritableRaster; |
| 7 | + |
| 8 | +public class ImageUtils |
| 9 | +{ |
| 10 | + private static final float ERROR_ONE_SIXTEENTH = 1f/16f; |
| 11 | + private static final float ERROR_THREE_SIXTEENTHS = 3f/16f; |
| 12 | + private static final float ERROR_FIVE_SIXTEENTHS = 5f/16f; |
| 13 | + private static final float ERROR_SEVEN_SIXTEENTHS = 7f/16f; |
| 14 | + private static final int BLACK = Color.BLACK.getRGB(); |
| 15 | + private static final int WHITE = Color.WHITE.getRGB(); |
| 16 | + |
| 17 | + /** |
| 18 | + * Thanks, ChatGPT! |
| 19 | + * @param image the source image |
| 20 | + * @param luminanceThreshold luminance threshold |
| 21 | + * @return the dithered 2-color image |
| 22 | + */ |
| 23 | + public static BufferedImage desaturateWithDithering(final BufferedImage image, int luminanceThreshold) |
| 24 | + { |
| 25 | + // Clone original image |
| 26 | + final ColorModel cm = image.getColorModel(); |
| 27 | + final boolean isAlphaPreMultiplied = cm.isAlphaPremultiplied(); |
| 28 | + final WritableRaster raster = image.copyData(null); |
| 29 | + final BufferedImage out = new BufferedImage(cm, raster, isAlphaPreMultiplied, null); |
| 30 | + |
| 31 | + // Apply dithering |
| 32 | + final int width = out.getWidth(); |
| 33 | + final int height = out.getHeight(); |
| 34 | + |
| 35 | + // Two colors for dithering, typically black and white |
| 36 | + |
| 37 | + for (int y = 0; y < height; y++) { |
| 38 | + for (int x = 0; x < width; x++) { |
| 39 | + final int pixel = out.getRGB(x, y); |
| 40 | + final int alpha = (pixel >> 24) & 0xff; |
| 41 | + final int red = (pixel >> 16) & 0xff; |
| 42 | + final int green = (pixel >> 8) & 0xff; |
| 43 | + final int blue = pixel & 0xff; |
| 44 | + |
| 45 | + // Convert to grayscale (or use other method to choose between color1 and color2) |
| 46 | + final int gray = (int)(0.299 * red + 0.587 * green + 0.114 * blue); |
| 47 | + final int newPixel = gray < luminanceThreshold ? BLACK : WHITE; |
| 48 | + |
| 49 | + final int error = gray - ((newPixel == BLACK) ? 0 : 255); |
| 50 | + |
| 51 | + // Distribute the error to neighboring pixels |
| 52 | + if (x < width - 1) out.setRGB(x + 1, y, applyError(out.getRGB(x + 1, y), Math.round(error * ERROR_SEVEN_SIXTEENTHS))); |
| 53 | + if (x > 0 && y < height - 1) out.setRGB(x - 1, y + 1, applyError(out.getRGB(x - 1, y + 1), Math.round(error * ERROR_THREE_SIXTEENTHS))); |
| 54 | + if (y < height - 1) out.setRGB(x, y + 1, applyError(out.getRGB(x, y + 1), Math.round(error * ERROR_FIVE_SIXTEENTHS))); |
| 55 | + if (x < width - 1 && y < height - 1) out.setRGB(x + 1, y + 1, applyError(out.getRGB(x + 1, y + 1), Math.round(error * ERROR_ONE_SIXTEENTH))); |
| 56 | + |
| 57 | + out.setRGB(x, y, newPixel | (alpha << 24)); |
| 58 | + } |
| 59 | + } |
| 60 | + return out; |
| 61 | + } |
| 62 | + |
| 63 | + private static int applyError(int pixel, int error) { |
| 64 | + int red = Math.min(Math.max(((pixel >> 16) & 0xff) + error, 0), 255); |
| 65 | + int green = Math.min(Math.max(((pixel >> 8) & 0xff) + error, 0), 255); |
| 66 | + int blue = Math.min(Math.max((pixel & 0xff) + error, 0), 255); |
| 67 | + return (pixel & 0xff000000) | (red << 16) | (green << 8) | blue; |
| 68 | + } |
| 69 | + |
| 70 | + public static BufferedImage desaturateWithHardCut(final BufferedImage image, final int luminanceThreshold, final int alphaThreshold) |
| 71 | + { |
| 72 | + final BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_BINARY); |
| 73 | + for (int y = 0; y < image.getHeight(); y++) |
| 74 | + { |
| 75 | + for (int x = 0; x < image.getWidth(); x++) |
| 76 | + { |
| 77 | + final Color pixelColor = new Color(image.getRGB(x, y), true); |
| 78 | + final float luminance = HSLColor.fromRGB(pixelColor)[2]; |
| 79 | + if (pixelColor.getAlpha() >= alphaThreshold && luminance <= luminanceThreshold) |
| 80 | + { |
| 81 | + out.setRGB(x, y, BLACK); |
| 82 | + } |
| 83 | + else |
| 84 | + { |
| 85 | + out.setRGB(x, y, WHITE); |
| 86 | + } |
| 87 | + } |
| 88 | + } |
| 89 | + return out; |
| 90 | + } |
| 91 | +} |
0 commit comments