summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/autotrace/median.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/3rdparty/autotrace/median.c
parentInitial commit. (diff)
downloadinkscape-upstream.tar.xz
inkscape-upstream.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/3rdparty/autotrace/median.c863
1 files changed, 863 insertions, 0 deletions
diff --git a/src/3rdparty/autotrace/median.c b/src/3rdparty/autotrace/median.c
new file mode 100644
index 0000000..60e8c7f
--- /dev/null
+++ b/src/3rdparty/autotrace/median.c
@@ -0,0 +1,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);
+}