/* DitherFloydSteinberg.java (c) 2016 Edward Swartz All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html */ package v9t9.video.imageimport; import java.awt.image.BufferedImage; import org.ejs.gui.images.IPaletteColorMapper; import org.ejs.gui.images.IPaletteMapper; import v9t9.video.imageimport.ImageImportOptions.Dither; /** * @author ejs * */ public class DitherFloydSteinberg implements IDither { private Dither ditherType; private boolean useColorMappedGreyScale; /** * @param thePalette * @param ditherType */ public DitherFloydSteinberg(boolean useColorMappedGreyScale, Dither ditherType) { this.useColorMappedGreyScale = useColorMappedGreyScale; this.ditherType = ditherType; } private final int clamp(int i) { return i < 0 ? 0 : i > 255 ? 255 : i; } // https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering private void ditherFSPixel(BufferedImage img, IPaletteColorMapper mapColor, int x, int y) { int pixel = img.getRGB(x, y); int newC = mapColor.getClosestPaletteEntry(pixel); int newPixel = mapColor.getPalettePixel(newC); img.setRGB(x, y, newPixel | 0xff000000); int r_error; int g_error; int b_error; r_error = ((pixel >> 16) & 0xff) - ((newPixel >> 16) & 0xff); g_error = ((pixel >> 8) & 0xff) - ((newPixel >> 8) & 0xff); b_error = ((pixel >> 0) & 0xff) - ((newPixel >> 0) & 0xff); if (ditherType == Dither.FSR) { // reduce bleed by ignoring some error r_error = reduceBleed(r_error); g_error = reduceBleed(g_error); b_error = reduceBleed(b_error); } if (useColorMappedGreyScale) { int lum = (299 * r_error + 587 * g_error + 114 * b_error) / 1000; r_error = g_error = b_error = lum; } if ((r_error | g_error | b_error) != 0) { if (x + 1 < img.getWidth()) { // x+1, y ditherFSApplyError(img, x + 1, y, 7, r_error, g_error, b_error); } if (y + 1 < img.getHeight()) { if (x > 0) { ditherFSApplyError(img, x - 1, y + 1, 3, r_error, g_error, b_error); } ditherFSApplyError(img, x, y + 1, 5, r_error, g_error, b_error); if (x + 1 < img.getWidth()) { ditherFSApplyError(img, x + 1, y + 1, 1, r_error, g_error, b_error); } } } } /** * @param b_error * @return */ private int reduceBleed(int v) { if (v < 0) return -(-v / 3); else return v / 3; } private void ditherFSApplyError(BufferedImage img, int x, int y, int sixteenths, int r_error, int g_error, int b_error) { int pixel = img.getRGB(x, y); int r = clamp(((pixel >> 16) & 0xff) + (sixteenths * r_error / 16)); int g = clamp(((pixel >> 8) & 0xff) + (sixteenths * g_error / 16)); int b = clamp(((pixel >> 0) & 0xff) + (sixteenths * b_error / 16)); img.setRGB(x, y, (r << 16) | (g << 8) | b | 0xff000000); } /* (non-Javadoc) * @see v9t9.video.imageimport.IDither#run(java.awt.image.BufferedImage, org.ejs.gui.images.IPaletteMapper, org.ejs.gui.images.Histogram) */ @Override public void run(BufferedImage img, IPaletteMapper mapColor) { int h = img.getHeight(); int w = img.getWidth(); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { ditherFSPixel(img, mapColor, x, y); } } } }