summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/autotrace/median.c
blob: 60e8c7fc8ce98e64c81e28cda518823ea99227b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
/* median.c: median cut - reducing a high color bitmap to certain number of colors

   Copyright (C) 2001, 2002 Martin Weber

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
   USA. */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* Def: HAVE_CONFIG_H */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "logreport.h"
#include "xstd.h"
#include "quantize.h"

#define MAXNUMCOLORS 256

#if 0
#define R_SCALE
#define G_SCALE
#define B_SCALE
#else

/* scale RGB distances by *2,*3,*1 */
#define R_SCALE  <<1
#define G_SCALE  *3
#define B_SCALE
#endif

#define BITS_IN_SAMPLE	8

#define R_SHIFT	(BITS_IN_SAMPLE - PRECISION_R)
#define G_SHIFT	(BITS_IN_SAMPLE - PRECISION_G)
#define B_SHIFT	(BITS_IN_SAMPLE - PRECISION_B)

typedef struct {
  /* The bounds of the box (inclusive); expressed as histogram indexes */
  int Rmin, Rmax;
  int Gmin, Gmax;
  int Bmin, Bmax;
  /* The volume (actually 2-norm) of the box */
  int volume;
  /* The number of nonzero histogram cells within this box */
  long colorcount;
} box, *boxptr;

static void zero_histogram_rgb(Histogram histogram)
{
  int r, g, b;
  for (r = 0; r < HIST_R_ELEMS; r++)
    for (g = 0; g < HIST_G_ELEMS; g++)
      for (b = 0; b < HIST_B_ELEMS; b++)
        histogram[r * MR + g * MG + b] = 0;
}

static void generate_histogram_rgb(Histogram histogram, at_bitmap * image, const at_color * ignoreColor)
{
  unsigned char *src = image->bitmap;
  int num_elems;
  ColorFreq *col;

  num_elems = AT_BITMAP_WIDTH(image) * AT_BITMAP_HEIGHT(image);
  zero_histogram_rgb(histogram);

  switch (AT_BITMAP_PLANES(image)) {
  case 3:
    while (num_elems--) {
      /* If we have an ignorecolor, skip it. */
      if (ignoreColor) {
        if ((src[0] == ignoreColor->r)
            && (src[1] == ignoreColor->g)
            && (src[2] == ignoreColor->b)) {
          src += 3;
          continue;
        }
      }
      col = &histogram[(src[0] >> R_SHIFT) * MR + (src[1] >> G_SHIFT) * MG + (src[2] >> B_SHIFT)];
      (*col)++;
      src += 3;
    }
    break;

  case 1:
    while (--num_elems >= 0) {
      if (ignoreColor && src[num_elems] == ignoreColor->r)
        continue;
      col = &histogram[(src[num_elems] >> R_SHIFT) * MR + (src[num_elems] >> G_SHIFT) * MG + (src[num_elems] >> B_SHIFT)];
      (*col)++;
    }
    break;
  default:
    /* To avoid compiler warning */ ;
  }
}

static boxptr find_biggest_volume(boxptr boxlist, int numboxes)
/* Find the splittable box with the largest (scaled) volume */
/* Returns 0 if no splittable boxes remain */
{
  boxptr boxp;
  int i;
  int maxv = 0;
  boxptr which = 0;

  for (i = 0, boxp = boxlist; i < numboxes; i++, boxp++) {
    if (boxp->volume > maxv) {
      which = boxp;
      maxv = boxp->volume;
    }
  }

  return which;
}

