blob: f907c41d5514fb8b3d0b684cc7e01714ff3ffbb6 [file] [log] [blame]
// 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.
//
// 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.
///////////////////////////////////////////////////////////////////////
// History
// -------
// January 2001: Adaptation of the Neural-Net Quantization Algorithm
// for the FreeImage 2 library
// Author: Hervé Drolon (drolon@infonie.fr)
// March 2004: Adaptation for the FreeImage 3 library (port to big endian processors)
// Author: Hervé Drolon (drolon@infonie.fr)
// April 2004: Algorithm rewritten as a C++ class.
// Fixed a bug in the algorithm with handling of 4-byte boundary alignment.
// Author: Hervé Drolon (drolon@infonie.fr)
///////////////////////////////////////////////////////////////////////
#include "Quantizers.h"
#include "FreeImage.h"
#include "Utilities.h"
// Four primes near 500 - assume no image has a length so large
// that it is divisible by all four primes
// ==========================================================
#define prime1 499
#define prime2 491
#define prime3 487
#define prime4 503
// ----------------------------------------------------------------
NNQuantizer::NNQuantizer(int PaletteSize)
{
netsize = PaletteSize;
maxnetpos = netsize - 1;
initrad = netsize < 8 ? 1 : (netsize >> 3);
initradius = (initrad * radiusbias);
network = NULL;
network = (pixel *)malloc(netsize * sizeof(pixel));
bias = (int *)malloc(netsize * sizeof(int));
freq = (int *)malloc(netsize * sizeof(int));
radpower = (int *)malloc(initrad * sizeof(int));
if( !network || !bias || !freq || !radpower ) {
if(network) free(network);
if(bias) free(bias);
if(freq) free(freq);
if(radpower) free(radpower);
throw FI_MSG_ERROR_MEMORY;
}
}
NNQuantizer::~NNQuantizer()
{
if(network) free(network);
if(bias) free(bias);
if(freq) free(freq);
if(radpower) free(radpower);
}
///////////////////////////////////////////////////////////////////////////
// Initialise network in range (0,0,0) to (255,255,255) and set parameters
// -----------------------------------------------------------------------
void NNQuantizer::initnet() {
int i, *p;
for (i = 0; i < netsize; i++) {
p = network[i];
p[FI_RGBA_BLUE] = p[FI_RGBA_GREEN] = p[FI_RGBA_RED] = (i << (netbiasshift+8))/netsize;
freq[i] = intbias/netsize; /* 1/netsize */
bias[i] = 0;
}
}
///////////////////////////////////////////////////////////////////////////////////////
// Unbias network to give byte values 0..255 and record position i to prepare for sort
// ------------------------------------------------------------------------------------
void NNQuantizer::unbiasnet() {
int i, j, temp;
for (i = 0; i < netsize; i++) {
for (j = 0; j < 3; j++) {
// OLD CODE: network[i][j] >>= netbiasshift;
// Fix based on bug report by Juergen Weigert jw@suse.de
temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;
if (temp > 255) temp = 255;
network[i][j] = temp;
}
network[i][3] = i; // record colour no
}
}
//////////////////////////////////////////////////////////////////////////////////
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
// -------------------------------------------------------------------------------
void NNQuantizer::inxbuild() {
int i,j,smallpos,smallval;
int *p,*q;
int previouscol,startpos;
previouscol = 0;
startpos = 0;
for (i = 0; i < netsize; i++) {
p = network[i];
smallpos = i;
smallval = p[FI_RGBA_GREEN]; // index on g
// find smallest in i..netsize-1
for (j = i+1; j < netsize; j++) {
q = network[j];
if (q[FI_RGBA_GREEN] < smallval) { // index on g
smallpos = j;
smallval = q[FI_RGBA_GREEN]; // index on g
}
}
q = network[smallpos];
// swap p (i) and q (smallpos) entries
if (i != smallpos) {
j = q[FI_RGBA_BLUE]; q[FI_RGBA_BLUE] = p[FI_RGBA_BLUE]; p[FI_RGBA_BLUE] = j;
j = q[FI_RGBA_GREEN]; q[FI_RGBA_GREEN] = p[FI_RGBA_GREEN]; p[FI_RGBA_GREEN] = j;
j = q[FI_RGBA_RED]; q[FI_RGBA_RED] = p[FI_RGBA_RED]; p[FI_RGBA_RED] = 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 (j = previouscol+1; j < smallval; j++)
netindex[j] = i;
previouscol = smallval;
startpos = i;
}
}
netindex[previouscol] = (startpos+maxnetpos)>>1;
for (j = previouscol+1; j < 256; j++)
netindex[j] = maxnetpos; // really 256
}
///////////////////////////////////////////////////////////////////////////////
// Search for BGR values 0..255 (after net is unbiased) and return colour index
// ----------------------------------------------------------------------------
int NNQuantizer::inxsearch(int b, int g, int r) {
int i, j, dist, a, bestd;
int *p;
int best;
bestd = 1000; // biggest possible dist is 256*3
best = -1;
i = netindex[g]; // index on g
j = i-1; // start at netindex[g] and work outwards
while ((i < netsize) || (j >= 0)) {
if (i < netsize) {
p = network[i];
dist = p[FI_RGBA_GREEN] - g; // inx key
if (dist >= bestd)
i = netsize; // stop iter
else {
i++;
if (dist < 0)
dist = -dist;
a = p[FI_RGBA_BLUE] - b;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
a = p[FI_RGBA_RED] - r;
if (a<0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
if (j >= 0) {
p = network[j];
dist = g - p[FI_RGBA_GREEN]; // inx key - reverse dif
if (dist >= bestd)
j = -1; // stop iter
else {
j--;
if (dist < 0)
dist = -dist;
a = p[FI_RGBA_BLUE] - b;
if (a<0)
a = -a;
dist += a;
if (dist < bestd) {
a = p[FI_RGBA_RED] - r;
if (a<0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
best = p[3];
}
}
}
}
}
return best;
}
///////////////////////////////
// Search for biased BGR values
// ----------------------------
int NNQuantizer::contest(int b, int g, int r) {
// 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])
int i,dist,a,biasdist,betafreq;
int bestpos,bestbiaspos,bestd,bestbiasd;
int *p,*f, *n;
bestd = ~(((int) 1)<<31);
bestbiasd = bestd;
bestpos = -1;
bestbiaspos = bestpos;
p = bias;
f = freq;
for (i = 0; i < netsize; i++) {
n = network[i];
dist = n[FI_RGBA_BLUE] - b;
if (dist < 0)
dist = -dist;
a = n[FI_RGBA_GREEN] - g;
if (a < 0)
a = -a;
dist += a;
a = n[FI_RGBA_RED] - r;
if (a < 0)
a = -a;
dist += a;
if (dist < bestd) {
bestd = dist;
bestpos = i;
}
biasdist = dist - ((*p)>>(intbiasshift-netbiasshift));
if (biasdist < bestbiasd) {
bestbiasd = biasdist;
bestbiaspos = i;
}
betafreq = (*f >> betashift);
*f++ -= betafreq;
*p++ += (betafreq << gammashift);
}
freq[bestpos] += beta;
bias[bestpos] -= betagamma;
return bestbiaspos;
}
///////////////////////////////////////////////////////
// Move neuron i towards biased (b,g,r) by factor alpha
// ----------------------------------------------------
void NNQuantizer::altersingle(int alpha, int i, int b, int g, int r) {
int *n;
n = network[i]; // alter hit neuron
n[FI_RGBA_BLUE] -= (alpha * (n[FI_RGBA_BLUE] - b)) / initalpha;
n[FI_RGBA_GREEN] -= (alpha * (n[FI_RGBA_GREEN] - g)) / initalpha;
n[FI_RGBA_RED] -= (alpha * (n[FI_RGBA_RED] - r)) / initalpha;
}
////////////////////////////////////////////////////////////////////////////////////
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
// ---------------------------------------------------------------------------------
void NNQuantizer::alterneigh(int rad, int i, int b, int g, int r) {
int j, k, lo, hi, a;
int *p, *q;
lo = i - rad; if (lo < -1) lo = -1;
hi = i + rad; if (hi > netsize) hi = netsize;
j = i+1;
k = i-1;
q = radpower;
while ((j < hi) || (k > lo)) {
a = (*(++q));
if (j < hi) {
p = network[j];
p[FI_RGBA_BLUE] -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias;
p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias;
p[FI_RGBA_RED] -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias;
j++;
}
if (k > lo) {
p = network[k];
p[FI_RGBA_BLUE] -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias;
p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias;
p[FI_RGBA_RED] -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias;
k--;
}
}
}
/////////////////////
// Main Learning Loop
// ------------------
/**
Get a pixel sample at position pos. Handle 4-byte boundary alignment.
@param pos pixel position in a WxHx3 pixel buffer
@param b blue pixel component
@param g green pixel component
@param r red pixel component
*/
void NNQuantizer::getSample(long pos, int *b, int *g, int *r) {
// get equivalent pixel coordinates
// - assume it's a 24-bit image -
int x = pos % img_line;
int y = pos / img_line;
BYTE *bits = FreeImage_GetScanLine(dib_ptr, y) + x;
*b = bits[FI_RGBA_BLUE] << netbiasshift;
*g = bits[FI_RGBA_GREEN] << netbiasshift;
*r = bits[FI_RGBA_RED] << netbiasshift;
}
void NNQuantizer::learn(int sampling_factor) {
int i, j, b, g, r;
int radius, rad, alpha, step, delta, samplepixels;
int alphadec; // biased by 10 bits
long pos, lengthcount;
// image size as viewed by the scan algorithm
lengthcount = img_width * img_height * 3;
// number of samples used for the learning phase
samplepixels = lengthcount / (3 * sampling_factor);
// decrease learning rate after delta pixel presentations
delta = samplepixels / ncycles;
if(delta == 0) {
// avoid a 'divide by zero' error with very small images
delta = 1;
}
// initialize learning parameters
alphadec = 30 + ((sampling_factor - 1) / 3);
alpha = initalpha;
radius = initradius;
rad = radius >> radiusbiasshift;
if (rad <= 1) rad = 0;
for (i = 0; i < rad; i++)
radpower[i] = alpha*(((rad*rad - i*i)*radbias)/(rad*rad));
// initialize pseudo-random scan
if ((lengthcount % prime1) != 0)
step = 3*prime1;
else {
if ((lengthcount % prime2) != 0)
step = 3*prime2;
else {
if ((lengthcount % prime3) != 0)
step = 3*prime3;
else
step = 3*prime4;
}
}
i = 0; // iteration
pos = 0; // pixel position
while (i < samplepixels) {
// get next learning sample
getSample(pos, &b, &g, &r);
// find winning neuron
j = contest(b, g, r);
// alter winner
altersingle(alpha, j, b, g, r);
// alter neighbours
if (rad) alterneigh(rad, j, b, g, r);
// next sample
pos += step;
while (pos >= lengthcount) pos -= lengthcount;
i++;
if (i % delta == 0) {
// decrease learning rate and also the neighborhood
alpha -= alpha / alphadec;
radius -= radius / radiusdec;
rad = radius >> radiusbiasshift;
if (rad <= 1) rad = 0;
for (j = 0; j < rad; j++)
radpower[j] = alpha * (((rad*rad - j*j) * radbias) / (rad*rad));
}
}
}
//////////////
// Quantizer
// -----------
FIBITMAP* NNQuantizer::Quantize(FIBITMAP *dib, int ReserveSize, RGBQUAD *ReservePalette, int sampling) {
if ((!dib) || (FreeImage_GetBPP(dib) != 24)) {
return NULL;
}
// 1) Select a sampling factor in range 1..30 (input parameter 'sampling')
// 1 => slower, 30 => faster. Default value is 1
// 2) Get DIB parameters
dib_ptr = dib;
img_width = FreeImage_GetWidth(dib); // DIB width
img_height = FreeImage_GetHeight(dib); // DIB height
img_line = FreeImage_GetLine(dib); // DIB line length in bytes (should be equal to 3 x W)
// For small images, adjust the sampling factor to avoid a 'divide by zero' error later
// (see delta in learn() routine)
int adjust = (img_width * img_height) / ncycles;
if(sampling >= adjust)
sampling = 1;
// 3) Initialize the network and apply the learning algorithm
if( netsize > ReserveSize ) {
netsize -= ReserveSize;
initnet();
learn(sampling);
unbiasnet();
netsize += ReserveSize;
}
// 3.5) Overwrite the last few palette entries with the reserved ones
for (int i = 0; i < ReserveSize; i++) {
network[netsize - ReserveSize + i][FI_RGBA_BLUE] = ReservePalette[i].rgbBlue;
network[netsize - ReserveSize + i][FI_RGBA_GREEN] = ReservePalette[i].rgbGreen;
network[netsize - ReserveSize + i][FI_RGBA_RED] = ReservePalette[i].rgbRed;
network[netsize - ReserveSize + i][3] = netsize - ReserveSize + i;
}
// 4) Allocate a new 8-bit DIB
FIBITMAP *new_dib = FreeImage_Allocate(img_width, img_height, 8);
if (new_dib == NULL)
return NULL;
// 5) Write the quantized palette
RGBQUAD *new_pal = FreeImage_GetPalette(new_dib);
for (int j = 0; j < netsize; j++) {
new_pal[j].rgbBlue = (BYTE)network[j][FI_RGBA_BLUE];
new_pal[j].rgbGreen = (BYTE)network[j][FI_RGBA_GREEN];
new_pal[j].rgbRed = (BYTE)network[j][FI_RGBA_RED];
}
inxbuild();
// 6) Write output image using inxsearch(b,g,r)
for (WORD rows = 0; rows < img_height; rows++) {
BYTE *new_bits = FreeImage_GetScanLine(new_dib, rows);
BYTE *bits = FreeImage_GetScanLine(dib_ptr, rows);
for (WORD cols = 0; cols < img_width; cols++) {
new_bits[cols] = (BYTE)inxsearch(bits[FI_RGBA_BLUE], bits[FI_RGBA_GREEN], bits[FI_RGBA_RED]);
bits += 3;
}
}
return (FIBITMAP*) new_dib;
}