From af3a0ded8d2559a4f8162b3ac7e5a305afa980ff Mon Sep 17 00:00:00 2001 From: Tommy Ettinger Date: Fri, 11 Nov 2022 20:38:20 -0800 Subject: [PATCH] Experiment with "Igneous dither." This is an error diffusion dither that uses IGN to introduce error. --- .../tommyettinger/anim8/PaletteReducer.java | 105 +++++++++++++++++- .../tommyettinger/InteractiveReducer.java | 2 +- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/github/tommyettinger/anim8/PaletteReducer.java b/src/main/java/com/github/tommyettinger/anim8/PaletteReducer.java index e4ad8520..1b449ee3 100644 --- a/src/main/java/com/github/tommyettinger/anim8/PaletteReducer.java +++ b/src/main/java/com/github/tommyettinger/anim8/PaletteReducer.java @@ -2670,6 +2670,110 @@ public Pixmap reduceJimenez(Pixmap pixmap) { return pixmap; } + public Pixmap reduceIgneous(Pixmap pixmap) { + boolean hasTransparent = (paletteArray[0] == 0); + final int lineLen = pixmap.getWidth(), h = pixmap.getHeight(); + float[] curErrorRed, nextErrorRed, curErrorGreen, nextErrorGreen, curErrorBlue, nextErrorBlue; + if (curErrorRedFloats == null) { + curErrorRed = (curErrorRedFloats = new FloatArray(lineLen)).items; + nextErrorRed = (nextErrorRedFloats = new FloatArray(lineLen)).items; + curErrorGreen = (curErrorGreenFloats = new FloatArray(lineLen)).items; + nextErrorGreen = (nextErrorGreenFloats = new FloatArray(lineLen)).items; + curErrorBlue = (curErrorBlueFloats = new FloatArray(lineLen)).items; + nextErrorBlue = (nextErrorBlueFloats = new FloatArray(lineLen)).items; + } else { + curErrorRed = curErrorRedFloats.ensureCapacity(lineLen); + nextErrorRed = nextErrorRedFloats.ensureCapacity(lineLen); + curErrorGreen = curErrorGreenFloats.ensureCapacity(lineLen); + nextErrorGreen = nextErrorGreenFloats.ensureCapacity(lineLen); + curErrorBlue = curErrorBlueFloats.ensureCapacity(lineLen); + nextErrorBlue = nextErrorBlueFloats.ensureCapacity(lineLen); + for (int i = 0; i < lineLen; i++) { + nextErrorRed[i] = 0; + nextErrorGreen[i] = 0; + nextErrorBlue[i] = 0; + } + } + Pixmap.Blending blending = pixmap.getBlending(); + pixmap.setBlending(Pixmap.Blending.None); + int color, used; + float rdiff, gdiff, bdiff; + float er, eg, eb; + byte paletteIndex; + float w1 = (6f * ditherStrength * populationBias * populationBias), w3 = w1 * 3f, w5 = w1 * 5f, w7 = w1 * 7f, + strength = 60f * ditherStrength / (populationBias * populationBias), + adj; + + for (int y = 0; y < h; y++) { + int ny = y + 1; + for (int i = 0; i < lineLen; i++) { + curErrorRed[i] = nextErrorRed[i]; + curErrorGreen[i] = nextErrorGreen[i]; + curErrorBlue[i] = nextErrorBlue[i]; + nextErrorRed[i] = 0; + nextErrorGreen[i] = 0; + nextErrorBlue[i] = 0; + } + for (int px = 0; px < lineLen; px++) { + color = pixmap.getPixel(px, y); + if ((color & 0x80) == 0 && hasTransparent) + pixmap.drawPixel(px, y, 0); + else { + adj = (px * 0.06711056f + y * 0.00583715f); + adj -= (int) adj; + adj *= 52.9829189f; + adj -= (int) adj; + adj -= 0.5f; + adj *= strength; + + er = adj + (curErrorRed[px]); + eg = adj + (curErrorGreen[px]); + eb = adj + (curErrorBlue[px]); + + int rr = MathUtils.clamp((int)(((color >>> 24) ) + er + 0.5f), 0, 0xFF); + int gg = MathUtils.clamp((int)(((color >>> 16) & 0xFF) + eg + 0.5f), 0, 0xFF); + int bb = MathUtils.clamp((int)(((color >>> 8) & 0xFF) + eb + 0.5f), 0, 0xFF); + paletteIndex = + paletteMapping[((rr << 7) & 0x7C00) + | ((gg << 2) & 0x3E0) + | ((bb >>> 3))]; + used = paletteArray[paletteIndex & 0xFF]; + pixmap.drawPixel(px, y, used); + rdiff = (0x3p-10f * ((color>>>24)- (used>>>24)) ); + gdiff = (0x3p-10f * ((color>>>16&255)-(used>>>16&255))); + bdiff = (0x3p-10f * ((color>>>8&255)- (used>>>8&255)) ); + + if(px < lineLen - 1) + { + curErrorRed[px+1] += rdiff * w7; + curErrorGreen[px+1] += gdiff * w7; + curErrorBlue[px+1] += bdiff * w7; + } + if(ny < h) + { + if(px > 0) + { + nextErrorRed[px-1] += rdiff * w3; + nextErrorGreen[px-1] += gdiff * w3; + nextErrorBlue[px-1] += bdiff * w3; + } + if(px < lineLen - 1) + { + nextErrorRed[px+1] += rdiff * w1; + nextErrorGreen[px+1] += gdiff * w1; + nextErrorBlue[px+1] += bdiff * w1; + } + nextErrorRed[px] += rdiff * w5; + nextErrorGreen[px] += gdiff * w5; + nextErrorBlue[px] += bdiff * w5; + } + } + } + } + pixmap.setBlending(blending); + return pixmap; + } + /** * An ordered dither that uses a sub-random sequence by Martin Roberts to disperse lightness adjustments across the * image. This is very similar to {@link #reduceJimenez(Pixmap)}, but is milder by default, and has subtly different @@ -2818,7 +2922,6 @@ public Pixmap reduceWoven(Pixmap pixmap) { return pixmap; } - /** * A blue-noise-based dither; does not diffuse error, and uses a tiling blue noise pattern (which can be accessed * with {@link #TRI_BLUE_NOISE}, but shouldn't usually be modified) as well as a 8x8 threshold matrix (the kind diff --git a/src/test/java/com/github/tommyettinger/InteractiveReducer.java b/src/test/java/com/github/tommyettinger/InteractiveReducer.java index cc1d8293..e418ebbb 100644 --- a/src/test/java/com/github/tommyettinger/InteractiveReducer.java +++ b/src/test/java/com/github/tommyettinger/InteractiveReducer.java @@ -98,7 +98,7 @@ public void refresh(){ reducer.reduceSolid(p); break; case 0: - reducer.reduceBlueNoiseSeparated(p); + reducer.reduceIgneous(p); break; case 1: reducer.reduceBlueNoise(p);