static void update_box_rgb(Histogram histogram, boxptr boxp)
/* Shrink the min/max bounds of a box to enclose only nonzero elements, */
/* and recompute its volume and population */
{
  ColorFreq *histp;
  int R, G, B;
  int Rmin, Rmax, Gmin, Gmax, Bmin, Bmax;
  int dist0, dist1, dist2;
  long ccount;

  Rmin = boxp->Rmin;
  Rmax = boxp->Rmax;
  Gmin = boxp->Gmin;
  Gmax = boxp->Gmax;
  Bmin = boxp->Bmin;
  Bmax = boxp->Bmax;

  if (Rmax > Rmin)
    for (R = Rmin; R <= Rmax; R++)
      for (G = Gmin; G <= Gmax; G++) {
        histp = histogram + R * MR + G * MG + Bmin;
        for (B = Bmin; B <= Bmax; B++)
          if (*histp++ != 0) {
            boxp->Rmin = Rmin = R;
            goto have_Rmin;
          }
      }
have_Rmin:
  if (Rmax > Rmin)
    for (R = Rmax; R >= Rmin; R--)
      for (G = Gmin; G <= Gmax; G++) {
        histp = histogram + R * MR + G * MG + Bmin;
        for (B = Bmin; B <= Bmax; B++)
          if (*histp++ != 0) {
            boxp->Rmax = Rmax = R;
            goto have_Rmax;
          }
      }
have_Rmax:
  if (Gmax > Gmin)
    for (G = Gmin; G <= Gmax; G++)
      for (R = Rmin; R <= Rmax; R++) {
        histp = histogram + R * MR + G * MG + Bmin;
        for (B = Bmin; B <= Bmax; B++)
          if (*histp++ != 0) {
            boxp->Gmin = Gmin = G;
            goto have_Gmin;
          }
      }
have_Gmin:
  if (Gmax > Gmin)
    for (G = Gmax; G >= Gmin; G--)
      for (R = Rmin; R <= Rmax; R++) {
        histp = histogram + R * MR + G * MG + Bmin;
        for (B = Bmin; B <= Bmax; B++)
          if (*histp++ != 0) {
            boxp->Gmax = Gmax = G;
            goto have_Gmax;
          }
      }
have_Gmax:
  if (Bmax > Bmin)
    for (B = Bmin; B <= Bmax; B++)
      for (R = Rmin; R <= Rmax; R++) {
        histp = histogram + R * MR + Gmin * MG + B;
        for (G = Gmin; G <= Gmax; G++, histp += MG)
          if (*histp != 0) {
            boxp->Bmin = Bmin = B;
            goto have_Bmin;
          }
      }
have_Bmin:
  if (Bmax > Bmin)
    for (B = Bmax; B >= Bmin; B--)
      for (R = Rmin; R <= Rmax; R++) {
        histp = histogram + R * MR + Gmin * MG + B;
        for (G = Gmin; G <= Gmax; G++, histp += MG)
          if (*histp != 0) {
            boxp->Bmax = Bmax = B;
            goto have_Bmax;
          }
      }
have_Bmax:

  /* Update box volume.
   * We use 2-norm rather than real volume here; this biases the method
   * against making long narrow boxes, and it has the side benefit that
   * a box is splittable iff norm > 0.
   * Since the differences are expressed in histogram-cell units,
   * we have to shift back to JSAMPLE units to get consistent distances;
   * after which, we scale according to the selected distance scale factors.
   */
  dist0 = Rmax - Rmin;
  dist1 = Gmax - Gmin;
  dist2 = Bmax - Bmin;
  boxp->volume = dist0 * dist0 + dist1 * dist1 + dist2 * dist2;

  /* Now scan remaining volume of box and compute population */
  ccount = 0;
  for (R = Rmin; R <= Rmax; R++)
    for (G = Gmin; G <= Gmax; G++) {
      histp = histogram + R * MR + G * MG + Bmin;
      for (B = Bmin; B <= Bmax; B++, histp++)
        if (*histp != 0) {
          ccount++;
        }
    }

  boxp->colorcount = ccount;
}

