]> git.imager.perl.org - imager.git/blobdiff - quant.c
correct an old bug link
[imager.git] / quant.c
diff --git a/quant.c b/quant.c
index 13096ccc841397298c5e3d816d26298b2291c256..419b81b76b52388f9e2b9f66cf13ab521393b2fb 100644 (file)
--- a/quant.c
+++ b/quant.c
@@ -3,10 +3,15 @@
    8-bit (or bigger indexed) png files at some point
 */
 #include "imager.h"
+#include "imageri.h"
 
+static void makemap_webmap(i_quantize *);
 static void makemap_addi(i_quantize *, i_img **imgs, int count);
 static void makemap_mediancut(i_quantize *, i_img **imgs, int count);
 static void makemap_mono(i_quantize *);
+static void makemap_gray(i_quantize *, int step);
+
+static int makemap_palette(i_quantize *, i_img **imgs, int count);
 
 static
 void
@@ -57,15 +62,7 @@ i_quant_makemap(i_quantize *quant, i_img **imgs, int count) {
     /* use user's specified map */
     break;
   case mc_web_map:
-    {
-      int r, g, b;
-      int i = 0;
-      for (r = 0; r < 256; r+=0x33)
-       for (g = 0; g < 256; g+=0x33)
-         for (b = 0; b < 256; b += 0x33)
-           setcol(quant->mc_colors+i++, r, g, b, 255);
-      quant->mc_count = i;
-    }
+    makemap_webmap(quant);
     break;
 
   case mc_median_cut:
@@ -76,6 +73,18 @@ i_quant_makemap(i_quantize *quant, i_img **imgs, int count) {
     makemap_mono(quant);
     break;
 
+  case mc_gray:
+    makemap_gray(quant, 1);
+    break;
+
+  case mc_gray4:
+    makemap_gray(quant, 85);
+    break;
+
+  case mc_gray16:
+    makemap_gray(quant, 17);
+    break;
+
   case mc_addi:
   default:
     makemap_addi(quant, imgs, count);
@@ -84,7 +93,7 @@ i_quant_makemap(i_quantize *quant, i_img **imgs, int count) {
 }
 
 static void translate_closest(i_quantize *, i_img *, i_palidx *);
-static void translate_errdiff(i_quantize *, i_img *, i_palidx *);
+static int translate_errdiff(i_quantize *, i_img *, i_palidx *);
 static void translate_addi(i_quantize *, i_img *, i_palidx *);
 
 /*
@@ -109,7 +118,7 @@ This function will fail if the supplied palette contains no colors.
 i_palidx *
 i_quant_translate(i_quantize *quant, i_img *img) {
   i_palidx *result;
-  int bytes;
+  size_t bytes;
 
   mm_log((1, "quant_translate(quant %p, img %p)\n", quant, img));
 
@@ -134,7 +143,10 @@ i_quant_translate(i_quantize *quant, i_img *img) {
     break;
     
   case pt_errdiff:
-    translate_errdiff(quant, img, result);
+    if (!translate_errdiff(quant, img, result)) {
+      myfree(result);
+      return NULL;
+    }
     break;
     
   case pt_perturb:
@@ -222,9 +234,12 @@ eucl_d_ch(cvec* cv,i_sample_t *chans) {
     + PWR2(cv->b - chans[2]);
 }
 
-static
-int
-ceucl_d(i_color *c1, i_color *c2) { return PWR2(c1->channel[0]-c2->channel[0])+PWR2(c1->channel[1]-c2->channel[1])+PWR2(c1->channel[2]-c2->channel[2]); }
+static int
+ceucl_d(i_color *c1, i_color *c2) {
+return PWR2(c1->channel[0]-c2->channel[0])
+  +PWR2(c1->channel[1]-c2->channel[1])
+  +PWR2(c1->channel[2]-c2->channel[2]);
+}
 
 static const int
 gray_samples[] = { 0, 0, 0 };
@@ -284,17 +299,21 @@ for each side of the cube, but this will require even more memory.
 static void
 makemap_addi(i_quantize *quant, i_img **imgs, int count) {
   cvec *clr;
-  int cnum, i, x, y, bst_idx=0, ld, cd, iter, currhb, img_num;
+  int cnum, i, bst_idx=0, ld, cd, iter, currhb, img_num;
+  i_img_dim x, y;
   i_sample_t *val;
   float dlt, accerr;
   hashbox *hb;
   i_mempool mp;
-  int maxwidth = 0;
+  i_img_dim maxwidth = 0;
   i_sample_t *line;
   const int *sample_indices;
 
   mm_log((1, "makemap_addi(quant %p { mc_count=%d, mc_colors=%p }, imgs %p, count %d)\n", 
           quant, quant->mc_count, quant->mc_colors, imgs, count));
+
+  if (makemap_palette(quant, imgs, count))
+    return;
          
   i_mempool_init(&mp);
 
@@ -442,11 +461,13 @@ makemap_addi(i_quantize *quant, i_img **imgs, int count) {
 #endif
 
   i_mempool_destroy(&mp);
+
+  mm_log((1, "makemap_addi() - %d colors\n", quant->mc_count));
 }
 
 typedef struct {
   i_sample_t rgb[3];
-  int count;
+  i_img_dim count;
 } quant_color_entry;
 
 #define MEDIAN_CUT_COLORS 32768
@@ -467,7 +488,7 @@ typedef struct {
   i_sample_t max[3]; /* maximum for each channel */
   i_sample_t width[3]; /* width for each channel */
   int start, size;   /* beginning and size of the partition */
-  int pixels; /* number of pixels represented by this partition */
+  i_img_dim pixels; /* number of pixels represented by this partition */
 } medcut_partition;
 
 /*
@@ -533,11 +554,11 @@ static void
 makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
   quant_color_entry *colors;
   i_mempool mp;
-  int imgn, x, y, i, ch;
-  int max_width;
+  int imgn, i, ch;
+  i_img_dim x, y, max_width;
   i_color *line;
   int color_count;
-  int total_pixels;
+  i_img_dim total_pixels;
   medcut_partition *parts;
   int part_num;
   int in, out;
@@ -545,7 +566,11 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
      this isn't terribly efficient, but it should work */
   int chan_count; 
 
-  /*printf("images %d  pal size %d\n", count, quant->mc_size);*/
+  mm_log((1, "makemap_mediancut(quant %p { mc_count=%d, mc_colors=%p }, imgs %p, count %d)\n", 
+          quant, quant->mc_count, quant->mc_colors, imgs, count));
+
+  if (makemap_palette(quant, imgs, count))
+    return;
 
   i_mempool_init(&mp);
 
@@ -608,6 +633,7 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
       for (ch = 0; ch < 3; ++ch) {
         quant->mc_colors[i].channel[ch] = colors[i].rgb[ch];
       }
+      quant->mc_colors[i].rgba.a = 255;
     }
     quant->mc_count = out;
   }
