rt #94413: autolevels no longer color-skews
authorTony Cook <tony@develop-help.com>
Sat, 12 Apr 2014 03:11:27 +0000 (13:11 +1000)
committerTony Cook <tony@develop-help.com>
Sat, 12 Apr 2014 03:11:27 +0000 (13:11 +1000)
The old autolevels filter has been renamed to "autolevels_skew", and
autolevels is now a filter that does histogram equalization over the
luminosity of the image, instead of per channel

Imager.pm
Imager.xs
filters.im
imager.h
lib/Imager/Filters.pod
t/400-filter/010-filters.t

index 1008f60..9671b04 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -204,12 +204,18 @@ BEGIN {
      callsub => sub { my %hsh=@_; i_hardinvertall($hsh{image}); }
     };
 
-  $filters{autolevels} ={
+  $filters{autolevels_skew} ={
                         callseq => ['image','lsat','usat','skew'],
                         defaults => { lsat=>0.1,usat=>0.1,skew=>0.0 },
                         callsub => sub { my %hsh=@_; i_autolevels($hsh{image},$hsh{lsat},$hsh{usat},$hsh{skew}); }
                        };
 
+  $filters{autolevels} ={
+                        callseq => ['image','lsat','usat'],
+                        defaults => { lsat=>0.1,usat=>0.1 },
+                        callsub => sub { my %hsh=@_; i_autolevels_mono($hsh{image},$hsh{lsat},$hsh{usat}); }
+                       };
+
   $filters{turbnoise} ={
                        callseq => ['image'],
                        defaults => { xo=>0.0,yo=>0.0,scale=>10.0 },
index 2b757fe..66d3c44 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2703,6 +2703,12 @@ i_autolevels(im,lsat,usat,skew)
              float     usat
              float     skew
 
+void
+i_autolevels_mono(im,lsat,usat)
+    Imager::ImgRaw     im
+             float     lsat
+             float     usat
+
 void
 i_radnoise(im,xo,yo,rscale,ascale)
     Imager::ImgRaw     im
index dda6fd9..d980fcb 100644 (file)
@@ -648,6 +648,89 @@ i_watermark(i_img *im, i_img *wmark, i_img_dim tx, i_img_dim ty, int pixdiff) {
   }
 }
 
+/*
+=item i_autolevels_mono(im, lsat, usat)
+
+Do autolevels, but monochromatically.
+
+=cut
+*/
+
+void
+i_autolevels_mono(i_img *im, float lsat, float usat) {
+  i_color val;
+  i_img_dim i, x, y, hist[256];
+  i_img_dim sum_lum, min_lum, max_lum;
+  i_img_dim upper_accum, lower_accum;
+  i_color *row;
+  dIMCTXim(im);
+  int adapt_channels = im->channels == 4 ? 2 : 1;
+  int color_channels = i_img_color_channels(im);
+  i_img_dim color_samples = im->xsize * color_channels;
+  
+
+  im_log((aIMCTX, 1,"i_autolevels_mono(im %p, lsat %f,usat %f)\n", im, lsat,usat));
+
+  /* build the histogram in 8-bits, unless the image has a very small
+     range it should make little difference to the result */
+  sum_lum = 0;
+  for (i = 0; i < 256; i++)
+    hist[i] = 0;
+
+  row = mymalloc(im->xsize * sizeof(i_color));
+  /* create histogram for each channel */
+  for (y = 0; y < im->ysize; y++) {
+    i_color *p = row;
+    i_glin(im, 0, im->xsize, y, row);
+    if (im->channels > 2)
+      i_adapt_colors(adapt_channels, im->channels, row, im->xsize);
+    for (x = 0; x < im->xsize; x++) {
+      hist[p->channel[0]]++;
+      ++p;
+    }
+  }
+  myfree(row);
+
+  for(i = 0; i < 256; i++) {
+    sum_lum += hist[i];
+  }
+  
+  min_lum = 0;
+  max_lum = 255;
+
+  lower_accum = upper_accum = 0;
+  
+  for(i=0; i<256; i++) { 
+    lower_accum += hist[i];
+    if (lower_accum < sum_lum * lsat)
+      min_lum = i;
+
+    upper_accum  += hist[255-i];
+    if (upper_accum < sum_lum * usat)
+      max_lum = 255-i;
+  }
+
+#code im->bits <= 8
+  IM_SAMPLE_T *srow = mymalloc(color_samples * sizeof(IM_SAMPLE_T));
+#ifdef IM_EIGHT_BIT
+  IM_WORK_T low = min_lum;
+#else
+  IM_WORK_T low = min_lum / 255.0 * IM_SAMPLE_MAX;
+#endif
+  IM_WORK_T scale = 255.0 / (max_lum - min_lum);
+
+  for(y = 0; y < im->ysize; y++) {
+    IM_GSAMP(im, 0, im->xsize, y, srow, NULL, color_channels);
+    for(i = 0; i < color_samples; ++i) {
+      IM_WORK_T tmp = (srow[i] - low) * scale;
+      srow[i] = IM_LIMIT(tmp);
+    }
+    IM_PSAMP(im, 0, im->xsize, y, srow, NULL, color_channels);
+  }
+  myfree(srow);
+#/code
+}
+
 
 /*
 =item i_autolevels(im, lsat, usat, skew)
@@ -661,6 +744,9 @@ occur when changing the contrast.
   usat - fraction of pixels that will be truncated at the higher end of the spectrum
   skew - not used yet
 
+Note: this code calculates levels and adjusts each channel separately,
+which will typically cause a color shift.
+
 =cut
 */
 
index 087a0e9..e114bca 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -271,6 +271,7 @@ void i_postlevels(i_img *im,int levels);
 void i_mosaic(i_img *im,i_img_dim size);
 void i_watermark(i_img *im,i_img *wmark,i_img_dim tx,i_img_dim ty,int pixdiff);
 void i_autolevels(i_img *im,float lsat,float usat,float skew);
+void i_autolevels_mono(i_img *im,float lsat,float usat);
 void i_radnoise(i_img *im,i_img_dim xo,i_img_dim yo,double rscale,double ascale);
 void i_turbnoise(i_img *im,double xo,double yo,double scale);
 void i_gradgen(i_img *im, int num, i_img_dim *xo, i_img_dim *yo, i_color *ival, int dmeasure);
index 6e14c8a..f3d9e5e 100644 (file)
@@ -64,6 +64,9 @@ that comes with the module source.
   Filter          Arguments   Default value
   autolevels      lsat        0.1
                   usat        0.1
+
+  autolevels_skew lsat        0.1
+                  usat        0.1
                   skew        0
 
   bumpmap         bump lightx lighty
@@ -148,19 +151,36 @@ A reference of the filters follows:
 
 =item autolevels
 
-scales the value of each channel so that the values in the image will
+Scales the luminosity of the image so that the luminosity will cover
+the possible range for the image.  C<lsat> and C<usat> truncate the
+range by the specified fraction at the top and bottom of the range
+respectively.
+
+  # increase contrast, losing little detail
+  $img->filter(type=>"autolevels")
+    or die $img->errstr;
+
+The method used here is typically called L<Histogram
+Equalization|http://en.wikipedia.org/wiki/Histogram_equalization>.
+
+=item autolevels_skew
+
+Scales the value of each channel so that the values in the image will
 cover the whole possible range for the channel.  C<lsat> and C<usat>
 truncate the range by the specified fraction at the top and bottom of
 the range respectively.
 
   # increase contrast per channel, losing little detail
-  $img->filter(type=>"autolevels")
+  $img->filter(type=>"autolevels_skew")
     or die $img->errstr;
 
   # increase contrast, losing 20% of highlight at top and bottom range
   $img->filter(type=>"autolevels", lsat=>0.2, usat=>0.2)
     or die $img->errstr;
 
+This filter was the original C<autolevels> filter, but it's typically
+useless due to the significant color skew it can produce.
+
 =item bumpmap
 
 uses the channel C<elevation> image C<bump> as a bump map on your
index 0fe334e..1be79b1 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use Imager qw(:handy);
-use Test::More tests => 122;
+use Test::More tests => 124;
 
 -d "testout" or mkdir "testout";
 
@@ -16,6 +16,8 @@ $im_other->box(xmin=>30, ymin=>60, xmax=>120, ymax=>90, filled=>1);
 
 test($imbase, {type=>'autolevels'}, 'testout/t61_autolev.ppm');
 
+test($imbase, {type=>'autolevels_skew'}, 'testout/t61_autoskew.ppm');
+
 test($imbase, {type=>'contrast', intensity=>0.5}, 
      'testout/t61_contrast.ppm');