static int median_cut_rgb(Histogram histogram, boxptr boxlist, int numboxes, int desired_colors)
/* Repeatedly select and split the largest box until we have enough boxes */
{
  int n, lb;
  int R, G, B, cmax;
  boxptr b1, b2;

  while (numboxes < desired_colors) {
    /* Select box to split.
     * Current algorithm: by population for first half, then by volume.
     */
    b1 = find_biggest_volume(boxlist, numboxes);

    if (b1 == 0)                /* no splittable boxes left! */
      break;
    b2 = boxlist + numboxes;    /* where new box will go */
    /* Copy the color bounds to the new box. */
    b2->Rmax = b1->Rmax;
    b2->Gmax = b1->Gmax;
    b2->Bmax = b1->Bmax;
    b2->Rmin = b1->Rmin;
    b2->Gmin = b1->Gmin;
    b2->Bmin = b1->Bmin;
    /* Choose which axis to split the box on.
     * Current algorithm: longest scaled axis.
     * See notes in update_box about scaling distances.
     */
    R = b1->Rmax - b1->Rmin;
    G = b1->Gmax - b1->Gmin;
    B = b1->Bmax - b1->Bmin;
    /* We want to break any ties in favor of green, then red, blue last.
     */
    cmax = G;
    n = 1;
    if (R > cmax) {
      cmax = R;
      n = 0;
    }
    if (B > cmax) {
      n = 2;
    }
    /* Choose split point along selected axis, and update box bounds.
     * Current algorithm: split at halfway point.
     * (Since the box has been shrunk to minimum volume,
     * any split will produce two nonempty subboxes.)
     * Note that lb value is max for lower box, so must be < old max.
     */
    switch (n) {
    case 0:
      lb = (b1->Rmax + b1->Rmin) / 2;
      b1->Rmax = lb;
      b2->Rmin = lb + 1;
      break;
    case 1:
      lb = (b1->Gmax + b1->Gmin) / 2;
      b1->Gmax = lb;
      b2->Gmin = lb + 1;
      break;
    case 2:
      lb = (b1->Bmax + b1->Bmin) / 2;
      b1->Bmax = lb;
      b2->Bmin = lb + 1;
      break;
    }
    /* Update stats for boxes */
    update_box_rgb(histogram, b1);
    update_box_rgb(histogram, b2);
    numboxes++;
  }
  return numboxes;
}

static void compute_color_rgb(QuantizeObj * quantobj, Histogram histogram, boxptr boxp, int icolor)
/* Compute representative color for a box, put it in colormap[icolor] */
{
  /* Current algorithm: mean weighted by pixels (not colors) */
  /* Note it is important to get the rounding correct! */
  ColorFreq *histp;
  int R, G, B;
  int Rmin, Rmax;
  int Gmin, Gmax;
  int Bmin, Bmax;
  unsigned long count;
  unsigned long total = 0;
  unsigned long Rtotal = 0;
  unsigned long Gtotal = 0;
  unsigned long Btotal = 0;

  Rmin = boxp->Rmin;
  Rmax = boxp->Rmax;
  Gmin = boxp->Gmin;
  Gmax = boxp->Gmax;
  Bmin = boxp->Bmin;
  Bmax = boxp->Bmax;

  for (R = Rmin; R <= Rmax; R++)
    for (G = Gmin; G <= Gmax; G++) {
      histp = histogram + R * MR + G * MG + Bmin;
      for (B = Bmin; B <= Bmax; B++) {
        if ((count = *histp++) != 0) {
          total += count;
          Rtotal += ((R << R_SHIFT) + ((1 << R_SHIFT) >> 1)) * count;
          Gtotal += ((G << G_SHIFT) + ((1 << G_SHIFT) >> 1)) * count;
          Btotal += ((B << B_SHIFT) + ((1 << B_SHIFT) >> 1)) * count;
        }
      }
    }

  quantobj->cmap[icolor].r = (unsigned char)((Rtotal + (total >> 1)) / total);
  quantobj->cmap[icolor].g = (unsigned char)((Gtotal + (total >> 1)) / total);
  quantobj->cmap[icolor].b = (unsigned char)((Btotal + (total >> 1)) / total);
  quantobj->freq[icolor] = total;
}