@@ -625,8 +651,8 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
       int max_index = 0, max_ch = 0; /* index/channel with biggest spread */
       int max_size;
       medcut_partition *workpart;
-      int cum_total;
-      int half;
+      i_img_dim cum_total;
+      i_img_dim half;
       
       /* find the partition with the most biggest span with more than 
          one color */
@@ -680,7 +706,7 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
     /* fill in the color table - since we could still have partitions
        that have more than one color, we need to average the colors */
     for (part_num = 0; part_num < color_count; ++part_num) {
-      long sums[3];
+      double sums[3];
       medcut_partition *workpart;
       
       workpart = parts+part_num;
@@ -689,17 +715,20 @@ makemap_mediancut(i_quantize *quant, i_img **imgs, int count) {
       
       for (i = workpart->start; i < workpart->start + workpart->size; ++i) {
         for (ch = 0; ch < 3; ++ch) {
-          sums[ch] += colors[i].rgb[ch] * colors[i].count;
+          sums[ch] += (int)(colors[i].rgb[ch]) * colors[i].count;
         }
       }
       for (ch = 0; ch < 3; ++ch) {
         quant->mc_colors[part_num].channel[ch] = sums[ch] / workpart->pixels;
       }
+      quant->mc_colors[part_num].rgba.a = 255;
     }
     quant->mc_count = color_count;
   }
   /*printf("out %d colors\n", quant->mc_count);*/
   i_mempool_destroy(&mp);
