/*
* NeuQuant Neural-Net Quantization Algorithm
* ------------------------------------------
*
* Copyright (c) 1994 Anthony Dekker
*
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See
* "Kohonen neural networks for optimal colour quantization" in
* "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a
* discussion of the algorithm. See also
* http://members.ozemail.com.au/~dekker/NEUQUANT.HTML
*
* Any party obtaining a copy of these files from the author, directly or
* indirectly, is granted, free of charge, a full and unrestricted irrevocable,
* world-wide, paid up, royalty-free, nonexclusive right and license to deal in
* this software and documentation files (the "Software"), including without
* limitation the rights to use, copy, modify, merge, publish, distribute,
* sublicense, and/or sell copies of the Software, and to permit persons who
* receive copies from any such party to do so, with the only requirement being
* that this copyright notice remain intact.
*/
/*
* Frank Shaka has modified the original codes for the sake of processing SWT images.
*/
package org.xmind.neuquant;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
/**
* The NeuQuant Neural-Net image quantization algorithm (© Anthony Dekker
* 1994) is a replacement for the common Median Cut algorithm. This is an SWT
* version of the algorithm with abilities of processing SWT images.
* <p>
* See <a
* href="http://members.ozemail.com.au/~dekker/NEUQUANT.HTML">http://members
* .ozemail.com.au/~dekker/NEUQUANT.HTML</a> for more details.
* </p>
*
* @author Anthony Dekker
*/
public class NeuQuant {
// Constructors:
// public NeuQuant ()
// Initialisation method: call this first
// public void init (ImageData image, int sample)
// Methods to look up pixels (use in a loop)
// public int convert (RGB pixel)
// public int lookup (RGB c)
// public int lookup (boolean rgb, int x, int g, int y)
// Other methods to interrogate colour map
// public int getColorCount ()
// public RGB getColor (int i)
public static final int ncycles = 100; // no. of learning cycles
public static final int netsize = 256; // number of colours used
public static final int specials = 3; // number of reserved colours used
public static final int bgColour = specials - 1; // reserved background colour
public static final int cutnetsize = netsize - specials;
public static final int maxnetpos = netsize - 1;
public static final int initrad = netsize / 8; // for 256 cols, radius starts at 32
public static final int radiusbiasshift = 6;
public static final int radiusbias = 1 << radiusbiasshift;
public static final int initBiasRadius = initrad * radiusbias;
public static final int radiusdec = 30; // factor of 1/30 each cycle
public static final int alphabiasshift = 10; // alpha starts at 1
public static final int initalpha = 1 << alphabiasshift; // biased by 10 bits
public static final double gamma = 1024.0;
public static final double beta = 1.0 / 1024.0;
public static final double betagamma = beta * gamma;
private double[][] network = new double[netsize][3]; // the network itself
protected int[][] colormap = new int[netsize][4]; // the network itself
private int[] netindex = new int[256]; // for network lookup - really 256
private double[] bias = new double[netsize]; // bias and freq arrays for learning
private double[] freq = new double[netsize];
// four primes near 500 - assume no image has a length so large
// that it is divisible by all four primes
public static final int prime1 = 499;
public static final int prime2 = 491;
public static final int prime3 = 487;
public static final int prime4 = 503;
public static final int maxprime = prime4;
private int samplefac = 0;
protected RGB[] pixels = null;
public void init(ImageData image, int sample) {
if (image == null)
throw new IllegalArgumentException();
if (sample < 1)
throw new IllegalArgumentException("Sample must be 1..30"); //$NON-NLS-1$
if (sample > 30)
throw new IllegalArgumentException("Sample must be 1..30"); //$NON-NLS-1$
samplefac = sample;
setUpPixels(image);
setUpArrays();
learn();
fix();
inxbuild();
}
/**
* @param image
*/
private void setUpPixels(ImageData image) {
int width = image.width;
int height = image.height;
pixels = new RGB[width * height];
PaletteData palette = image.palette;
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
pixels[i + j * width] = palette.getRGB(image.getPixel(i, j));
}
}
}
public int getColorCount() {
return netsize;
}
public RGB getColor(int i) {
if (i < 0 || i >= netsize)
return null;
int bb = colormap[i][0];
int gg = colormap[i][1];
int rr = colormap[i][2];
return new RGB(rr, gg, bb);
}
public RGB[] getColourMap() {
RGB[] map = new RGB[netsize];
for (int i = 0; i < netsize; i++) {
int bb = colormap[i][0];
int gg = colormap[i][1];
int rr = colormap[i][2];
map[i] = new RGB(rr, gg, bb);
}
return map;
}
// public int writeColourMap (boolean rgb, OutputStream out) throws IOException {
// for (int i=0; i<netsize; i++) {
// int bb = colormap[i][0];
// int gg = colormap[i][1];
// int rr = colormap[i][2];
// out.write (rgb ? rr : bb);
// out.write (gg);
// out.write (rgb ? bb : rr);
// }
// return netsize;
// }
protected void setUpArrays() {
network[0][0] = 0.0; // black
network[0][1] = 0.0;
network[0][2] = 0.0;
network[1][0] = 1.0; // white
network[1][1] = 1.0;
network[1][2] = 1.0;
// RESERVED bgColour // background
for (int i = 0; i < specials; i++) {
freq[i] = 1.0 / netsize;
bias[i] = 0.0;
}
for (int i = specials; i < netsize; i++) {
double[] p = network[i];
p[0] = (256.0 * (i - specials)) / cutnetsize;
p[1] = (256.0 * (i - specials)) / cutnetsize;
p[2] = (256.0 * (i - specials)) / cutnetsize;
freq[i] = 1.0 / netsize;
bias[i] = 0.0;
}
}
// private void setPixels (Image im, ImageObserver obs) throws IOException {
// if (im == null) throw new IOException ("Image is null");
// int w = im.getWidth(obs);
// int h = im.getHeight(obs);
// setPixels (im, w, h);
// }
//
// private void setPixels (Image im, int w, int h) throws IOException {
// if (w*h < maxprime) throw new IOException ("Image is too small");
// pixels = new int [w * h];
// java.awt.image.PixelGrabber pg
// = new java.awt.image.PixelGrabber(im, 0, 0, w, h, pixels, 0, w);
// try {
// pg.grabPixels();
// } catch (InterruptedException e) { }
// if ((pg.getStatus() & java.awt.image.ImageObserver.ABORT) != 0) {
// throw new IOException ("Image pixel grab aborted or errored");
// }
// }
private void altersingle(double alpha, int i, double b, double g, double r) {
// Move neuron i towards biased (b,g,r) by factor alpha
double[] n = network[i]; // alter hit neuron
n[0] -= (alpha * (n[0] - b));
n[1] -= (alpha * (n[1] - g));
n[2] -= (alpha * (n[2] - r));
}
private void alterneigh(double alpha, int rad, int i, double b, double g,
double r) {
int lo = i - rad;
if (lo < specials - 1)
lo = specials - 1;
int hi = i + rad;
if (hi > netsize)
hi = netsize;
int j = i + 1;
int k = i - 1;
int q = 0;
while ((j < hi) || (k > lo)) {
double a = (alpha * (rad * rad - q * q)) / (rad * rad);
q++;
if (j < hi) {
double[] p = network[j];
p[0] -= (a * (p[0] - b));
p[1] -= (a * (p[1] - g));
p[2] -= (a * (p[2] - r));
j++;
}
if (k > lo) {
double[] p = network[k];
p[0] -= (a * (p[0] - b));
p[1] -= (a * (p[1] - g));
p[2] -= (a * (p[2] - r));
k--;
}
}
}
private int contest(double b, double g, double r) { // Search for biased BGR values
// finds closest neuron (min dist) and updates freq
// finds best neuron (min dist-bias) and returns position
// for frequently chosen neurons, freq[i] is high and bias[i] is negative
// bias[i] = gamma*((1/netsize)-freq[i])
double bestd = Float.MAX_VALUE;
double bestbiasd = bestd;
int bestpos = -1;
int bestbiaspos = bestpos;
for (int i = specials; i < netsize; i++) {
double[] n = network[i];
double dist = n[0] - b;
if (dist < 0)
dist = -dist;
double a = n[1] - g;
if (a < 0)
a = -a;
dist += a;
a = n[2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
bestpos = i;
}
double biasdist = dist - bias[i];
if (biasdist < bestbiasd) {
bestbiasd = biasdist;
bestbiaspos = i;
}
freq[i] -= beta * freq[i];
bias[i] += betagamma * freq[i];
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return bestbiaspos;
}
private int specialFind(double b, double g, double r) {
for (int i = 0; i < specials; i++) {
double[] n = network[i];
if (n[0] == b && n[1] == g && n[2] == r)
return i;
}
return -1;
}
private void learn() {
int biasRadius = initBiasRadius;
int alphadec = 30 + ((samplefac - 1) / 3);
int lengthcount = pixels.length;
int samplepixels = lengthcount / samplefac;
int delta = samplepixels / ncycles;
int alpha = initalpha;
int i = 0;
int rad = biasRadius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
// System.err.println("beginning 1D learning: samplepixels=" + samplepixels + " rad=" + rad);
int step = 0;
int pos = 0;
if ((lengthcount % prime1) != 0)
step = prime1;
else {
if ((lengthcount % prime2) != 0)
step = prime2;
else {
if ((lengthcount % prime3) != 0)
step = prime3;
else
step = prime4;
}
}
i = 0;
while (i < samplepixels) {
// int p = pixels [pos];
RGB p = pixels[pos];
int red = p.red;//(p >> 16) & 0xff;
int green = p.green;//(p >> 8) & 0xff;
int blue = p.blue;//(p ) & 0xff;
double b = blue;
double g = green;
double r = red;
if (i == 0) { // remember background colour
network[bgColour][0] = b;
network[bgColour][1] = g;
network[bgColour][2] = r;
}
int j = specialFind(b, g, r);
j = j < 0 ? contest(b, g, r) : j;
if (j >= specials) { // don't learn for specials
double a = (1.0 * alpha) / initalpha;
altersingle(a, j, b, g, r);
if (rad > 0)
alterneigh(a, rad, j, b, g, r); // alter neighbours
}
pos += step;
while (pos >= lengthcount)
pos -= lengthcount;
i++;
if (i % delta == 0) {
alpha -= alpha / alphadec;
biasRadius -= biasRadius / radiusdec;
rad = biasRadius >> radiusbiasshift;
if (rad <= 1)
rad = 0;
}
}
// System.err.println("finished 1D learning: final alpha=" + (1.0 * alpha)/initalpha + "!");
}
private void fix() {
for (int i = 0; i < netsize; i++) {
for (int j = 0; j < 3; j++) {
int x = (int) (0.5 + network[i][j]);
if (x < 0)
x = 0;
if (x > 255)
x = 255;
colormap[i][j] = x;
}
colormap[i][3] = i;
}
}
private void inxbuild() {
// Insertion sort of network and building of netindex[0..255]
int previouscol = 0;
int startpos = 0;
for (int i = 0; i < netsize; i++) {
int[] p = colormap[i];
int[] q = null;
int smallpos = i;
int smallval = p[1]; // index on g
// find smallest in i..netsize-1
for (int j = i + 1; j < netsize; j++) {
q = colormap[j];
if (q[1] < smallval) { // index on g
smallpos = j;
smallval = q[1]; // index on g
}
}
q = colormap[smallpos];
// swap p (i) and q (smallpos) entries
if (i != smallpos) {
int j = q[0];
q[0] = p[0];
p[0] = j;
j = q[1];
q[1] = p[1];
p[1] = j;
j = q[2];
q[2] = p[2];
p[2] = j;
j = q[3];
q[3] = p[3];
p[3] = j;
}
// smallval entry is now in position i
if (smallval != previouscol) {
netindex[previouscol] = (startpos + i) >> 1;
for (int j = previouscol + 1; j < smallval; j++)
netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
netindex[previouscol] = (startpos + maxnetpos) >> 1;
for (int j = previouscol + 1; j < 256; j++)
netindex[j] = maxnetpos; // really 256
}
public RGB convert(RGB pixel) {
int i = inxsearch(pixel.blue, pixel.green, pixel.red);
int bb = colormap[i][0];
int gg = colormap[i][1];
int rr = colormap[i][2];
return new RGB(rr, gg, bb);
}
// public int convert (int pixel) {
// int alfa = (pixel >> 24) & 0xff;
// int r = (pixel >> 16) & 0xff;
// int g = (pixel >> 8) & 0xff;
// int b = (pixel ) & 0xff;
// int i = inxsearch(b, g, r);
// int bb = colormap[i][0];
// int gg = colormap[i][1];
// int rr = colormap[i][2];
// return (alfa << 24) | (rr << 16) | (gg << 8) | (bb);
// }
// public int lookup (int pixel) {
// int r = (pixel >> 16) & 0xff;
// int g = (pixel >> 8) & 0xff;
// int b = (pixel ) & 0xff;
// int i = inxsearch(b, g, r);
// return i;
// }
public int lookup(RGB c) {
int r = c.red;
int g = c.green;
int b = c.blue;
int i = inxsearch(b, g, r);
return i;
}
public int lookup(boolean rgb, int x, int g, int y) {
int i = rgb ? inxsearch(y, g, x) : inxsearch(x, g, y);
return i;
}
// protected int not_used_slow_inxsearch(int b, int g, int r) {
// // Search for BGR values 0..255 and return colour index
// int bestd = 1000; // biggest possible dist is 256*3
// int best = -1;
// for (int i = 0; i < netsize; i++) {
// int[] p = colormap[i];
// int dist = p[1] - g;
// if (dist < 0)
// dist = -dist;
// int a = p[0] - b;
// if (a < 0)
// a = -a;
// dist += a;
// a = p[2] - r;
// if (a < 0)
// a = -a;
// dist += a;
// if (dist < bestd) {
// bestd = dist;
// best = i;
// }
// }
// return best;
// }
protected int inxsearch(int b, int g, int r) {
// Search for BGR values 0..255 and return colour index
int bestd = 1000; // biggest possible dist is 256*3
int best = -1;
int i = netindex[g]; // index on g
int j = i - 1; // start at netindex[g] and work outwards
while ((i < netsize) || (j >= 0)) {
if (i < netsize) {
int[] p = colormap[i];
int dist = p[1] - g; // inx key
if (dist >= bestd)
i = netsize; // stop iter
else {
if (dist < 0)
dist = -dist;
int a = p[0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = i;
}
}
i++;
}
}
if (j >= 0) {
int[] p = colormap[j];
int dist = g - p[1]; // inx key - reverse dif
if (dist >= bestd)
j = -1; // stop iter
else {
if (dist < 0)
dist = -dist;
int a = p[0] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
a = p[2] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = j;
}
}
j--;
}
}
}
return best;
}
}