static void select_colors_rgb(QuantizeObj * quantobj, Histogram histogram)
/* Master routine for color selection */
{
  boxptr boxlist;
  int numboxes;
  int desired = quantobj->desired_number_of_colors;
  int i;

  /* Allocate workspace for box list */
  XMALLOC(boxlist, desired * sizeof(box));

  /* Initialize one box containing whole space */
  numboxes = 1;
  boxlist[0].Rmin = 0;
  boxlist[0].Rmax = (1 << PRECISION_R) - 1;
  boxlist[0].Gmin = 0;
  boxlist[0].Gmax = (1 << PRECISION_G) - 1;
  boxlist[0].Bmin = 0;
  boxlist[0].Bmax = (1 << PRECISION_B) - 1;
  /* Shrink it to actually-used volume and set its statistics */
  update_box_rgb(histogram, boxlist);
  /* Perform median-cut to produce final box list */
  numboxes = median_cut_rgb(histogram, boxlist, numboxes, desired);
  quantobj->actual_number_of_colors = numboxes;
  /* Compute the representative color for each box, fill colormap */
  for (i = 0; i < numboxes; i++)
    compute_color_rgb(quantobj, histogram, boxlist + i, i);
  free(boxlist);
}

/*
 * These routines are concerned with the time-critical task of mapping input
 * colors to the nearest color in the selected colormap.
 *
 * We re-use the histogram space as an "inverse color map", essentially a
 * cache for the results of nearest-color searches.  All colors within a
 * histogram cell will be mapped to the same colormap entry, namely the one
 * closest to the cell's center.  This may not be quite the closest entry to
 * the actual input color, but it's almost as good.  A zero in the cache
 * indicates we haven't found the nearest color for that cell yet; the array
 * is cleared to zeroes before starting the mapping pass.  When we find the
 * nearest color for a cell, its colormap index plus one is recorded in the
 * cache for future use.  The pass2 scanning routines call fill_inverse_cmap
 * when they need to use an unfilled entry in the cache.
 *
 * Our method of efficiently finding nearest colors is based on the "locally
 * sorted search" idea described by Heckbert and on the incremental distance
 * calculation described by Spencer W. Thomas in chapter III.1 of Graphics
 * Gems II (James Arvo, ed.  Academic Press, 1991).  Thomas points out that
 * the distances from a given colormap entry to each cell of the histogram can
 * be computed quickly using an incremental method: the differences between
 * distances to adjacent cells themselves differ by a constant.  This allows a
 * fairly fast implementation of the "brute force" approach of computing the
 * distance from every colormap entry to every histogram cell.  Unfortunately,
 * it needs a work array to hold the best-distance-so-far for each histogram
 * cell (because the inner loop has to be over cells, not colormap entries).
 * The work array elements have to be ints, so the work array would need
 * 256Kb at our recommended precision.  This is not feasible in DOS machines.

[ 256*1024/4 = 65,536 ]

 * To get around these problems, we apply Thomas' method to compute the
 * nearest colors for only the cells within a small subbox of the histogram.
 * The work array need be only as big as the subbox, so the memory usage
 * problem is solved.  Furthermore, we need not fill subboxes that are never
 * referenced in pass2; many images use only part of the color gamut, so a
 * fair amount of work is saved.  An additional advantage of this
 * approach is that we can apply Heckbert's locality criterion to quickly
 * eliminate colormap entries that are far away from the subbox; typically
 * three-fourths of the colormap entries are rejected by Heckbert's criterion,
 * and we need not compute their distances to individual cells in the subbox.
 * The speed of this approach is heavily influenced by the subbox size: too
 * small means too much overhead, too big loses because Heckbert's criterion
 * can't eliminate as many colormap entries.  Empirically the best subbox
 * size seems to be about 1/512th of the histogram (1/8th in each direction).
 *
 * Thomas' article also describes a refined method which is asymptotically
 * faster than the brute-force method, but it is also far more complex and
 * cannot efficiently be applied to small subboxes.  It is therefore not
 * useful for programs intended to be portable to DOS machines.  On machines
 * with plenty of memory, filling the whole histogram in one shot with Thomas'
 * refined method might be faster than the present code --- but then again,
 * it might not be any faster, and it's certainly more complicated.
 */

/* log2(histogram cells in update box) for each axis; this can be adjusted */
#define BOX_R_LOG  (PRECISION_R-3)
#define BOX_G_LOG  (PRECISION_G-3)
#define BOX_B_LOG  (PRECISION_B-3)

#define BOX_R_ELEMS  (1<<BOX_R_LOG) /* # of hist cells in update box */
#define BOX_G_ELEMS  (1<<BOX_G_LOG)
#define BOX_B_ELEMS  (1<<BOX_B_LOG)

