/* img_quant.c - image quantizer. based on Antony Dekker original code Copyright (C) 2011 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "imagelib.h" #define netsize 256 // number of colours used #define prime1 499 #define prime2 491 #define prime3 487 #define prime4 503 #define minpicturebytes (3*prime4) // minimum size for input image #define maxnetpos (netsize-1) #define netbiasshift 4 // bias for colour values #define ncycles 100 // no. of learning cycles // defs for freq and bias #define intbiasshift 16 // bias for fractions #define intbias (1<>betashift) // beta = 1 / 1024 #define betagamma (intbias<<(gammashift - betashift)) // defs for decreasing radius factor #define initrad (netsize>>3) // for 256 cols, radius starts #define radiusbiasshift 6 // at 32.0 biased by 6 bits #define radiusbias (1<>= 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 num } } // Insertion sort of network and building of netindex[0..255] (to do after unbias) void inxbuild( void ) { register int *p, *q; register int i, j, smallpos, smallval; int previouscol, startpos; previouscol = 0; startpos = 0; for( i = 0; i < netsize; i++ ) { p = network[i]; smallpos = i; smallval = p[1]; // index on g // find smallest in i..netsize-1 for( j = i + 1; j < netsize; j++ ) { q = network[j]; if( q[1] < smallval ) { // index on g smallpos = j; smallval = q[1]; // index on g } } q = network[smallpos]; // swap p (i) and q (smallpos) entries if( i != smallpos ) { 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( 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 inxsearch( int r, int g, int b ) { register int i, j, dist, a, bestd; register 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[1] - g; // inx key if( dist >= bestd ) { i = netsize; // stop iter } else { i++; if( dist < 0 ) dist = -dist; a = p[2] - b; if( a < 0 ) a = -a; dist += a; if( dist < bestd ) { a = p[0] - 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[1]; // inx key - reverse dif if( dist >= bestd ) { j = -1; // stop iter } else { j--; if( dist < 0 ) dist = -dist; a = p[2] - b; if( a < 0 ) a = -a; dist += a; if( dist < bestd ) { a = p[0] - r; if( a < 0 ) a = -a; dist += a; if( dist < bestd ) { bestd = dist; best = p[3]; } } } } } return best; } // Search for biased BGR values int contest( int r, int g, int b ) { register int *p, *f, *n; register int i, dist, a, biasdist, betafreq; int bestpos, bestbiaspos, bestd, bestbiasd; // 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]) bestd = INT_MAX; bestbiasd = bestd; bestpos = -1; bestbiaspos = bestpos; p = bias; f = freq; for( i = 0; i < netsize; i++ ) { n = network[i]; dist = n[2] - b; if( dist < 0 ) dist = -dist; a = n[1] - g; if( a < 0 ) a = -a; dist += a; a = n[0] - 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 altersingle( int alpha, int i, int r, int g, int b ) { register int *n; n = network[i]; // alter hit neuron *n -= (alpha * (*n - r)) / initalpha; n++; *n -= (alpha * (*n - g)) / initalpha; n++; *n -= (alpha * (*n - b)) / initalpha; } // Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] void alterneigh( int rad, int i, int r, int g, int b ) { register int j, k, lo, hi, a; register 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 -= (a * (*p - r)) / alpharadbias; p++; *p -= (a * (*p - g)) / alpharadbias; p++; *p -= (a * (*p - b)) / alpharadbias; j++; } if( k > lo ) { p = network[k]; *p -= (a * (*p - r)) / alpharadbias; p++; *p -= (a * (*p - g)) / alpharadbias; p++; *p -= (a * (*p - b)) / alpharadbias; k--; } } } // Main Learning Loop void learn( void ) { register byte *p; register int i, j, r, g, b; int radius, rad, alpha, step; int delta, samplepixels; byte *lim; alphadec = 30 + ((samplefac - 1) / 3); p = thepicture; lim = thepicture + lengthcount; samplepixels = lengthcount / (image.bpp * samplefac); delta = samplepixels / ncycles; 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 )); if( delta <= 0 ) return; if(( lengthcount % prime1 ) != 0 ) { step = prime1 * image.bpp; } else if(( lengthcount % prime2 ) != 0 ) { step = prime2 * image.bpp; } else if(( lengthcount % prime3 ) != 0 ) { step = prime3 * image.bpp; } else { step = prime4 * image.bpp; } i = 0; while( i < samplepixels ) { r = p[0] << netbiasshift; g = p[1] << netbiasshift; b = p[2] << netbiasshift; j = contest( r, g, b ); altersingle( alpha, j, r, g, b ); if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours p += step; if( p >= lim ) p -= lengthcount; i++; if( i % delta == 0 ) { 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 )); } } } // returns the actual number of palette entries. rgbdata_t *Image_Quantize( rgbdata_t *pic ) { int i; // quick case to reject unneeded conversions if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) return pic; Image_CopyParms( pic ); image.size = image.width * image.height; image.bpp = PFDesc[pic->type].bpp; image.ptr = 0; // allocate 8-bit buffer image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, image.size ); initnet( pic->buffer, pic->size, 10 ); learn(); unbiasnet(); pic->palette = Mem_Malloc( host.imagepool, netsize * 3 ); for( i = 0; i < netsize; i++ ) { pic->palette[i*3+0] = network[i][0]; // red pic->palette[i*3+1] = network[i][1]; // green pic->palette[i*3+2] = network[i][2]; // blue } inxbuild(); for( i = 0; i < image.width * image.height; i++ ) { image.tempbuffer[i] = inxsearch( pic->buffer[i*image.bpp+0], pic->buffer[i*image.bpp+1], pic->buffer[i*image.bpp+2] ); } pic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size ); memcpy( pic->buffer, image.tempbuffer, image.size ); pic->type = PF_INDEXED_24; pic->size = image.size; return pic; }