+
+  mm_log((1, "makemap_mediancut() - %d colors\n", quant->mc_count));
 }
 
 static void
@@ -715,6 +744,125 @@ makemap_mono(i_quantize *quant) {
   quant->mc_count = 2;
 }
 
+static void
+makemap_gray(i_quantize *quant, int step) {
+  int gray = 0;
+  int i = 0;
+
+  while (gray < 256) {
+    setcol(quant->mc_colors+i, gray, gray, gray, 255);
+    ++i;
+    gray += step;
+  }
+  quant->mc_count = i;
+}
+
+static void
+makemap_webmap(i_quantize *quant) {
+  int r, g, b;
+
+  int i = 0;
+  for (r = 0; r < 256; r+=0x33)
+    for (g = 0; g < 256; g+=0x33)
+      for (b = 0; b < 256; b += 0x33)
+       setcol(quant->mc_colors+i++, r, g, b, 255);
+  quant->mc_count = i;
+}
+
+static int 
+in_palette(i_color *c, i_quantize *quant, int size) {
+  int i;
+
+  for (i = 0; i < size; ++i) {
+    if (c->channel[0] == quant->mc_colors[i].channel[0]
+        && c->channel[1] == quant->mc_colors[i].channel[1]
+        && c->channel[2] == quant->mc_colors[i].channel[2]) {
+      return i;
+    }
+  }
+
+  return -1;
+}
+
+/*
+=item makemap_palette(quant, imgs, count)
+
+Tests if all the given images are paletted and have a common palette,
+if they do it builds that palette.
+
+A possible improvement might be to eliminate unused colors in the
+images palettes.
+
+=cut
+*/
+
+static int
+makemap_palette(i_quantize *quant, i_img **imgs, int count) {
+  int size = quant->mc_count;
+  int i;
+  int imgn;
+  char used[256];
+  int col_count;
+
+  mm_log((1, "makemap_palette(quant %p { mc_count=%d, mc_colors=%p }, imgs %p, count %d)\n", 
+          quant, quant->mc_count, quant->mc_colors, imgs, count));
+  /* we try to build a common palette here, if we can manage that, then
+     that's the palette we use */
+  for (imgn = 0; imgn < count; ++imgn) {
+    int eliminate_unused;
+    if (imgs[imgn]->type != i_palette_type) {
+      mm_log((1, "makemap_palette() -> 0 (non-palette image)\n"));
+      return 0;
+    }
+
+    if (!i_tags_get_int(&imgs[imgn]->tags, "gif_eliminate_unused", 0, 
+                        &eliminate_unused)) {
+      eliminate_unused = 1;
+    }
+
+    if (eliminate_unused) {
+      i_palidx *line = mymalloc(sizeof(i_palidx) * imgs[imgn]->xsize);
+      i_img_dim x, y;
+      memset(used, 0, sizeof(used));
+
+      for (y = 0; y < imgs[imgn]->ysize; ++y) {
+        i_gpal(imgs[imgn], 0, imgs[imgn]->xsize, y, line);
+        for (x = 0; x < imgs[imgn]->xsize; ++x)
+          used[line[x]] = 1;
+      }
+
+      myfree(line);
+    }
+    else {
+      /* assume all are in use */
+      memset(used, 1, sizeof(used));
+    }
+
+    col_count = i_colorcount(imgs[imgn]);
+    for (i = 0; i < col_count; ++i) {
+      i_color c;
+      
+      i_getcolors(imgs[imgn], i, &c, 1);
+      if (used[i]) {
+        if (in_palette(&c, quant, size) < 0) {
+          if (size < quant->mc_size) {
+            quant->mc_colors[size++] = c;
+          }
+          else {
+           mm_log((1, "makemap_palette() -> 0 (too many colors)\n"));
+            return 0;
+          }
+        }
+      }
+    }
+  }
+
+  mm_log((1, "makemap_palette() -> 1 (%d total colors)\n", size));
+  quant->mc_count = size;
+
+  return 1;
+}
+
 #define pboxjump 32
 
 /* Define one of the following 4 symbols to choose a colour search method
@@ -754,6 +902,23 @@ makemap_mono(i_quantize *quant) {
 /*#define IM_CFRAND2DIST*/
 #endif
 