#define BOX_R_SHIFT  (R_SHIFT + BOX_R_LOG)
#define BOX_G_SHIFT  (G_SHIFT + BOX_G_LOG)
#define BOX_B_SHIFT  (B_SHIFT + BOX_B_LOG)

/*
 * The next three routines implement inverse colormap filling.  They could
 * all be folded into one big routine, but splitting them up this way saves
 * some stack space (the mindist[] and bestdist[] arrays need not coexist)
 * and may allow some compilers to produce better code by registerizing more
 * inner-loop variables.
 */

static int find_nearby_colors(QuantizeObj * quantobj, int minR, int minG, int minB, int *colorlist)
/* Locate the colormap entries close enough to an update box to be candidates
 * for the nearest entry to some cell(s) in the update box.  The update box
 * is specified by the center coordinates of its first cell.  The number of
 * candidate colormap entries is returned, and their colormap indexes are
 * placed in colorlist[].
 * This routine uses Heckbert's "locally sorted search" criterion to select
 * the colors that need further consideration.
 */
{
  int numcolors = quantobj->actual_number_of_colors;
  int maxR, maxG, maxB;
  int centerR, centerG, centerB;
  int i, x, ncolors;
  int minmaxdist, min_dist = 0, max_dist, tdist;
  int mindist[MAXNUMCOLORS];    /* min distance to colormap entry i */

  /* Compute TRUE coordinates of update box's upper corner and center.
   * Actually we compute the coordinates of the center of the upper-corner
   * histogram cell, which are the upper bounds of the volume we care about.
   * Note that since ">>" rounds down, the "center" values may be closer to
   * min than to max; hence comparisons to them must be "<=", not "<".
   */
  maxR = minR + ((1 << BOX_R_SHIFT) - (1 << R_SHIFT));
  centerR = (minR + maxR) >> 1;
  maxG = minG + ((1 << BOX_G_SHIFT) - (1 << G_SHIFT));
  centerG = (minG + maxG) >> 1;
  maxB = minB + ((1 << BOX_B_SHIFT) - (1 << B_SHIFT));
  centerB = (minB + maxB) >> 1;

  /* For each color in colormap, find:
   *  1. its minimum squared-distance to any point in the update box
   *     (zero if color is within update box);
   *  2. its maximum squared-distance to any point in the update box.
   * Both of these can be found by considering only the corners of the box.
   * We save the minimum distance for each color in mindist[];
   * only the smallest maximum distance is of interest.
   */
  minmaxdist = 0x7FFFFFFFL;

  for (i = 0; i < numcolors; i++) {
    /* We compute the squared-R-distance term, then add in the other two. */
    x = quantobj->cmap[i].r;
    if (x < minR) {
      tdist = (x - minR) R_SCALE;
      min_dist = tdist * tdist;
      tdist = (x - maxR) R_SCALE;
      max_dist = tdist * tdist;
    } else if (x > maxR) {
      tdist = (x - maxR) R_SCALE;
      min_dist = tdist * tdist;
      tdist = (x - minR) R_SCALE;
      max_dist = tdist * tdist;
    } else {
      /* within cell range so no contribution to min_dist */
      min_dist = 0;
      if (x <= centerR) {
        tdist = (x - maxR) R_SCALE;
        max_dist = tdist * tdist;
      } else {
        tdist = (x - minR) R_SCALE;
        max_dist = tdist * tdist;
      }
    }

    x = quantobj->cmap[i].g;
    if (x < minG) {
      tdist = (x - minG) G_SCALE;
      min_dist += tdist * tdist;
      tdist = (x - maxG) G_SCALE;
      max_dist += tdist * tdist;
    } else if (x > maxG) {
      tdist = (x - maxG) G_SCALE;
      min_dist += tdist * tdist;
      tdist = (x - minG) G_SCALE;
      max_dist += tdist * tdist;
    } else {
      /* within cell range so no contribution to min_dist */
      if (x <= centerG) {
        tdist = (x - maxG) G_SCALE;
        max_dist += tdist * tdist;
      } else {
        tdist = (x - minG) G_SCALE;
        max_dist += tdist * tdist;
      }
    }

    x = quantobj->cmap[i].b;
    if (x < minB) {
      tdist = (x - minB) B_SCALE;
      min_dist += tdist * tdist;
      tdist = (x - maxB) B_SCALE;
      max_dist += tdist * tdist;
    } else if (x > maxB) {
      tdist = (x - maxB) B_SCALE;
      min_dist += tdist * tdist;
      tdist = (x - minB) B_SCALE;
      max_dist += tdist * tdist;
    } else {
      /* within cell range so no contribution to min_dist */
      if (x <= centerB) {
        tdist = (x - maxB) B_SCALE;
        max_dist += tdist * tdist;
      } else {
        tdist = (x - minB) B_SCALE;
        max_dist += tdist * tdist;
      }
    }

    mindist[i] = min_dist;      /* save away the results */
    if (max_dist < minmaxdist)
      minmaxdist = max_dist;
  }

  /* Now we know that no cell in the update box is more than minmaxdist
   * away from some colormap entry.  Therefore, only colors that are
   * within minmaxdist of some part of the box need be considered.
   */
  ncolors = 0;
  for (i = 0; i < numcolors; i++) {
    if (mindist[i] <= minmaxdist)
      colorlist[ncolors++] = i;
  }
  return ncolors;
}

