Commits

pornel  committed 1d8f87f

Configurable speed/quality for LIQ and NeuQuant

  • Participants
  • Parent commits 115f6aa
  • Branches liq

Comments (0)

Files changed (2)

 	GD_CROP_SIDES
 };
 
+enum gdPaletteQuantizationMethod {
+  GD_QUANT_DEFAULT = 0,
+  GD_QUANT_JQUANT = 1,  /* libjpeg's old median cut. Fast, but only uses 16-bit color. */
+  GD_QUANT_NEUQUANT = 2, /* neuquant - approximation using kohonen neural network. */
+  GD_QUANT_LIQ = 3 /* combination of algorithms used in libimagequant/pngquant2 aiming for highest quality at cost of speed */
+};
 
 /* This function accepts truecolor pixel values only. The
 	source color is composited with the destination color
     /* 2.1.0: allows to specify resolution in dpi */
     unsigned int res_x;
     unsigned int res_y;
+
+    /* Selects quantization method, see gdImageTrueColorToPaletteSetMethod() and gdPaletteQuantizationMethod enum. */
+    int paletteQuantizationMethod;
+    /* speed/quality trade-off. 1 = best quality, 10 = best speed. 0 = method-specific default.
+       Applicable to GD_QUANT_LIQ and GD_QUANT_NEUQUANT. */
+    int paletteQuantizationSpeed;
+    /* Image will remain true-color if conversion to palette cannot achieve given quality.
+       Value from 1 to 100, 1 = ugly, 100 = perfect. Applicable to GD_QUANT_LIQ.*/
+    int paletteQuantizationMinQuality;
+    /* Image will use minimum number of palette colors needed to achieve given quality. Must be higher than paletteQuantizationMinQuality
+       Value from 1 to 100, 1 = ugly, 100 = perfect. Applicable to GD_QUANT_LIQ.*/
+    int paletteQuantizationMaxQuality;
   }
   gdImage;
 
 BGD_DECLARE(void) gdImageTrueColorToPalette (gdImagePtr im, int ditherFlag,
 				  int colorsWanted);
 
+BGD_DECLARE(void) gdImageTrueColorToPalette (gdImagePtr im, int ditherFlag,
+          int colorsWanted);
+
+/*
+  Selects quantization method used for subsequent gdImageTrueColorToPalette calls.
+  See gdPaletteQuantizationMethod enum (e.g. GD_QUANT_NEUQUANT, GD_QUANT_LIQ).
+  Speed is from 1 (highest quality) to 10 (fastest).
+  Speed 0 selects method-specific default (recommended).
+*/
+BGD_DECLARE(void) gdImageTrueColorToPaletteSetMethod (gdImagePtr im, int method, int speed);
+
+/*
+  Chooses quality range that subsequent call to gdImageTrueColorToPalette will aim for.
+  Min and max quality is in range 1-100 (1 = ugly, 100 = perfect). Max must be higher than min.
+  If palette cannot represent image with at least min_quality, then image will remain true-color.
+  If palette can represent image with quality better than max_quality, then lower number of colors will be used.
+  This function has effect only when GD_QUANT_LIQ method has been selected and the source image is true-color.
+*/
+BGD_DECLARE(void) gdImageTrueColorToPaletteSetQuality (gdImagePtr im, int min_quality, int max_quality);
+
 /* Specifies a color index (if a palette image) or an
 	RGB color (if a truecolor image) which should be
 	considered 100% transparent. FOR TRUECOLOR IMAGES,

File src/gd_topal.c

     }
 }
 
+
+/*
+  Selects quantization method used for subsequent gdImageTrueColorToPalette calls.
+  See gdPaletteQuantizationMethod enum (e.g. GD_QUANT_NEUQUANT, GD_QUANT_LIQ).
+  Speed is from 1 (highest quality) to 10 (fastest).
+  Speed 0 selects method-specific default (recommended).
+*/
+BGD_DECLARE(void) gdImageTrueColorToPaletteSetMethod (gdImagePtr im, int method, int speed)
+{
+    if (method >= GD_QUANT_DEFAULT && method <= GD_QUANT_LIQ)
+      {
+        im->paletteQuantizationMethod = method;
+
+        if (speed < 0 || speed > 10)
+          {
+            speed = 0;
+          }
+        im->paletteQuantizationSpeed = speed;
+      }
+}
+
+/*
+  Chooses quality range that subsequent call to gdImageTrueColorToPalette will aim for.
+  Min and max quality is in range 1-100 (1 = ugly, 100 = perfect). Max must be higher than min.
+  If palette cannot represent image with at least min_quality, then image will remain true-color.
+  If palette can represent image with quality better than max_quality, then lower number of colors will be used.
+  This function has effect only when GD_QUANT_LIQ method has been selected.
+*/
+BGD_DECLARE(void) gdImageTrueColorToPaletteSetQuality (gdImagePtr im, int min_quality, int max_quality)
+{
+    if (min_quality >= 0 && min_quality <= 100 &&
+        max_quality >= 0 && max_quality <= 100 && min_quality <= max_quality)
+      {
+        im->paletteQuantizationMinQuality = min_quality;
+        im->paletteQuantizationMaxQuality = max_quality;
+      }
+}
+
 static void gdImageTrueColorToPaletteBody (gdImagePtr oim, int dither, int colorsWanted, gdImagePtr *cimP);
 
 BGD_DECLARE(gdImagePtr) gdImageCreatePaletteFromTrueColor (gdImagePtr im, int dither, int colorsWanted)
   size_t arraysize;
   int maxColors = gdMaxColors;
   gdImagePtr nim;