+/* return true if the color map contains only grays */
+static int
+is_gray_map(const i_quantize *quant) {
+  int i;
+
+  for (i = 0; i < quant->mc_count; ++i) {
+    if (quant->mc_colors[i].rgb.r != quant->mc_colors[i].rgb.g
+       || quant->mc_colors[i].rgb.r != quant->mc_colors[i].rgb.b) {
+      mm_log((1, "  not a gray map\n"));
+      return 0;
+    }
+  }
+
+  mm_log((1, "  is a gray map\n"));
+  return 1;
+}
+
 #ifdef IM_CFHASHBOX
 
 /* The original version I wrote for this used the sort.
@@ -1106,7 +1271,8 @@ static int rand2dist_find(i_color val, i_quantize *quant, i_dists *dists, int in
 #endif
 
 static void translate_addi(i_quantize *quant, i_img *img, i_palidx *out) {
-  int x, y, i, k, bst_idx = 0;
+  i_img_dim x, y, k;
+  int i, bst_idx = 0;
   i_color val;
   int pixdev = quant->perturb;
   CF_VARS;
@@ -1193,17 +1359,17 @@ typedef struct errdiff_tag {
 } errdiff_t;
 
 /* perform an error diffusion dither */
-static
-void
+static int
 translate_errdiff(i_quantize *quant, i_img *img, i_palidx *out) {
   int *map;
   int mapw, maph, mapo;
   int i;
   errdiff_t *err;
-  int errw;
+  i_img_dim errw;
   int difftotal;
-  int x, y, dx, dy;
+  i_img_dim x, y, dx, dy;
   int bst_idx = 0;
+  int is_gray = is_gray_map(quant);
   CF_VARS;
 
   if ((quant->errdiff & ed_mask) == ed_custom) {
@@ -1221,14 +1387,25 @@ translate_errdiff(i_quantize *quant, i_img *img, i_palidx *out) {
     mapo = maps[index].orig;
   }
   
+  difftotal = 0;
+  for (i = 0; i < maph * mapw; ++i) {
+    if (map[i] < 0) {
+      i_push_errorf(0, "errdiff_map values must be non-negative, errdiff[%d] is negative", i);
+      goto fail;
+    }
+    difftotal += map[i];
+  }
+
+  if (!difftotal) {
+    i_push_error(0, "error diffusion map must contain some non-zero values");
+    goto fail;
+  }
+
   errw = img->xsize+mapw;
   err = mymalloc(sizeof(*err) * maph * errw);
   /*errp = err+mapo;*/
   memset(err, 0, sizeof(*err) * maph * errw);
   
-  difftotal = 0;
-  for (i = 0; i < maph * mapw; ++i)
-    difftotal += map[i];
   /*printf("map:\n");
  for (dy = 0; dy < maph; ++dy) {
    for (dx = 0; dx < mapw; ++dx) {
@@ -1242,12 +1419,15 @@ translate_errdiff(i_quantize *quant, i_img *img, i_palidx *out) {
   for (y = 0; y < img->ysize; ++y) {
     for (x = 0; x < img->xsize; ++x) {
       i_color val;
-      long ld, cd;
       errdiff_t perr;
       i_gpix(img, x, y, &val);
       if (img->channels < 3) {
         val.channel[1] = val.channel[2] = val.channel[0];
       }
+      else if (is_gray) {
+       int gray = 0.5 + color_to_grey(&val);
+       val.channel[0] = val.channel[1] = val.channel[2] = gray;
+      }
       perr = err[x+mapo];
       perr.r = perr.r < 0 ? -((-perr.r)/difftotal) : perr.r/difftotal;
       perr.g = perr.g < 0 ? -((-perr.g)/difftotal) : perr.g/difftotal;
@@ -1279,13 +1459,21 @@ translate_errdiff(i_quantize *quant, i_img *img, i_palidx *out) {
   }
   CF_CLEANUP;
   myfree(err);
+
+  return 1;
+
+ fail:
+  CF_CLEANUP;
+
+  return 0;
 }
 /* Prescan finds the boxes in the image that have the highest number of colors 
    and that result is used as the initial value for the vectores */
 
 
 static void prescan(i_img **imgs,int count, int cnum, cvec *clr, i_sample_t *line) {
-  int i,k,j,x,y;
+  int i,k,j;
+  i_img_dim x,y;
   i_sample_t *val;
   const int *chans;
 
@@ -1348,7 +1536,7 @@ static void reorder(pbox prescan[512]) {
   c.cand++;
   c.pdc=c.pixcnt/(c.cand*c.cand); 
   /*  c.pdc=c.pixcnt/c.cand; */
-  while(c.pdc < prescan[nidx+1].pdc && nidx < 511) {
+  while(nidx < 511 && c.pdc < prescan[nidx+1].pdc) {
     prescan[nidx]=prescan[nidx+1];
     nidx++;
   }
@@ -1408,7 +1596,7 @@ static
 void
 cr_hashindex(cvec clr[256],int cnum,hashbox hb[512]) {
   
-  int bx,mind,cd,cumcnt,bst_idx,i;
+  int bx,mind,cd,cumcnt,i;
 /*  printf("indexing... \n");*/
   
   cumcnt=0;
@@ -1416,7 +1604,7 @@ cr_hashindex(cvec clr[256],int cnum,hashbox hb[512]) {
     mind=196608;
     for(i=0; i<cnum; i++) { 
       cd = maxdist(bx,&clr[i]);
-      if (cd < mind) { mind=cd; bst_idx=i; 
+      if (cd < mind) { mind=cd; } 
     }
     
     hb[bx].cnt=0;
@@ -1527,7 +1715,7 @@ static void
 transparent_threshold(i_quantize *quant, i_palidx *data, i_img *img,
                      i_palidx trans_index)
 {
-  int x, y;
+  i_img_dim x, y;
   i_sample_t *line = mymalloc(img->xsize * sizeof(i_sample_t));
   int trans_chan = img->channels > 2 ? 3 : 1;
   
@@ -1550,7 +1738,8 @@ transparent_errdiff(i_quantize *quant, i_palidx *data, i_img *img,
   int mapw, maph, mapo;
   int errw, *err, *errp;
   int difftotal, out, error;
-  int x, y, dx, dy, i;
+  i_img_dim x, y, dx, dy;
+  int i;
   i_sample_t *line;
   int trans_chan = img->channels > 2 ? 3 : 1;
 
@@ -1709,7 +1898,7 @@ transparent_ordered(i_quantize *quant, i_palidx *data, i_img *img,
                    i_palidx trans_index)
 {
   unsigned char *spot;
-  int x, y;
+  i_img_dim x, y;
   i_sample_t *line;
   int trans_chan = img->channels > 2 ? 3 : 1;
   if (quant->tr_orddith == od_custom)