static void find_best_colors(QuantizeObj * quantobj, int minR, int minG, int minB, int numcolors, int *colorlist, int *bestcolor)
/* Find the closest colormap entry for each cell in the update box,
  given the list of candidate colors prepared by find_nearby_colors.
  Return the indexes of the closest entries in the bestcolor[] array.
  This routine uses Thomas' incremental distance calculation method to
  find the distance from a colormap entry to successive cells in the box.
 */
{
  int iR, iG, iB;
  int i, icolor;
  int *bptr;                    /* pointer into bestdist[] array */
  int *cptr;                    /* pointer into bestcolor[] array */
  int dist0, dist1;             /* initial distance values */
  int dist2;                    /* current distance in inner loop */
  int xx0, xx1;                 /* distance increments */
  int xx2;
  int inR, inG, inB;            /* initial values for increments */

  /* This array holds the distance to the nearest-so-far color for each cell */
  int bestdist[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS];

  /* Initialize best-distance for each cell of the update box */
  bptr = bestdist;
  for (i = BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS - 1; i >= 0; i--)
    *bptr++ = 0x7FFFFFFFL;

  /* For each color selected by find_nearby_colors,
   * compute its distance to the center of each cell in the box.
   * If that's less than best-so-far, update best distance and color number.
   */

  /* Nominal steps between cell centers ("x" in Thomas article) */
#define STEP_R  ((1 << R_SHIFT) R_SCALE)
#define STEP_G  ((1 << G_SHIFT) G_SCALE)
#define STEP_B  ((1 << B_SHIFT) B_SCALE)

  for (i = 0; i < numcolors; i++) {
    icolor = colorlist[i];
    /* Compute (square of) distance from minR/G/B to this color */
    inR = (minR - quantobj->cmap[icolor].r) R_SCALE;
    dist0 = inR * inR;
    inG = (minG - quantobj->cmap[icolor].g) G_SCALE;
    dist0 += inG * inG;
    inB = (minB - quantobj->cmap[icolor].b) B_SCALE;
    dist0 += inB * inB;
    /* Form the initial difference increments */
    inR = inR * (2 * STEP_R) + STEP_R * STEP_R;
    inG = inG * (2 * STEP_G) + STEP_G * STEP_G;
    inB = inB * (2 * STEP_B) + STEP_B * STEP_B;
    /* Now loop over all cells in box, updating distance per Thomas method */
    bptr = bestdist;
    cptr = bestcolor;
    xx0 = inR;
    for (iR = BOX_R_ELEMS - 1; iR >= 0; iR--) {
      dist1 = dist0;
      xx1 = inG;
      for (iG = BOX_G_ELEMS - 1; iG >= 0; iG--) {
        dist2 = dist1;
        xx2 = inB;
        for (iB = BOX_B_ELEMS - 1; iB >= 0; iB--) {
          if (dist2 < *bptr) {
            *bptr = dist2;
            *cptr = icolor;
          }
          dist2 += xx2;
          xx2 += 2 * STEP_B * STEP_B;
          bptr++;
          cptr++;
        }
        dist1 += xx1;
        xx1 += 2 * STEP_G * STEP_G;
      }
      dist0 += xx0;
      xx0 += 2 * STEP_R * STEP_R;
    }
  }
}