+
   if (cimP) {
     nim = gdImageCreate(oim->sx, oim->sy);
     *cimP = nim;
       }
   }
 
+
+  if (oim->paletteQuantizationMethod == GD_QUANT_NEUQUANT)
+    {
+      if (cimP) /* NeuQuant alwasy creates a copy, so the new blank image can't be used */
+        {
+          gdImageDestroy(nim);
+        }
+      nim = gdImageNeuQuant(oim, colorsWanted, oim->paletteQuantizationSpeed ? oim->paletteQuantizationSpeed : 2);
+      if (cimP)
+        {
+          *cimP = nim;
+        }
+      else
+        {
+          gdImageCopy(oim, nim, 0, 0, 0, 0, oim->sx, oim->sy);
+          gdImageDestroy(nim);
+        }
+      return;
+    }
+
+
 #ifdef HAVE_LIBIMAGEQUANT_H
+  if (oim->paletteQuantizationMethod == GD_QUANT_DEFAULT ||
+      oim->paletteQuantizationMethod == GD_QUANT_LIQ)
   {
-    liq_attr *attr = liq_attr_create();
+    liq_attr *attr = liq_attr_create_with_allocator(gdMalloc, gdFree);
     liq_image *image;
     liq_result *remap;
     int remapped_ok = 0;
 
-    liq_set_speed(attr, 9); /* make it fast to match speed of previous implementation */
+    liq_set_max_colors(attr, colorsWanted);
+
+    /* by default make it fast to match speed of previous implementation */
+    liq_set_speed(attr, oim->paletteQuantizationSpeed ? oim->paletteQuantizationSpeed : 9);
+    if (oim->paletteQuantizationMaxQuality)
+      {
+        liq_set_quality(attr, oim->paletteQuantizationMinQuality, oim->paletteQuantizationMaxQuality);
+      }
     image = liq_image_create_custom(attr, convert_gdpixel_to_rgba, oim, oim->sx, oim->sy, 0);
     remap = liq_quantize_image(attr, image);
+    if (!remap)  /* minimum quality not met, leave image unmodified */
+      {
+        liq_image_destroy(image);
+        liq_attr_destroy(attr);
+        goto outOfMemory;
+      }
 
     liq_set_dithering_level(remap, dither ? 1 : 0);
     if (LIQ_OK == liq_write_remapped_image_rows(remap, image, output_buf))