Revert nQuant upgrade

Latest changes are not compatible with SDK 23

* This reverts commit 07b1e593b3.
* This reverts commit c8da8b4717.
This commit is contained in:
José Rebelo
2026-03-19 19:09:19 +00:00
parent a11fdbc067
commit 109677c729
8 changed files with 1534 additions and 1784 deletions
@@ -3,140 +3,17 @@ package com.android.nQuant;
import android.graphics.Color; import android.graphics.Color;
public class BitmapUtilities { public class BitmapUtilities {
static final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE; static final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
static int getColorIndex(final int c, boolean hasSemiTransparency, boolean hasTransparency) static int getColorIndex(final int c, boolean hasSemiTransparency, boolean hasTransparency) {
{ if (hasSemiTransparency)
if(hasSemiTransparency) return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4);
return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4); if (hasTransparency)
if (hasTransparency) return (Color.alpha(c) & 0x80) << 8 | (Color.red(c) & 0xF8) << 7 | (Color.green(c) & 0xF8) << 2 | (Color.blue(c) >> 3);
return (Color.alpha(c) & 0x80) << 8 | (Color.red(c) & 0xF8) << 7 | (Color.green(c) & 0xF8) << 2 | (Color.blue(c) >> 3); return (Color.red(c) & 0xF8) << 8 | (Color.green(c) & 0xFC) << 3 | (Color.blue(c) >> 3);
return (Color.red(c) & 0xF8) << 8 | (Color.green(c) & 0xFC) << 3 | (Color.blue(c) >> 3); }
}
static double sqr(double value) static double sqr(double value) {
{ return value * value;
return value * value; }
}
static int[] calcDitherPixel(int c, int[] clamp, int[] rowerr, int cursor, boolean noBias)
{
int[] ditherPixel = new int[4];
if (noBias) {
ditherPixel[0] = clamp[((rowerr[cursor] + 0x1008) >> 4) + Color.red(c)];
ditherPixel[1] = clamp[((rowerr[cursor + 1] + 0x1008) >> 4) + Color.green(c)];
ditherPixel[2] = clamp[((rowerr[cursor + 2] + 0x1008) >> 4) + Color.blue(c)];
ditherPixel[3] = clamp[((rowerr[cursor + 3] + 0x1008) >> 4) + Color.alpha(c)];
return ditherPixel;
}
ditherPixel[0] = clamp[((rowerr[cursor] + 0x2010) >> 5) + Color.red(c)];
ditherPixel[1] = clamp[((rowerr[cursor + 1] + 0x1008) >> 4) + Color.green(c)];
ditherPixel[2] = clamp[((rowerr[cursor + 2] + 0x2010) >> 5) + Color.blue(c)];
ditherPixel[3] = Color.alpha(c);
return ditherPixel;
}
static int[] quantize_image(final int width, final int height, final int[] pixels, final int[] palette, final Ditherable ditherable, final boolean hasSemiTransparency, final boolean dither)
{
int[] qPixels = new int[pixels.length];
int nMaxColors = palette.length;
int pixelIndex = 0;
if (dither) {
final int DJ = 4;
final int BLOCK_SIZE = 256;
final int DITHER_MAX = 20;
final int err_len = (width + 2) * DJ;
int[] clamp = new int[DJ * BLOCK_SIZE];
int[] limtb = new int[2 * BLOCK_SIZE];
for (short i = 0; i < BLOCK_SIZE; ++i) {
clamp[i] = 0;
clamp[i + BLOCK_SIZE] = i;
clamp[i + BLOCK_SIZE * 2] = BYTE_MAX;
clamp[i + BLOCK_SIZE * 3] = BYTE_MAX;
limtb[i] = -DITHER_MAX;
limtb[i + BLOCK_SIZE] = DITHER_MAX;
}
for (short i = -DITHER_MAX; i <= DITHER_MAX; ++i)
limtb[i + BLOCK_SIZE] = i % 4 == 3 ? 0 : i;
boolean noBias = hasSemiTransparency || nMaxColors < 64;
int dir = 1;
int[] row0 = new int[err_len];
int[] row1 = new int[err_len];
int[] lookup = new int[65536];
for (int i = 0; i < height; ++i) {
if (dir < 0)
pixelIndex += width - 1;
int cursor0 = DJ, cursor1 = width * DJ;
row1[cursor1] = row1[cursor1 + 1] = row1[cursor1 + 2] = row1[cursor1 + 3] = 0;
for (int j = 0; j < width; ++j) {
int c = pixels[pixelIndex];
int[] ditherPixel = calcDitherPixel(c, clamp, row0, cursor0, noBias);
int r_pix = ditherPixel[0];
int g_pix = ditherPixel[1];
int b_pix = ditherPixel[2];
int a_pix = ditherPixel[3];
int c1 = Color.argb(a_pix, r_pix, g_pix, b_pix);
if(noBias && a_pix > 0xF0) {
int offset = ditherable.getColorIndex(c1);
if (lookup[offset] == 0)
lookup[offset] = (Color.alpha(c) == 0) ? 1 : ditherable.nearestColorIndex(palette, c1, i + j) + 1;
qPixels[pixelIndex] = palette[lookup[offset] - 1];
}
else {
short qIndex = (Color.alpha(c) == 0) ? 0 : ditherable.nearestColorIndex(palette, c1, i + j);
qPixels[pixelIndex] = palette[qIndex];
}
int c2 = qPixels[pixelIndex];
r_pix = limtb[r_pix - Color.red(c2) + BLOCK_SIZE];
g_pix = limtb[g_pix - Color.green(c2) + BLOCK_SIZE];
b_pix = limtb[b_pix - Color.blue(c2) + BLOCK_SIZE];
a_pix = limtb[a_pix - Color.alpha(c2) + BLOCK_SIZE];
int k = r_pix * 2;
row1[cursor1 - DJ] = r_pix;
row1[cursor1 + DJ] += (r_pix += k);
row1[cursor1] += (r_pix += k);
row0[cursor0 + DJ] += (r_pix + k);
k = g_pix * 2;
row1[cursor1 + 1 - DJ] = g_pix;
row1[cursor1 + 1 + DJ] += (g_pix += k);
row1[cursor1 + 1] += (g_pix += k);
row0[cursor0 + 1 + DJ] += (g_pix + k);
k = b_pix * 2;
row1[cursor1 + 2 - DJ] = b_pix;
row1[cursor1 + 2 + DJ] += (b_pix += k);
row1[cursor1 + 2] += (b_pix += k);
row0[cursor0 + 2 + DJ] += (b_pix + k);
k = a_pix * 2;
row1[cursor1 + 3 - DJ] = a_pix;
row1[cursor1 + 3 + DJ] += (a_pix += k);
row1[cursor1 + 3] += (a_pix += k);
row0[cursor0 + 3 + DJ] += (a_pix + k);
cursor0 += DJ;
cursor1 -= DJ;
pixelIndex += dir;
}
if ((i % 2) == 1)
pixelIndex += width + 1;
dir *= -1;
int[] temp = row0; row0 = row1; row1 = temp;
}
return qPixels;
}
return qPixels;
}
} }
@@ -8,216 +8,206 @@ package com.android.nQuant;
import android.graphics.Color; import android.graphics.Color;
public class BlueNoise { public class BlueNoise {
// Reference mask from: https://tellusim.com/download/noise/64x64_l64_s16.png // Reference mask from: https://tellusim.com/download/noise/64x64_l64_s16.png
// Made from: https://github.com/Tellusim/BlueNoise // Made from: https://github.com/Tellusim/BlueNoise
static final byte[] TELL_BLUE_NOISE = { static final byte[] TELL_BLUE_NOISE = {
18, 60, -13, 24, -93, -44, -107, 18, -34, 123, -120, 63, 115, 32, 91, -28, 118, -12, 97, -42, 77, -76, 30, -97, -39, 18, 60, -13, 24, -93, -44, -107, 18, -34, 123, -120, 63, 115, 32, 91, -28, 118, -12, 97, -42, 77, -76, 30, -97, -39,
87, -10, -57, 54, -3, -55, 118, 10, -35, 42, 124, -97, 57, -60, 1, -41, -103, -68, 97, -116, 44, -74, 60, -48, 87, 87, -10, -57, 54, -3, -55, 118, 10, -35, 42, 124, -97, 57, -60, 1, -41, -103, -68, 97, -116, 44, -74, 60, -48, 87,
-9, -117, 10, -73, 98, 40, -77, 55, -17, -86, 65, 36, 114, -48, -93, -39, -121, 98, -21, 119, 37, 84, -100, -2, 38, -9, -117, 10, -73, 98, 40, -77, 55, -17, -86, 65, 36, 114, -48, -93, -39, -121, 98, -21, 119, 37, 84, -100, -2, 38,
-26, -89, -10, -124, 13, -66, -115, 34, -89, -5, 121, -51, 98, 38, -118, 65, -88, 113, -124, 60, -18, -77, 101, -128, 2, -26, -89, -10, -124, 13, -66, -115, 34, -89, -5, 121, -51, 98, 38, -118, 65, -88, 113, -124, 60, -18, -77, 101, -128, 2,
-37, 95, -118, 86, 25, 58, -24, 69, -47, -9, 81, -127, 125, -77, 41, 111, -49, 53, -22, -108, 79, -61, 21, 126, -7, -37, 95, -118, 86, 25, 58, -24, 69, -47, -9, 81, -127, 125, -77, 41, 111, -49, 53, -22, -108, 79, -61, 21, 126, -7,
-128, -71, 71, 107, 36, 81, -56, 52, -80, -5, -55, 47, -77, 99, -57, 27, 76, -41, 106, 43, 78, -34, 107, 53, -123, -128, -71, 71, 107, 36, 81, -56, 52, -80, -5, -55, 47, -77, 99, -57, 27, 76, -41, 106, 43, 78, -34, 107, 53, -123,
21, -16, -70, 124, -29, 24, -48, 8, 96, -102, 78, -49, 25, 77, -70, 12, 47, -51, -80, 102, 13, -87, 36, 107, -85, 21, -16, -70, 124, -29, 24, -48, 8, 96, -102, 78, -49, 25, 77, -70, 12, 47, -51, -80, 102, 13, -87, 36, 107, -85,
27, -28, 10, -99, -24, 73, -101, 123, 15, -38, 97, -113, -51, 81, -34, 46, -20, 8, -82, -4, -100, 11, 74, -128, 112, 27, -28, 10, -99, -24, 73, -101, 123, 15, -38, 97, -113, -51, 81, -34, 46, -20, 8, -82, -4, -100, 11, 74, -128, 112,
-23, 81, 9, -112, 111, -79, 50, -103, -24, -81, 19, -99, -22, -65, 70, -101, 52, 5, -99, 84, 50, -78, -33, 39, -5, -23, 81, 9, -112, 111, -79, 50, -103, -24, -81, 19, -99, -22, -65, 70, -101, 52, 5, -99, 84, 50, -78, -33, 39, -5,
56, -93, -16, 111, -86, -27, 122, -2, -123, -53, 120, -106, -55, -16, 66, -59, 55, 105, -69, 29, -5, -57, 55, -94, 3, 56, -93, -16, 111, -86, -27, 122, -2, -123, -53, 120, -106, -55, -16, 66, -59, 55, 105, -69, 29, -5, -57, 55, -94, 3,
60, 31, -96, 17, 92, -109, -50, 124, 49, -30, 110, -58, 34, -81, 62, -105, -39, 48, -22, 14, -49, 87, 0, 126, -56, 60, 31, -96, 17, 92, -109, -50, 124, 49, -30, 110, -58, 34, -81, 62, -105, -39, 48, -22, 14, -49, 87, 0, 126, -56,
84, 31, 96, -8, 110, -47, 91, -67, -4, -116, 102, 68, -60, -117, 119, -64, 45, -117, 26, 69, -105, 45, 77, -15, 29, 84, 31, 96, -8, 110, -47, 91, -67, -4, -116, 102, 68, -60, -117, 119, -64, 45, -117, 26, 69, -105, 45, 77, -15, 29,
72, 8, 100, -98, 91, -113, -15, 82, -120, 108, -84, 90, -16, 117, -75, -20, 111, -57, -80, 65, 30, -120, -62, 69, -108, 72, 8, 100, -98, 91, -113, -15, 82, -120, 108, -84, 90, -16, 117, -75, -20, 111, -57, -80, 65, 30, -120, -62, 69, -108,
-9, 101, 11, -46, 30, 125, -85, 94, -119, 70, -83, 33, -126, 56, -4, -111, -42, -86, 20, -121, 35, -30, 116, 23, -21, -9, 101, 11, -46, 30, 125, -85, 94, -119, 70, -83, 33, -126, 56, -4, -111, -42, -86, 20, -121, 35, -30, 116, 23, -21,
-93, 11, 85, -40, 3, 75, -33, 100, -66, 8, -45, -84, 92, -76, -35, -122, 46, -65, -6, 31, -49, 49, 11, -27, 47, -93, 11, 85, -40, 3, 75, -33, 100, -66, 8, -45, -84, 92, -76, -35, -122, 46, -65, -6, 31, -49, 49, 11, -27, 47,
18, -125, -53, 67, -107, 51, -3, 102, -26, 86, 0, 97, 15, -72, 43, -35, -114, 70, -75, -9, 72, -47, 30, -7, 108, 18, -125, -53, 67, -107, 51, -3, 102, -26, 86, 0, 97, 15, -72, 43, -35, -114, 70, -75, -9, 72, -47, 30, -7, 108,
-42, 95, -25, -78, 115, 40, 68, -21, 80, -79, 57, -107, -53, 44, 127, -32, 30, -104, 107, -85, 20, -99, -13, 112, 67, -42, 95, -25, -78, 115, 40, 68, -21, 80, -79, 57, -107, -53, 44, 127, -32, 30, -104, 107, -85, 20, -99, -13, 112, 67,
24, -113, 1, 111, 22, -40, 121, 68, -79, 113, -97, -65, 69, -105, -42, 80, 28, -8, 87, -39, -124, 16, -93, 42, -38, 24, -113, 1, 111, 22, -40, 121, 68, -79, 113, -97, -65, 69, -105, -42, 80, 28, -8, 87, -39, -124, 16, -93, 42, -38,
-99, -23, 120, 66, -86, 93, -19, 103, -117, 10, -93, 62, -67, -103, 13, -89, 30, 76, 2, -64, -104, 123, -61, 14, 98, -99, -23, 120, 66, -86, 93, -19, 103, -117, 10, -93, 62, -67, -103, 13, -89, 30, 76, 2, -64, -104, 123, -61, 14, 98,
0, 71, -69, -125, 78, -79, 57, -14, 48, -45, 78, 36, -126, -73, -26, 105, 45, -61, 70, -83, 10, -116, -19, 57, 4, 0, 71, -69, -125, 78, -79, 57, -14, 48, -45, 78, 36, -126, -73, -26, 105, 45, -61, 70, -83, 10, -116, -19, 57, 4,
92, -35, 119, 1, -88, 110, -100, -60, 36, 122, 63, -58, 116, -73, 63, 24, -122, -51, 5, 38, -59, 27, -37, 50, 99, 92, -35, 119, 1, -88, 110, -100, -60, 36, 122, 63, -58, 116, -73, 63, 24, -122, -51, 5, 38, -59, 27, -37, 50, 99,
-38, 120, -21, 53, 86, -57, -118, -34, 101, 11, -32, 53, -103, -38, -85, 110, -6, 61, -50, 5, 100, -63, -120, 125, -90, -38, 120, -21, 53, 86, -57, -118, -34, 101, 11, -32, 53, -103, -38, -85, 110, -6, 61, -50, 5, 100, -63, -120, 125, -90,
-35, 57, 2, 73, -51, -90, -8, -109, 90, -31, 41, 95, -51, -126, 27, -81, 39, -59, 65, -34, 9, 101, -88, -24, -102, -35, 57, 2, 73, -51, -90, -8, -109, 90, -31, 41, 95, -51, -126, 27, -81, 39, -59, 65, -34, 9, 101, -88, -24, -102,
-13, -116, 4, 106, -62, 80, -11, 108, -109, 86, -89, 117, -65, -3, -123, 24, 77, -113, -35, 122, 7, 71, -89, 47, -128, -13, -116, 4, 106, -62, 80, -11, 108, -109, 86, -89, 117, -65, -3, -123, 24, 77, -113, -35, 122, 7, 71, -89, 47, -128,
103, -9, 64, 20, -45, -101, 17, 108, -112, -35, 15, 59, -25, 9, 97, -60, 116, -104, -3, 58, 126, 21, -54, 59, -98, 103, -9, 64, 20, -45, -101, 17, 108, -112, -35, 15, 59, -25, 9, 97, -60, 116, -104, -3, 58, 126, 21, -54, 59, -98,
-69, -2, 124, -24, 83, -111, 95, 20, -120, 54, -44, 25, 91, 13, 77, -39, 51, -102, -27, 39, -95, 56, -64, -15, 13, -69, -2, 124, -24, 83, -111, 95, 20, -120, 54, -44, 25, 91, 13, 77, -39, 51, -102, -27, 39, -95, 56, -64, -15, 13,
58, -101, 41, 91, -84, -58, -3, 31, -96, 41, -63, -5, 89, -45, 24, -90, 120, -118, 89, 52, -32, -67, 55, 82, -97, 58, -101, 41, 91, -84, -58, -3, 31, -96, 41, -63, -5, 89, -45, 24, -90, 120, -118, 89, 52, -32, -67, 55, 82, -97,
103, -80, 73, -111, 26, -81, 38, 92, -118, -32, -78, 99, -1, 115, 35, 74, -84, 61, -54, 3, -68, -18, 127, -78, 72, 103, -80, 73, -111, 26, -81, 38, 92, -118, -32, -78, 99, -1, 115, 35, 74, -84, 61, -54, 3, -68, -18, 127, -78, 72,
-114, -48, -83, 35, -64, 96, -6, 85, -72, 124, -37, 18, 70, -126, -29, 80, -47, -15, 12, 112, 56, -71, 75, -31, 108, -114, -48, -83, 35, -64, 96, -6, 85, -72, 124, -37, 18, 70, -126, -29, 80, -47, -15, 12, 112, 56, -71, 75, -31, 108,
-106, 33, -72, 84, -20, -52, 42, -23, -82, 115, 28, -90, -11, 35, -46, -3, 41, -50, -11, 82, -35, -62, 25, 82, 51, -106, 33, -72, 84, -20, -52, 42, -23, -82, 115, 28, -90, -11, 35, -46, -3, 41, -50, -11, 82, -35, -62, 25, 82, 51,
-106, -67, -22, -116, -38, 16, -104, 32, 109, 59, -86, 34, 4, -28, 104, 48, -15, 113, 18, -90, 37, -127, -12, 23, -112, -106, -67, -22, -116, -38, 16, -104, 32, 109, 59, -86, 34, 4, -28, 104, 48, -15, 113, 18, -90, 37, -127, -12, 23, -112,
91, -79, 110, 2, -87, 127, -111, 75, -34, -108, 102, -10, -121, 52, -40, 126, -10, -116, 57, 93, -70, 2, 70, -123, -4, 91, -79, 110, 2, -87, 127, -111, 75, -34, -108, 102, -10, -121, 52, -40, 126, -10, -116, 57, 93, -70, 2, 70, -123, -4,
97, -59, 119, -127, 87, -97, 122, 48, -123, 10, 117, -93, -2, -48, 18, 56, 86, 24, 110, -75, 78, -14, -123, -34, 84, 97, -59, 119, -127, 87, -97, 122, 48, -123, 10, 117, -93, -2, -48, 18, 56, 86, 24, 110, -75, 78, -14, -123, -34, 84,
-106, 91, -64, -3, -93, 84, -120, -17, 76, -43, 104, 60, -60, 49, -4, -42, 45, -61, 60, 21, -66, 50, -87, 7, -58, -106, 91, -64, -3, -93, 84, -120, -17, 76, -43, 104, 60, -60, 49, -4, -42, 45, -61, 60, 21, -66, 50, -87, 7, -58,
25, 94, -74, 18, -94, 67, 20, -80, 6, -111, 100, 26, -49, 49, -28, -108, 16, 68, -23, 14, -70, -26, 96, -72, 64, 25, 94, -74, 18, -94, 67, 20, -80, 6, -111, 100, 26, -49, 49, -28, -108, 16, 68, -23, 14, -70, -26, 96, -72, 64,
-27, 94, -128, 120, -33, -86, -58, -12, 40, -50, 121, -70, 48, -2, -52, 51, -126, 120, 29, -46, 61, 126, -65, 10, -94, -27, 94, -128, 120, -33, -86, -58, -12, 40, -50, 121, -70, 48, -2, -52, 51, -126, 120, 29, -46, 61, 126, -65, 10, -94,
-30, 87, -81, 113, -108, 13, 98, -118, -36, 106, -21, 31, 121, 71, -86, -26, 61, -11, 98, -62, -31, 118, 46, -59, -29, -30, 87, -81, 113, -108, 13, 98, -118, -36, 106, -21, 31, 121, 71, -86, -26, 61, -11, 98, -62, -31, 118, 46, -59, -29,
-88, 122, -100, 89, 62, -16, -90, -55, 95, 55, -102, 29, -7, -108, 33, -62, 58, 4, -104, 67, 104, -122, 86, -96, 28, -88, 122, -100, 89, 62, -16, -90, -55, 95, 55, -102, 29, -7, -108, 33, -62, 58, 4, -104, 67, 104, -122, 86, -96, 28,
5, 98, -96, 115, 6, -30, 60, -72, 8, -95, -27, -115, 49, 114, 21, -118, 8, -29, 69, -71, -17, 79, 7, -105, 81, 5, 98, -96, 115, 6, -30, 60, -72, 8, -95, -27, -115, 49, 114, 21, -118, 8, -29, 69, -71, -17, 79, 7, -105, 81,
-51, -127, -15, 45, -109, 115, -50, -125, 40, 74, -108, -14, 107, 9, 72, -14, 11, -78, -49, 30, 115, 42, -116, -5, 114, -51, -127, -15, 45, -109, 115, -50, -125, 40, 74, -108, -14, 107, 9, 72, -14, 11, -78, -49, 30, 115, 42, -116, -5, 114,
-61, 86, -40, 111, -5, -82, 80, -22, 31, -43, 52, -3, -33, 66, -115, -56, -21, 32, -78, 96, -102, -14, 83, 35, 96, -61, 86, -40, 111, -5, -82, 80, -22, 31, -43, 52, -3, -33, 66, -115, -56, -21, 32, -78, 96, -102, -14, 83, 35, 96,
68, -13, -75, -52, 63, 101, -62, 24, 122, -98, 31, -56, 53, -85, -5, 61, -61, 99, 16, -80, 34, 86, 4, -81, -42, 68, -13, -75, -52, 63, 101, -62, 24, 122, -98, 31, -56, 53, -85, -5, 61, -61, 99, 16, -80, 34, 86, 4, -81, -42,
81, -98, 44, -121, -64, 108, 43, 83, -104, -64, 4, 74, -42, -89, 64, 7, -80, 63, -115, 45, -58, 106, -82, 10, -103, 81, -98, 44, -121, -64, 108, 43, 83, -104, -64, 4, 74, -42, -89, 64, 7, -80, 63, -115, 45, -58, 106, -82, 10, -103,
118, -81, 89, -13, 44, 80, -111, 63, -50, 21, 106, -119, -55, -87, 14, -101, 82, 35, -12, -91, 49, -125, -23, 56, -74, 118, -81, 89, -13, 44, 80, -111, 63, -50, 21, 106, -119, -55, -87, 14, -101, 82, 35, -12, -91, 49, -125, -23, 56, -74,
109, -25, 91, 21, 114, -96, -2, -41, 64, -7, -103, -30, 114, 52, 16, -69, -37, 98, 34, -43, -115, -25, 14, 97, -29, 109, -25, 91, 21, 114, -96, -2, -41, 64, -7, -103, -30, 114, 52, 16, -69, -37, 98, 34, -43, -115, -25, 14, 97, -29,
-78, 103, 34, -32, -123, 100, 12, -43, 123, 13, -112, 55, -51, 73, 35, -45, 23, -94, 124, -77, 5, -27, 77, -84, -31, -78, 103, 34, -32, -123, 100, 12, -43, 123, 13, -112, 55, -51, 73, 35, -45, 23, -94, 124, -77, 5, -27, 77, -84, -31,
63, -1, 42, -45, 121, -28, -114, 110, -41, 88, -1, -59, 101, 1, -120, 36, -66, -112, -36, 49, 84, -120, 124, -61, 93, 63, -1, 42, -45, 121, -28, -114, 110, -41, 88, -1, -59, 101, 1, -120, 36, -66, -112, -36, 49, 84, -120, 124, -61, 93,
22, -55, -121, -11, 124, 60, -5, -92, 68, 0, 125, -87, 33, -125, 55, 7, -105, 127, 53, -17, -74, 36, -95, -35, 86, 22, -55, -121, -11, 124, 60, -5, -92, 68, 0, 125, -87, 33, -125, 55, 7, -105, 127, 53, -17, -74, 36, -95, -35, 86,
-21, 112, -75, -15, -127, 107, -27, 16, -44, 98, 45, -124, 122, 43, -67, 112, -108, 58, -71, 19, 66, -74, 27, -104, 72, -21, 112, -75, -15, -127, 107, -27, 16, -44, 98, 45, -124, 122, 43, -67, 112, -108, 58, -71, 19, 66, -74, 27, -104, 72,
26, -99, 75, -38, 121, -11, 73, 15, -90, -50, 38, -85, -18, 59, -87, 104, 72, -83, 27, -111, -55, 22, 105, -76, 58, 26, -99, 75, -38, 121, -11, 73, 15, -90, -50, 38, -85, -18, 59, -87, 104, 72, -83, 27, -111, -55, 22, 105, -76, 58,
-47, 68, -14, 91, -58, -9, -77, 15, -107, 62, 94, 1, 59, -66, 39, -117, 18, 84, 43, -61, 63, -113, 70, -93, -59, -47, 68, -14, 91, -58, -9, -77, 15, -107, 62, 94, 1, 59, -66, 39, -117, 18, 84, 43, -61, 63, -113, 70, -93, -59,
11, -44, 2, -99, -20, 88, -12, 102, -96, -8, 90, -21, 125, -69, -27, 51, -72, 22, -101, 44, -74, 95, -13, 109, 3, 11, -44, 2, -99, -20, 88, -12, 102, -96, -8, 90, -21, 125, -69, -27, 51, -72, 22, -101, 44, -74, 95, -13, 109, 3,
83, 32, -115, 5, -34, 35, -22, -63, 95, 71, -30, -128, -11, 27, -105, 100, -65, -99, 38, 68, 90, -52, 111, -30, -63, 83, 32, -115, 5, -34, 35, -22, -63, 95, 71, -30, -128, -11, 27, -105, 100, -65, -99, 38, 68, 90, -52, 111, -30, -63,
-125, 116, -90, -1, 97, -38, -87, -2, 93, -83, 38, -12, 116, 29, 85, -76, 101, 67, 24, -48, -128, 34, -41, 52, -121, -125, 116, -90, -1, 97, -38, -87, -2, 93, -83, 38, -12, 116, 29, 85, -76, 101, 67, 24, -48, -128, 34, -41, 52, -121,
-54, 11, 44, -116, 115, -18, 93, -46, 85, -23, -122, 55, -69, -110, -34, -75, 101, 69, -69, -106, 111, 50, -6, -95, 38, -54, 11, 44, -116, 115, -18, 93, -46, 85, -23, -122, 55, -69, -110, -34, -75, 101, 69, -69, -106, 111, 50, -6, -95, 38,
90, -60, 112, -31, 20, -6, 121, -39, -121, -23, 47, -84, 31, 83, -8, 29, -33, 76, -62, 55, 126, -111, -28, 10, 103, 90, -60, 112, -31, 20, -6, 121, -39, -121, -23, 47, -84, 31, 83, -8, 29, -33, 76, -62, 55, 126, -111, -28, 10, 103,
-74, -36, -110, -16, 37, -116, -33, -80, 127, -5, 78, -88, 118, 3, 65, -95, 102, -6, -83, 63, -128, 1, -87, 31, 126, -74, -36, -110, -16, 37, -116, -33, -80, 127, -5, 78, -88, 118, 3, 65, -95, 102, -6, -83, 63, -128, 1, -87, 31, 126,
-42, 19, 62, 119, 12, -52, -14, 40, 85, 7, -123, -47, 119, -71, 3, 69, -92, 51, -120, 73, -82, 2, 103, 23, -107, -42, 19, 62, 119, 12, -52, -14, 40, 85, 7, -123, -47, 119, -71, 3, 69, -92, 51, -120, 73, -82, 2, 103, 23, -107,
76, 5, -98, -46, 100, -110, 37, -103, 9, -71, 47, 78, -53, -123, 26, 88, 60, -86, 113, 6, 91, 42, -102, 47, -64, 76, 5, -98, -46, 100, -110, 37, -103, 9, -71, 47, 78, -53, -123, 26, 88, 60, -86, 113, 6, 91, 42, -102, 47, -64,
20, -25, -69, 93, -18, -61, 78, 18, -63, 33, 112, 66, -57, 3, -84, 92, -26, -94, 49, -127, 114, -93, -40, -78, 75, 20, -25, -69, 93, -18, -61, 78, 18, -63, 33, 112, 66, -57, 3, -84, 92, -26, -94, 49, -127, 114, -93, -40, -78, 75,
17, 56, -24, -113, 32, -15, -70, 104, -54, 40, 65, -93, -56, 113, -19, -54, 125, 60, -76, 4, 89, -18, 108, -36, 17, 17, 56, -24, -113, 32, -15, -70, 104, -54, 40, 65, -93, -56, 113, -19, -54, 125, 60, -76, 4, 89, -18, 108, -36, 17,
-91, 119, 57, -10, -62, 16, -46, 65, -59, -91, -23, 85, -44, 109, -117, 86, 38, -112, 21, 58, -105, -40, 99, -33, -94, -91, 119, 57, -10, -62, 16, -46, 65, -59, -91, -23, 85, -44, 109, -117, 86, 38, -112, 21, 58, -105, -40, 99, -33, -94,
-17, -114, 77, 36, -109, 4, -54, 100, -30, 17, 77, -3, 127, -25, -66, -101, 98, 73, -54, 124, 59, 11, -17, -113, -37, -17, -114, 77, 36, -109, 4, -54, 100, -30, 17, 77, -3, 127, -25, -66, -101, 98, 73, -54, 124, 59, 11, -17, -113, -37,
88, 7, 57, -81, 44, -115, 16, -27, 49, -53, -88, 43, -120, 69, -6, -40, -82, 99, -108, 123, -13, -124, -1, 74, 31, 88, 7, 57, -81, 44, -115, 16, -27, 49, -53, -88, 43, -120, 69, -6, -40, -82, 99, -108, 123, -13, -124, -1, 74, 31,
10, -90, -10, 66, -38, -86, 112, -73, -32, 124, 28, -113, 73, 0, 40, 104, -28, -61, 109, 67, 24, -79, 56, -102, -63, 10, -90, -10, 66, -38, -86, 112, -73, -32, 124, 28, -113, 73, 0, 40, 104, -28, -61, 109, 67, 24, -79, 56, -102, -63,
35, -112, 53, 106, 37, -4, -76, 4, -109, -39, -91, 75, 116, 25, -73, -25, -127, 100, -7, 80, -85, 96, -124, 121, -7, 35, -112, 53, 106, 37, -4, -76, 4, -109, -39, -91, 75, 116, 25, -73, -25, -127, 100, -7, 80, -85, 96, -124, 121, -7,
84, -44, 105, -103, 14, 40, -26, 48, -78, 79, 41, 108, -49, -112, -31, 94, 27, -74, 51, 14, -15, 47, 81, -92, -8, 84, -44, 105, -103, 14, 40, -26, 48, -78, 79, 41, 108, -49, -112, -31, 94, 27, -74, 51, 14, -15, 47, 81, -92, -8,
57, -70, 120, -52, -82, 10, 51, -90, -19, -118, 88, -3, 110, -19, 67, -45, 0, -90, -38, -126, 52, 102, 22, 89, 37, 57, -70, 120, -52, -82, 10, 51, -90, -19, -118, 88, -3, 110, -19, 67, -45, 0, -90, -38, -126, 52, 102, 22, 89, 37,
-67, -3, -106, 52, 121, 27, -34, -73, 34, -38, 0, 68, -77, 19, -100, 29, -62, 54, 96, -113, 77, -55, 17, -32, -95, -67, -3, -106, 52, 121, 27, -34, -73, 34, -38, 0, 68, -77, 19, -100, 29, -62, 54, 96, -113, 77, -55, 17, -32, -95,
-68, 22, 120, 46, -125, -54, 123, -109, 89, -53, -121, -2, -67, 107, -45, 22, -105, 54, 89, -124, 117, -46, 82, 44, -40, -68, 22, 120, 46, -125, -54, 123, -109, 89, -53, -121, -2, -67, 107, -45, 22, -105, 54, 89, -124, 117, -46, 82, 44, -40,
-68, 29, -123, 102, -74, 92, 23, 67, 121, -28, -64, -91, -13, -124, 62, -49, 95, -17, -91, -57, 59, 92, -111, 117, -60, -68, 29, -123, 102, -74, 92, 23, 67, 121, -28, -64, -91, -13, -124, 62, -49, 95, -17, -91, -57, 59, 92, -111, 117, -60,
26, -36, 62, -19, 116, -8, -87, -29, -68, -8, 94, -117, 105, 32, 71, -11, -85, 85, 3, 71, -25, 6, -76, 59, 118, 26, -36, 62, -19, 116, -8, -87, -29, -68, -8, 94, -117, 105, 32, 71, -11, -85, 85, 3, 71, -25, 6, -76, 59, 118,
24, 74, -20, -126, 69, -5, -40, 23, -68, -2, 32, -106, -7, 123, -98, 83, -31, 5, 44, -110, -16, -60, -94, -1, 79, 24, 74, -20, -126, 69, -5, -40, 23, -68, -2, 32, -106, -7, 123, -98, 83, -31, 5, 44, -110, -16, -60, -94, -1, 79,
48, 119, -37, 104, 20, -79, 74, 17, 107, -100, 8, -21, 49, -97, 106, -114, 91, -68, -128, 67, 28, 108, 63, 22, -84, 48, 119, -37, 104, 20, -79, 74, 17, 107, -100, 8, -21, 49, -97, 106, -114, 91, -68, -128, 67, 28, 108, 63, 22, -84,
54, 0, -50, -108, 102, -43, -17, -70, 40, -87, 108, 33, -26, -89, -42, -101, 37, 90, -75, 109, -99, 65, -27, 86, -85, 54, 0, -50, -108, 102, -43, -17, -70, 40, -87, 108, 33, -26, -89, -42, -101, 37, 90, -75, 109, -99, 65, -27, 86, -85,
99, -75, 39, -12, 54, -62, -93, 122, -39, 82, 39, 94, 26, -115, -49, 13, -80, -2, -110, 50, -44, -119, -24, 42, -51, 99, -75, 39, -12, 54, -62, -93, 122, -39, 82, 39, 94, 26, -115, -49, 13, -80, -2, -110, 50, -44, -119, -24, 42, -51,
79, -70, -14, 56, 5, -47, 37, 82, -39, -99, 4, -121, -40, 126, -26, -77, 115, 15, 53, -100, 34, 117, -105, 78, -50, 79, -70, -14, 56, 5, -47, 37, 82, -39, -99, 4, -121, -40, 126, -26, -77, 115, 15, 53, -100, 34, 117, -105, 78, -50,
-116, 87, 6, 101, 63, -57, -12, 28, -48, 5, 127, -112, 47, -44, 14, 68, -52, -114, 104, 12, 73, 25, -82, -6, -119, -116, 87, 6, 101, 63, -57, -12, 28, -48, 5, 127, -112, 47, -44, 14, 68, -52, -114, 104, 12, 73, 25, -82, -6, -119,
-67, -35, 111, 40, -104, 56, 86, -52, 127, -4, 92, 64, -77, 123, -123, 13, 99, -55, -92, 125, 9, -82, -5, 119, -53, -67, -35, 111, 40, -104, 56, 86, -52, 127, -4, 92, 64, -77, 123, -123, 13, 99, -55, -92, 125, 9, -82, -5, 119, -53,
89, 59, -105, 38, 84, -126, -21, -58, 93, -119, -37, 17, -20, 49, -4, -73, 46, -122, -1, -89, 100, -113, 72, -89, 32, 89, 59, -105, 38, 84, -126, -21, -58, 93, -119, -37, 17, -20, 49, -4, -73, 46, -122, -1, -89, 100, -113, 72, -89, 32,
-59, -4, 111, -126, -18, 114, 28, -76, -22, -121, -48, 87, 46, 117, 15, 64, -87, -11, 79, -68, -17, 33, -99, -67, 26, -59, -4, 111, -126, -18, 114, 28, -76, -22, -121, -48, 87, 46, 117, 15, 64, -87, -11, 79, -68, -17, 33, -99, -67, 26,
-101, -5, 31, -28, 59, -106, 40, 69, -19, -110, 96, 58, -69, 35, -14, -72, 7, -61, -6, 50, 75, -89, 7, 66, 88, -101, -5, 31, -28, 59, -106, 40, 69, -19, -110, 96, 58, -69, 35, -14, -72, 7, -61, -6, 50, 75, -89, 7, 66, 88,
-73, 105, -85, 71, 125, -24, -55, 110, 20, -34, 41, -7, 102, -20, 89, -97, 72, -67, 58, -92, -37, 81, 47, 106, 0, -73, 105, -85, 71, 125, -24, -55, 110, 20, -34, 41, -7, 102, -20, 89, -97, 72, -67, 58, -92, -37, 81, 47, 106, 0,
-100, -33, -60, -102, -20, 97, -56, 17, 119, -121, 99, 3, 78, -36, 106, -56, 88, -84, 113, -43, -1, -72, 88, -51, 21, -100, -33, -60, -102, -20, 97, -56, 17, 119, -121, 99, 3, 78, -36, 106, -56, 88, -84, 113, -43, -1, -72, 88, -51, 21,
-33, -118, 84, -92, 106, 47, 91, -90, -48, 12, 124, -31, -96, -53, 38, -113, 8, -46, -103, 23, 80, -108, 68, -79, 117, -33, -118, 84, -92, 106, 47, 91, -90, -48, 12, 124, -31, -96, -53, 38, -113, 8, -46, -103, 23, 80, -108, 68, -79, 117,
-70, -42, -117, 59, 6, -38, 34, -9, 92, 3, -108, -44, -86, 39, 112, 25, 65, 91, 36, -118, 58, -98, -30, 66, -39, -70, -42, -117, 59, 6, -38, 34, -9, 92, 3, -108, -44, -86, 39, 112, 25, 65, 91, 36, -118, 58, -98, -30, 66, -39,
-88, 43, -128, 16, 66, -113, 10, -62, 28, 102, -125, 3, 54, -95, 108, 50, 15, -45, -2, -122, -26, 31, 103, -115, -66, -88, 43, -128, 16, 66, -113, 10, -62, 28, 102, -125, 3, 54, -95, 108, 50, 15, -45, -2, -122, -26, 31, 103, -115, -66,
45, 20, -12, 95, -27, 113, 33, 65, -70, -9, 38, -40, 6, -122, 75, 49, 19, -65, 98, -83, 119, -116, -57, 41, 125, 45, 20, -12, 95, -27, 113, 33, 65, -70, -9, 38, -40, 6, -122, 75, 49, 19, -65, 98, -83, 119, -116, -57, 41, 125,
17, 72, -24, -64, -127, -3, -89, -54, 8, 110, -13, 41, -78, 26, 87, -63, 116, -19, -73, -12, 79, 54, -103, -18, 77, 17, 72, -24, -64, -127, -3, -89, -54, 8, 110, -13, 41, -78, 26, 87, -63, 116, -19, -73, -12, 79, 54, -103, -18, 77,
-66, 118, -20, -74, -11, -101, 118, 54, -59, 112, -98, -15, 76, 0, 101, -124, 70, -92, 54, -67, -127, -23, 103, -95, 121, -66, 118, -20, -74, -11, -101, 118, 54, -59, 112, -98, -15, 76, 0, 101, -124, 70, -92, 54, -67, -127, -23, 103, -95, 121,
-72, 92, 30, -27, -96, 122, -17, -109, 51, -25, 13, 67, -94, -15, -68, -116, 99, 13, 83, -32, 126, 57, -35, -81, 76, -72, 92, 30, -27, -96, 122, -17, -109, 51, -25, 13, 67, -94, -15, -68, -116, 99, 13, 83, -32, 126, 57, -35, -81, 76,
-60, 114, -114, 6, -32, 62, -95, 45, 122, -41, -77, 110, -54, 45, 11, -107, 32, 64, 88, -63, 24, -108, 80, 2, 48, -60, 114, -114, 6, -32, 62, -95, 45, 122, -41, -77, 110, -54, 45, 11, -107, 32, 64, 88, -63, 24, -108, 80, 2, 48,
-54, 34, -91, -44, 121, -57, 22, -38, 89, -3, 81, -54, 19, -28, 71, -98, -49, 105, 8, -56, 81, 27, -50, 75, -77, -54, 34, -91, -44, 121, -57, 22, -38, 89, -3, 81, -54, 19, -28, 71, -98, -49, 105, 8, -56, 81, 27, -50, 75, -77,
103, -36, 60, 108, 32, -39, -92, 42, -67, 16, -105, 94, 36, -125, 5, 53, -23, 100, -106, 20, 102, -58, 17, -107, 38, 103, -36, 60, 108, 32, -39, -92, 42, -67, 16, -105, 94, 36, -125, 5, 53, -23, 100, -106, 20, 102, -58, 17, -107, 38,
1, 64, -90, -27, 98, -43, -124, 12, -33, 97, -8, -42, -88, 72, -109, 113, -26, 50, -94, -8, 103, -108, 46, -83, 38, 1, 64, -90, -27, 98, -43, -124, 12, -33, 97, -8, -42, -88, 72, -109, 113, -26, 50, -94, -8, 103, -108, 46, -83, 38,
-103, 61, -120, 25, -10, 60, -79, 67, -128, 1, -86, 114, -103, -13, 32, -125, 9, -49, -99, 81, 1, 113, -112, 86, -9, -103, 61, -120, 25, -10, 60, -79, 67, -128, 1, -86, 114, -103, -13, 32, -125, 9, -49, -99, 81, 1, 113, -112, 86, -9,
-65, -19, 83, -47, -93, 73, -74, 50, -47, -6, -119, 83, -11, 99, -62, -120, 127, 29, -75, 77, -12, 122, -90, 57, -74, -65, -19, 83, -47, -93, 73, -74, 50, -47, -6, -119, 83, -11, 99, -62, -120, 127, 29, -75, 77, -12, 122, -90, 57, -74,
42, 126, -19, 12, -71, 73, 5, 83, 39, -71, -16, 126, 9, -42, 114, -1, 94, -53, 117, -112, 29, -18, 106, 43, -34, 42, 126, -19, 12, -71, 73, 5, 83, 39, -71, -16, 126, 9, -42, 114, -1, 94, -53, 117, -112, 29, -18, 106, 43, -34,
54, 15, 87, -69, 113, -82, 90, -7, 50, -60, -23, 66, -39, 45, 110, -100, 19, 120, 34, -54, 12, 125, -85, 89, 54, 54, 15, 87, -69, 113, -82, 90, -7, 50, -60, -23, 66, -39, 45, 110, -100, 19, 120, 34, -54, 12, 125, -85, 89, 54,
-36, -84, 54, -25, 84, -9, -53, 60, -101, 39, -69, 22, -110, 79, 6, -128, -52, 96, 37, -114, -52, -29, -119, 88, 26, -36, -84, 54, -25, 84, -9, -53, 60, -101, 39, -69, 22, -110, 79, 6, -128, -52, 96, 37, -114, -52, -29, -119, 88, 26,
-56, -117, 70, -84, -35, -96, 54, -75, 9, 91, -71, -44, -102, 80, -68, -119, -40, 42, -23, 19, 57, -66, -123, 103, -91, -56, -117, 70, -84, -35, -96, 54, -75, 9, 91, -71, -44, -102, 80, -68, -119, -40, 42, -23, 19, 57, -66, -123, 103, -91,
29, -72, -122, 6, 67, -79, -24, -109, 95, -20, -125, -33, 8, -66, 19, 108, -104, 28, -81, 69, -112, 10, 112, -41, 85, 29, -72, -122, 6, 67, -79, -24, -109, 95, -20, -125, -33, 8, -66, 19, 108, -104, 28, -81, 69, -112, 10, 112, -41, 85,
-19, 109, -33, -63, 88, 56, -96, -13, 108, 21, 118, 53, -41, -91, 55, 99, -17, 45, 79, 18, -12, 82, -29, -97, 63, -19, 109, -33, -63, 88, 56, -96, -13, 108, 21, 118, 53, -41, -91, 55, 99, -17, 45, 79, 18, -12, 82, -29, -97, 63,
15, 127, -23, 7, 61, 120, -98, 74, -114, -35, 124, 30, -17, 71, 1, 121, 58, -58, -32, 100, 5, 49, -84, 79, 33, 15, 127, -23, 7, 61, 120, -98, 74, -114, -35, 124, 30, -17, 71, 1, 121, 58, -58, -32, 100, 5, 49, -84, 79, 33,
64, 109, -113, 76, -70, 0, 116, -42, 19, 97, -48, -88, -1, -121, 52, -80, 11, 46, -90, 2, -42, 78, -60, -92, -7, 64, 109, -113, 76, -70, 0, 116, -42, 19, 97, -48, -88, -1, -121, 52, -80, 11, 46, -90, 2, -42, 78, -60, -92, -7,
-75, 4, 112, -29, -94, 28, -65, -126, 124, -63, -115, 43, 113, -2, -118, 32, -83, 101, -59, -16, 10, -55, 106, -1, -96, -75, 4, 112, -29, -94, 28, -65, -126, 124, -63, -115, 43, 113, -2, -118, 32, -83, 101, -59, -16, 10, -55, 106, -1, -96,
-46, 90, -109, -63, -28, -98, 90, 48, -118, -61, 112, -41, -1, -63, -95, -7, -43, 39, -17, 63, -127, 57, -94, -19, 35, -46, 90, -109, -63, -28, -98, 90, 48, -118, -61, 112, -41, -1, -63, -95, -7, -43, 39, -17, 63, -127, 57, -94, -19, 35,
65, 95, 25, -52, 74, -114, 116, -30, 99, 25, -119, 40, 70, -106, 91, 41, -123, 21, 85, -45, 107, 3, -39, 36, 96, 65, 95, 25, -52, 74, -114, 116, -30, 99, 25, -119, 40, 70, -106, 91, 41, -123, 21, 85, -45, 107, 3, -39, 36, 96,
-52, -89, -34, 93, -51, 74, -113, 48, -84, 81, 44, -78, 61, 83, 19, -80, 33, 62, 98, 12, -15, -82, 30, 74, 16, -52, -89, -34, 93, -51, 74, -113, 48, -84, 81, 44, -78, 61, 83, 19, -80, 33, 62, 98, 12, -15, -82, 30, 74, 16,
-114, 56, 118, 13, 88, -85, 117, -100, -46, 94, -10, -66, 123, -118, -64, -32, -98, 121, -7, 28, -58, 64, -107, -61, 123, -114, 56, 118, 13, 88, -85, 117, -100, -46, 94, -10, -66, 123, -118, -64, -32, -98, 121, -7, 28, -58, 64, -107, -61, 123,
2, -46, 22, -23, -58, 73, -76, -1, -109, 53, -82, 73, -101, 11, -14, 75, 30, -77, 45, -1, -31, 16, 105, -124, -27, 2, -46, 22, -23, -58, 73, -76, -1, -109, 53, -82, 73, -101, 11, -14, 75, 30, -77, 45, -1, -31, 16, 105, -124, -27,
21, -107, -18, -56, 117, -5, -41, -120, 43, -55, 103, -4, -97, -19, 85, -77, -36, -110, 52, -32, 8, 85, 31, -88, 21, 21, -107, -18, -56, 117, -5, -41, -120, 43, -55, 103, -4, -97, -19, 85, -77, -36, -110, 52, -32, 8, 85, 31, -88, 21,
79, 44, -5, 107, 14, 62, -73, -36, 90, -92, -12, 34, 76, -24, -80, 107, 77, -83, 120, -18, 49, 114, -25, 15, 92, 79, 44, -5, 107, 14, 62, -73, -36, 90, -92, -12, 34, 76, -24, -80, 107, 77, -83, 120, -18, 49, 114, -25, 15, 92,
-16, 53, -76, 118, -123, -8, 111, -109, 82, -80, 67, -58, -7, 123, -51, 46, 107, -127, 64, -94, 85, -67, 127, -103, 50, -16, 53, -76, 118, -123, -8, 111, -109, 82, -80, 67, -58, -7, 123, -51, 46, 107, -127, 64, -94, 85, -67, 127, -103, 50,
-43, 116, -56, 37, -11, 93, 31, -62, 72, -122, -65, -21, 119, -48, -104, -36, -84, 55, -108, -13, 103, -126, 60, 18, 106, -43, 116, -56, 37, -11, 93, 31, -62, 72, -122, -65, -21, 119, -48, -104, -36, -84, 55, -108, -13, 103, -126, 60, 18, 106,
-50, -96, 14, 52, -126, -2, 37, -102, 16, -115, -65, 74, -91, -48, -117, 103, -35, 34, -67, 70, -46, 17, -59, 120, -103, -50, -96, 14, 52, -126, -2, 37, -102, 16, -115, -65, 74, -91, -48, -117, 103, -35, 34, -67, 70, -46, 17, -59, 120, -103,
11, 60, -99, 71, -74, -6, 25, -38, 42, -15, 28, -33, 74, 14, -128, 59, -88, 101, -121, 7, -91, 126, -13, 20, 77, 11, 60, -99, 71, -74, -6, 25, -38, 42, -15, 28, -33, 74, 14, -128, 59, -88, 101, -121, 7, -91, 126, -13, 20, 77,
46, -115, 59, -2, 104, 33, -47, 80, -60, 41, 6, -76, -32, -117, 55, 115, -45, 92, -65, 61, -49, 79, -42, 97, 27, 46, -115, 59, -2, 104, 33, -47, 80, -60, 41, 6, -76, -32, -117, 55, 115, -45, 92, -65, 61, -49, 79, -42, 97, 27,
-37, 42, 123, 29, 1, -96, 88, -18, 26, -95, 95, -9, 35, -26, 104, -70, 29, -29, 94, -97, 69, -70, 102, -115, 89, -37, 42, 123, 29, 1, -96, 88, -18, 26, -95, 95, -9, 35, -26, 104, -70, 29, -29, 94, -97, 69, -70, 102, -115, 89,
-93, -7, -78, 95, -18, 23, -37, 70, -66, 51, -43, -106, 102, -87, -38, 14, 91, -65, 17, -123, 118, 4, -95, 86, -46, -93, -7, -78, 95, -18, 23, -37, 70, -66, 51, -43, -106, 102, -87, -38, 14, 91, -65, 17, -123, 118, 4, -95, 86, -46,
125, 66, 12, -73, -2, -112, 24, -22, -110, 104, -79, 53, -13, -94, 88, -111, -29, -75, 65, -54, 52, -110, 125, -27, 42, 125, 66, 12, -73, -2, -112, 24, -22, -110, 104, -79, 53, -13, -94, 88, -111, -29, -75, 65, -54, 52, -110, 125, -27, 42,
-114, -71, 53, -111, -14, 79, -122, 19, -50, 120, 6, -86, 13, -60, 59, 113, -47, 43, -67, 124, -102, 15, 111, -6, 86, -114, -71, 53, -111, -14, 79, -122, 19, -50, 120, 6, -86, 13, -60, 59, 113, -47, 43, -67, 124, -102, 15, 111, -6, 86,
27, -48, 4, 114, -76, -22, -97, 65, -16, -71, 62, -23, 25, -107, -4, -91, 100, -22, 75, 42, -81, 127, -68, -3, 27, 27, -48, 4, 114, -76, -22, -97, 65, -16, -71, 62, -23, 25, -107, -4, -91, 100, -22, 75, 42, -81, 127, -68, -3, 27,
-124, 121, -61, -9, 64, 7, 88, -125, 115, -31, 9, -71, 80, -50, 71, 112, -47, 94, 9, -57, 109, -17, 47, -117, -16, -124, 121, -61, -9, 64, 7, 88, -125, 115, -31, 9, -71, 80, -50, 71, 112, -47, 94, 9, -57, 109, -17, 47, -117, -16,
55, 115, -32, 19, -117, 7, 87, -111, -10, 55, -80, -31, -114, -72, 70, -94, 58, -126, 74, 39, 126, -44, 96, 43, -113, 55, 115, -32, 19, -117, 7, 87, -111, -10, 55, -80, -31, -114, -72, 70, -94, 58, -126, 74, 39, 126, -44, 96, 43, -113,
107, -72, 92, 52, -55, 34, -105, -63, 98, -40, 49, 80, -47, 63, -31, 11, 48, 106, -87, -56, 37, -22, 23, -95, 94, 107, -72, 92, 52, -55, 34, -105, -63, 98, -40, 49, 80, -47, 63, -31, 11, 48, 106, -87, -56, 37, -22, 23, -95, 94,
50, -128, 23, -87, -10, 20, -81, 56, -103, 33, -88, 100, -68, 86, -44, -110, 42, -75, 77, -86, -28, 35, 83, -45, 92, 50, -128, 23, -87, -10, 20, -81, 56, -103, 33, -88, 100, -68, 86, -44, -110, 42, -75, 77, -86, -28, 35, 83, -45, 92,
39, 105, 12, -34, 107, -19, 24, -51, -4, -111, 24, -91, -53, 11, -40, 69, -15, -122, 114, -30, 88, 15, -11, -100, 13, 39, 105, 12, -34, 107, -19, 24, -51, -4, -111, 24, -91, -53, 11, -40, 69, -15, -122, 114, -30, 88, 15, -11, -100, 13,
-117, 117, -93, 83, -72, -104, -35, 27, 93, -100, 107, -73, 72, -52, -22, 2, 101, -39, 61, -121, 84, -26, 123, -48, 75, -117, 117, -93, 83, -72, -104, -35, 27, 93, -100, 107, -73, 72, -52, -22, 2, 101, -39, 61, -121, 84, -26, 123, -48, 75,
-32, 10, 37, -83, 65, -3, 110, -21, 28, 115, -60, -93, 22, -122, 1, -62, 62, -110, 32, -81, 90, -101, 105, -65, 87, -32, 10, 37, -83, 65, -3, 110, -21, 28, 115, -60, -93, 22, -122, 1, -62, 62, -110, 32, -81, 90, -101, 105, -65, 87,
0, 71, 122, -102, 26, -82, 43, 5, -91, 62, -128, 116, 56, -27, 28, -10, -57, 21, 111, -4, 76, -122, -16, 66, -45, 0, 71, 122, -102, 26, -82, 43, 5, -91, 62, -128, 116, 56, -27, 28, -10, -57, 21, 111, -4, 76, -122, -16, 66, -45,
1, 33, -105, 109, -85, 36, -102, 118, -56, 35, -69, 0, 22, -116, 61, -98, 125, -31, 93, -59, -102, 53, -48, -126, 71, 1, 33, -105, 109, -85, 36, -102, 118, -56, 35, -69, 0, 22, -116, 61, -98, 125, -31, 93, -59, -102, 53, -48, -126, 71,
-8, 120, -53, 76, -96, -15, 123, -48, 67, -37, 4, 61, -32, 41, -125, -20, -76, 55, -34, 109, -61, 78, -44, 26, -20, -8, 120, -53, 76, -96, -15, 123, -48, 67, -37, 4, 61, -32, 41, -125, -20, -76, 55, -34, 109, -61, 78, -44, 26, -20,
-56, -88, 96, -78, 54, 75, -118, -45, 61, -68, 126, 19, -77, 48, -120, 122, -12, 44, -56, 69, -28, 25, -8, 105, -109, -56, -88, 96, -78, 54, 75, -118, -45, 61, -68, 126, 19, -77, 48, -120, 122, -12, 44, -56, 69, -28, 25, -8, 105, -109,
82, -79, 104, -8, -50, 3, -125, 23, -10, 84, -70, 99, 9, -41, 39, -81, 51, -36, 97, 41, -89, 16, -119, 114, -69, 82, -79, 104, -8, -50, 3, -125, 23, -10, 84, -70, 99, 9, -41, 39, -81, 51, -36, 97, 41, -89, 16, -119, 114, -69,
21, -89, 118, -63, 34, 82, 3, -119, 70, -1, -107, 124, -75, 93, 36, 72, -41, 111, -107, -30, 93, 16, -97, 44, -28, 21, -89, 118, -63, 34, 82, 3, -119, 70, -1, -107, 124, -75, 93, 36, 72, -41, 111, -107, -30, 93, 16, -97, 44, -28,
-107, 102, -23, 78, -43, -77, 76, 9, -118, 85, -72, -97, 58, -16, 32, -40, 47, -73, 79, 40, 106, -79, 47, -116, -17, -107, 102, -23, 78, -43, -77, 76, 9, -118, 85, -72, -97, 58, -16, 32, -40, 47, -73, 79, 40, 106, -79, 47, -116, -17,
35, -92, 77, -110, 104, -14, -107, 5, -57, 89, -29, 46, -11, 75, -113, 85, -18, 61, -39, -98, 105, -15, -71, 49, -30, 35, -92, 77, -110, 104, -14, -107, 5, -57, 89, -29, 46, -11, 75, -113, 85, -18, 61, -39, -98, 105, -15, -71, 49, -30,
15, 65, -98, 2, -118, 18, -60, 6, 43, -69, -17, 105, -54, 8, 58, -71, 25, -102, 12, 99, -108, -37, 124, 3, 50, 15, 65, -98, 2, -118, 18, -60, 6, 43, -69, -17, 105, -54, 8, 58, -71, 25, -102, 12, 99, -108, -37, 124, 3, 50,
96, -44, -81, 112, -122, 9, 117, -105, -64, -29, 76, -46, 114, 66, -56, 126, -28, 19, -58, 73, 36, 117, -124, 30, -72, 96, -44, -81, 112, -122, 9, 117, -105, -64, -29, 76, -46, 114, 66, -56, 126, -28, 19, -58, 73, 36, 117, -124, 30, -72,
81, -86, -43, 44, -73, 15, -106, 100, 20, -54, 37, 116, -92, 92, -119, -12, -50, 117, -25, 77, -16, 82, -105, 113, 54, 81, -86, -43, 44, -73, 15, -106, 100, 20, -54, 37, 116, -92, 92, -119, -12, -50, 117, -25, 77, -16, 82, -105, 113, 54,
-127, 78, -91, 116, -11, 93, -65, 51, -21, 29, 58, -92, -66, -30, -126, 22, 80, 0, 65, -85, -20, 27, 72, -4, -100, -127, 78, -91, 116, -11, 93, -65, 51, -21, 29, 58, -92, -66, -30, -126, 22, 80, 0, 65, -85, -20, 27, 72, -4, -100,
14, -82, 1, -111, 27, -78, 90, -6, -99, -65, -30, 64, -9, 112, -105, 14, 125, -9, 104, -47, 73, -10, -74, 76, -110, 14, -82, 1, -111, 27, -78, 90, -6, -99, -65, -30, 64, -9, 112, -105, 14, 125, -9, 104, -47, 73, -10, -74, 76, -110,
-20, 23, -55, 55, 106, 30, -78, 57, -97, 125, -75, 21, -82, -39, 24, -22, 37, -42, -121, 62, -31, 119, -86, -58, 109, -20, 23, -55, 55, 106, 30, -78, 57, -97, 125, -75, 21, -82, -39, 24, -22, 37, -42, -121, 62, -31, 119, -86, -58, 109,
-7, 81, 39, 116, -22, -100, -62, -29, 94, 48, -49, -115, 122, 36, 96, -26, 46, 94, -11, 57, -119, 46, 110, 1, 84, -7, 81, 39, 116, -22, -100, -62, -29, 94, 48, -49, -115, 122, 36, 96, -26, 46, 94, -11, 57, -119, 46, 110, 1, 84,
-82, -51, 25, -24, 66, -71, -128, 29, -89, 41, -121, 127, -33, 59, -69, 70, -8, -88, -36, -111, 93, -52, 14, 62, -36, -82, -51, 25, -24, 66, -71, -128, 29, -89, 41, -121, 127, -33, 59, -69, 70, -8, -88, -36, -111, 93, -52, 14, 62, -36,
47, -7, 76, -95, 111, -68, 89, 23, -83, 7, -112, 70, 2, -119, -46, 17, -106, -57, 72, 37, 127, 18, -104, -69, 103, 47, -7, 76, -95, 111, -68, 89, 23, -83, 7, -112, 70, 2, -119, -46, 17, -106, -57, 72, 37, 127, 18, -104, -69, 103,
19, -37, -64, -127, 69, -94, -45, -71, 111, -21, -52, -87, 29, -110, 44, 95, -116, 52, -53, 89, -28, 79, 0, 105, -24, 19, -37, -64, -127, 69, -94, -45, -71, 111, -21, -52, -87, 29, -110, 44, 95, -116, 52, -53, 89, -28, 79, 0, 105, -24,
48, -95, -5, 95, -127, 122, 36, 84, 8, -24, 45, -125, -57, 92, -116, 119, -59, 6, 45, -112, -1, 68, -52, 87, 41, 48, -95, -5, 95, -127, 122, 36, 84, 8, -24, 45, -125, -57, 92, -116, 119, -59, 6, 45, -112, -1, 68, -52, 87, 41,
-44, 100, 43, 90, -83, 102, 2, -89, -6, -119, 57, -37, 66, -13, -83, 87, 58, 7, -36, 120, 13, 74, -101, 23, 99, -44, 100, 43, 90, -83, 102, 2, -89, -6, -119, 57, -37, 66, -13, -83, 87, 58, 7, -36, 120, 13, 74, -101, 23, 99,
67, -24, 123, -43, 10, -68, 109, -95, 5, 46, -74, -41, -100, 20, -73, 79, 30, -53, 18, -30, -66, -100, 53, -74, 109, 67, -24, 123, -43, 10, -68, 109, -95, 5, 46, -74, -41, -100, 20, -73, 79, 30, -53, 18, -30, -66, -100, 53, -74, 109,
-6, -85, 9, 68, -22, -107, 87, -51, 105, -26, -97, 115, -14, -79, 17, -98, -64, -19, 59, -43, 51, 109, -46, 90, -73, -6, -85, 9, 68, -22, -107, 87, -51, 105, -26, -97, 115, -14, -79, 17, -98, -64, -19, 59, -43, 51, 109, -46, 90, -73,
4, 114, -122, 47, -9, -102, 103, -79, 40, -117, -33, 52, -81, 6, -126, -66, 4, -103, 69, -18, 31, -38, 98, -106, 122, 4, 114, -122, 47, -9, -102, 103, -79, 40, -117, -33, 52, -81, 6, -126, -66, 4, -103, 69, -18, 31, -38, 98, -106, 122,
40, 85, -55, 113, -29, -106, 110, -80, 83, 2, 110, -43, 78, -101, 33, 115, -45, -90, 28, 61, -4, -92, 15, 56, -59, 40, 85, -55, 113, -29, -106, 110, -80, 83, 2, 110, -43, 78, -101, 33, 115, -45, -90, 28, 61, -4, -92, 15, 56, -59,
23, -127, 68, 126, -6, 30, 76, -124, 23, -67, -109, 40, -26, 64, -99, 31, -66, 98, -53, 35, -25, 72, -49, 93, -3, 23, -127, 68, 126, -6, 30, 76, -124, 23, -67, -109, 40, -26, 64, -99, 31, -66, 98, -53, 35, -25, 72, -49, 93, -3,
118, -41, 75, -10, 56, 97, 32, -79, 116, -124, 62, -76, -21, 13, -123, -10, 61, -115, 12, 63, -12, 33, -112, 46, -19, 118, -41, 75, -10, 56, 97, 32, -79, 116, -124, 62, -76, -21, 13, -123, -10, 61, -115, 12, 63, -12, 33, -112, 46, -19,
-122, 17, -16, 66, -120, 45, 97, -37, -75, 117, -35, 80, -114, 97, -29, 51, -70, -31, -114, 107, -76, -25, 123, -9, 78, -122, 17, -16, 66, -120, 45, 97, -37, -75, 117, -35, 80, -114, 97, -29, 51, -70, -31, -114, 107, -76, -25, 123, -9, 78,
-86, 12, 101, -51, -20, 71, 9, -88, 125, -120, 16, -92, 33, -68, -106, 39, -63, 115, -96, -55, -22, 85, -47, 18, -10, -86, 12, 101, -51, -20, 71, 9, -88, 125, -120, 16, -92, 33, -68, -106, 39, -63, 115, -96, -55, -22, 85, -47, 18, -10,
81, 38, -50, 96, -77, 28, -41, 94, -84, -49, 74, -58, 101, -80, 68, 121, -72, -40, -18, 8, -104, 71, 2, -124, 41, 81, 38, -50, 96, -77, 28, -41, 94, -84, -49, 74, -58, 101, -80, 68, 121, -72, -40, -18, 8, -104, 71, 2, -124, 41,
-64, -2, 32, -91, 104, 11, 83, 42, -43, 7, 82, -96, 60, -36, 113, -59, -127, 44, 119, -111, -38, 64, -20, 82, -41, -64, -2, 32, -91, 104, 11, 83, 42, -43, 7, 82, -96, 60, -36, 113, -59, -127, 44, 119, -111, -38, 64, -20, 82, -41,
109, -21, 83, 14, 90, -115, -25, 46, 12, -119, 51, -92, 75, -64, -109, 108, -85, 67, -24, 119, -94, -14, 50, 120, -97, 109, -21, 83, 14, 90, -115, -25, 46, 12, -119, 51, -92, 75, -64, -109, 108, -85, 67, -24, 119, -94, -14, 50, 120, -97,
20, -37, 38, 0, -99, 51, 95, 81, -76, 127, -61, 50, 87, -14, 105, -84, 68, -47, -4, -104, -49, -83, 63, -104, 39, 20, -37, 38, 0, -99, 51, 95, 81, -76, 127, -61, 50, 87, -14, 105, -84, 68, -47, -4, -104, -49, -83, 63, -104, 39,
-63, 18, -114, 48, -13, 82, 3, -78, 26, 101, -101, 30, -83, 43, -110, 64, -88, -53, -14, 22, 104, -87, 73, 127, -8, -63, 18, -114, 48, -13, 82, 3, -78, 26, 101, -101, 30, -83, 43, -110, 64, -88, -53, -14, 22, 104, -87, 73, 127, -8,
26, -34, 118, 8, -25, 22, -104, 48, -57, 80, 26, -125, -33, 1, 108, -117, 90, -62, -31, 6, -107 26, -34, 118, 8, -25, 22, -104, 48, -57, 80, 26, -125, -33, 1, 108, -117, 90, -62, -31, 6, -107
}; };
public static int diffuse(final int pixel, final int qPixel, final float weight, final float strength, final int x, final int y) public static int diffuse(final int pixel, final int qPixel, final float weight, final float strength, final int x, final int y) {
{ int r_pix = Color.red(pixel);
int r_pix = Color.red(pixel); int g_pix = Color.green(pixel);
int g_pix = Color.green(pixel); int b_pix = Color.blue(pixel);
int b_pix = Color.blue(pixel); int a_pix = Color.alpha(pixel);
int a_pix = Color.alpha(pixel);
float adj = (TELL_BLUE_NOISE[(x & 63) | (y & 63) << 6] + 0.5f) / 127.5f; float adj = (TELL_BLUE_NOISE[(x & 63) | (y & 63) << 6] + 0.5f) / 127.5f;
adj += ((x + y & 1) - 0.5f) * strength / 8f; adj += ((x + y & 1) - 0.5f) * strength / 8f;
adj *= weight; adj *= weight;
r_pix = (int) Math.min(0xFF, Math.max(r_pix + (adj * (r_pix - Color.red(qPixel))), 0.0)); r_pix = (int) Math.min(0xFF, Math.max(r_pix + (adj * (r_pix - Color.red(qPixel))), 0.0));
g_pix = (int) Math.min(0xFF, Math.max(g_pix + (adj * (g_pix - Color.green(qPixel))), 0.0)); g_pix = (int) Math.min(0xFF, Math.max(g_pix + (adj * (g_pix - Color.green(qPixel))), 0.0));
b_pix = (int) Math.min(0xFF, Math.max(b_pix + (adj * (b_pix - Color.blue(qPixel))), 0.0)); b_pix = (int) Math.min(0xFF, Math.max(b_pix + (adj * (b_pix - Color.blue(qPixel))), 0.0));
a_pix = (int) Math.min(0xFF, Math.max(a_pix + (adj * (a_pix - Color.alpha(qPixel))), 0.0)); a_pix = (int) Math.min(0xFF, Math.max(a_pix + (adj * (a_pix - Color.alpha(qPixel))), 0.0));
return Color.argb(a_pix, r_pix, g_pix, b_pix);
}
static int[] processImagePixels(final int[] palette, final int[] qPixels) { return Color.argb(a_pix, r_pix, g_pix, b_pix);
int[] qPixel32s = new int[qPixels.length];
for (var i = 0; i < qPixels.length; ++i)
qPixel32s[i] = palette[qPixels[i]];
return qPixel32s;
} }
public static int[] dither(final int width, final int height, final int[] pixels, final int[] palette, final Ditherable ditherable, final int[] qPixels, final float weight) public static int[] dither(final int width, final int height, final int[] pixels, final int[] palette, final Ditherable ditherable, final int[] qPixels, final float weight) {
{ final float strength = 1 / 3f;
final float strength = 1 / 3f; for (int y = 0; y < height; ++y) {
for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) {
for (int x = 0; x < width; ++x) { final int bidx = x + y * width;
final int bidx = x + y * width; int pixel = pixels[bidx];
int pixel = pixels[bidx]; int qPixel = palette[qPixels[bidx]];
int qPixel = palette[qPixels[bidx]];
int c1 = diffuse(pixel, qPixel, weight, strength, x, y); int c1 = diffuse(pixel, qPixel, weight, strength, x, y);
qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c1, bidx)]; qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c1, bidx)];
} }
} }
return qPixels; return qPixels;
} }
} }
@@ -1,239 +1,229 @@
package com.android.nQuant; package com.android.nQuant;
import android.graphics.Color; import android.graphics.Color;
import androidx.core.graphics.ColorUtils; import androidx.core.graphics.ColorUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
public class CIELABConvertor { public class CIELABConvertor {
private final static char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE; private final static char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
private static final double XYZ_WHITE_REFERENCE_Y = 100; private static final double XYZ_WHITE_REFERENCE_Y = 100;
static class MutableDouble extends Number { static class MutableDouble extends Number {
private static final long serialVersionUID = -8826262264116498065L; private static final long serialVersionUID = -8826262264116498065L;
private double value; private double value;
public MutableDouble(double value) { public MutableDouble(double value) {
this.value = value; this.value = value;
} }
public MutableDouble() {
this(0.0);
}
public void setValue(double value) { public MutableDouble() {
this.value = value; this(0.0);
} }
@Override public void setValue(double value) {
public int intValue() { this.value = value;
return (int) value; }
}
@Override @Override
public long longValue() { public int intValue() {
return (long) value; return (int) value;
} }
@Override @Override
public float floatValue() { public long longValue() {
return (float) value; return (long) value;
} }
@Override @Override
public double doubleValue() { public float floatValue() {
return value; return (float) value;
} }
}
static class Lab {
float alpha = BYTE_MAX;
float A = 0f;
float B = 0f;
float L = 0f;
}
static Lab RGB2LAB(final int c1)
{
double[] labs = new double[3];
ColorUtils.colorToLAB(c1, labs);
Lab lab = new Lab(); @Override
lab.alpha = Color.alpha(c1); public double doubleValue() {
lab.L = (float) labs[0]; return value;
lab.A = (float) labs[1]; }
lab.B = (float) labs[2];
return lab;
}
protected static double gammaToLinear(int channel) }
{
final double c = channel / 255.0;
return c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}
static int LAB2RGB(final Lab lab){ static class Lab {
int color = ColorUtils.LABToColor(lab.L, lab.A, lab.B); float alpha = BYTE_MAX;
return ColorUtils.setAlphaComponent(color, (int) lab.alpha); float A = 0f;
} float B = 0f;
float L = 0f;
}
/******************************************************************************* static Lab RGB2LAB(final int c1) {
* Conversions. double[] labs = new double[3];
******************************************************************************/ ColorUtils.colorToLAB(c1, labs);
private static final float deg2Rad(final double deg) Lab lab = new Lab();
{ lab.alpha = Color.alpha(c1);
return (float) (deg * (Math.PI / 180.0)); lab.L = (float) labs[0];
} lab.A = (float) labs[1];
lab.B = (float) labs[2];
return lab;
}
static float L_prime_div_k_L_S_L(final Lab lab1, final Lab lab2) protected static double gammaToLinear(int channel) {
{ final double c = channel / 255.0;
final float k_L = 1.0f; return c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
float deltaLPrime = lab2.L - lab1.L; }
float barLPrime = (lab1.L + lab2.L) / 2f;
float S_L = (float)(1 + ((0.015f * Math.pow(barLPrime - 50f, 2f)) / Math.sqrt(20 + Math.pow(barLPrime - 50f, 2f))));
return deltaLPrime / (k_L * S_L);
}
static float C_prime_div_k_L_S_L(final Lab lab1, final Lab lab2, MutableDouble a1Prime, MutableDouble a2Prime, MutableDouble CPrime1, MutableDouble CPrime2) static int LAB2RGB(final Lab lab) {
{ int color = ColorUtils.LABToColor(lab.L, lab.A, lab.B);
final float k_C = 1f; return ColorUtils.setAlphaComponent(color, (int) lab.alpha);
final float pow25To7 = 6103515625f; /* pow(25, 7) */ }
float C1 = (float)(Math.sqrt((lab1.A * lab1.A) + (lab1.B * lab1.B)));
float C2 = (float)(Math.sqrt((lab2.A * lab2.A) + (lab2.B * lab2.B)));
float barC = (C1 + C2) / 2f;
float G = (float)(0.5f * (1 - Math.sqrt(Math.pow(barC, 7) / (Math.pow(barC, 7) + pow25To7))));
a1Prime.setValue((1.0 + G) * lab1.A);
a2Prime.setValue((1.0 + G) * lab2.A);
CPrime1.setValue(Math.sqrt((a1Prime.doubleValue() * a1Prime.doubleValue()) + (lab1.B * lab1.B))); /*******************************************************************************
CPrime2.setValue(Math.sqrt((a2Prime.doubleValue() * a2Prime.doubleValue()) + (lab2.B * lab2.B))); * Conversions.
float deltaCPrime = CPrime2.floatValue() - CPrime1.floatValue(); ******************************************************************************/
float barCPrime = (CPrime1.floatValue() + CPrime2.floatValue()) / 2f;
float S_C = 1 + (0.045f * barCPrime);
return deltaCPrime / (k_C * S_C);
}
static float H_prime_div_k_L_S_L(final Lab lab1, final Lab lab2, final Number a1Prime, final Number a2Prime, final Number CPrime1, final Number CPrime2, MutableDouble barCPrime, MutableDouble barhPrime) private static final float deg2Rad(final double deg) {
{ return (float) (deg * (Math.PI / 180.0));
final float k_H = 1f; }
final float deg360InRad = deg2Rad(360f);
final float deg180InRad = deg2Rad(180f);
double CPrimeProduct = CPrime1.doubleValue() * CPrime2.doubleValue();
double hPrime1;
if (BigDecimal.ZERO.equals(new BigDecimal(lab1.B)) && BigDecimal.ZERO.equals(new BigDecimal(a1Prime.doubleValue())))
hPrime1 = 0.0;
else {
hPrime1 = Math.atan2(lab1.B, a1Prime.doubleValue());
/*
* This must be converted to a hue angle in degrees between 0
* and 360 by addition of 2π to negative hue angles.
*/
if (hPrime1 < 0)
hPrime1 += deg360InRad;
}
double hPrime2;
if (BigDecimal.ZERO.equals(new BigDecimal(lab2.B)) && BigDecimal.ZERO.equals(new BigDecimal(a2Prime.doubleValue())))
hPrime2 = 0.0;
else {
hPrime2 = Math.atan2(lab2.B, a2Prime.doubleValue());
/*
* This must be converted to a hue angle in degrees between 0
* and 360 by addition of 2π to negative hue angles.
*/
if (hPrime2 < 0)
hPrime2 += deg360InRad;
}
double deltahPrime;
if (BigDecimal.ZERO.equals(new BigDecimal(CPrimeProduct)))
deltahPrime = 0;
else {
/* Avoid the Math.abs() call */
deltahPrime = hPrime2 - hPrime1;
if (deltahPrime < -deg180InRad)
deltahPrime += deg360InRad;
else if (deltahPrime > deg180InRad)
deltahPrime -= deg360InRad;
}
double deltaHPrime = 2.0 * Math.sqrt(CPrimeProduct) * Math.sin(deltahPrime / 2.0); static float L_prime_div_k_L_S_L(final Lab lab1, final Lab lab2) {
double hPrimeSum = hPrime1 + hPrime2; final float k_L = 1.0f;
if (BigDecimal.ZERO.equals(new BigDecimal(CPrime1.doubleValue() * CPrime2.doubleValue()))) { float deltaLPrime = lab2.L - lab1.L;
barhPrime.setValue(hPrimeSum); float barLPrime = (lab1.L + lab2.L) / 2f;
} float S_L = (float) (1 + ((0.015f * Math.pow(barLPrime - 50f, 2f)) / Math.sqrt(20 + Math.pow(barLPrime - 50f, 2f))));
else { return deltaLPrime / (k_L * S_L);
if (Math.abs(hPrime1 - hPrime2) <= deg180InRad) }
barhPrime.setValue(hPrimeSum / 2.0);
else {
if (hPrimeSum < deg360InRad)
barhPrime.setValue((hPrimeSum + deg360InRad) / 2.0);
else
barhPrime.setValue((hPrimeSum - deg360InRad) / 2.0);
}
}
barCPrime.setValue((CPrime1.doubleValue() + CPrime2.doubleValue()) / 2.0); static float C_prime_div_k_L_S_L(final Lab lab1, final Lab lab2, MutableDouble a1Prime, MutableDouble a2Prime, MutableDouble CPrime1, MutableDouble CPrime2) {
double T = 1.0 - (0.17 * Math.cos(barhPrime.doubleValue() - deg2Rad(30f))) + final float k_C = 1f;
(0.24 * Math.cos(2.0 * barhPrime.doubleValue())) + final float pow25To7 = 6103515625f; /* pow(25, 7) */
(0.32 * Math.cos((3.0 * barhPrime.doubleValue()) + deg2Rad(6f))) - float C1 = (float) (Math.sqrt((lab1.A * lab1.A) + (lab1.B * lab1.B)));
(0.20 * Math.cos((4.0 * barhPrime.doubleValue()) - deg2Rad(63f))); float C2 = (float) (Math.sqrt((lab2.A * lab2.A) + (lab2.B * lab2.B)));
double S_H = 1 + (0.015f * barCPrime.doubleValue() * T); float barC = (C1 + C2) / 2f;
return (float) (deltaHPrime / (k_H * S_H)); float G = (float) (0.5f * (1 - Math.sqrt(Math.pow(barC, 7) / (Math.pow(barC, 7) + pow25To7))));
} a1Prime.setValue((1.0 + G) * lab1.A);
a2Prime.setValue((1.0 + G) * lab2.A);
static float R_T(final Number barCPrime, final Number barhPrime, final float C_prime_div_k_L_S_L, final float H_prime_div_k_L_S_L) CPrime1.setValue(Math.sqrt((a1Prime.doubleValue() * a1Prime.doubleValue()) + (lab1.B * lab1.B)));
{ CPrime2.setValue(Math.sqrt((a2Prime.doubleValue() * a2Prime.doubleValue()) + (lab2.B * lab2.B)));
final double pow25To7 = 6103515625.0; /* Math.pow(25, 7) */ float deltaCPrime = CPrime2.floatValue() - CPrime1.floatValue();
double deltaTheta = deg2Rad(30f) * Math.exp(-Math.pow((barhPrime.doubleValue() - deg2Rad(275f)) / deg2Rad(25f), 2.0)); float barCPrime = (CPrime1.floatValue() + CPrime2.floatValue()) / 2f;
double R_C = 2.0 * Math.sqrt(Math.pow(barCPrime.doubleValue(), 7.0) / (Math.pow(barCPrime.doubleValue(), 7.0) + pow25To7));
double R_T = (-Math.sin(2.0 * deltaTheta)) * R_C;
return (float) (R_T * C_prime_div_k_L_S_L * H_prime_div_k_L_S_L);
}
/* From the paper "The CIEDE2000 Color-Difference Formula: Implementation Notes, */ float S_C = 1 + (0.045f * barCPrime);
/* Supplementary Test Data, and Mathematical Observations", by */ return deltaCPrime / (k_C * S_C);
/* Gaurav Sharma, Wencheng Wu and Edul N. Dalal, */ }
/* Color Res. Appl., vol. 30, no. 1, pp. 21-30, Feb. 2005. */
/* Return the CIEDE2000 Delta E color difference measure squared, for two Lab values */
static float CIEDE2000(final Lab lab1, final Lab lab2)
{
float deltaL_prime_div_k_L_S_L = L_prime_div_k_L_S_L(lab1, lab2);
MutableDouble a1Prime = new MutableDouble(), a2Prime = new MutableDouble(), CPrime1 = new MutableDouble(), CPrime2 = new MutableDouble();
float deltaC_prime_div_k_L_S_L = C_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2);
MutableDouble barCPrime = new MutableDouble(), barhPrime = new MutableDouble();
float deltaH_prime_div_k_L_S_L = H_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2, barCPrime, barhPrime);
float deltaR_T = R_T(barCPrime, barhPrime, deltaC_prime_div_k_L_S_L, deltaH_prime_div_k_L_S_L);
return (float) (Math.pow(deltaL_prime_div_k_L_S_L, 2.0) +
Math.pow(deltaC_prime_div_k_L_S_L, 2.0) +
Math.pow(deltaH_prime_div_k_L_S_L, 2.0) +
deltaR_T);
}
static double Y_Diff(final int c1, final int c2) static float H_prime_div_k_L_S_L(final Lab lab1, final Lab lab2, final Number a1Prime, final Number a2Prime, final Number CPrime1, final Number CPrime2, MutableDouble barCPrime, MutableDouble barhPrime) {
{ final float k_H = 1f;
java.util.function.Function<Integer, Double> color2Y = c -> { final float deg360InRad = deg2Rad(360f);
double sr = gammaToLinear(Color.red(c)); final float deg180InRad = deg2Rad(180f);
double sg = gammaToLinear(Color.green(c)); double CPrimeProduct = CPrime1.doubleValue() * CPrime2.doubleValue();
double sb = gammaToLinear(Color.blue(c)); double hPrime1;
return sr * 0.2126 + sg * 0.7152 + sb * 0.0722; if (BigDecimal.ZERO.equals(new BigDecimal(lab1.B)) && BigDecimal.ZERO.equals(new BigDecimal(a1Prime.doubleValue())))
}; hPrime1 = 0.0;
else {
double y = color2Y.apply(c1); hPrime1 = Math.atan2(lab1.B, a1Prime.doubleValue());
double y2 = color2Y.apply(c2); /*
return Math.abs(y2 - y) * XYZ_WHITE_REFERENCE_Y; * This must be converted to a hue angle in degrees between 0
} * and 360 by addition of 2π to negative hue angles.
*/
if (hPrime1 < 0)
hPrime1 += deg360InRad;
}
double hPrime2;
if (BigDecimal.ZERO.equals(new BigDecimal(lab2.B)) && BigDecimal.ZERO.equals(new BigDecimal(a2Prime.doubleValue())))
hPrime2 = 0.0;
else {
hPrime2 = Math.atan2(lab2.B, a2Prime.doubleValue());
/*
* This must be converted to a hue angle in degrees between 0
* and 360 by addition of 2π to negative hue angles.
*/
if (hPrime2 < 0)
hPrime2 += deg360InRad;
}
double deltahPrime;
if (BigDecimal.ZERO.equals(new BigDecimal(CPrimeProduct)))
deltahPrime = 0;
else {
/* Avoid the Math.abs() call */
deltahPrime = hPrime2 - hPrime1;
if (deltahPrime < -deg180InRad)
deltahPrime += deg360InRad;
else if (deltahPrime > deg180InRad)
deltahPrime -= deg360InRad;
}
static double U_Diff(final int c1, final int c2) double deltaHPrime = 2.0 * Math.sqrt(CPrimeProduct) * Math.sin(deltahPrime / 2.0);
{ double hPrimeSum = hPrime1 + hPrime2;
java.util.function.Function<Integer, Double> color2U = c -> { if (BigDecimal.ZERO.equals(new BigDecimal(CPrime1.doubleValue() * CPrime2.doubleValue()))) {
return -0.09991 * Color.red(c) - 0.33609 * Color.green(c) + 0.436 * Color.blue(c); barhPrime.setValue(hPrimeSum);
}; } else {
if (Math.abs(hPrime1 - hPrime2) <= deg180InRad)
double u = color2U.apply(c1); barhPrime.setValue(hPrimeSum / 2.0);
double u2 = color2U.apply(c2); else {
return Math.abs(u2 - u); if (hPrimeSum < deg360InRad)
} barhPrime.setValue((hPrimeSum + deg360InRad) / 2.0);
else
barhPrime.setValue((hPrimeSum - deg360InRad) / 2.0);
}
}
barCPrime.setValue((CPrime1.doubleValue() + CPrime2.doubleValue()) / 2.0);
double T = 1.0 - (0.17 * Math.cos(barhPrime.doubleValue() - deg2Rad(30f))) +
(0.24 * Math.cos(2.0 * barhPrime.doubleValue())) +
(0.32 * Math.cos((3.0 * barhPrime.doubleValue()) + deg2Rad(6f))) -
(0.20 * Math.cos((4.0 * barhPrime.doubleValue()) - deg2Rad(63f)));
double S_H = 1 + (0.015f * barCPrime.doubleValue() * T);
return (float) (deltaHPrime / (k_H * S_H));
}
static float R_T(final Number barCPrime, final Number barhPrime, final float C_prime_div_k_L_S_L, final float H_prime_div_k_L_S_L) {
final double pow25To7 = 6103515625.0; /* Math.pow(25, 7) */
double deltaTheta = deg2Rad(30f) * Math.exp(-Math.pow((barhPrime.doubleValue() - deg2Rad(275f)) / deg2Rad(25f), 2.0));
double R_C = 2.0 * Math.sqrt(Math.pow(barCPrime.doubleValue(), 7.0) / (Math.pow(barCPrime.doubleValue(), 7.0) + pow25To7));
double R_T = (-Math.sin(2.0 * deltaTheta)) * R_C;
return (float) (R_T * C_prime_div_k_L_S_L * H_prime_div_k_L_S_L);
}
/* From the paper "The CIEDE2000 Color-Difference Formula: Implementation Notes, */
/* Supplementary Test Data, and Mathematical Observations", by */
/* Gaurav Sharma, Wencheng Wu and Edul N. Dalal, */
/* Color Res. Appl., vol. 30, no. 1, pp. 21-30, Feb. 2005. */
/* Return the CIEDE2000 Delta E color difference measure squared, for two Lab values */
static float CIEDE2000(final Lab lab1, final Lab lab2) {
float deltaL_prime_div_k_L_S_L = L_prime_div_k_L_S_L(lab1, lab2);
MutableDouble a1Prime = new MutableDouble(), a2Prime = new MutableDouble(), CPrime1 = new MutableDouble(), CPrime2 = new MutableDouble();
float deltaC_prime_div_k_L_S_L = C_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2);
MutableDouble barCPrime = new MutableDouble(), barhPrime = new MutableDouble();
float deltaH_prime_div_k_L_S_L = H_prime_div_k_L_S_L(lab1, lab2, a1Prime, a2Prime, CPrime1, CPrime2, barCPrime, barhPrime);
float deltaR_T = R_T(barCPrime, barhPrime, deltaC_prime_div_k_L_S_L, deltaH_prime_div_k_L_S_L);
return (float) (Math.pow(deltaL_prime_div_k_L_S_L, 2.0) +
Math.pow(deltaC_prime_div_k_L_S_L, 2.0) +
Math.pow(deltaH_prime_div_k_L_S_L, 2.0) +
deltaR_T);
}
static double Y_Diff(final int c1, final int c2) {
java.util.function.Function<Integer, Double> color2Y = c -> {
double sr = gammaToLinear(Color.red(c));
double sg = gammaToLinear(Color.green(c));
double sb = gammaToLinear(Color.blue(c));
return sr * 0.2126 + sg * 0.7152 + sb * 0.0722;
};
double y = color2Y.apply(c1);
double y2 = color2Y.apply(c2);
return Math.abs(y2 - y) * XYZ_WHITE_REFERENCE_Y;
}
static double U_Diff(final int c1, final int c2) {
java.util.function.Function<Integer, Double> color2U = c -> {
return -0.09991 * Color.red(c) - 0.33609 * Color.green(c) + 0.436 * Color.blue(c);
};
double u = color2U.apply(c1);
double u2 = color2U.apply(c2);
return Math.abs(u2 - u);
}
} }
@@ -1,7 +1,7 @@
package com.android.nQuant; package com.android.nQuant;
public interface Ditherable { public interface Ditherable {
public int getColorIndex(final int c); public int getColorIndex(final int c);
public short nearestColorIndex(final int[] palette, final int c, final int pos); public short nearestColorIndex(final int[] palette, final int c, final int pos);
} }
@@ -1,11 +1,11 @@
package com.android.nQuant; package com.android.nQuant;
/* Generalized Hilbert ("gilbert") space-filling curve for rectangular domains of arbitrary (non-power of two) sizes. /* Generalized Hilbert ("gilbert") space-filling curve for rectangular domains of arbitrary (non-power of two) sizes.
Copyright (c) 2021 - 2026 Miller Cy Chan Copyright (c) 2021 - 2025 Miller Cy Chan
* A general rectangle with a known orientation is split into three regions ("up", "right", "down"), for which the function calls itself recursively, until a trivial path can be produced. */ * A general rectangle with a known orientation is split into three regions ("up", "right", "down"), for which the function calls itself recursively, until a trivial path can be produced. */
import android.graphics.Color; import android.graphics.Color;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.PriorityQueue; import java.util.PriorityQueue;
import java.util.Queue; import java.util.Queue;
@@ -13,362 +13,278 @@ import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
public class GilbertCurve { public class GilbertCurve {
private static final class ErrorBox private static final class ErrorBox implements Comparable<ErrorBox> {
{ private double yDiff = 0;
private double yDiff = 0; private final float[] p;
private final float[] p;
private ErrorBox() {
p = new float[4];
}
private ErrorBox(int c) { private ErrorBox() {
p = new float[] { p = new float[4];
Color.red(c), }
Color.green(c),
Color.blue(c),
Color.alpha(c)
};
}
}
private byte ditherMax, DITHER_MAX; private ErrorBox(int c) {
private float beta; p = new float[]{
private float[] weights; Color.red(c),
private final boolean dither, hasAlpha, sortedByYDiff; Color.green(c),
private final int width, height; Color.blue(c),
private final double weight; Color.alpha(c)
private final int[] pixels; };
private final int[] palette; }
private final int[] qPixels;
private final Ditherable ditherable;
private final float[] saliencies;
private final Queue<ErrorBox> errorq;
private final int margin, thresold; @Override
private static final float BLOCK_SIZE = 343f; public int compareTo(final ErrorBox other) {
return Double.compare(yDiff, other.yDiff);
}
}
private GilbertCurve(final int width, final int height, final int[] image, final int[] palette, final int[] qPixels, final Ditherable ditherable, final float[] saliencies, double weight, boolean dither) private byte ditherMax, DITHER_MAX;
{ private float beta;
this.width = width; private float[] weights;
this.height = height; private final boolean dither, sortedByYDiff;
this.pixels = image; private final int width;
this.palette = palette; private final int height;
this.qPixels = qPixels; private final int[] pixels;
this.ditherable = ditherable; private final int[] palette;
this.hasAlpha = weight < 0; private final int[] qPixels;
this.saliencies = saliencies; private final Ditherable ditherable;
this.dither = dither; private final float[] saliencies;
this.weight = Math.abs(weight); private final Queue<ErrorBox> errorq;
margin = weight < .0025 ? 12 : weight < .004 ? 8 : 6; private final int[] lookup;
sortedByYDiff = palette.length > 128 && weight >= .02 && (!hasAlpha || weight < .18);
beta = palette.length > 4 ? (float) (.6f - .00625f * palette.length) : 1;
if (palette.length > 4) {
double boundary = .005 - .0000625 * palette.length;
beta = (float) (weight > boundary ? .25 : Math.min(1.5, beta + palette.length * weight));
if (palette.length > 16 && palette.length <= 32 && weight < .003)
beta += .075f;
else if (weight < .0015 || (palette.length > 32 && palette.length < 256))
beta += .1f;
if (palette.length >= 64 && (weight > .012 && weight < .0125) || (weight > .025 && weight < .03))
beta += .05f;
else if (palette.length > 32 && palette.length < 64 && weight < .015)
beta = .55f;
else if (palette.length > 16 && palette.length <= 32 && weight <= .005)
beta += (float) (.05 + weight * palette.length);
}
else
beta *= .95f;
if (palette.length > 64 || (palette.length > 4 && weight > .02))
beta *= .4f;
if (palette.length > 64 && weight < .02)
beta = .18f;
errorq = sortedByYDiff ? new PriorityQueue<>(new Comparator<ErrorBox>() { private final int margin, thresold;
private static final float BLOCK_SIZE = 343f;
@Override private GilbertCurve(final int width, final int height, final int[] image, final int[] palette, final int[] qPixels, final Ditherable ditherable, final float[] saliencies, double weight, boolean dither) {
public int compare(ErrorBox o1, ErrorBox o2) { this.width = width;
return Double.compare(o2.yDiff, o1.yDiff); this.height = height;
} this.pixels = image;
this.palette = palette;
}) : new ArrayDeque<>(); this.qPixels = qPixels;
this.ditherable = ditherable;
DITHER_MAX = weight < .015 ? (weight > .0025) ? (byte) 25 : 16 : 9; boolean hasAlpha = weight < 0;
if (weight > .99) { this.saliencies = hasAlpha ? null : saliencies;
beta = (float) weight; this.dither = dither;
DITHER_MAX = 25; weight = Math.abs(weight);
} margin = weight < .0025 ? 12 : weight < .004 ? 8 : 6;
sortedByYDiff = palette.length > 128 && (!hasAlpha || weight < .18);
double edge = hasAlpha ? 1 : Math.exp(weight) - .25; beta = palette.length > 4 ? (float) (.6f - .00625f * palette.length) : 1;
double deviation = weight > .002 ? -.25 : 1; if (palette.length > 4) {
ditherMax = (hasAlpha || DITHER_MAX > 9) ? (byte) BitmapUtilities.sqr(Math.sqrt(DITHER_MAX) + edge * deviation) : (byte) (DITHER_MAX * (saliencies != null ? 2 : Math.E)); double boundary = .005 - .0000625 * palette.length;
final int density = palette.length > 16 ? 3200 : 1500; beta = (float) (weight > boundary ? .25 : Math.min(1.5, beta + palette.length * weight));
if(palette.length / weight > 5000 && (weight > .045 || (weight > .01 && palette.length < 64))) if (palette.length > 32 && palette.length < 256)
ditherMax = (byte) BitmapUtilities.sqr(5 + edge); beta += .1f;
else if(weight < .03 && palette.length / weight < density && palette.length >= 16 && palette.length < 256) } else
ditherMax = (byte) BitmapUtilities.sqr(5 + edge); beta *= .95f;
thresold = DITHER_MAX > 9 ? -112 : -64; if (palette.length > 64 || (palette.length > 4 && weight > .02))
weights = new float[0]; beta *= .4f;
}
private static float normalDistribution(float x, float peak) { errorq = sortedByYDiff ? new PriorityQueue<>() : new ArrayDeque<>();
final float mean = .5f, stdDev = .1f;
// Calculate the probability density function (PDF) DITHER_MAX = weight < .015 ? (weight > .0025) ? (byte) 25 : 16 : 9;
double exponent = -Math.pow(x - mean, 2) / (2 * Math.pow(stdDev, 2)); double edge = hasAlpha ? 1 : Math.exp(weight) - .25;
double pdf = (1 / (stdDev * Math.sqrt(2 * Math.PI))) * Math.exp(exponent); double deviation = weight > .002 ? -.25 : 1;
double maxPdf = 1 / (stdDev * Math.sqrt(2 * Math.PI)); // Peak at x = mean ditherMax = (hasAlpha || DITHER_MAX > 9) ? (byte) BitmapUtilities.sqr(Math.sqrt(DITHER_MAX) + edge * deviation) : (byte) (DITHER_MAX * Math.E);
double scaledPdf = (pdf / maxPdf) * peak; final int density = palette.length > 16 ? 3200 : 1500;
return (float) Math.max(0.0, Math.min(peak, scaledPdf)); if (palette.length / weight > 5000 && (weight > .045 || (weight > .01 && palette.length < 64)))
} ditherMax = (byte) BitmapUtilities.sqr(5 + edge);
else if (weight < .03 && palette.length / weight < density && palette.length >= 16 && palette.length < 256)
ditherMax = (byte) BitmapUtilities.sqr(5 + edge);
thresold = DITHER_MAX > 9 ? -112 : -64;
weights = new float[0];
lookup = new int[65536];
}
private int ditherPixel(int x, int y, int c2, float beta) { private void ditherPixel(int x, int y) {
final int bidx = x + y * width; final int bidx = x + y * width;
final int pixel = pixels[bidx]; final int pixel = pixels[bidx];
int r_pix = Color.red(c2); ErrorBox error = new ErrorBox(pixel);
int g_pix = Color.green(c2);
int b_pix = Color.blue(c2);
int a_pix = Color.alpha(c2);
final float strength = 1 / 3f;
final int acceptedDiff = Math.max(2, palette.length - margin);
if (palette.length <= 4 && saliencies[bidx] > .2f && saliencies[bidx] < .25f)
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta * 2 / saliencies[bidx], strength, x, y);
else if (palette.length <= 4 || CIELABConvertor.Y_Diff(pixel, c2) < (2 * acceptedDiff)) {
if (palette.length > 64) {
float kappa = saliencies[bidx] < .6f ? beta * .15f / saliencies[bidx] : beta * .4f / saliencies[bidx];
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], kappa, strength, x, y);
}
else if (palette.length > 16 && weight < .005)
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta * normalDistribution(saliencies[bidx], .5f) + beta, strength, x, y);
else
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta * .5f / saliencies[bidx], strength, x, y);
}
double gamma = (palette.length <= 32 && weight < .01 && weight > .007) ? 1 - beta : beta; float maxErr = DITHER_MAX - 1;
if (palette.length > 4 && CIELABConvertor.Y_Diff(pixel, c2) > (gamma * acceptedDiff)) { int i = sortedByYDiff ? weights.length - 1 : 0;
if (margin > 6 || gamma > beta) { for (ErrorBox eb : errorq) {
float kappa = saliencies[bidx] < .4f ? beta * .4f * saliencies[bidx] : beta * .4f / saliencies[bidx]; if (i < 0 || i >= weights.length)
int c1 = Color.argb(a_pix, r_pix, g_pix, b_pix); break;
if (palette.length > 32 && saliencies[bidx] < .9)
kappa = beta * normalDistribution(saliencies[bidx], 2f);
else {
if (weight >= .0015 && saliencies[bidx] < .6)
c1 = pixel;
if (weight >= .005 && saliencies[bidx] < .6)
kappa = beta * normalDistribution(saliencies[bidx], weight < .0008 ? 2.5f : 1.75f);
else if (palette.length >= 32 || CIELABConvertor.Y_Diff(c1, c2) > (gamma * Math.PI * acceptedDiff)) {
double ub = 1 - palette.length / 320.0;
if (saliencies[bidx] > .15 && saliencies[bidx] < ub)
kappa = beta * (!sortedByYDiff && weight < .0025 ? .55f : .5f) / saliencies[bidx];
else
kappa = beta * normalDistribution(saliencies[bidx], weight < .0025 ? 1.82f : 2f);
}
}
c2 = BlueNoise.diffuse(c1, palette[qPixels[bidx]], kappa, strength, x, y); for (int j = 0; j < eb.p.length; ++j) {
} error.p[j] += eb.p[j] * weights[i];
else if (palette.length <= 32 && weight >= .004) if (error.p[j] > maxErr)
c2 = BlueNoise.diffuse(c2, palette[qPixels[bidx]], beta * normalDistribution(saliencies[bidx], .25f), strength, x, y); maxErr = error.p[j];
else }
c2 = Color.argb(a_pix, r_pix, g_pix, b_pix); i += sortedByYDiff ? -1 : 1;
} }
if (DITHER_MAX < 16 && palette.length > 4 && saliencies[bidx] < .6f && CIELABConvertor.Y_Diff(pixel, c2) > margin - 1)
c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
if (palette.length > 32 && saliencies[bidx] > .95) {
float kappa = beta * Math.max(.05f, .75f - palette.length / 128f) * saliencies[bidx];
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], kappa, strength, x, y);
}
return ditherable.nearestColorIndex(palette, c2, bidx); int r_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[0], 0.0));
} int g_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[1], 0.0));
int b_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[2], 0.0));
int a_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[3], 0.0));
private void diffusePixel(int x, int y) { int c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
final int bidx = x + y * width; if (saliencies != null && dither && !sortedByYDiff) {
final int pixel = pixels[bidx]; final float strength = 1 / 3f;
ErrorBox error = new ErrorBox(pixel); final int acceptedDiff = Math.max(2, palette.length - margin);
if (palette.length <= 4 && saliencies[bidx] > .2f && saliencies[bidx] < .25f)
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta * 2 / saliencies[bidx], strength, x, y);
else if (palette.length <= 4 || CIELABConvertor.Y_Diff(pixel, c2) < (2 * acceptedDiff)) {
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta * .5f / saliencies[bidx], strength, x, y);
if (palette.length <= 4 && CIELABConvertor.U_Diff(pixel, c2) > (8 * acceptedDiff)) {
int c1 = saliencies[bidx] > .65f ? pixel : Color.argb(a_pix, r_pix, g_pix, b_pix);
c2 = BlueNoise.diffuse(c1, palette[qPixels[bidx]], beta * saliencies[bidx], strength, x, y);
}
if (CIELABConvertor.U_Diff(pixel, c2) > (margin * acceptedDiff))
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta / saliencies[bidx], strength, x, y);
}
float maxErr = DITHER_MAX - 1; if (palette.length < 3 || margin > 6) {
int i = sortedByYDiff ? weights.length - 1 : 0; if (palette.length > 4 && (CIELABConvertor.Y_Diff(pixel, c2) > (beta * acceptedDiff) || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) {
for (ErrorBox eb : errorq) { float kappa = saliencies[bidx] < .4f ? beta * .4f * saliencies[bidx] : beta * .4f / saliencies[bidx];
if (i < 0 || i >= weights.length) int c1 = saliencies[bidx] < .6f ? pixel : Color.argb(a_pix, r_pix, g_pix, b_pix);
break; c2 = BlueNoise.diffuse(c1, palette[qPixels[bidx]], kappa, strength, x, y);
}
} else if (palette.length > 4 && (CIELABConvertor.Y_Diff(pixel, c2) > (beta * acceptedDiff) || CIELABConvertor.U_Diff(pixel, c2) > acceptedDiff)) {
if (beta < .3f && (palette.length <= 32 || saliencies[bidx] < beta))
c2 = BlueNoise.diffuse(c2, palette[qPixels[bidx]], beta * .4f * saliencies[bidx], strength, x, y);
else
c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
}
for (int j = 0; j < eb.p.length; ++j) { if (DITHER_MAX < 16 && saliencies[bidx] < .6f && CIELABConvertor.Y_Diff(pixel, c2) > margin - 1)
error.p[j] += eb.p[j] * weights[i]; c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
if(error.p[j] > maxErr)
maxErr = error.p[j];
}
i += sortedByYDiff ? -1 : 1;
}
int r_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[0], 0.0)); int offset = ditherable.getColorIndex(c2);
int g_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[1], 0.0)); if (lookup[offset] == 0)
int b_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[2], 0.0)); lookup[offset] = ditherable.nearestColorIndex(palette, c2, bidx) + 1;
int a_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[3], 0.0)); qPixels[bidx] = palette[lookup[offset] - 1];
} else if (palette.length <= 32 && a_pix > 0xF0) {
int offset = ditherable.getColorIndex(c2);
if (lookup[offset] == 0)
lookup[offset] = ditherable.nearestColorIndex(palette, c2, bidx) + 1;
qPixels[bidx] = palette[lookup[offset] - 1];
int c2 = Color.argb(a_pix, r_pix, g_pix, b_pix); final int acceptedDiff = Math.max(2, palette.length - margin);
if (saliencies != null && dither && !sortedByYDiff && (!hasAlpha || Color.alpha(pixel) < a_pix)) { if (saliencies != null && (CIELABConvertor.Y_Diff(pixel, c2) > acceptedDiff || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) {
if (palette.length >= 256 && saliencies[bidx] > .99f) final float strength = 1 / 3f;
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx); c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], 1 / saliencies[bidx], strength, x, y);
else qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c2, bidx)];
qPixels[bidx] = ditherPixel(x, y, c2, beta); }
} } else
else if (palette.length <= 32 && a_pix > 0xF0) { qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c2, bidx)];
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
final int acceptedDiff = Math.max(2, palette.length - margin); if (errorq.size() >= DITHER_MAX)
if(saliencies != null && (CIELABConvertor.Y_Diff(pixel, c2) > acceptedDiff || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) { errorq.poll();
final float strength = 1 / 3f; else if (!errorq.isEmpty())
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], 1 / saliencies[bidx], strength, x, y); initWeights(errorq.size());
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
}
}
else
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
if(errorq.size() >= DITHER_MAX) c2 = qPixels[bidx];
errorq.poll(); error.p[0] = r_pix - Color.red(c2);
else if(!errorq.isEmpty()) error.p[1] = g_pix - Color.green(c2);
initWeights(errorq.size()); error.p[2] = b_pix - Color.blue(c2);
error.p[3] = a_pix - Color.alpha(c2);
c2 = palette[qPixels[bidx]]; boolean denoise = palette.length > 2;
error.p[0] = r_pix - Color.red(c2); boolean diffuse = BlueNoise.TELL_BLUE_NOISE[bidx & 4095] > thresold;
error.p[1] = g_pix - Color.green(c2); error.yDiff = sortedByYDiff ? CIELABConvertor.Y_Diff(pixel, c2) : 1;
error.p[2] = b_pix - Color.blue(c2); boolean illusion = !diffuse && BlueNoise.TELL_BLUE_NOISE[(int) (error.yDiff * 4096) & 4095] > thresold;
error.p[3] = a_pix - Color.alpha(c2);
boolean denoise = palette.length > 2; int errLength = denoise ? error.p.length - 1 : 0;
boolean diffuse = BlueNoise.TELL_BLUE_NOISE[bidx & 4095] > thresold; for (int j = 0; j < errLength; ++j) {
error.yDiff = sortedByYDiff ? CIELABConvertor.Y_Diff(pixel, c2) : 1; if (Math.abs(error.p[j]) >= ditherMax) {
boolean illusion = !diffuse && BlueNoise.TELL_BLUE_NOISE[(int) (error.yDiff * 4096) & 4095] > thresold; if (diffuse)
error.p[j] = (float) Math.tanh(error.p[j] / maxErr * 20) * (ditherMax - 1);
else if (illusion)
error.p[j] = (float) (error.p[j] / maxErr * error.yDiff) * (ditherMax - 1);
else
error.p[j] /= (float) (1 + Math.sqrt(ditherMax));
}
}
errorq.add(error);
}
boolean unaccepted = false; private void generate2d(int x, int y, int ax, int ay, int bx, int by) {
int errLength = denoise ? error.p.length - 1 : 0; int w = Math.abs(ax + ay);
for (int j = 0; j < errLength; ++j) { int h = Math.abs(bx + by);
if (Math.abs(error.p[j]) >= ditherMax) { int dax = Integer.signum(ax);
if (sortedByYDiff && saliencies != null) int day = Integer.signum(ay);
unaccepted = true; int dbx = Integer.signum(bx);
int dby = Integer.signum(by);
if (diffuse) if (h == 1) {
error.p[j] = (float) Math.tanh(error.p[j] / maxErr * 20) * (ditherMax - 1); for (int i = 0; i < w; ++i) {
else if(illusion) ditherPixel(x, y);
error.p[j] = (float) (error.p[j] / maxErr * error.yDiff) * (ditherMax - 1); x += dax;
else y += day;
error.p[j] /= (float) (1 + Math.sqrt(ditherMax)); }
} return;
}
if (sortedByYDiff && saliencies == null && Math.abs(error.p[j]) >= DITHER_MAX) if (w == 1) {
unaccepted = true; for (int i = 0; i < h; ++i) {
} ditherPixel(x, y);
x += dbx;
y += dby;
}
return;
}
if (unaccepted) { int ax2 = ax / 2;
if (saliencies != null) int ay2 = ay / 2;
qPixels[bidx] = ditherPixel(x, y, c2, beta); int bx2 = bx / 2;
else if (CIELABConvertor.Y_Diff(pixel, c2) > 3 && CIELABConvertor.U_Diff(pixel, c2) > 3) { int by2 = by / 2;
final float strength = 1 / 3f;
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], strength, strength, x, y);
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
}
}
errorq.add(error); int w2 = Math.abs(ax2 + ay2);
int h2 = Math.abs(bx2 + by2);
if (dither || palette.length <= 32) if (2 * w > 3 * h) {
qPixels[bidx] = palette[qPixels[bidx]]; if ((w2 % 2) != 0 && w > 2) {
} ax2 += dax;
ay2 += day;
}
generate2d(x, y, ax2, ay2, bx, by);
generate2d(x + ax2, y + ay2, ax - ax2, ay - ay2, bx, by);
return;
}
private void generate2d(int x, int y, int ax, int ay, int bx, int by) { if ((h2 % 2) != 0 && h > 2) {
int w = Math.abs(ax + ay); bx2 += dbx;
int h = Math.abs(bx + by); by2 += dby;
int dax = Integer.signum(ax); }
int day = Integer.signum(ay);
int dbx = Integer.signum(bx);
int dby = Integer.signum(by);
if (h == 1) { generate2d(x, y, bx2, by2, ax2, ay2);
for (int i = 0; i < w; ++i){ generate2d(x + bx2, y + by2, ax, ay, bx - bx2, by - by2);
diffusePixel(x, y); generate2d(x + (ax - dax) + (bx2 - dbx), y + (ay - day) + (by2 - dby), -bx2, -by2, -(ax - ax2), -(ay - ay2));
x += dax; }
y += day;
}
return;
}
if (w == 1) { private void initWeights(int size) {
for (int i = 0; i < h; ++i){ /* Dithers all pixels of the image in sequence using
diffusePixel(x, y); * the Gilbert path, and distributes the error in
x += dbx; * a sequence of pixels size.
y += dby; */
} final float weightRatio = (float) Math.pow(BLOCK_SIZE + 1f, 1f / (size - 1f));
return; float weight = 1f, sumweight = 0f;
} weights = new float[size];
for (int c = 0; c < size; ++c) {
errorq.add(new ErrorBox());
sumweight += (weights[size - c - 1] = weight);
weight /= weightRatio;
}
int ax2 = ax / 2; weight = 0f; /* Normalize */
int ay2 = ay / 2; for (int c = 0; c < size; ++c)
int bx2 = bx / 2; weight += (weights[c] /= sumweight);
int by2 = by / 2; weights[0] += 1f - weight;
}
int w2 = Math.abs(ax2 + ay2); private void run() {
int h2 = Math.abs(bx2 + by2); if (!sortedByYDiff)
initWeights(DITHER_MAX);
if (2 * w > 3 * h) { if (width >= height)
if ((w2 % 2) != 0 && w > 2) { generate2d(0, 0, width, 0, 0, height);
ax2 += dax; else
ay2 += day; generate2d(0, 0, 0, height, width, 0);
} }
generate2d(x, y, ax2, ay2, bx, by);
generate2d(x + ax2, y + ay2, ax - ax2, ay - ay2, bx, by);
return;
}
if ((h2 % 2) != 0 && h > 2) { public static int[] dither(final int width, final int height, final int[] pixels, final int[] palette, final Ditherable ditherable, final float[] saliencies, final double weight, final boolean dither) {
bx2 += dbx;
by2 += dby;
}
generate2d(x, y, bx2, by2, ax2, ay2);
generate2d(x + bx2, y + by2, ax, ay, bx - bx2, by - by2);
generate2d(x + (ax - dax) + (bx2 - dbx), y + (ay - day) + (by2 - dby), -bx2, -by2, -(ax - ax2), -(ay - ay2));
}
private void initWeights(int size) {
/* Dithers all pixels of the image in sequence using
* the Gilbert path, and distributes the error in
* a sequence of pixels size.
*/
final float weightRatio = (float) Math.pow(BLOCK_SIZE + 1f, 1f / (size - 1f));
float weight = 1f, sumweight = 0f;
weights = new float[size];
for(int c = 0; c < size; ++c) {
errorq.add(new ErrorBox());
sumweight += (weights[size - c - 1] = weight);
weight /= weightRatio;
}
weight = 0f; /* Normalize */
for(int c = 0; c < size; ++c)
weight += (weights[c] /= sumweight);
weights[0] += 1f - weight;
}
private void run()
{
if(!sortedByYDiff)
initWeights(DITHER_MAX);
if (width >= height)
generate2d(0, 0, width, 0, 0, height);
else
generate2d(0, 0, 0, height, width, 0);
}
public static int[] dither(final int width, final int height, final int[] pixels, final int[] palette, final Ditherable ditherable, final float[] saliencies, final double weight, final boolean dither)
{
int[] qPixels = new int[pixels.length]; int[] qPixels = new int[pixels.length];
new GilbertCurve(width, height, pixels, palette, qPixels, ditherable, saliencies, weight, dither).run(); new GilbertCurve(width, height, pixels, palette, qPixels, ditherable, saliencies, weight, dither).run();
return qPixels;
return qPixels; }
}
} }
File diff suppressed because it is too large Load Diff
@@ -1,469 +1,449 @@
package com.android.nQuant; package com.android.nQuant;
/* Fast pairwise nearest neighbor based algorithm for multilevel thresholding /* Fast pairwise nearest neighbor based algorithm for multilevel thresholding
Copyright (C) 2004-2016 Mark Tyler and Dmitry Groshev Copyright (C) 2004-2016 Mark Tyler and Dmitry Groshev
Copyright (c) 2018-2026 Miller Cy Chan Copyright (c) 2018-2023 Miller Cy Chan
* error measure; time used is proportional to number of bins squared - WJ */ * error measure; time used is proportional to number of bins squared - WJ */
import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.util.Pair; import android.util.Pair;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
public class PnnQuantizer { public class PnnQuantizer {
protected short alphaThreshold = 0xF; protected short alphaThreshold = 0xF;
protected boolean hasSemiTransparency = false; protected boolean hasSemiTransparency = false;
protected int m_transparentPixelIndex = -1; protected int m_transparentPixelIndex = -1;
protected int width, height; protected final int width, height;
protected int[] pixels = null; protected final int[] pixels;
protected Integer m_transparentColor = Color.argb(0, BYTE_MAX, BYTE_MAX, BYTE_MAX); protected Integer m_transparentColor = Color.argb(0, BYTE_MAX, BYTE_MAX, BYTE_MAX);
protected double PR = 0.299, PG = 0.587, PB = 0.114, PA = .3333; protected double PR = 0.299, PG = 0.587, PB = 0.114, PA = .3333;
protected double ratio = .5, weight = 1; protected double ratio = .5, weight = 1;
protected static final float[][] coeffs = new float[][] { protected static final float[][] coeffs = new float[][]{
{0.299f, 0.587f, 0.114f}, {0.299f, 0.587f, 0.114f},
{-0.14713f, -0.28886f, 0.436f}, {-0.14713f, -0.28886f, 0.436f},
{0.615f, -0.51499f, -0.10001f} {0.615f, -0.51499f, -0.10001f}
}; };
protected Map<Integer, int[]> closestMap = new HashMap<>();
protected Map<Integer, Short> nearestMap = new HashMap<>();
public PnnQuantizer(String fname) { protected Map<Integer, int[]> closestMap = new HashMap<>();
fromBitmap(fname); protected Map<Integer, Short> nearestMap = new HashMap<>();
}
public PnnQuantizer(Bitmap bitmap) { public PnnQuantizer(Bitmap bitmap) {
fromBitmap(bitmap); width = bitmap.getWidth();
} height = bitmap.getHeight();
pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
}
private void fromBitmap(Bitmap bitmap) { private static final class Pnnbin {
width = bitmap.getWidth(); double ac = 0, rc = 0, gc = 0, bc = 0;
height = bitmap.getHeight(); float cnt = 0, err = 0;
pixels = new int [width * height]; int nn, fw, bk, tm, mtm;
bitmap.getPixels(pixels, 0, width, 0, 0, width, height); }
}
private void fromBitmap(String fname) { private void find_nn(Pnnbin[] bins, int idx) {
Bitmap bitmap = BitmapFactory.decodeFile(fname); int nn = 0;
fromBitmap(bitmap); double err = 1e100;
}
private static final class Pnnbin { Pnnbin bin1 = bins[idx];
double ac = 0, rc = 0, gc = 0, bc = 0; float n1 = bin1.cnt;
float cnt = 0, err = 0; double wa = bin1.ac;
int nn, fw, bk, tm, mtm; double wr = bin1.rc;
} double wg = bin1.gc;
double wb = bin1.bc;
private void find_nn(Pnnbin[] bins, int idx) int start = 0;
{ if (BlueNoise.TELL_BLUE_NOISE[idx & 4095] > -88)
int nn = 0; start = (PG < coeffs[0][1]) ? coeffs.length : 1;
double err = 1e100;
Pnnbin bin1 = bins[idx]; for (int i = bin1.fw; i != 0; i = bins[i].fw) {
float n1 = bin1.cnt; double n2 = bins[i].cnt, nerr2 = (n1 * n2) / (n1 + n2);
double wa = bin1.ac; if (nerr2 >= err)
double wr = bin1.rc; continue;
double wg = bin1.gc;
double wb = bin1.bc;
int start = 0; double nerr = 0.0;
if(BlueNoise.TELL_BLUE_NOISE[idx & 4095] > 0) if (hasSemiTransparency) {
start = (PG < coeffs[0][1]) ? coeffs.length : 1; start = 1;
nerr += nerr2 * (1 - ratio) * PA * BitmapUtilities.sqr(bins[i].ac - wa);
for (int i = bin1.fw; i != 0; i = bins[i].fw) { if (nerr >= err)
double n2 = bins[i].cnt, nerr2 = (n1 * n2) / (n1 + n2); continue;
if (nerr2 >= err) }
continue;
double nerr = 0.0;
if(hasSemiTransparency) {
nerr += nerr2 * PA * BitmapUtilities.sqr(bins[i].ac - wa);
if (nerr >= err)
continue;
}
nerr += nerr2 * (1 - ratio) * PR * BitmapUtilities.sqr(bins[i].rc - wr);
if (nerr >= err)
continue;
nerr += nerr2 * (1 - ratio) * PG * BitmapUtilities.sqr(bins[i].gc - wg); nerr += nerr2 * (1 - ratio) * PR * BitmapUtilities.sqr(bins[i].rc - wr);
if (nerr >= err) if (nerr >= err)
continue; continue;
nerr += nerr2 * (1 - ratio) * PB * BitmapUtilities.sqr(bins[i].bc - wb); nerr += nerr2 * (1 - ratio) * PG * BitmapUtilities.sqr(bins[i].gc - wg);
if (nerr >= err) if (nerr >= err)
continue; continue;
for (int j = start; j < coeffs.length; ++j) {
nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][0] * (bins[i].rc - wr));
if (nerr >= err)
break;
nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][1] * (bins[i].gc - wg));
if (nerr >= err)
break;
nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][2] * (bins[i].bc - wb));
if (nerr >= err)
break;
}
err = nerr;
nn = i;
}
bin1.err = (float) err;
bin1.nn = nn;
}
@FunctionalInterface
protected interface QuanFn {
float get(float cnt);
}
protected QuanFn getQuanFn(int nMaxColors, short quan_rt) { nerr += nerr2 * (1 - ratio) * PB * BitmapUtilities.sqr(bins[i].bc - wb);
if (quan_rt > 0) { if (nerr >= err)
if (nMaxColors < 64) continue;
return cnt -> (float) Math.sqrt(cnt);
return cnt -> (int) Math.sqrt(cnt);
}
if (quan_rt < 0)
return cnt -> (int) Math.cbrt(cnt);
return cnt -> cnt;
}
protected int[] pnnquan(final int[] pixels, int nMaxColors) for (int j = start; j < coeffs.length; ++j) {
{ nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][0] * (bins[i].rc - wr));
short quan_rt = (short) 1; if (nerr >= err)
Pnnbin[] bins = new Pnnbin[65536]; break;
/* Build histogram */ nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][1] * (bins[i].gc - wg));
for (int pixel : pixels) { if (nerr >= err)
if (Color.alpha(pixel) <= alphaThreshold) break;
pixel = m_transparentColor;
int index = BitmapUtilities.getColorIndex(pixel, hasSemiTransparency, nMaxColors < 64 || m_transparentPixelIndex >= 0);
if(bins[index] == null) nerr += nerr2 * ratio * BitmapUtilities.sqr(coeffs[j][2] * (bins[i].bc - wb));
bins[index] = new Pnnbin(); if (nerr >= err)
Pnnbin tb = bins[index]; break;
tb.ac += Color.alpha(pixel); }
tb.rc += Color.red(pixel);
tb.gc += Color.green(pixel);
tb.bc += Color.blue(pixel);
tb.cnt++;
}
/* Cluster nonempty bins at one end of array */ err = nerr;
int maxbins = 0; nn = i;
}
bin1.err = (float) err;
bin1.nn = nn;
}
for (int i = 0; i < bins.length; ++i) { @FunctionalInterface
if (bins[i] == null) protected interface QuanFn {
continue; float get(float cnt);
}
float d = 1f / bins[i].cnt; protected QuanFn getQuanFn(int nMaxColors, short quan_rt) {
bins[i].ac *= d; if (quan_rt > 0) {
bins[i].rc *= d; if (nMaxColors < 64)
bins[i].gc *= d; return cnt -> (float) Math.sqrt(cnt);
bins[i].bc *= d; return cnt -> (int) Math.sqrt(cnt);
}
bins[maxbins++] = bins[i]; if (quan_rt < 0)
} return cnt -> (int) Math.cbrt(cnt);
return cnt -> cnt;
}
if(nMaxColors < 16) protected int[] pnnquan(final int[] pixels, int nMaxColors) {
quan_rt = -1; short quan_rt = (short) 1;
Pnnbin[] bins = new Pnnbin[65536];
weight = Math.min(0.9, nMaxColors * 1.0 / maxbins);
if (weight < .04 && PG >= coeffs[0][1]) {
PR = PG = PB = PA = 1;
if (nMaxColors >= 64)
quan_rt = 0;
}
QuanFn quanFn = getQuanFn(nMaxColors, quan_rt);
int j = 0; /* Build histogram */
for (; j < maxbins - 1; ++j) { for (int pixel : pixels) {
bins[j].fw = j + 1; if (Color.alpha(pixel) <= alphaThreshold)
bins[j + 1].bk = j; pixel = m_transparentColor;
bins[j].cnt = quanFn.get(bins[j].cnt);
}
bins[j].cnt = quanFn.get(bins[j].cnt);
int h, l, l2; int index = BitmapUtilities.getColorIndex(pixel, hasSemiTransparency, nMaxColors < 64 || m_transparentPixelIndex >= 0);
/* Initialize nearest neighbors and build heap of them */
int[] heap = new int[bins.length + 1];
for (int i = 0; i < maxbins; i++) {
find_nn(bins, i);
/* Push slot on heap */
float err = bins[i].err;
for (l = ++heap[0]; l > 1; l = l2) {
l2 = l >> 1;
if (bins[h = heap[l2]].err <= err)
break;
heap[l] = h;
}
heap[l] = i;
}
/* Merge bins which increase error the least */ if (bins[index] == null)
int extbins = maxbins - nMaxColors; bins[index] = new Pnnbin();
for (int i = 0; i < extbins; ) { Pnnbin tb = bins[index];
Pnnbin tb; tb.ac += Color.alpha(pixel);
/* Use heap to find which bins to merge */ tb.rc += Color.red(pixel);
for (;;) { tb.gc += Color.green(pixel);
int b1 = heap[1]; tb.bc += Color.blue(pixel);
tb = bins[b1]; /* One with least error */ tb.cnt++;
/* Is stored error up to date? */ }
if ((tb.tm >= tb.mtm) && (bins[tb.nn].mtm <= tb.tm))
break;
if (tb.mtm == 0xFFFF) /* Deleted node */
b1 = heap[1] = heap[heap[0]--];
else /* Too old error value */
{
find_nn(bins, b1);
tb.tm = i;
}
/* Push slot down */
float err = bins[b1].err;
for (l = 1; (l2 = l + l) <= heap[0]; l = l2) {
if ((l2 < heap[0]) && (bins[heap[l2]].err > bins[heap[l2 + 1]].err))
++l2;
if (err <= bins[h = heap[l2]].err)
break;
heap[l] = h;
}
heap[l] = b1;
}
/* Do a merge */ /* Cluster nonempty bins at one end of array */
Pnnbin nb = bins[tb.nn]; int maxbins = 0;
float n1 = tb.cnt;
float n2 = nb.cnt;
float d = 1f / (n1 + n2);
tb.ac = d * Math.round(n1 * tb.ac + n2 * nb.ac);
tb.rc = d * Math.round(n1 * tb.rc + n2 * nb.rc);
tb.gc = d * Math.round(n1 * tb.gc + n2 * nb.gc);
tb.bc = d * Math.round(n1 * tb.bc + n2 * nb.bc);
tb.cnt += n2;
tb.mtm = ++i;
/* Unchain deleted bin */ for (int i = 0; i < bins.length; ++i) {
bins[nb.bk].fw = nb.fw; if (bins[i] == null)
bins[nb.fw].bk = nb.bk; continue;
nb.mtm = 0xFFFF;
}
/* Fill palette */ float d = 1f / bins[i].cnt;
int[] palette = new int[extbins > 0 ? nMaxColors : maxbins]; bins[i].ac *= d;
short k = 0; bins[i].rc *= d;
for (int i = 0; k < palette.length; ++k) { bins[i].gc *= d;
palette[k] = Color.argb((int) bins[i].ac, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc); bins[i].bc *= d;
i = bins[i].fw; bins[maxbins++] = bins[i];
} }
return palette; if (nMaxColors < 16)
} quan_rt = -1;
protected short nearestColorIndex(final int[] palette, int c, final int pos) weight = Math.min(0.9, nMaxColors * 1.0 / maxbins);
{ if (weight < .03 && PG >= coeffs[0][1]) {
final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); PR = PG = PB = PA = 1;
Short got = nearestMap.get(offset); if (nMaxColors >= 64)
if (got != null) quan_rt = 0;
return got; }
short k = 0;
if (Color.alpha(c) <= alphaThreshold)
c = m_transparentColor;
if(palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
k = 1;
double pr = PR, pg = PG, pb = PB, pa = PA;
if(palette.length < 3)
pr = pg = pb = pa = 1;
double mindist = Integer.MAX_VALUE; QuanFn quanFn = getQuanFn(nMaxColors, quan_rt);
for (short i=k; i<palette.length; ++i) {
int c2 = palette[i];
double curdist = pa * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c)); int j = 0;
if (curdist > mindist) for (; j < maxbins - 1; ++j) {
continue; bins[j].fw = j + 1;
bins[j + 1].bk = j;
curdist += pr * BitmapUtilities.sqr(Color.red(c2) - Color.red(c)); bins[j].cnt = quanFn.get(bins[j].cnt);
if (curdist > mindist) }
continue; bins[j].cnt = quanFn.get(bins[j].cnt);
curdist += pg * BitmapUtilities.sqr(Color.green(c2) - Color.green(c)); int h, l, l2;
if (curdist > mindist) /* Initialize nearest neighbors and build heap of them */
continue; int[] heap = new int[bins.length + 1];
for (int i = 0; i < maxbins; i++) {
find_nn(bins, i);
/* Push slot on heap */
float err = bins[i].err;
for (l = ++heap[0]; l > 1; l = l2) {
l2 = l >> 1;
if (bins[h = heap[l2]].err <= err)
break;
heap[l] = h;
}
heap[l] = i;
}
curdist += pb * BitmapUtilities.sqr(Color.blue(c2) - Color.blue(c)); /* Merge bins which increase error the least */
if (curdist > mindist) int extbins = maxbins - nMaxColors;
continue; for (int i = 0; i < extbins; ) {
Pnnbin tb = null;
/* Use heap to find which bins to merge */
for (; ; ) {
int b1 = heap[1];
tb = bins[b1]; /* One with least error */
/* Is stored error up to date? */
if ((tb.tm >= tb.mtm) && (bins[tb.nn].mtm <= tb.tm))
break;
if (tb.mtm == 0xFFFF) /* Deleted node */
b1 = heap[1] = heap[heap[0]--];
else /* Too old error value */ {
find_nn(bins, b1);
tb.tm = i;
}
/* Push slot down */
float err = bins[b1].err;
for (l = 1; (l2 = l + l) <= heap[0]; l = l2) {
if ((l2 < heap[0]) && (bins[heap[l2]].err > bins[heap[l2 + 1]].err))
++l2;
if (err <= bins[h = heap[l2]].err)
break;
heap[l] = h;
}
heap[l] = b1;
}
mindist = curdist; /* Do a merge */
k = i; Pnnbin nb = bins[tb.nn];
} float n1 = tb.cnt;
nearestMap.put(offset, k); float n2 = nb.cnt;
return k; float d = 1f / (n1 + n2);
} tb.ac = d * Math.round(n1 * tb.ac + n2 * nb.ac);
tb.rc = d * Math.round(n1 * tb.rc + n2 * nb.rc);
tb.gc = d * Math.round(n1 * tb.gc + n2 * nb.gc);
tb.bc = d * Math.round(n1 * tb.bc + n2 * nb.bc);
tb.cnt += n2;
tb.mtm = ++i;
protected short closestColorIndex(final int[] palette, int c, final int pos) /* Unchain deleted bin */
{ bins[nb.bk].fw = nb.fw;
short k = 0; bins[nb.fw].bk = nb.bk;
if (Color.alpha(c) <= alphaThreshold) nb.mtm = 0xFFFF;
return nearestColorIndex(palette, c, pos); }
final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); /* Fill palette */
int[] closest = closestMap.get(c); int[] palette = new int[extbins > 0 ? nMaxColors : maxbins];
if (closest == null) { short k = 0;
closest = new int[4]; for (int i = 0; ; ++k) {
closest[2] = closest[3] = Integer.MAX_VALUE; palette[k] = Color.argb((int) bins[i].ac, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc);
double pr = PR, pg = PG, pb = PB, pa = PA;
if(palette.length < 3)
pr = pg = pb = pa = 1;
for (; k < palette.length; ++k) { if ((i = bins[i].fw) == 0)
int c2 = palette[k]; break;
}
double err = pr * BitmapUtilities.sqr(Color.red(c2) - Color.red(c)); return palette;
if (err >= closest[3]) }
continue;
err += pg * BitmapUtilities.sqr(Color.green(c2) - Color.green(c));
if (err >= closest[3])
continue;
err += pb * BitmapUtilities.sqr(Color.blue(c2) - Color.blue(c));
if (err >= closest[3])
continue;
if (hasSemiTransparency)
err += pa * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
if (err < closest[2]) {
closest[1] = closest[0];
closest[3] = closest[2];
closest[0] = k;
closest[2] = (int) err;
}
else if (err < closest[3]) {
closest[1] = k;
closest[3] = (int) err;
}
}
if (closest[3] == Integer.MAX_VALUE) protected short nearestColorIndex(final int[] palette, int c, final int pos) {
closest[1] = closest[0]; Short got = nearestMap.get(c);
if (got != null)
return got;
closestMap.put(offset, closest); short k = 0;
} if (Color.alpha(c) <= alphaThreshold)
c = m_transparentColor;
if (palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
k = 1;
int MAX_ERR = palette.length << 2; double pr = PR, pg = PG, pb = PB, pa = PA;
int idx = (pos + 1) % 2; if (palette.length < 3)
if (closest[3] * .67 < (closest[3] - closest[2])) pr = pg = pb = pa = 1;
idx = 0;
else if (closest[0] > closest[1])
idx = pos % 2;
if(closest[idx + 2] >= MAX_ERR || (hasAlpha() && closest[idx] == 0)) double mindist = Integer.MAX_VALUE;
return nearestColorIndex(palette, c, pos); for (short i = k; i < palette.length; ++i) {
return (short) closest[idx]; int c2 = palette[i];
}
protected Ditherable getDitherFn(final boolean dither) { double curdist = pa * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
return new Ditherable() { if (curdist > mindist)
@Override continue;
public int getColorIndex(int c) {
return BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
}
@Override curdist += pr * BitmapUtilities.sqr(Color.red(c2) - Color.red(c));
public short nearestColorIndex(int[] palette, int c, final int pos) { if (curdist > mindist)
if(dither) continue;
return PnnQuantizer.this.nearestColorIndex(palette, c, pos);
return PnnQuantizer.this.closestColorIndex(palette, c, pos);
}
};
}
protected int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither) curdist += pg * BitmapUtilities.sqr(Color.green(c2) - Color.green(c));
{ if (curdist > mindist)
Ditherable ditherable = getDitherFn(dither); continue;
if(hasSemiTransparency)
weight *= -1;
int[] qPixels = GilbertCurve.dither(width, height, cPixels, palette, ditherable, null, weight, dither);
if (!dither && palette.length > 32) curdist += pb * BitmapUtilities.sqr(Color.blue(c2) - Color.blue(c));
BlueNoise.dither(width, height, cPixels, palette, ditherable, qPixels, 1.0f); if (curdist > mindist)
continue;
closestMap.clear(); mindist = curdist;
nearestMap.clear(); k = i;
}
nearestMap.put(c, k);
return k;
}
return qPixels; protected short closestColorIndex(final int[] palette, int c, final int pos) {
} short k = 0;
if (Color.alpha(c) <= alphaThreshold)
return nearestColorIndex(palette, c, pos);
public Pair<Bitmap, int[]> convert(int nMaxColors, boolean dither) { int[] closest = closestMap.get(c);
int semiTransCount = 0; if (closest == null) {
for (int i = 0; i < pixels.length; ++i) { closest = new int[4];
int pixel = pixels[i]; closest[2] = closest[3] = Integer.MAX_VALUE;
int alfa = (pixel >> 24) & 0xff;
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = (pixel ) & 0xff;
pixels[i] = Color.argb(alfa, r, g, b);
if (alfa < 0xE0) {
if (alfa == 0) {
m_transparentPixelIndex = i;
if(nMaxColors > 2)
m_transparentColor = pixels[i];
else
pixels[i] = m_transparentColor;
}
else if (alfa > alphaThreshold)
++semiTransCount;
}
}
hasSemiTransparency = semiTransCount > 0;
if (nMaxColors <= 32)
PR = PG = PB = PA = 1;
else {
PR = coeffs[0][0]; PG = coeffs[0][1]; PB = coeffs[0][2];
}
int[] palette; double pr = PR, pg = PG, pb = PB, pa = PA;
if (nMaxColors > 2) if (palette.length < 3)
palette = pnnquan(pixels, nMaxColors); pr = pg = pb = pa = 1;
else {
palette = new int[nMaxColors];
weight = 1;
if (m_transparentPixelIndex >= 0) {
palette[0] = m_transparentColor;
palette[1] = Color.BLACK;
}
else {
palette[0] = Color.BLACK;
palette[1] = Color.WHITE;
}
}
int[] qPixels = dither(pixels, palette, width, height, dither); for (; k < palette.length; ++k) {
return new Pair<>( int c2 = palette[k];
Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888),
palette); double err = pr * BitmapUtilities.sqr(Color.red(c2) - Color.red(c));
} if (err >= closest[3])
continue;
public boolean hasAlpha() {
return m_transparentPixelIndex > -1; err += pg * BitmapUtilities.sqr(Color.green(c2) - Color.green(c));
} if (err >= closest[3])
continue;
err += pb * BitmapUtilities.sqr(Color.blue(c2) - Color.blue(c));
if (err >= closest[3])
continue;
if (hasSemiTransparency)
err += pa * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
if (err < closest[2]) {
closest[1] = closest[0];
closest[3] = closest[2];
closest[0] = k;
closest[2] = (int) err;
} else if (err < closest[3]) {
closest[1] = k;
closest[3] = (int) err;
}
}
if (closest[3] == Integer.MAX_VALUE)
closest[1] = closest[0];
closestMap.put(c, closest);
}
int MAX_ERR = palette.length << 2;
int idx = (pos + 1) % 2;
if (closest[3] * .67 < (closest[3] - closest[2]))
idx = 0;
else if (closest[0] > closest[1])
idx = pos % 2;
if (closest[idx + 2] >= MAX_ERR || (hasAlpha() && closest[idx] == 0))
return nearestColorIndex(palette, c, pos);
return (short) closest[idx];
}
protected Ditherable getDitherFn(final boolean dither) {
return new Ditherable() {
@Override
public int getColorIndex(int c) {
return BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
}
@Override
public short nearestColorIndex(int[] palette, int c, final int pos) {
if (dither)
return PnnQuantizer.this.nearestColorIndex(palette, c, pos);
return PnnQuantizer.this.closestColorIndex(palette, c, pos);
}
};
}
protected int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither) {
Ditherable ditherable = getDitherFn(dither);
if (hasSemiTransparency)
weight *= -1;
int[] qPixels = GilbertCurve.dither(width, height, cPixels, palette, ditherable, null, weight, dither);
if (!dither && palette.length > 32)
BlueNoise.dither(width, height, cPixels, palette, ditherable, qPixels, 1.0f);
closestMap.clear();
nearestMap.clear();
return qPixels;
}
public Pair<Bitmap, int[]> convert(int nMaxColors, boolean dither) {
int semiTransCount = 0;
for (int i = 0; i < pixels.length; ++i) {
int pixel = pixels[i];
int alfa = (pixel >> 24) & 0xff;
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = (pixel) & 0xff;
pixels[i] = Color.argb(alfa, r, g, b);
if (alfa < 0xE0) {
if (alfa == 0) {
m_transparentPixelIndex = i;
if (nMaxColors > 2)
m_transparentColor = pixels[i];
else
pixels[i] = m_transparentColor;
} else if (alfa > alphaThreshold)
++semiTransCount;
}
}
hasSemiTransparency = semiTransCount > 0;
if (nMaxColors <= 32)
PR = PG = PB = PA = 1;
else {
PR = coeffs[0][0];
PG = coeffs[0][1];
PB = coeffs[0][2];
}
int[] palette;
if (nMaxColors > 2)
palette = pnnquan(pixels, nMaxColors);
else {
palette = new int[nMaxColors];
weight = 1;
if (m_transparentPixelIndex >= 0) {
palette[0] = m_transparentColor;
palette[1] = Color.BLACK;
} else {
palette[0] = Color.BLACK;
palette[1] = Color.WHITE;
}
}
int[] qPixels = dither(pixels, palette, width, height, dither);
return Pair.create(
Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888),
palette
);
}
public boolean hasAlpha() {
return m_transparentPixelIndex > -1;
}
} }
@@ -1,6 +1,6 @@
/** /**
* All files under this package were adapted from https://github.com/mcychan/nQuant.android * All files under this package were adapted from https://github.com/mcychan/nQuant.android
* at 12822f15c92695136f1761b6b72c9bd18dc70c46 * at 8f18e9d7536e71a90d0d1333968e07688b7ebf09
* <p> * <p>
* Changes done: * Changes done:
* - Update PnnQuantizer to allow for access to the palette after convertion * - Update PnnQuantizer to allow for access to the palette after convertion