static void fill_inverse_cmap_rgb(QuantizeObj * quantobj, Histogram histogram, int R, int G, int B)
/* Fill the inverse-colormap entries in the update box that contains
 histogram cell R/G/B.  (Only that one cell MUST be filled, but
 we can fill as many others as we wish.) */
{
  int minR, minG, minB;         /* lower left corner of update box */
  int iR, iG, iB;
  int *cptr;                    /* pointer into bestcolor[] array */
  ColorFreq *cachep;            /* pointer into main cache array */
  /* This array lists the candidate colormap indexes. */
  int colorlist[MAXNUMCOLORS];
  int numcolors;                /* number of candidate colors */
  /* This array holds the actually closest colormap index for each cell. */
  int bestcolor[BOX_R_ELEMS * BOX_G_ELEMS * BOX_B_ELEMS];

  /* Convert cell coordinates to update box ID */
  R >>= BOX_R_LOG;
  G >>= BOX_G_LOG;
  B >>= BOX_B_LOG;

  /* Compute TRUE coordinates of update box's origin corner.
   * Actually we compute the coordinates of the center of the corner
   * histogram cell, which are the lower bounds of the volume we care about.
   */
  minR = (R << BOX_R_SHIFT) + ((1 << R_SHIFT) >> 1);
  minG = (G << BOX_G_SHIFT) + ((1 << G_SHIFT) >> 1);
  minB = (B << BOX_B_SHIFT) + ((1 << B_SHIFT) >> 1);

  /* Determine which colormap entries are close enough to be candidates
   * for the nearest entry to some cell in the update box.
   */
  numcolors = find_nearby_colors(quantobj, minR, minG, minB, colorlist);

  /* Determine the actually nearest colors. */
  find_best_colors(quantobj, minR, minG, minB, numcolors, colorlist, bestcolor);

  /* Save the best color numbers (plus 1) in the main cache array */
  R <<= BOX_R_LOG;              /* convert ID back to base cell indexes */
  G <<= BOX_G_LOG;
  B <<= BOX_B_LOG;
  cptr = bestcolor;
  for (iR = 0; iR < BOX_R_ELEMS; iR++) {
    for (iG = 0; iG < BOX_G_ELEMS; iG++) {
      cachep = &histogram[(R + iR) * MR + (G + iG) * MG + B];
      for (iB = 0; iB < BOX_B_ELEMS; iB++) {
        *cachep++ = (*cptr++) + 1;
      }
    }
  }
}

/*  This is pass 1  */
static void median_cut_pass1_rgb(QuantizeObj * quantobj, at_bitmap * image, const at_color * ignoreColor)
{
  generate_histogram_rgb(quantobj->histogram, image, ignoreColor);
  select_colors_rgb(quantobj, quantobj->histogram);
}

