Coverage Report

Created: 2025-02-07 06:10

/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/NNQuantizer.cpp
Line
Count
Source (jump to first uncovered line)
1
// NeuQuant Neural-Net Quantization Algorithm
2
// ------------------------------------------
3
//
4
// Copyright (c) 1994 Anthony Dekker
5
//
6
// NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994.
7
// See "Kohonen neural networks for optimal colour quantization"
8
// in "Network: Computation in Neural Systems" Vol. 5 (1994) pp 351-367.
9
// for a discussion of the algorithm.
10
//
11
// Any party obtaining a copy of these files from the author, directly or
12
// indirectly, is granted, free of charge, a full and unrestricted irrevocable,
13
// world-wide, paid up, royalty-free, nonexclusive right and license to deal
14
// in this software and documentation files (the "Software"), including without
15
// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
16
// and/or sell copies of the Software, and to permit persons who receive
17
// copies from any such party to do so, with the only requirement being
18
// that this copyright notice remain intact.
19
20
///////////////////////////////////////////////////////////////////////
21
// History
22
// -------
23
// January 2001: Adaptation of the Neural-Net Quantization Algorithm
24
//               for the FreeImage 2 library
25
//               Author: Hervé Drolon (drolon@infonie.fr)
26
// March 2004:   Adaptation for the FreeImage 3 library (port to big endian processors)
27
//               Author: Hervé Drolon (drolon@infonie.fr)
28
// April 2004:   Algorithm rewritten as a C++ class. 
29
//               Fixed a bug in the algorithm with handling of 4-byte boundary alignment.
30
//               Author: Hervé Drolon (drolon@infonie.fr)
31
///////////////////////////////////////////////////////////////////////
32
33
#include "Quantizers.h"
34
#include "FreeImage.h"
35
#include "Utilities.h"
36
37
38
// Four primes near 500 - assume no image has a length so large
39
// that it is divisible by all four primes
40
// ==========================================================
41
42
0
#define prime1    499
43
0
#define prime2    491
44
0
#define prime3    487
45
0
#define prime4    503
46
47
// ----------------------------------------------------------------
48
49
NNQuantizer::NNQuantizer(int PaletteSize)
50
0
{
51
0
  netsize = PaletteSize;
52
0
  maxnetpos = netsize - 1;
53
0
  initrad = netsize < 8 ? 1 : (netsize >> 3);
54
0
  initradius = (initrad * radiusbias);
55
56
0
  network = NULL;
57
58
0
  network = (pixel *)malloc(netsize * sizeof(pixel));
59
0
  bias = (int *)malloc(netsize * sizeof(int));
60
0
  freq = (int *)malloc(netsize * sizeof(int));
61
0
  radpower = (int *)malloc(initrad * sizeof(int));
62
63
0
  if( !network || !bias || !freq || !radpower ) {
64
0
    if(network) free(network);
65
0
    if(bias) free(bias);
66
0
    if(freq) free(freq);
67
0
    if(radpower) free(radpower);
68
0
    throw FI_MSG_ERROR_MEMORY;
69
0
  }
70
0
}
71
72
NNQuantizer::~NNQuantizer()
73
0
{
74
0
  if(network) free(network);
75
0
  if(bias) free(bias);
76
0
  if(freq) free(freq);
77
0
  if(radpower) free(radpower);
78
0
}
79
80
///////////////////////////////////////////////////////////////////////////
81
// Initialise network in range (0,0,0) to (255,255,255) and set parameters
82
// -----------------------------------------------------------------------
83
84
0
void NNQuantizer::initnet() {
85
0
  int i, *p;
86
87
0
  for (i = 0; i < netsize; i++) {
88
0
    p = network[i];
89
0
    p[FI_RGBA_BLUE] = p[FI_RGBA_GREEN] = p[FI_RGBA_RED] = (i << (netbiasshift+8))/netsize;
90
0
    freq[i] = intbias/netsize;  /* 1/netsize */
91
0
    bias[i] = 0;
92
0
  }
93
0
}
94
95
/////////////////////////////////////////////////////////////////////////////////////// 
96
// Unbias network to give byte values 0..255 and record position i to prepare for sort
97
// ------------------------------------------------------------------------------------
98
99
0
void NNQuantizer::unbiasnet() {
100
0
  int i, j, temp;
101
102
0
  for (i = 0; i < netsize; i++) {
103
0
    for (j = 0; j < 3; j++) {
104
      // OLD CODE: network[i][j] >>= netbiasshift; 
105
      // Fix based on bug report by Juergen Weigert jw@suse.de
106
0
      temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift;
107
0
      if (temp > 255) temp = 255;
108
0
      network[i][j] = temp;
109
0
    }
110
0
    network[i][3] = i;      // record colour no 
111
0
  }
112
0
}
113
114
//////////////////////////////////////////////////////////////////////////////////
115
// Insertion sort of network and building of netindex[0..255] (to do after unbias)
116
// -------------------------------------------------------------------------------
117
118
0
void NNQuantizer::inxbuild() {
119
0
  int i,j,smallpos,smallval;
120
0
  int *p,*q;
121
0
  int previouscol,startpos;
122
123
0
  previouscol = 0;
124
0
  startpos = 0;
125
0
  for (i = 0; i < netsize; i++) {
126
0
    p = network[i];
127
0
    smallpos = i;
128
0
    smallval = p[FI_RGBA_GREEN];      // index on g
129
    // find smallest in i..netsize-1
130
0
    for (j = i+1; j < netsize; j++) {
131
0
      q = network[j];
132
0
      if (q[FI_RGBA_GREEN] < smallval) { // index on g
133
0
        smallpos = j;
134
0
        smallval = q[FI_RGBA_GREEN];  // index on g
135
0
      }
136
0
    }
137
0
    q = network[smallpos];
138
    // swap p (i) and q (smallpos) entries
139
0
    if (i != smallpos) {
140
0
      j = q[FI_RGBA_BLUE];  q[FI_RGBA_BLUE]  = p[FI_RGBA_BLUE];  p[FI_RGBA_BLUE]  = j;
141
0
      j = q[FI_RGBA_GREEN]; q[FI_RGBA_GREEN] = p[FI_RGBA_GREEN]; p[FI_RGBA_GREEN] = j;
142
0
      j = q[FI_RGBA_RED];   q[FI_RGBA_RED]   = p[FI_RGBA_RED];   p[FI_RGBA_RED]   = j;
143
0
      j = q[3];   q[3] = p[3];   p[3] = j;
144
0
    }
145
    // smallval entry is now in position i
146
0
    if (smallval != previouscol) {
147
0
      netindex[previouscol] = (startpos+i)>>1;
148
0
      for (j = previouscol+1; j < smallval; j++)
149
0
        netindex[j] = i;
150
0
      previouscol = smallval;
151
0
      startpos = i;
152
0
    }
153
0
  }
154
0
  netindex[previouscol] = (startpos+maxnetpos)>>1;
155
0
  for (j = previouscol+1; j < 256; j++)
156
0
    netindex[j] = maxnetpos; // really 256
157
0
}
158
159
///////////////////////////////////////////////////////////////////////////////
160
// Search for BGR values 0..255 (after net is unbiased) and return colour index
161
// ----------------------------------------------------------------------------
162
163
0
int NNQuantizer::inxsearch(int b, int g, int r) {
164
0
  int i, j, dist, a, bestd;
165
0
  int *p;
166
0
  int best;
167
168
0
  bestd = 1000;   // biggest possible dist is 256*3
169
0
  best = -1;
170
0
  i = netindex[g];  // index on g
171
0
  j = i-1;      // start at netindex[g] and work outwards
172
173
0
  while ((i < netsize) || (j >= 0)) {
174
0
    if (i < netsize) {
175
0
      p = network[i];
176
0
      dist = p[FI_RGBA_GREEN] - g;        // inx key
177
0
      if (dist >= bestd)
178
0
        i = netsize; // stop iter
179
0
      else {
180
0
        i++;
181
0
        if (dist < 0)
182
0
          dist = -dist;
183
0
        a = p[FI_RGBA_BLUE] - b;
184
0
        if (a < 0)
185
0
          a = -a;
186
0
        dist += a;
187
0
        if (dist < bestd) {
188
0
          a = p[FI_RGBA_RED] - r;
189
0
          if (a<0)
190
0
            a = -a;
191
0
          dist += a;
192
0
          if (dist < bestd) {
193
0
            bestd = dist;
194
0
            best = p[3];
195
0
          }
196
0
        }
197
0
      }
198
0
    }
199
0
    if (j >= 0) {
200
0
      p = network[j];
201
0
      dist = g - p[FI_RGBA_GREEN];      // inx key - reverse dif
202
0
      if (dist >= bestd)
203
0
        j = -1; // stop iter
204
0
      else {
205
0
        j--;
206
0
        if (dist < 0)
207
0
          dist = -dist;
208
0
        a = p[FI_RGBA_BLUE] - b;
209
0
        if (a<0)
210
0
          a = -a;
211
0
        dist += a;
212
0
        if (dist < bestd) {
213
0
          a = p[FI_RGBA_RED] - r;
214
0
          if (a<0)
215
0
            a = -a;
216
0
          dist += a;
217
0
          if (dist < bestd) {
218
0
            bestd = dist;
219
0
            best = p[3];
220
0
          }
221
0
        }
222
0
      }
223
0
    }
224
0
  }
225
0
  return best;
226
0
}
227
228
///////////////////////////////
229
// Search for biased BGR values
230
// ----------------------------
231
232
0
int NNQuantizer::contest(int b, int g, int r) {
233
  // finds closest neuron (min dist) and updates freq
234
  // finds best neuron (min dist-bias) and returns position
235
  // for frequently chosen neurons, freq[i] is high and bias[i] is negative
236
  // bias[i] = gamma*((1/netsize)-freq[i])
237
238
0
  int i,dist,a,biasdist,betafreq;
239
0
  int bestpos,bestbiaspos,bestd,bestbiasd;
240
0
  int *p,*f, *n;
241
242
0
  bestd = ~(((int) 1)<<31);
243
0
  bestbiasd = bestd;
244
0
  bestpos = -1;
245
0
  bestbiaspos = bestpos;
246
0
  p = bias;
247
0
  f = freq;
248
249
0
  for (i = 0; i < netsize; i++) {
250
0
    n = network[i];
251
0
    dist = n[FI_RGBA_BLUE] - b;
252
0
    if (dist < 0)
253
0
      dist = -dist;
254
0
    a = n[FI_RGBA_GREEN] - g;
255
0
    if (a < 0)
256
0
      a = -a;
257
0
    dist += a;
258
0
    a = n[FI_RGBA_RED] - r;
259
0
    if (a < 0)
260
0
      a = -a;
261
0
    dist += a;
262
0
    if (dist < bestd) {
263
0
      bestd = dist;
264
0
      bestpos = i;
265
0
    }
266
0
    biasdist = dist - ((*p)>>(intbiasshift-netbiasshift));
267
0
    if (biasdist < bestbiasd) {
268
0
      bestbiasd = biasdist;
269
0
      bestbiaspos = i;
270
0
    }
271
0
    betafreq = (*f >> betashift);
272
0
    *f++ -= betafreq;
273
0
    *p++ += (betafreq << gammashift);
274
0
  }
275
0
  freq[bestpos] += beta;
276
0
  bias[bestpos] -= betagamma;
277
0
  return bestbiaspos;
278
0
}
279
280
///////////////////////////////////////////////////////
281
// Move neuron i towards biased (b,g,r) by factor alpha
282
// ---------------------------------------------------- 
283
284
0
void NNQuantizer::altersingle(int alpha, int i, int b, int g, int r) {
285
0
  int *n;
286
287
0
  n = network[i];       // alter hit neuron
288
0
  n[FI_RGBA_BLUE]  -= (alpha * (n[FI_RGBA_BLUE]  - b)) / initalpha;
289
0
  n[FI_RGBA_GREEN] -= (alpha * (n[FI_RGBA_GREEN] - g)) / initalpha;
290
0
  n[FI_RGBA_RED]   -= (alpha * (n[FI_RGBA_RED]   - r)) / initalpha;
291
0
}
292
293
////////////////////////////////////////////////////////////////////////////////////
294
// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|]
295
// ---------------------------------------------------------------------------------
296
297
0
void NNQuantizer::alterneigh(int rad, int i, int b, int g, int r) {
298
0
  int j, k, lo, hi, a;
299
0
  int *p, *q;
300
301
0
  lo = i - rad;   if (lo < -1) lo = -1;
302
0
  hi = i + rad;   if (hi > netsize) hi = netsize;
303
304
0
  j = i+1;
305
0
  k = i-1;
306
0
  q = radpower;
307
0
  while ((j < hi) || (k > lo)) {
308
0
    a = (*(++q));
309
0
    if (j < hi) {
310
0
      p = network[j];
311
0
      p[FI_RGBA_BLUE]  -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias;
312
0
      p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias;
313
0
      p[FI_RGBA_RED]   -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias;
314
0
      j++;
315
0
    }
316
0
    if (k > lo) {
317
0
      p = network[k];
318
0
      p[FI_RGBA_BLUE]  -= (a * (p[FI_RGBA_BLUE] - b)) / alpharadbias;
319
0
      p[FI_RGBA_GREEN] -= (a * (p[FI_RGBA_GREEN] - g)) / alpharadbias;
320
0
      p[FI_RGBA_RED]   -= (a * (p[FI_RGBA_RED] - r)) / alpharadbias;
321
0
      k--;
322
0
    }
323
0
  }
324
0
}
325
326
/////////////////////
327
// Main Learning Loop
328
// ------------------
329
330
/**
331
 Get a pixel sample at position pos. Handle 4-byte boundary alignment.
332
 @param pos pixel position in a WxHx3 pixel buffer
333
 @param b blue pixel component
334
 @param g green pixel component
335
 @param r red pixel component
336
*/
337
0
void NNQuantizer::getSample(long pos, int *b, int *g, int *r) {
338
  // get equivalent pixel coordinates 
339
  // - assume it's a 24-bit image -
340
0
  int x = pos % img_line;
341
0
  int y = pos / img_line;
342
343
0
  BYTE *bits = FreeImage_GetScanLine(dib_ptr, y) + x;
344
345
0
  *b = bits[FI_RGBA_BLUE] << netbiasshift;
346
0
  *g = bits[FI_RGBA_GREEN] << netbiasshift;
347
0
  *r = bits[FI_RGBA_RED] << netbiasshift;
348
0
}
349
350
0
void NNQuantizer::learn(int sampling_factor) {
351
0
  int i, j, b, g, r;
352
0
  int radius, rad, alpha, step, delta, samplepixels;
353
0
  int alphadec; // biased by 10 bits
354
0
  long pos, lengthcount;
355
356
  // image size as viewed by the scan algorithm
357
0
  lengthcount = img_width * img_height * 3;
358
359
  // number of samples used for the learning phase
360
0
  samplepixels = lengthcount / (3 * sampling_factor);
361
362
  // decrease learning rate after delta pixel presentations
363
0
  delta = samplepixels / ncycles;
364
0
  if(delta == 0) {
365
    // avoid a 'divide by zero' error with very small images
366
0
    delta = 1;
367
0
  }
368
369
  // initialize learning parameters
370
0
  alphadec = 30 + ((sampling_factor - 1) / 3);
371
0
  alpha = initalpha;
372
0
  radius = initradius;
373
  
374
0
  rad = radius >> radiusbiasshift;
375
0
  if (rad <= 1) rad = 0;
376
0
  for (i = 0; i < rad; i++) 
377
0
    radpower[i] = alpha*(((rad*rad - i*i)*radbias)/(rad*rad));
378
  
379
  // initialize pseudo-random scan
380
0
  if ((lengthcount % prime1) != 0)
381
0
    step = 3*prime1;
382
0
  else {
383
0
    if ((lengthcount % prime2) != 0)
384
0
      step = 3*prime2;
385
0
    else {
386
0
      if ((lengthcount % prime3) != 0) 
387
0
        step = 3*prime3;
388
0
      else
389
0
        step = 3*prime4;
390
0
    }
391
0
  }
392
  
393
0
  i = 0;    // iteration
394
0
  pos = 0;  // pixel position
395
396
0
  while (i < samplepixels) {
397
    // get next learning sample
398
0
    getSample(pos, &b, &g, &r);
399
400
    // find winning neuron
401
0
    j = contest(b, g, r);
402
403
    // alter winner
404
0
    altersingle(alpha, j, b, g, r);
405
406
    // alter neighbours 
407
0
    if (rad) alterneigh(rad, j, b, g, r);
408
409
    // next sample
410
0
    pos += step;
411
0
    while (pos >= lengthcount) pos -= lengthcount;
412
  
413
0
    i++;
414
0
    if (i % delta == 0) { 
415
      // decrease learning rate and also the neighborhood
416
0
      alpha -= alpha / alphadec;
417
0
      radius -= radius / radiusdec;
418
0
      rad = radius >> radiusbiasshift;
419
0
      if (rad <= 1) rad = 0;
420
0
      for (j = 0; j < rad; j++) 
421
0
        radpower[j] = alpha * (((rad*rad - j*j) * radbias) / (rad*rad));
422
0
    }
423
0
  }
424
  
425
0
}
426
427
//////////////
428
// Quantizer
429
// -----------
430
431
0
FIBITMAP* NNQuantizer::Quantize(FIBITMAP *dib, int ReserveSize, RGBQUAD *ReservePalette, int sampling) {
432
433
0
  if ((!dib) || (FreeImage_GetBPP(dib) != 24)) {
434
0
    return NULL;
435
0
  }
436
437
  // 1) Select a sampling factor in range 1..30 (input parameter 'sampling')
438
  //    1 => slower, 30 => faster. Default value is 1
439
440
441
  // 2) Get DIB parameters
442
443
0
  dib_ptr = dib;
444
  
445
0
  img_width  = FreeImage_GetWidth(dib); // DIB width
446
0
  img_height = FreeImage_GetHeight(dib);  // DIB height
447
0
  img_line   = FreeImage_GetLine(dib);  // DIB line length in bytes (should be equal to 3 x W)
448
449
  // For small images, adjust the sampling factor to avoid a 'divide by zero' error later 
450
  // (see delta in learn() routine)
451
0
  int adjust = (img_width * img_height) / ncycles;
452
0
  if(sampling >= adjust)
453
0
    sampling = 1;
454
455
456
  // 3) Initialize the network and apply the learning algorithm
457
458
0
  if( netsize > ReserveSize ) {
459
0
    netsize -= ReserveSize;
460
0
    initnet();
461
0
    learn(sampling);
462
0
    unbiasnet();
463
0
    netsize += ReserveSize;
464
0
  }
465
466
  // 3.5) Overwrite the last few palette entries with the reserved ones
467
0
    for (int i = 0; i < ReserveSize; i++) {
468
0
    network[netsize - ReserveSize + i][FI_RGBA_BLUE] = ReservePalette[i].rgbBlue;
469
0
    network[netsize - ReserveSize + i][FI_RGBA_GREEN] = ReservePalette[i].rgbGreen;
470
0
    network[netsize - ReserveSize + i][FI_RGBA_RED] = ReservePalette[i].rgbRed;
471
0
    network[netsize - ReserveSize + i][3] = netsize - ReserveSize + i;
472
0
  }
473
474
  // 4) Allocate a new 8-bit DIB
475
476
0
  FIBITMAP *new_dib = FreeImage_Allocate(img_width, img_height, 8);
477
478
0
  if (new_dib == NULL)
479
0
    return NULL;
480
481
  // 5) Write the quantized palette
482
483
0
  RGBQUAD *new_pal = FreeImage_GetPalette(new_dib);
484
485
0
    for (int j = 0; j < netsize; j++) {
486
0
    new_pal[j].rgbBlue  = (BYTE)network[j][FI_RGBA_BLUE];
487
0
    new_pal[j].rgbGreen = (BYTE)network[j][FI_RGBA_GREEN];
488
0
    new_pal[j].rgbRed = (BYTE)network[j][FI_RGBA_RED];
489
0
  }
490
491
0
  inxbuild();
492
493
  // 6) Write output image using inxsearch(b,g,r)
494
495
0
  for (WORD rows = 0; rows < img_height; rows++) {
496
0
    BYTE *new_bits = FreeImage_GetScanLine(new_dib, rows);      
497
0
    BYTE *bits = FreeImage_GetScanLine(dib_ptr, rows);
498
499
0
    for (WORD cols = 0; cols < img_width; cols++) {
500
0
      new_bits[cols] = (BYTE)inxsearch(bits[FI_RGBA_BLUE], bits[FI_RGBA_GREEN], bits[FI_RGBA_RED]);
501
502
0
      bits += 3;
503
0
    }
504
0
  }
505
506
0
  return (FIBITMAP*) new_dib;
507
0
}