nQuant: backport Gadgetbridge fixups

This commit is contained in:
Thomas Kuehne
2026-03-09 22:28:03 +01:00
committed by José Rebelo
parent c8da8b4717
commit 07b1e593b3
7 changed files with 39 additions and 27 deletions
@@ -37,7 +37,7 @@ public class BitmapUtilities {
return ditherPixel; return ditherPixel;
} }
static int[] quantize_image(final int width, final int height, final int[] pixels, final Integer[] palette, final Ditherable ditherable, final boolean hasSemiTransparency, final boolean dither) 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[] qPixels = new int[pixels.length];
int nMaxColors = palette.length; int nMaxColors = palette.length;
@@ -196,7 +196,7 @@ public class BlueNoise {
return Color.argb(a_pix, r_pix, g_pix, b_pix); return Color.argb(a_pix, r_pix, g_pix, b_pix);
} }
static int[] processImagePixels(final Integer[] palette, final int[] qPixels) { static int[] processImagePixels(final int[] palette, final int[] qPixels) {
int[] qPixel32s = new int[qPixels.length]; int[] qPixel32s = new int[qPixels.length];
for (var i = 0; i < qPixels.length; ++i) for (var i = 0; i < qPixels.length; ++i)
qPixel32s[i] = palette[qPixels[i]]; qPixel32s[i] = palette[qPixels[i]];
@@ -204,7 +204,7 @@ public class BlueNoise {
return qPixel32s; return qPixel32s;
} }
public static int[] dither(final int width, final int height, final int[] pixels, final Integer[] 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) {
@@ -3,5 +3,5 @@ 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 Integer[] palette, final int c, final int pos); public short nearestColorIndex(final int[] palette, final int c, final int pos);
} }
@@ -38,7 +38,7 @@ public class GilbertCurve {
private final int width, height; private final int width, height;
private final double weight; private final double weight;
private final int[] pixels; private final int[] pixels;
private final Integer[] palette; private final int[] palette;
private final int[] qPixels; private final int[] qPixels;
private final Ditherable ditherable; private final Ditherable ditherable;
private final float[] saliencies; private final float[] saliencies;
@@ -47,7 +47,7 @@ public class GilbertCurve {
private final int margin, thresold; private final int margin, thresold;
private static final float BLOCK_SIZE = 343f; private static final float BLOCK_SIZE = 343f;
private GilbertCurve(final int width, final int height, final int[] image, final Integer[] 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.width = width;
this.height = height; this.height = height;
@@ -279,7 +279,7 @@ public class GilbertCurve {
qPixels[bidx] = palette[qPixels[bidx]]; qPixels[bidx] = palette[qPixels[bidx]];
} }
private void generate2d(int x, int y, int ax, int ay, int bx, int by) throws Exception { private void generate2d(int x, int y, int ax, int ay, int bx, int by) {
int w = Math.abs(ax + ay); int w = Math.abs(ax + ay);
int h = Math.abs(bx + by); int h = Math.abs(bx + by);
int dax = Integer.signum(ax); int dax = Integer.signum(ax);
@@ -353,7 +353,7 @@ public class GilbertCurve {
weights[0] += 1f - weight; weights[0] += 1f - weight;
} }
private void run() throws Exception private void run()
{ {
if(!sortedByYDiff) if(!sortedByYDiff)
initWeights(DITHER_MAX); initWeights(DITHER_MAX);
@@ -364,7 +364,7 @@ public class GilbertCurve {
generate2d(0, 0, 0, height, width, 0); generate2d(0, 0, 0, height, width, 0);
} }
public static int[] dither(final int width, final int height, final int[] pixels, final Integer[] palette, final Ditherable ditherable, final float[] saliencies, final double weight, final boolean dither) throws Exception 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();
@@ -4,6 +4,7 @@ package com.android.nQuant;
Copyright (c) 2018-2026 Miller Cy Chan Copyright (c) 2018-2026 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 android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import com.android.nQuant.CIELABConvertor.Lab; import com.android.nQuant.CIELABConvertor.Lab;
@@ -25,6 +26,10 @@ public class PnnLABQuantizer extends PnnQuantizer {
super(fname); super(fname);
} }
public PnnLABQuantizer(Bitmap bitmap) {
super(bitmap);
}
private static final class Pnnbin { private static final class Pnnbin {
float ac = 0, Lc = 0, Ac = 0, Bc = 0, err = 0; float ac = 0, Lc = 0, Ac = 0, Bc = 0, err = 0;
float cnt = 0; float cnt = 0;
@@ -128,7 +133,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
@Override @Override
protected Integer[] pnnquan(final int[] pixels, int nMaxColors) protected int[] pnnquan(final int[] pixels, int nMaxColors)
{ {
short quan_rt = (short) 1; short quan_rt = (short) 1;
Pnnbin[] bins = new Pnnbin[65536]; Pnnbin[] bins = new Pnnbin[65536];
@@ -192,7 +197,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
if(pixelMap.size() <= nMaxColors) { if(pixelMap.size() <= nMaxColors) {
/* Fill palette */ /* Fill palette */
Integer[] palette = new Integer[pixelMap.size()]; int[] palette = new int[pixelMap.size()];
int k = 0; int k = 0;
for (Integer pixel : pixelMap.keySet()) { for (Integer pixel : pixelMap.keySet()) {
palette[k++] = pixel; palette[k++] = pixel;
@@ -312,7 +317,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
/* Fill palette */ /* Fill palette */
Integer[] palette = new Integer[extbins > 0 ? nMaxColors : maxbins]; int[] palette = new int[extbins > 0 ? nMaxColors : maxbins];
short k = 0; short k = 0;
for (int i = 0; k < palette.length; ++k) { for (int i = 0; k < palette.length; ++k) {
Lab lab1 = new Lab(); Lab lab1 = new Lab();
@@ -327,7 +332,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
@Override @Override
protected short nearestColorIndex(final Integer[] palette, int c, final int pos) protected short nearestColorIndex(final int[] palette, int c, final int pos)
{ {
final int offset = !isNano ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); final int offset = !isNano ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
Short got = nearestMap.get(offset); Short got = nearestMap.get(offset);
@@ -404,7 +409,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
@Override @Override
protected short closestColorIndex(final Integer[] palette, int c, final int pos) protected short closestColorIndex(final int[] palette, int c, final int pos)
{ {
if (Color.alpha(c) <= alphaThreshold) if (Color.alpha(c) <= alphaThreshold)
return nearestColorIndex(palette, c, pos); return nearestColorIndex(palette, c, pos);
@@ -481,7 +486,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
@Override @Override
public short nearestColorIndex(Integer[] palette, int c, final int pos) { public short nearestColorIndex(int[] palette, int c, final int pos) {
if (palette.length <= 4) if (palette.length <= 4)
return PnnLABQuantizer.this.nearestColorIndex(palette, c, pos); return PnnLABQuantizer.this.nearestColorIndex(palette, c, pos);
return PnnLABQuantizer.this.closestColorIndex(palette, c, pos); return PnnLABQuantizer.this.closestColorIndex(palette, c, pos);
@@ -490,7 +495,7 @@ public class PnnLABQuantizer extends PnnQuantizer {
} }
@Override @Override
protected int[] dither(final int[] cPixels, Integer[] palette, int width, int height, boolean dither) throws Exception public int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither)
{ {
Ditherable ditherable = getDitherFn(); Ditherable ditherable = getDitherFn();
if(hasSemiTransparency) if(hasSemiTransparency)
@@ -7,6 +7,7 @@ Copyright (c) 2018-2026 Miller Cy Chan
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.Color; import android.graphics.Color;
import android.util.Pair;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -36,6 +37,10 @@ public class PnnQuantizer {
fromBitmap(fname); fromBitmap(fname);
} }
public PnnQuantizer(Bitmap bitmap) {
fromBitmap(bitmap);
}
private void fromBitmap(Bitmap bitmap) { private void fromBitmap(Bitmap bitmap) {
width = bitmap.getWidth(); width = bitmap.getWidth();
height = bitmap.getHeight(); height = bitmap.getHeight();
@@ -131,7 +136,7 @@ public class PnnQuantizer {
return cnt -> cnt; return cnt -> cnt;
} }
protected Integer[] pnnquan(final int[] pixels, int nMaxColors) protected int[] pnnquan(final int[] pixels, int nMaxColors)
{ {
short quan_rt = (short) 1; short quan_rt = (short) 1;
Pnnbin[] bins = new Pnnbin[65536]; Pnnbin[] bins = new Pnnbin[65536];
@@ -255,7 +260,7 @@ public class PnnQuantizer {
} }
/* Fill palette */ /* Fill palette */
Integer[] palette = new Integer[extbins > 0 ? nMaxColors : maxbins]; int[] palette = new int[extbins > 0 ? nMaxColors : maxbins];
short k = 0; short k = 0;
for (int i = 0; k < palette.length; ++k) { for (int i = 0; k < palette.length; ++k) {
palette[k] = Color.argb((int) bins[i].ac, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc); palette[k] = Color.argb((int) bins[i].ac, (int) bins[i].rc, (int) bins[i].gc, (int) bins[i].bc);
@@ -266,7 +271,7 @@ public class PnnQuantizer {
return palette; return palette;
} }
protected short nearestColorIndex(final Integer[] palette, int c, final int pos) protected short nearestColorIndex(final int[] palette, int c, final int pos)
{ {
final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0); final int offset = weight > .015 ? c : BitmapUtilities.getColorIndex(c, hasSemiTransparency, m_transparentPixelIndex >= 0);
Short got = nearestMap.get(offset); Short got = nearestMap.get(offset);
@@ -310,7 +315,7 @@ public class PnnQuantizer {
return k; return k;
} }
protected short closestColorIndex(final Integer[] palette, int c, final int pos) protected short closestColorIndex(final int[] palette, int c, final int pos)
{ {
short k = 0; short k = 0;
if (Color.alpha(c) <= alphaThreshold) if (Color.alpha(c) <= alphaThreshold)
@@ -382,7 +387,7 @@ public class PnnQuantizer {
} }
@Override @Override
public short nearestColorIndex(Integer[] palette, int c, final int pos) { public short nearestColorIndex(int[] palette, int c, final int pos) {
if(dither) if(dither)
return PnnQuantizer.this.nearestColorIndex(palette, c, pos); return PnnQuantizer.this.nearestColorIndex(palette, c, pos);
return PnnQuantizer.this.closestColorIndex(palette, c, pos); return PnnQuantizer.this.closestColorIndex(palette, c, pos);
@@ -390,7 +395,7 @@ public class PnnQuantizer {
}; };
} }
protected int[] dither(final int[] cPixels, Integer[] palette, int width, int height, boolean dither) throws Exception protected int[] dither(final int[] cPixels, int[] palette, int width, int height, boolean dither)
{ {
Ditherable ditherable = getDitherFn(dither); Ditherable ditherable = getDitherFn(dither);
if(hasSemiTransparency) if(hasSemiTransparency)
@@ -406,7 +411,7 @@ public class PnnQuantizer {
return qPixels; return qPixels;
} }
public Bitmap convert(int nMaxColors, boolean dither) throws Exception { public Pair<Bitmap, int[]> convert(int nMaxColors, boolean dither) {
int semiTransCount = 0; int semiTransCount = 0;
for (int i = 0; i < pixels.length; ++i) { for (int i = 0; i < pixels.length; ++i) {
int pixel = pixels[i]; int pixel = pixels[i];
@@ -435,11 +440,11 @@ public class PnnQuantizer {
PR = coeffs[0][0]; PG = coeffs[0][1]; PB = coeffs[0][2]; PR = coeffs[0][0]; PG = coeffs[0][1]; PB = coeffs[0][2];
} }
Integer[] palette; int[] palette;
if (nMaxColors > 2) if (nMaxColors > 2)
palette = pnnquan(pixels, nMaxColors); palette = pnnquan(pixels, nMaxColors);
else { else {
palette = new Integer[nMaxColors]; palette = new int[nMaxColors];
weight = 1; weight = 1;
if (m_transparentPixelIndex >= 0) { if (m_transparentPixelIndex >= 0) {
palette[0] = m_transparentColor; palette[0] = m_transparentColor;
@@ -452,7 +457,9 @@ public class PnnQuantizer {
} }
int[] qPixels = dither(pixels, palette, width, height, dither); int[] qPixels = dither(pixels, palette, width, height, dither);
return Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888); return new Pair<>(
Bitmap.createBitmap(qPixels, width, height, Bitmap.Config.ARGB_8888),
palette);
} }
public boolean hasAlpha() { public boolean hasAlpha() {
@@ -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 8f18e9d7536e71a90d0d1333968e07688b7ebf09 * at 12822f15c92695136f1761b6b72c9bd18dc70c46
* <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