// 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; }