mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Revert nQuant upgrade
Latest changes are not compatible with SDK 23 * This reverts commit07b1e593b3. * This reverts commitc8da8b4717.
This commit is contained in:
@@ -5,138 +5,15 @@ import android.graphics.Color;
|
||||
public class BitmapUtilities {
|
||||
static final char BYTE_MAX = -Byte.MIN_VALUE + Byte.MAX_VALUE;
|
||||
|
||||
static int getColorIndex(final int c, boolean hasSemiTransparency, boolean hasTransparency)
|
||||
{
|
||||
if(hasSemiTransparency)
|
||||
static int getColorIndex(final int c, boolean hasSemiTransparency, boolean hasTransparency) {
|
||||
if (hasSemiTransparency)
|
||||
return (Color.alpha(c) & 0xF0) << 8 | (Color.red(c) & 0xF0) << 4 | (Color.green(c) & 0xF0) | (Color.blue(c) >> 4);
|
||||
if (hasTransparency)
|
||||
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);
|
||||
}
|
||||
|
||||
static double sqr(double value)
|
||||
{
|
||||
static double sqr(double 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,8 +177,7 @@ public class BlueNoise {
|
||||
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 g_pix = Color.green(pixel);
|
||||
int b_pix = Color.blue(pixel);
|
||||
@@ -196,16 +195,7 @@ public class BlueNoise {
|
||||
return Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
}
|
||||
|
||||
static int[] processImagePixels(final int[] palette, final int[] qPixels) {
|
||||
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;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.nQuant;
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -55,8 +56,7 @@ public class CIELABConvertor {
|
||||
float L = 0f;
|
||||
}
|
||||
|
||||
static Lab RGB2LAB(final int c1)
|
||||
{
|
||||
static Lab RGB2LAB(final int c1) {
|
||||
double[] labs = new double[3];
|
||||
ColorUtils.colorToLAB(c1, labs);
|
||||
|
||||
@@ -68,13 +68,12 @@ public class CIELABConvertor {
|
||||
return lab;
|
||||
}
|
||||
|
||||
protected static double gammaToLinear(int channel)
|
||||
{
|
||||
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 int LAB2RGB(final Lab lab) {
|
||||
int color = ColorUtils.LABToColor(lab.L, lab.A, lab.B);
|
||||
return ColorUtils.setAlphaComponent(color, (int) lab.alpha);
|
||||
}
|
||||
@@ -83,28 +82,25 @@ public class CIELABConvertor {
|
||||
* Conversions.
|
||||
******************************************************************************/
|
||||
|
||||
private static final float deg2Rad(final double deg)
|
||||
{
|
||||
private static final float deg2Rad(final double deg) {
|
||||
return (float) (deg * (Math.PI / 180.0));
|
||||
}
|
||||
|
||||
static float L_prime_div_k_L_S_L(final Lab lab1, final Lab lab2)
|
||||
{
|
||||
static float L_prime_div_k_L_S_L(final Lab lab1, final Lab lab2) {
|
||||
final float k_L = 1.0f;
|
||||
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))));
|
||||
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 float C_prime_div_k_L_S_L(final Lab lab1, final Lab lab2, MutableDouble a1Prime, MutableDouble a2Prime, MutableDouble CPrime1, MutableDouble CPrime2) {
|
||||
final float k_C = 1f;
|
||||
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 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))));
|
||||
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);
|
||||
|
||||
@@ -117,8 +113,7 @@ public class CIELABConvertor {
|
||||
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)
|
||||
{
|
||||
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;
|
||||
final float deg360InRad = deg2Rad(360f);
|
||||
final float deg180InRad = deg2Rad(180f);
|
||||
@@ -163,8 +158,7 @@ public class CIELABConvertor {
|
||||
double hPrimeSum = hPrime1 + hPrime2;
|
||||
if (BigDecimal.ZERO.equals(new BigDecimal(CPrime1.doubleValue() * CPrime2.doubleValue()))) {
|
||||
barhPrime.setValue(hPrimeSum);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (Math.abs(hPrime1 - hPrime2) <= deg180InRad)
|
||||
barhPrime.setValue(hPrimeSum / 2.0);
|
||||
else {
|
||||
@@ -184,8 +178,7 @@ public class CIELABConvertor {
|
||||
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)
|
||||
{
|
||||
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));
|
||||
@@ -198,8 +191,7 @@ public class CIELABConvertor {
|
||||
/* 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)
|
||||
{
|
||||
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);
|
||||
@@ -212,8 +204,7 @@ public class CIELABConvertor {
|
||||
deltaR_T);
|
||||
}
|
||||
|
||||
static double Y_Diff(final int c1, final int c2)
|
||||
{
|
||||
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));
|
||||
@@ -226,8 +217,7 @@ public class CIELABConvertor {
|
||||
return Math.abs(y2 - y) * XYZ_WHITE_REFERENCE_Y;
|
||||
}
|
||||
|
||||
static double U_Diff(final int c1, final int c2)
|
||||
{
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.android.nQuant;
|
||||
/* 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. */
|
||||
|
||||
import android.graphics.Color;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Comparator;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.Queue;
|
||||
|
||||
@@ -13,178 +13,87 @@ import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
|
||||
|
||||
public class GilbertCurve {
|
||||
|
||||
private static final class ErrorBox
|
||||
{
|
||||
private static final class ErrorBox implements Comparable<ErrorBox> {
|
||||
private double yDiff = 0;
|
||||
private final float[] p;
|
||||
|
||||
private ErrorBox() {
|
||||
p = new float[4];
|
||||
}
|
||||
|
||||
private ErrorBox(int c) {
|
||||
p = new float[] {
|
||||
p = new float[]{
|
||||
Color.red(c),
|
||||
Color.green(c),
|
||||
Color.blue(c),
|
||||
Color.alpha(c)
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final ErrorBox other) {
|
||||
return Double.compare(yDiff, other.yDiff);
|
||||
}
|
||||
}
|
||||
|
||||
private byte ditherMax, DITHER_MAX;
|
||||
private float beta;
|
||||
private float[] weights;
|
||||
private final boolean dither, hasAlpha, sortedByYDiff;
|
||||
private final int width, height;
|
||||
private final double weight;
|
||||
private final boolean dither, sortedByYDiff;
|
||||
private final int width;
|
||||
private final int height;
|
||||
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[] lookup;
|
||||
|
||||
private final int margin, thresold;
|
||||
private static final float BLOCK_SIZE = 343f;
|
||||
|
||||
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 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) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.pixels = image;
|
||||
this.palette = palette;
|
||||
this.qPixels = qPixels;
|
||||
this.ditherable = ditherable;
|
||||
this.hasAlpha = weight < 0;
|
||||
this.saliencies = saliencies;
|
||||
boolean hasAlpha = weight < 0;
|
||||
this.saliencies = hasAlpha ? null : saliencies;
|
||||
this.dither = dither;
|
||||
this.weight = Math.abs(weight);
|
||||
weight = Math.abs(weight);
|
||||
margin = weight < .0025 ? 12 : weight < .004 ? 8 : 6;
|
||||
sortedByYDiff = palette.length > 128 && weight >= .02 && (!hasAlpha || weight < .18);
|
||||
sortedByYDiff = palette.length > 128 && (!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))
|
||||
if (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
|
||||
} 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>() {
|
||||
|
||||
@Override
|
||||
public int compare(ErrorBox o1, ErrorBox o2) {
|
||||
return Double.compare(o2.yDiff, o1.yDiff);
|
||||
}
|
||||
|
||||
}) : new ArrayDeque<>();
|
||||
errorq = sortedByYDiff ? new PriorityQueue<>() : new ArrayDeque<>();
|
||||
|
||||
DITHER_MAX = weight < .015 ? (weight > .0025) ? (byte) 25 : 16 : 9;
|
||||
if (weight > .99) {
|
||||
beta = (float) weight;
|
||||
DITHER_MAX = 25;
|
||||
}
|
||||
|
||||
double edge = hasAlpha ? 1 : Math.exp(weight) - .25;
|
||||
double deviation = weight > .002 ? -.25 : 1;
|
||||
ditherMax = (hasAlpha || DITHER_MAX > 9) ? (byte) BitmapUtilities.sqr(Math.sqrt(DITHER_MAX) + edge * deviation) : (byte) (DITHER_MAX * (saliencies != null ? 2 : Math.E));
|
||||
ditherMax = (hasAlpha || DITHER_MAX > 9) ? (byte) BitmapUtilities.sqr(Math.sqrt(DITHER_MAX) + edge * deviation) : (byte) (DITHER_MAX * Math.E);
|
||||
final int density = palette.length > 16 ? 3200 : 1500;
|
||||
if(palette.length / weight > 5000 && (weight > .045 || (weight > .01 && palette.length < 64)))
|
||||
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)
|
||||
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 static float normalDistribution(float x, float peak) {
|
||||
final float mean = .5f, stdDev = .1f;
|
||||
|
||||
// Calculate the probability density function (PDF)
|
||||
double exponent = -Math.pow(x - mean, 2) / (2 * Math.pow(stdDev, 2));
|
||||
double pdf = (1 / (stdDev * Math.sqrt(2 * Math.PI))) * Math.exp(exponent);
|
||||
double maxPdf = 1 / (stdDev * Math.sqrt(2 * Math.PI)); // Peak at x = mean
|
||||
double scaledPdf = (pdf / maxPdf) * peak;
|
||||
return (float) Math.max(0.0, Math.min(peak, scaledPdf));
|
||||
}
|
||||
|
||||
private int ditherPixel(int x, int y, int c2, float beta) {
|
||||
final int bidx = x + y * width;
|
||||
final int pixel = pixels[bidx];
|
||||
int r_pix = Color.red(c2);
|
||||
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;
|
||||
if (palette.length > 4 && CIELABConvertor.Y_Diff(pixel, c2) > (gamma * acceptedDiff)) {
|
||||
if (margin > 6 || gamma > beta) {
|
||||
float kappa = saliencies[bidx] < .4f ? beta * .4f * saliencies[bidx] : beta * .4f / saliencies[bidx];
|
||||
int c1 = Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
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);
|
||||
}
|
||||
else if (palette.length <= 32 && weight >= .004)
|
||||
c2 = BlueNoise.diffuse(c2, palette[qPixels[bidx]], beta * normalDistribution(saliencies[bidx], .25f), strength, x, y);
|
||||
else
|
||||
c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void diffusePixel(int x, int y) {
|
||||
private void ditherPixel(int x, int y) {
|
||||
final int bidx = x + y * width;
|
||||
final int pixel = pixels[bidx];
|
||||
ErrorBox error = new ErrorBox(pixel);
|
||||
@@ -197,7 +106,7 @@ public class GilbertCurve {
|
||||
|
||||
for (int j = 0; j < eb.p.length; ++j) {
|
||||
error.p[j] += eb.p[j] * weights[i];
|
||||
if(error.p[j] > maxErr)
|
||||
if (error.p[j] > maxErr)
|
||||
maxErr = error.p[j];
|
||||
}
|
||||
i += sortedByYDiff ? -1 : 1;
|
||||
@@ -209,31 +118,62 @@ public class GilbertCurve {
|
||||
int a_pix = (int) Math.min(BYTE_MAX, Math.max(error.p[3], 0.0));
|
||||
|
||||
int c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
if (saliencies != null && dither && !sortedByYDiff && (!hasAlpha || Color.alpha(pixel) < a_pix)) {
|
||||
if (palette.length >= 256 && saliencies[bidx] > .99f)
|
||||
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
|
||||
else
|
||||
qPixels[bidx] = ditherPixel(x, y, c2, beta);
|
||||
if (saliencies != null && dither && !sortedByYDiff) {
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
else if (palette.length <= 32 && a_pix > 0xF0) {
|
||||
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
|
||||
if (CIELABConvertor.U_Diff(pixel, c2) > (margin * acceptedDiff))
|
||||
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], beta / saliencies[bidx], strength, x, y);
|
||||
}
|
||||
|
||||
if (palette.length < 3 || margin > 6) {
|
||||
if (palette.length > 4 && (CIELABConvertor.Y_Diff(pixel, c2) > (beta * acceptedDiff) || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) {
|
||||
float kappa = saliencies[bidx] < .4f ? beta * .4f * saliencies[bidx] : beta * .4f / saliencies[bidx];
|
||||
int c1 = saliencies[bidx] < .6f ? pixel : Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
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);
|
||||
}
|
||||
|
||||
if (DITHER_MAX < 16 && saliencies[bidx] < .6f && CIELABConvertor.Y_Diff(pixel, c2) > margin - 1)
|
||||
c2 = Color.argb(a_pix, r_pix, g_pix, b_pix);
|
||||
|
||||
int offset = ditherable.getColorIndex(c2);
|
||||
if (lookup[offset] == 0)
|
||||
lookup[offset] = ditherable.nearestColorIndex(palette, c2, bidx) + 1;
|
||||
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];
|
||||
|
||||
final int acceptedDiff = Math.max(2, palette.length - margin);
|
||||
if(saliencies != null && (CIELABConvertor.Y_Diff(pixel, c2) > acceptedDiff || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) {
|
||||
if (saliencies != null && (CIELABConvertor.Y_Diff(pixel, c2) > acceptedDiff || CIELABConvertor.U_Diff(pixel, c2) > (2 * acceptedDiff))) {
|
||||
final float strength = 1 / 3f;
|
||||
c2 = BlueNoise.diffuse(pixel, palette[qPixels[bidx]], 1 / saliencies[bidx], strength, x, y);
|
||||
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
|
||||
qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c2, bidx)];
|
||||
}
|
||||
}
|
||||
else
|
||||
qPixels[bidx] = ditherable.nearestColorIndex(palette, c2, bidx);
|
||||
} else
|
||||
qPixels[bidx] = palette[ditherable.nearestColorIndex(palette, c2, bidx)];
|
||||
|
||||
if(errorq.size() >= DITHER_MAX)
|
||||
if (errorq.size() >= DITHER_MAX)
|
||||
errorq.poll();
|
||||
else if(!errorq.isEmpty())
|
||||
else if (!errorq.isEmpty())
|
||||
initWeights(errorq.size());
|
||||
|
||||
c2 = palette[qPixels[bidx]];
|
||||
c2 = qPixels[bidx];
|
||||
error.p[0] = r_pix - Color.red(c2);
|
||||
error.p[1] = g_pix - Color.green(c2);
|
||||
error.p[2] = b_pix - Color.blue(c2);
|
||||
@@ -244,39 +184,18 @@ public class GilbertCurve {
|
||||
error.yDiff = sortedByYDiff ? CIELABConvertor.Y_Diff(pixel, c2) : 1;
|
||||
boolean illusion = !diffuse && BlueNoise.TELL_BLUE_NOISE[(int) (error.yDiff * 4096) & 4095] > thresold;
|
||||
|
||||
boolean unaccepted = false;
|
||||
int errLength = denoise ? error.p.length - 1 : 0;
|
||||
for (int j = 0; j < errLength; ++j) {
|
||||
if (Math.abs(error.p[j]) >= ditherMax) {
|
||||
if (sortedByYDiff && saliencies != null)
|
||||
unaccepted = true;
|
||||
|
||||
if (diffuse)
|
||||
error.p[j] = (float) Math.tanh(error.p[j] / maxErr * 20) * (ditherMax - 1);
|
||||
else if(illusion)
|
||||
else if (illusion)
|
||||
error.p[j] = (float) (error.p[j] / maxErr * error.yDiff) * (ditherMax - 1);
|
||||
else
|
||||
error.p[j] /= (float) (1 + Math.sqrt(ditherMax));
|
||||
}
|
||||
|
||||
if (sortedByYDiff && saliencies == null && Math.abs(error.p[j]) >= DITHER_MAX)
|
||||
unaccepted = true;
|
||||
}
|
||||
|
||||
if (unaccepted) {
|
||||
if (saliencies != null)
|
||||
qPixels[bidx] = ditherPixel(x, y, c2, beta);
|
||||
else if (CIELABConvertor.Y_Diff(pixel, c2) > 3 && CIELABConvertor.U_Diff(pixel, c2) > 3) {
|
||||
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);
|
||||
|
||||
if (dither || palette.length <= 32)
|
||||
qPixels[bidx] = palette[qPixels[bidx]];
|
||||
}
|
||||
|
||||
private void generate2d(int x, int y, int ax, int ay, int bx, int by) {
|
||||
@@ -288,8 +207,8 @@ public class GilbertCurve {
|
||||
int dby = Integer.signum(by);
|
||||
|
||||
if (h == 1) {
|
||||
for (int i = 0; i < w; ++i){
|
||||
diffusePixel(x, y);
|
||||
for (int i = 0; i < w; ++i) {
|
||||
ditherPixel(x, y);
|
||||
x += dax;
|
||||
y += day;
|
||||
}
|
||||
@@ -297,8 +216,8 @@ public class GilbertCurve {
|
||||
}
|
||||
|
||||
if (w == 1) {
|
||||
for (int i = 0; i < h; ++i){
|
||||
diffusePixel(x, y);
|
||||
for (int i = 0; i < h; ++i) {
|
||||
ditherPixel(x, y);
|
||||
x += dbx;
|
||||
y += dby;
|
||||
}
|
||||
@@ -341,21 +260,20 @@ public class GilbertCurve {
|
||||
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) {
|
||||
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)
|
||||
for (int c = 0; c < size; ++c)
|
||||
weight += (weights[c] /= sumweight);
|
||||
weights[0] += 1f - weight;
|
||||
}
|
||||
|
||||
private void run()
|
||||
{
|
||||
if(!sortedByYDiff)
|
||||
private void run() {
|
||||
if (!sortedByYDiff)
|
||||
initWeights(DITHER_MAX);
|
||||
|
||||
if (width >= height)
|
||||
@@ -364,11 +282,9 @@ public class GilbertCurve {
|
||||
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)
|
||||
{
|
||||
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];
|
||||
new GilbertCurve(width, height, pixels, palette, qPixels, ditherable, saliencies, weight, dither).run();
|
||||
|
||||
return qPixels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.android.nQuant;
|
||||
|
||||
/* Fast pairwise nearest neighbor based algorithm with CIELAB color space advanced version
|
||||
Copyright (c) 2018-2026 Miller Cy Chan
|
||||
Copyright (c) 2018-2025 Miller Cy Chan
|
||||
* error measure; time used is proportional to number of bins squared - WJ */
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
@@ -10,23 +10,17 @@ import android.graphics.Color;
|
||||
import com.android.nQuant.CIELABConvertor.Lab;
|
||||
import com.android.nQuant.CIELABConvertor.MutableDouble;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
public class PnnLABQuantizer extends PnnQuantizer {
|
||||
private boolean isNano = false;
|
||||
protected float[] saliencies;
|
||||
private final Map<Integer, Lab> pixelMap = new HashMap<>();
|
||||
|
||||
private static Random random = new Random();
|
||||
|
||||
public PnnLABQuantizer(String fname) throws IOException {
|
||||
super(fname);
|
||||
}
|
||||
|
||||
public PnnLABQuantizer(Bitmap bitmap) {
|
||||
public PnnLABQuantizer(final Bitmap bitmap) {
|
||||
super(bitmap);
|
||||
}
|
||||
|
||||
@@ -36,8 +30,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
int nn, fw, bk, tm, mtm;
|
||||
}
|
||||
|
||||
private Lab getLab(final int c)
|
||||
{
|
||||
private Lab getLab(final int c) {
|
||||
Lab lab1 = pixelMap.get(c);
|
||||
if (lab1 == null) {
|
||||
lab1 = CIELABConvertor.RGB2LAB(c);
|
||||
@@ -46,8 +39,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
return lab1;
|
||||
}
|
||||
|
||||
private void find_nn(Pnnbin[] bins, int idx, boolean texicab)
|
||||
{
|
||||
private void find_nn(Pnnbin[] bins, int idx, boolean texicab) {
|
||||
int nn = 0;
|
||||
double err = 1e100;
|
||||
|
||||
@@ -55,7 +47,10 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
float n1 = bin1.cnt;
|
||||
|
||||
Lab lab1 = new Lab();
|
||||
lab1.alpha = bin1.ac; lab1.L = bin1.Lc; lab1.A = bin1.Ac; lab1.B = bin1.Bc;
|
||||
lab1.alpha = bin1.ac;
|
||||
lab1.L = bin1.Lc;
|
||||
lab1.A = bin1.Ac;
|
||||
lab1.B = bin1.Bc;
|
||||
for (int i = bin1.fw; i != 0; i = bins[i].fw) {
|
||||
float n2 = bins[i].cnt;
|
||||
double nerr2 = (n1 * n2) / (n1 + n2);
|
||||
@@ -63,13 +58,16 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
continue;
|
||||
|
||||
Lab lab2 = new Lab();
|
||||
lab2.alpha = bins[i].ac; lab2.L = bins[i].Lc; lab2.A = bins[i].Ac; lab2.B = bins[i].Bc;
|
||||
lab2.alpha = bins[i].ac;
|
||||
lab2.L = bins[i].Lc;
|
||||
lab2.A = bins[i].Ac;
|
||||
lab2.B = bins[i].Bc;
|
||||
double alphaDiff = hasSemiTransparency ? BitmapUtilities.sqr(lab2.alpha - lab1.alpha) / Math.exp(1.75) : 0;
|
||||
double nerr = nerr2 * alphaDiff;
|
||||
if (nerr >= err)
|
||||
continue;
|
||||
|
||||
if(!texicab) {
|
||||
if (!texicab) {
|
||||
nerr += (1 - ratio) * nerr2 * BitmapUtilities.sqr(lab2.L - lab1.L);
|
||||
if (nerr >= err)
|
||||
continue;
|
||||
@@ -79,8 +77,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
continue;
|
||||
|
||||
nerr += (1 - ratio) * nerr2 * BitmapUtilities.sqr(lab2.B - lab1.B);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
nerr += (1 - ratio) * nerr2 * Math.abs(lab2.L - lab1.L);
|
||||
if (nerr >= err)
|
||||
continue;
|
||||
@@ -133,8 +130,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] pnnquan(final int[] pixels, int nMaxColors)
|
||||
{
|
||||
protected int[] pnnquan(final int[] pixels, int nMaxColors) {
|
||||
short quan_rt = (short) 1;
|
||||
Pnnbin[] bins = new Pnnbin[65536];
|
||||
saliencies = nMaxColors >= 128 ? null : new float[pixels.length];
|
||||
@@ -149,7 +145,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
int index = BitmapUtilities.getColorIndex(pixel, hasSemiTransparency, nMaxColors < 64 || m_transparentPixelIndex >= 0);
|
||||
Lab lab1 = getLab(pixel);
|
||||
|
||||
if(bins[index] == null)
|
||||
if (bins[index] == null)
|
||||
bins[index] = new Pnnbin();
|
||||
Pnnbin tb = bins[index];
|
||||
tb.ac += lab1.alpha;
|
||||
@@ -157,8 +153,8 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
tb.Ac += lab1.A;
|
||||
tb.Bc += lab1.B;
|
||||
tb.cnt += 1.0f;
|
||||
if(saliencies != null)
|
||||
saliencies[i] = saliencyBase + (1 - saliencyBase) * lab1.L / 100f * lab1.alpha / 255f;
|
||||
if (saliencies != null && lab1.alpha > alphaThreshold)
|
||||
saliencies[i] = saliencyBase + (1 - saliencyBase) * lab1.L / 100f;
|
||||
}
|
||||
|
||||
/* Cluster nonempty bins at one end of array */
|
||||
@@ -178,14 +174,16 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
}
|
||||
|
||||
double proportional = BitmapUtilities.sqr(nMaxColors) / maxbins;
|
||||
if((m_transparentPixelIndex >= 0 || hasSemiTransparency) && nMaxColors < 32)
|
||||
if ((m_transparentPixelIndex >= 0 || hasSemiTransparency) && nMaxColors < 32)
|
||||
quan_rt = -1;
|
||||
|
||||
weight = Math.min(0.9, nMaxColors * 1.0 / maxbins);
|
||||
isNano = weight <= .015;
|
||||
if ((nMaxColors < 16 && weight < .0075) || weight < .001 || (weight > .0015 && weight < .0022))
|
||||
quan_rt = 2;
|
||||
if (weight < .04 && PG < 1 && PG >= coeffs[0][1]) {
|
||||
double delta = Math.exp(1.75) * weight;
|
||||
PG -= delta;
|
||||
PB += delta;
|
||||
if (nMaxColors >= 64)
|
||||
quan_rt = 0;
|
||||
}
|
||||
@@ -195,15 +193,16 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
quan_rt = 2;
|
||||
}
|
||||
|
||||
if(pixelMap.size() <= nMaxColors) {
|
||||
if (pixelMap.size() <= nMaxColors) {
|
||||
/* Fill palette */
|
||||
int[] palette = new int[pixelMap.size()];
|
||||
int k = 0;
|
||||
for (Integer pixel : pixelMap.keySet()) {
|
||||
palette[k++] = pixel;
|
||||
|
||||
if(k > 1 && Color.alpha(pixel) == 0) {
|
||||
palette[k - 1] = palette[0]; palette[0] = pixel;
|
||||
if (k > 1 && Color.alpha(pixel) == 0) {
|
||||
palette[k - 1] = palette[0];
|
||||
palette[0] = pixel;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,23 +220,22 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
}
|
||||
bins[j].cnt = quanFn.get(bins[j].cnt);
|
||||
|
||||
final boolean texicab = proportional > .0225 && !hasSemiTransparency;
|
||||
final boolean texicab = proportional > .0275;
|
||||
|
||||
if(hasSemiTransparency)
|
||||
if (hasSemiTransparency)
|
||||
ratio = .5;
|
||||
else if(quan_rt != 0 && nMaxColors < 64) {
|
||||
else if (quan_rt != 0 && nMaxColors < 64) {
|
||||
if (proportional > .018 && proportional < .022)
|
||||
ratio = Math.min(1.0, proportional + weight * Math.exp(3.13));
|
||||
else if (proportional > .1)
|
||||
ratio = Math.min(1.0, 1.0 - weight);
|
||||
else if(proportional > .04)
|
||||
else if (proportional > .04)
|
||||
ratio = Math.min(1.0, weight * Math.exp(1.56));
|
||||
else if(proportional > .025 && (weight < .002 || weight > .0022))
|
||||
else if (proportional > .025 && (weight < .002 || weight > .0022))
|
||||
ratio = Math.min(1.0, proportional + weight * Math.exp(3.66));
|
||||
else
|
||||
ratio = Math.min(1.0, proportional + weight * Math.exp(1.718));
|
||||
}
|
||||
else if(nMaxColors > 256)
|
||||
} else if (nMaxColors > 256)
|
||||
ratio = Math.min(1.0, 1 - 1.0 / proportional);
|
||||
else
|
||||
ratio = Math.min(1.0, 1 - weight * .7);
|
||||
@@ -273,7 +271,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
for (int i = 0; i < extbins; ) {
|
||||
Pnnbin tb;
|
||||
/* Use heap to find which bins to merge */
|
||||
for (;;) {
|
||||
for (; ; ) {
|
||||
int b1 = heap[1];
|
||||
tb = bins[b1]; /* One with least error */
|
||||
/* Is stored error up to date? */
|
||||
@@ -281,9 +279,8 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
break;
|
||||
if (tb.mtm == 0xFFFF) /* Deleted node */
|
||||
b1 = heap[1] = heap[heap[0]--];
|
||||
else /* Too old error value */
|
||||
{
|
||||
find_nn(bins, b1, texicab);
|
||||
else /* Too old error value */ {
|
||||
find_nn(bins, b1, texicab && proportional < 1);
|
||||
tb.tm = i;
|
||||
}
|
||||
/* Push slot down */
|
||||
@@ -319,30 +316,31 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
/* Fill palette */
|
||||
int[] palette = new int[extbins > 0 ? nMaxColors : maxbins];
|
||||
short k = 0;
|
||||
for (int i = 0; k < palette.length; ++k) {
|
||||
for (int i = 0; ; ++k) {
|
||||
Lab lab1 = new Lab();
|
||||
lab1.alpha = (int) bins[i].ac;
|
||||
lab1.L = bins[i].Lc; lab1.A = bins[i].Ac; lab1.B = bins[i].Bc;
|
||||
lab1.L = bins[i].Lc;
|
||||
lab1.A = bins[i].Ac;
|
||||
lab1.B = bins[i].Bc;
|
||||
palette[k] = CIELABConvertor.LAB2RGB(lab1);
|
||||
|
||||
i = bins[i].fw;
|
||||
if ((i = bins[i].fw) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short nearestColorIndex(final int[] palette, int c, final int pos)
|
||||
{
|
||||
final int offset = !isNano ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
|
||||
Short got = nearestMap.get(offset);
|
||||
protected short nearestColorIndex(final int[] palette, int c, final int pos) {
|
||||
Short got = nearestMap.get(c);
|
||||
if (got != null)
|
||||
return got;
|
||||
|
||||
short k = 0;
|
||||
if (Color.alpha(c) <= alphaThreshold)
|
||||
c = m_transparentColor;
|
||||
if(palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
|
||||
if (palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
|
||||
k = 1;
|
||||
|
||||
double mindist = Integer.MAX_VALUE;
|
||||
@@ -357,10 +355,9 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
Lab lab2 = getLab(c2);
|
||||
if (palette.length <= 4) {
|
||||
curdist = BitmapUtilities.sqr(Color.red(c2) - Color.red(c)) + BitmapUtilities.sqr(Color.green(c2) - Color.green(c)) + BitmapUtilities.sqr(Color.blue(c2) - Color.blue(c));
|
||||
if(hasSemiTransparency)
|
||||
if (hasSemiTransparency)
|
||||
curdist += BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
|
||||
}
|
||||
else if (hasSemiTransparency || palette.length < 16) {
|
||||
} else if (hasSemiTransparency || palette.length < 16) {
|
||||
curdist += BitmapUtilities.sqr(lab2.L - lab1.L);
|
||||
if (curdist > mindist)
|
||||
continue;
|
||||
@@ -370,15 +367,13 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
continue;
|
||||
|
||||
curdist += BitmapUtilities.sqr(lab2.B - lab1.B);
|
||||
}
|
||||
else if (palette.length > 32) {
|
||||
} else if (palette.length > 32) {
|
||||
curdist += Math.abs(lab2.L - lab1.L);
|
||||
if (curdist > mindist)
|
||||
continue;
|
||||
|
||||
curdist += Math.sqrt(BitmapUtilities.sqr(lab2.A - lab1.A) + BitmapUtilities.sqr(lab2.B - lab1.B));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
float deltaL_prime_div_k_L_S_L = CIELABConvertor.L_prime_div_k_L_S_L(lab1, lab2);
|
||||
curdist += BitmapUtilities.sqr(deltaL_prime_div_k_L_S_L);
|
||||
if (curdist > mindist)
|
||||
@@ -404,22 +399,24 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
mindist = curdist;
|
||||
k = i;
|
||||
}
|
||||
nearestMap.put(offset, k);
|
||||
nearestMap.put(c, k);
|
||||
return k;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected short closestColorIndex(final int[] palette, int c, final int pos)
|
||||
{
|
||||
protected short closestColorIndex(final int[] palette, int c, final int pos) {
|
||||
if (Color.alpha(c) <= alphaThreshold)
|
||||
return nearestColorIndex(palette, c, pos);
|
||||
|
||||
final int offset = !isNano ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
|
||||
int[] closest = closestMap.get(c);
|
||||
if (closest == null) {
|
||||
closest = new int[4];
|
||||
closest[2] = closest[3] = Integer.MAX_VALUE;
|
||||
|
||||
int start = 0;
|
||||
if (Color.alpha(c) > 0xE0 && BlueNoise.TELL_BLUE_NOISE[pos & 4095] > -88)
|
||||
start = 1;
|
||||
|
||||
for (short k = 0; k < palette.length; ++k) {
|
||||
int c2 = palette[k];
|
||||
|
||||
@@ -435,10 +432,12 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
if (err >= closest[3])
|
||||
continue;
|
||||
|
||||
if(hasSemiTransparency)
|
||||
err += PA * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
|
||||
if (hasSemiTransparency) {
|
||||
err += PA * (1 - ratio) * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
|
||||
start = 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < coeffs.length; ++i) {
|
||||
for (int i = start; i < coeffs.length; ++i) {
|
||||
err += ratio * BitmapUtilities.sqr(coeffs[i][0] * (Color.red(c2) - Color.red(c)));
|
||||
if (err >= closest[3])
|
||||
break;
|
||||
@@ -455,8 +454,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
closest[3] = closest[2];
|
||||
closest[0] = k;
|
||||
closest[2] = (int) err;
|
||||
}
|
||||
else if (err < closest[3]) {
|
||||
} else if (err < closest[3]) {
|
||||
closest[1] = k;
|
||||
closest[3] = (int) err;
|
||||
}
|
||||
@@ -465,15 +463,18 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
if (closest[3] == Integer.MAX_VALUE)
|
||||
closest[1] = closest[0];
|
||||
|
||||
closestMap.put(offset, closest);
|
||||
closestMap.put(c, closest);
|
||||
}
|
||||
|
||||
int MAX_ERR = palette.length;
|
||||
if (PG < coeffs[0][1] && BlueNoise.TELL_BLUE_NOISE[pos & 4095] > -88)
|
||||
return nearestColorIndex(palette, c, pos);
|
||||
|
||||
int idx = 1;
|
||||
if (closest[2] == 0 || (random.nextInt(32767) % (closest[3] + closest[2])) <= closest[3])
|
||||
idx = 0;
|
||||
|
||||
int MAX_ERR = palette.length;
|
||||
if(closest[idx + 2] >= MAX_ERR || closest[idx] == 0 || Color.alpha(palette[closest[idx]]) < Color.alpha(c))
|
||||
if (closest[idx + 2] >= MAX_ERR || (hasAlpha() && closest[idx] == 0))
|
||||
return nearestColorIndex(palette, c, pos);
|
||||
return (short) closest[idx];
|
||||
}
|
||||
@@ -487,28 +488,25 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
|
||||
@Override
|
||||
public short nearestColorIndex(int[] palette, int c, final int pos) {
|
||||
if (palette.length <= 4)
|
||||
return PnnLABQuantizer.this.nearestColorIndex(palette, c, pos);
|
||||
return PnnLABQuantizer.this.closestColorIndex(palette, c, pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither)
|
||||
{
|
||||
public int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither) {
|
||||
Ditherable ditherable = getDitherFn();
|
||||
if(hasSemiTransparency)
|
||||
if (hasSemiTransparency)
|
||||
weight *= -1;
|
||||
|
||||
if(dither && saliencies == null && (palette.length <= 256 || weight > .99)) {
|
||||
if (dither && !hasSemiTransparency && saliencies == null && (palette.length <= 128 || weight > .99)) {
|
||||
saliencies = new float[pixels.length];
|
||||
float saliencyBase = .1f;
|
||||
|
||||
for (int i = 0; i < pixels.length; ++i) {
|
||||
Lab lab1 = getLab(pixels[i]);
|
||||
|
||||
saliencies[i] = saliencyBase + (1 - saliencyBase) * lab1.L / 100f * lab1.alpha / 255f;
|
||||
saliencies[i] = saliencyBase + (1 - saliencyBase) * lab1.L / 100f;
|
||||
}
|
||||
}
|
||||
int[] qPixels = GilbertCurve.dither(width, height, cPixels, palette, ditherable, saliencies, weight, dither);
|
||||
@@ -525,5 +523,4 @@ public class PnnLABQuantizer extends PnnQuantizer {
|
||||
|
||||
return qPixels;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
package com.android.nQuant;
|
||||
/* Fast pairwise nearest neighbor based algorithm for multilevel thresholding
|
||||
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 */
|
||||
|
||||
import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.android.nQuant.BitmapUtilities.BYTE_MAX;
|
||||
|
||||
public class PnnQuantizer {
|
||||
protected short alphaThreshold = 0xF;
|
||||
protected boolean hasSemiTransparency = false;
|
||||
protected int m_transparentPixelIndex = -1;
|
||||
protected int width, height;
|
||||
protected int[] pixels = null;
|
||||
protected final int width, height;
|
||||
protected final int[] pixels;
|
||||
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 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.14713f, -0.28886f, 0.436f},
|
||||
{0.615f, -0.51499f, -0.10001f}
|
||||
@@ -33,34 +32,20 @@ public class PnnQuantizer {
|
||||
protected Map<Integer, int[]> closestMap = new HashMap<>();
|
||||
protected Map<Integer, Short> nearestMap = new HashMap<>();
|
||||
|
||||
public PnnQuantizer(String fname) {
|
||||
fromBitmap(fname);
|
||||
}
|
||||
|
||||
public PnnQuantizer(Bitmap bitmap) {
|
||||
fromBitmap(bitmap);
|
||||
}
|
||||
|
||||
private void fromBitmap(Bitmap bitmap) {
|
||||
width = bitmap.getWidth();
|
||||
height = bitmap.getHeight();
|
||||
pixels = new int [width * height];
|
||||
pixels = new int[width * height];
|
||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
|
||||
}
|
||||
|
||||
private void fromBitmap(String fname) {
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(fname);
|
||||
fromBitmap(bitmap);
|
||||
}
|
||||
|
||||
private static final class Pnnbin {
|
||||
double ac = 0, rc = 0, gc = 0, bc = 0;
|
||||
float cnt = 0, err = 0;
|
||||
int nn, fw, bk, tm, mtm;
|
||||
}
|
||||
|
||||
private void find_nn(Pnnbin[] bins, int idx)
|
||||
{
|
||||
private void find_nn(Pnnbin[] bins, int idx) {
|
||||
int nn = 0;
|
||||
double err = 1e100;
|
||||
|
||||
@@ -72,7 +57,7 @@ public class PnnQuantizer {
|
||||
double wb = bin1.bc;
|
||||
|
||||
int start = 0;
|
||||
if(BlueNoise.TELL_BLUE_NOISE[idx & 4095] > 0)
|
||||
if (BlueNoise.TELL_BLUE_NOISE[idx & 4095] > -88)
|
||||
start = (PG < coeffs[0][1]) ? coeffs.length : 1;
|
||||
|
||||
for (int i = bin1.fw; i != 0; i = bins[i].fw) {
|
||||
@@ -81,8 +66,9 @@ public class PnnQuantizer {
|
||||
continue;
|
||||
|
||||
double nerr = 0.0;
|
||||
if(hasSemiTransparency) {
|
||||
nerr += nerr2 * PA * BitmapUtilities.sqr(bins[i].ac - wa);
|
||||
if (hasSemiTransparency) {
|
||||
start = 1;
|
||||
nerr += nerr2 * (1 - ratio) * PA * BitmapUtilities.sqr(bins[i].ac - wa);
|
||||
if (nerr >= err)
|
||||
continue;
|
||||
}
|
||||
@@ -136,8 +122,7 @@ public class PnnQuantizer {
|
||||
return cnt -> cnt;
|
||||
}
|
||||
|
||||
protected int[] pnnquan(final int[] pixels, int nMaxColors)
|
||||
{
|
||||
protected int[] pnnquan(final int[] pixels, int nMaxColors) {
|
||||
short quan_rt = (short) 1;
|
||||
Pnnbin[] bins = new Pnnbin[65536];
|
||||
|
||||
@@ -148,7 +133,7 @@ public class PnnQuantizer {
|
||||
|
||||
int index = BitmapUtilities.getColorIndex(pixel, hasSemiTransparency, nMaxColors < 64 || m_transparentPixelIndex >= 0);
|
||||
|
||||
if(bins[index] == null)
|
||||
if (bins[index] == null)
|
||||
bins[index] = new Pnnbin();
|
||||
Pnnbin tb = bins[index];
|
||||
tb.ac += Color.alpha(pixel);
|
||||
@@ -174,11 +159,11 @@ public class PnnQuantizer {
|
||||
bins[maxbins++] = bins[i];
|
||||
}
|
||||
|
||||
if(nMaxColors < 16)
|
||||
if (nMaxColors < 16)
|
||||
quan_rt = -1;
|
||||
|
||||
weight = Math.min(0.9, nMaxColors * 1.0 / maxbins);
|
||||
if (weight < .04 && PG >= coeffs[0][1]) {
|
||||
if (weight < .03 && PG >= coeffs[0][1]) {
|
||||
PR = PG = PB = PA = 1;
|
||||
if (nMaxColors >= 64)
|
||||
quan_rt = 0;
|
||||
@@ -214,9 +199,9 @@ public class PnnQuantizer {
|
||||
/* Merge bins which increase error the least */
|
||||
int extbins = maxbins - nMaxColors;
|
||||
for (int i = 0; i < extbins; ) {
|
||||
Pnnbin tb;
|
||||
Pnnbin tb = null;
|
||||
/* Use heap to find which bins to merge */
|
||||
for (;;) {
|
||||
for (; ; ) {
|
||||
int b1 = heap[1];
|
||||
tb = bins[b1]; /* One with least error */
|
||||
/* Is stored error up to date? */
|
||||
@@ -224,8 +209,7 @@ public class PnnQuantizer {
|
||||
break;
|
||||
if (tb.mtm == 0xFFFF) /* Deleted node */
|
||||
b1 = heap[1] = heap[heap[0]--];
|
||||
else /* Too old error value */
|
||||
{
|
||||
else /* Too old error value */ {
|
||||
find_nn(bins, b1);
|
||||
tb.tm = i;
|
||||
}
|
||||
@@ -262,34 +246,33 @@ public class PnnQuantizer {
|
||||
/* Fill palette */
|
||||
int[] palette = new int[extbins > 0 ? nMaxColors : maxbins];
|
||||
short k = 0;
|
||||
for (int i = 0; k < palette.length; ++k) {
|
||||
for (int i = 0; ; ++k) {
|
||||
palette[k] = Color.argb((int) bins[i].ac, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc);
|
||||
|
||||
i = bins[i].fw;
|
||||
if ((i = bins[i].fw) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
||||
protected short nearestColorIndex(final int[] palette, int c, final int pos)
|
||||
{
|
||||
final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
|
||||
Short got = nearestMap.get(offset);
|
||||
protected short nearestColorIndex(final int[] palette, int c, final int pos) {
|
||||
Short got = nearestMap.get(c);
|
||||
if (got != null)
|
||||
return got;
|
||||
|
||||
short k = 0;
|
||||
if (Color.alpha(c) <= alphaThreshold)
|
||||
c = m_transparentColor;
|
||||
if(palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
|
||||
if (palette.length > 2 && hasAlpha() && Color.alpha(c) > alphaThreshold)
|
||||
k = 1;
|
||||
|
||||
double pr = PR, pg = PG, pb = PB, pa = PA;
|
||||
if(palette.length < 3)
|
||||
if (palette.length < 3)
|
||||
pr = pg = pb = pa = 1;
|
||||
|
||||
double mindist = Integer.MAX_VALUE;
|
||||
for (short i=k; i<palette.length; ++i) {
|
||||
for (short i = k; i < palette.length; ++i) {
|
||||
int c2 = palette[i];
|
||||
|
||||
double curdist = pa * BitmapUtilities.sqr(Color.alpha(c2) - Color.alpha(c));
|
||||
@@ -311,24 +294,22 @@ public class PnnQuantizer {
|
||||
mindist = curdist;
|
||||
k = i;
|
||||
}
|
||||
nearestMap.put(offset, k);
|
||||
nearestMap.put(c, k);
|
||||
return k;
|
||||
}
|
||||
|
||||
protected short closestColorIndex(final int[] palette, int c, final int pos)
|
||||
{
|
||||
protected short closestColorIndex(final int[] palette, int c, final int pos) {
|
||||
short k = 0;
|
||||
if (Color.alpha(c) <= alphaThreshold)
|
||||
return nearestColorIndex(palette, c, pos);
|
||||
|
||||
final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
|
||||
int[] closest = closestMap.get(c);
|
||||
if (closest == null) {
|
||||
closest = new int[4];
|
||||
closest[2] = closest[3] = Integer.MAX_VALUE;
|
||||
|
||||
double pr = PR, pg = PG, pb = PB, pa = PA;
|
||||
if(palette.length < 3)
|
||||
if (palette.length < 3)
|
||||
pr = pg = pb = pa = 1;
|
||||
|
||||
for (; k < palette.length; ++k) {
|
||||
@@ -354,8 +335,7 @@ public class PnnQuantizer {
|
||||
closest[3] = closest[2];
|
||||
closest[0] = k;
|
||||
closest[2] = (int) err;
|
||||
}
|
||||
else if (err < closest[3]) {
|
||||
} else if (err < closest[3]) {
|
||||
closest[1] = k;
|
||||
closest[3] = (int) err;
|
||||
}
|
||||
@@ -364,7 +344,7 @@ public class PnnQuantizer {
|
||||
if (closest[3] == Integer.MAX_VALUE)
|
||||
closest[1] = closest[0];
|
||||
|
||||
closestMap.put(offset, closest);
|
||||
closestMap.put(c, closest);
|
||||
}
|
||||
|
||||
int MAX_ERR = palette.length << 2;
|
||||
@@ -374,7 +354,7 @@ public class PnnQuantizer {
|
||||
else if (closest[0] > closest[1])
|
||||
idx = pos % 2;
|
||||
|
||||
if(closest[idx + 2] >= MAX_ERR || (hasAlpha() && closest[idx] == 0))
|
||||
if (closest[idx + 2] >= MAX_ERR || (hasAlpha() && closest[idx] == 0))
|
||||
return nearestColorIndex(palette, c, pos);
|
||||
return (short) closest[idx];
|
||||
}
|
||||
@@ -388,17 +368,16 @@ public class PnnQuantizer {
|
||||
|
||||
@Override
|
||||
public short nearestColorIndex(int[] palette, int c, final int pos) {
|
||||
if(dither)
|
||||
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)
|
||||
{
|
||||
protected int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither) {
|
||||
Ditherable ditherable = getDitherFn(dither);
|
||||
if(hasSemiTransparency)
|
||||
if (hasSemiTransparency)
|
||||
weight *= -1;
|
||||
int[] qPixels = GilbertCurve.dither(width, height, cPixels, palette, ditherable, null, weight, dither);
|
||||
|
||||
@@ -418,17 +397,16 @@ public class PnnQuantizer {
|
||||
int alfa = (pixel >> 24) & 0xff;
|
||||
int r = (pixel >> 16) & 0xff;
|
||||
int g = (pixel >> 8) & 0xff;
|
||||
int b = (pixel ) & 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)
|
||||
if (nMaxColors > 2)
|
||||
m_transparentColor = pixels[i];
|
||||
else
|
||||
pixels[i] = m_transparentColor;
|
||||
}
|
||||
else if (alfa > alphaThreshold)
|
||||
} else if (alfa > alphaThreshold)
|
||||
++semiTransCount;
|
||||
}
|
||||
}
|
||||
@@ -437,7 +415,9 @@ public class PnnQuantizer {
|
||||
if (nMaxColors <= 32)
|
||||
PR = PG = PB = PA = 1;
|
||||
else {
|
||||
PR = coeffs[0][0]; PG = coeffs[0][1]; PB = coeffs[0][2];
|
||||
PR = coeffs[0][0];
|
||||
PG = coeffs[0][1];
|
||||
PB = coeffs[0][2];
|
||||
}
|
||||
|
||||
int[] palette;
|
||||
@@ -449,17 +429,17 @@ public class PnnQuantizer {
|
||||
if (m_transparentPixelIndex >= 0) {
|
||||
palette[0] = m_transparentColor;
|
||||
palette[1] = Color.BLACK;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
palette[0] = Color.BLACK;
|
||||
palette[1] = Color.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
int[] qPixels = dither(pixels, palette, width, height, dither);
|
||||
return new Pair<>(
|
||||
return Pair.create(
|
||||
Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888),
|
||||
palette);
|
||||
palette
|
||||
);
|
||||
}
|
||||
|
||||
public boolean hasAlpha() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* All files under this package were adapted from https://github.com/mcychan/nQuant.android
|
||||
* at 12822f15c92695136f1761b6b72c9bd18dc70c46
|
||||
* at 8f18e9d7536e71a90d0d1333968e07688b7ebf09
|
||||
* <p>
|
||||
* Changes done:
|
||||
* - Update PnnQuantizer to allow for access to the palette after convertion
|
||||
|
||||
Reference in New Issue
Block a user