/* Map some rows of pixels to the output colormapped representation. */
static void median_cut_pass2_rgb(QuantizeObj * quantobj, at_bitmap * image, const at_color * bgColor)
 /* This version performs no dithering */
{
  Histogram histogram = quantobj->histogram;
  ColorFreq *cachep;
  int R, G, B;
  int origR, origG, origB;
  int row, col;
  int spp = AT_BITMAP_PLANES(image);
  int width = AT_BITMAP_WIDTH(image);
  int height = AT_BITMAP_HEIGHT(image);
  unsigned char *src, *dest;
  at_color bg_color = { 0xff, 0xff, 0xff };

  zero_histogram_rgb(histogram);

  if (bgColor) {
    /* Find the nearest colormap entry for the background color. */
    R = bgColor->r >> R_SHIFT;
    G = bgColor->g >> G_SHIFT;
    B = bgColor->b >> B_SHIFT;
    cachep = &histogram[R * MR + G * MG + B];
    if (*cachep == 0)
      fill_inverse_cmap_rgb(quantobj, histogram, R, G, B);
    bg_color = quantobj->cmap[*cachep - 1];
  }

  src = dest = image->bitmap;
  if (spp == 3) {
    for (row = 0; row < height; row++) {
      for (col = 0; col < width; col++) {
        /* get pixel value and index into the cache */
        origR = (*src++);
        origG = (*src++);
        origB = (*src++);

        /*
           if (origR > 253 && origG > 253 && origB > 253)
           {
           (*dest++) = 255; (*dest++) = 255; (*dest++) = 255;
           continue;
           }
         */

        /* get pixel value and index into the cache */
        R = origR >> R_SHIFT;
        G = origG >> G_SHIFT;
        B = origB >> B_SHIFT;
        cachep = &histogram[R * MR + G * MG + B];
        /* If we have not seen this color before, find nearest
           colormap entry and update the cache */
        if (*cachep == 0) {
          fill_inverse_cmap_rgb(quantobj, histogram, R, G, B);
        }
        /* Now emit the colormap index for this cell */
        dest[0] = quantobj->cmap[*cachep - 1].r;
        dest[1] = quantobj->cmap[*cachep - 1].g;
        dest[2] = quantobj->cmap[*cachep - 1].b;

        /* If the colormap entry for this pixel is the same as the
           background's colormap entry, set the pixel to the
           background color. */
        if (bgColor && (dest[0] == bg_color.r && dest[1] == bg_color.g && dest[2] == bg_color.b)) {
          dest[0] = bgColor->r;
          dest[1] = bgColor->g;
          dest[2] = bgColor->b;
        }
        dest += 3;
      }
    }
  } else if (spp == 1) {
    long idx = width * height;
    while (--idx >= 0) {
      origR = src[idx];
      R = origR >> R_SHIFT;
      G = origR >> G_SHIFT;
      B = origR >> B_SHIFT;
      cachep = &histogram[R * MR + G * MG + B];
      if (*cachep == 0)
        fill_inverse_cmap_rgb(quantobj, histogram, R, G, B);

      dest[idx] = quantobj->cmap[*cachep - 1].r;

      /* If the colormap entry for this pixel is the same as the
         background's colormap entry, set the pixel to the
         background color. */
      if (bgColor && dest[idx] == bg_color.r)
        dest[idx] = bgColor->r;
    }
  }
}

static QuantizeObj *initialize_median_cut(int num_colors)
{
  QuantizeObj *quantobj;

  /* Initialize the data structures */
  XMALLOC(quantobj, sizeof(QuantizeObj));

  XMALLOC(quantobj->histogram, sizeof(ColorFreq) * HIST_R_ELEMS * HIST_G_ELEMS * HIST_B_ELEMS);
  quantobj->desired_number_of_colors = num_colors;

  return quantobj;
}

void quantize(at_bitmap * image, long ncolors, const at_color * bgColor, QuantizeObj ** iQuant, at_exception_type * exp)
{
  QuantizeObj *quantobj;
  unsigned int spp = AT_BITMAP_PLANES(image);

  if (spp != 3 && spp != 1) {
    LOG("quantize: %u-plane images are not supported", spp);
    at_exception_fatal(exp, "quantize: wrong plane images are passed");
    return;
  }

  /* If a pointer was sent in, let's use it. */
  if (iQuant) {
    if (*iQuant == NULL) {
      quantobj = initialize_median_cut(ncolors);
      median_cut_pass1_rgb(quantobj, image, bgColor);
      *iQuant = quantobj;
    } else
      quantobj = *iQuant;
  } else {
    quantobj = initialize_median_cut(ncolors);
    median_cut_pass1_rgb(quantobj, image, NULL);
  }

  median_cut_pass2_rgb(quantobj, image, bgColor);

  if (iQuant == NULL)
    quantize_object_free(quantobj);
}

void quantize_object_free(QuantizeObj * quantobj)
{
  free(quantobj->histogram);
  free(quantobj);
}