Leolo's guassian2 patch
authorTony Cook <tony@develop-help.com>
Sat, 21 Mar 2020 00:47:31 +0000 (11:47 +1100)
committerTony Cook <tony@develop-help.com>
Sat, 21 Mar 2020 00:47:31 +0000 (11:47 +1100)
https://rt.cpan.org/Ticket/Display.html?id=129769

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

index 343cc5e..e5edb91 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -279,6 +279,11 @@ BEGIN {
                         defaults => { },
                         callsub => sub { my %hsh = @_; i_gaussian($hsh{image}, $hsh{stddev}); },
                        };
+  $filters{gaussian2} = {
+                        callseq => [ 'image', 'stddevX', 'stddevY' ],
+                        defaults => { },
+                        callsub => sub { my %hsh = @_; i_gaussian2($hsh{image}, $hsh{stddevX}, $hsh{stddevY}); },
+                       };
   $filters{mosaic} =
     {
      callseq => [ qw(image size) ],
index 7b6433d..4bd58b6 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -2251,9 +2251,15 @@ i_matrix_transform(im, xsize, ysize, matrix_av, ...)
     RETVAL
 
 undef_int
-i_gaussian(im,stdev)
+i_gaussian(im,stddev)
     Imager::ImgRaw     im
-           im_double     stdev
+           im_double     stddev
+
+undef_int
+i_gaussian2(im,stddevX,stddevY)
+    Imager::ImgRaw     im
+           im_double     stddevX
+           im_double     stddevY
 
 void
 i_unsharp_mask(im,stdev,scale)
index 7e93769..6ab7eca 100644 (file)
@@ -19,28 +19,22 @@ gauss(int x, double std) {
 
 int
 i_gaussian(i_img *im, double stddev) {
-  int i, c, ch;
-  i_img_dim x, y;
-  double pc;
-  double *coeff;
-  double res[MAXCHANNELS];
-  i_img *timg;
-  int radius, diameter;
-  dIMCTXim(im);
+  return i_gaussian2( im, stddev, stddev );
+}
 
-  im_log((aIMCTX, 1,"i_gaussian(im %p, stdev %.2f)\n",im,stddev));
-  i_clear_error();
+typedef struct s_gauss_coeff {
+    int diameter;
+    int radius;
+    double *coeff;
+} t_gauss_coeff;
 
-  if (stddev <= 0) {
-    i_push_error(0, "stddev must be positive");
-    return 0;
-  }
-  /* totally silly cutoff */
-  if (stddev > 1000) {
-    stddev = 1000;
-  }
  
-  timg = i_sametype(im, im->xsize, im->ysize);
+static t_gauss_coeff *build_coeff( i_img *im, double stddev ) {
+  double *coeff = NULL;
+  double pc;
+  int radius, diameter, i;
+  t_gauss_coeff *ret = mymalloc(sizeof(struct s_gauss_coeff));
+  ret->coeff = NULL;
 
   if (im->bits <= 8)
     radius = ceil(2 * stddev);
@@ -53,25 +47,98 @@ i_gaussian(i_img *im, double stddev) {
 
   for(i=0;i <= radius;i++) 
     coeff[radius + i]=coeff[radius - i]=gauss(i, stddev);
-  pc=0;
+  pc=0.0;
   for(i=0; i < diameter; i++)
     pc+=coeff[i];
-  for(i=0;i < diameter;i++)
+  for(i=0;i < diameter;i++) {
     coeff[i] /= pc;
+    // im_log((aIMCTX, 1, "i_gaussian2 Y i=%i coeff=%.2f\n", i, coeff[i] ));
+  }
+
+  ret->diameter = diameter;
+  ret->radius = radius;
+  ret->coeff = coeff;
+  return ret;
+}
+
+static void free_coeff(t_gauss_coeff *co ) {
+
+   if( co->coeff != NULL )
+     myfree( co->coeff );
+   myfree( co );
+}
+
+#define img_copy(dest, src) i_copyto( (dest), (src), 0,0, (src)->xsize,(src)->ysize, 0,0);
+
+
+
+int
+i_gaussian2(i_img *im, double stddevX, double stddevY) {
+  int c, ch;
+  i_img_dim x, y;
+  double pc;
+  t_gauss_coeff *co = NULL;
+  double res[MAXCHANNELS];
+  i_img *timg;
+  dIMCTXim(im);
+
+  im_log((aIMCTX, 1,"i_gaussian2(im %p, stddev %.2f,%.2f)\n",im,stddevX,stddevY));
+  i_clear_error();
+
+  if (stddevX < 0) {
+    i_push_error(0, "stddevX must be positive");
+    return 0;
+  }
+  if (stddevY < 0) {
+    i_push_error(0, "stddevY must be positive");
+    return 0;
+  }
 
+  if( stddevX == stddevY && stddevY == 0 ) {
+    i_push_error(0, "stddevX or stddevY must be positive");
+    return 0;
+  }
+
+
+  /* totally silly cutoff */
+  if (stddevX > 1000) {
+    stddevX = 1000;
+  }
+  if (stddevY > 1000) {
+    stddevY = 1000;
+  }
+
+  timg = i_sametype(im, im->xsize, im->ysize);
+
+  if( stddevX > 0 ) {
+    /* Build Y coefficient matrix */
+    co = build_coeff( im, stddevX );
+    im_log((aIMCTX, 1, "i_gaussian2 X coeff radius=%i diamter=%i coeff=%p\n", co->radius, co->diameter, co->coeff));
+  }
+  else {
+    im_log((aIMCTX, 1, "i_gaussian2 X coeff is unity\n"));
+  }
 
 #code im->bits <= 8
   IM_COLOR rcolor;
+  i_img *yin;
+  i_img *yout;
+
+  if( stddevX > 0 ) {
+    /******************/
+    /* Process X blur */
+    im_log((aIMCTX, 1, "i_gaussian2 X blur from im=%p to timg=%p\n", im, timg));
+
   for(y = 0; y < im->ysize; y++) {
     for(x = 0; x < im->xsize; x++) {
       pc=0.0;
       for(ch=0;ch<im->channels;ch++) 
        res[ch]=0; 
-      for(c = 0;c < diameter; c++)
-       if (IM_GPIX(im,x+c-radius,y,&rcolor)!=-1) {
-         for(ch=0;ch<im->channels;ch++) 
-           res[ch]+= rcolor.channel[ch] * coeff[c];
-         pc+=coeff[c];
+        for(c = 0;c < co->diameter; c++)
+          if (IM_GPIX(im,x+c-co->radius,y,&rcolor)!=-1) {
+         for(ch=0;ch<im->channels;ch++)
+              res[ch]+= rcolor.channel[ch] * co->coeff[c];
+            pc+=co->coeff[c];
        }
       for(ch=0;ch<im->channels;ch++) {
        double value = res[ch] / pc;
@@ -80,27 +147,73 @@ i_gaussian(i_img *im, double stddev) {
       IM_PPIX(timg, x, y, &rcolor);
     }
   }
+    /* processing is im -> timg=yin -> im=yout */
+    yin = timg;
+    yout = im;
+  }
+  else {
+    /* processing is im=yin -> timg=yout -> im */
+    yin = im;
+    yout = timg;
+  }
   
+  if( stddevY > 0 ) {
+    if( stddevX != stddevY ) {
+      if( co != NULL ) {
+        free_coeff(co);
+        co = NULL;
+      }
+
+      /* Build Y coefficient matrix */
+      co = build_coeff( im, stddevY );
+      im_log((aIMCTX, 1, "i_gaussian2 Y coeff radius=%i diamter=%i coeff=%p\n", co->radius, co->diameter, co->coeff));
+    }
+
+    /******************/
+    /* Process Y blur */
+    im_log((aIMCTX, 1, "i_gaussian2 Y blur from yin=%p to yout=%p\n", yin, yout));
   for(x = 0;x < im->xsize; x++) {
     for(y = 0; y < im->ysize; y++) {
       pc=0.0;
       for(ch=0; ch<im->channels; ch++)
        res[ch]=0; 
-      for(c=0; c < diameter; c++)
-       if (IM_GPIX(timg, x, y+c-radius, &rcolor)!=-1) {
-         for(ch=0;ch<im->channels;ch++) 
-           res[ch]+= rcolor.channel[ch] * coeff[c];
-         pc+=coeff[c];
+        for(c=0; c < co->diameter; c++)
+          if (IM_GPIX(yin, x, y+c-co->radius, &rcolor)!=-1) {
+            for(ch=0;ch<yin->channels;ch++) 
+              res[ch]+= rcolor.channel[ch] * co->coeff[c];
+            pc+=co->coeff[c];
        }
-      for(ch=0;ch<im->channels;ch++) {
+        for(ch=0;ch<yin->channels;ch++) {
        double value = res[ch]/pc;
        rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : IM_ROUND(value);
       }
-      IM_PPIX(im, x, y, &rcolor);
+        IM_PPIX(yout, x, y, &rcolor);
+      }
     }
+    if( im != yout ) {
+      im_log((aIMCTX, 1, "i_gaussian2 copying yout=%p to im=%p\n", yout, im));
+      img_copy( im, yout );
   }
+  }
+  else {
+    im_log((aIMCTX, 1, "i_gaussian2 Y coeff is unity\n"));
+    if( yin==timg ) {
+      im_log((aIMCTX, 1, "i_gaussian2 copying timg=%p to im=%p\n", timg, im));
+      img_copy( im, timg );
+    }
+  }
+
+  im_log((aIMCTX, 1, "i_gaussian2 im=%p\n", im));
+  im_log((aIMCTX, 1, "i_gaussian2 timg=%p\n", timg));
+  im_log((aIMCTX, 1, "i_gaussian2 yin=%p\n", yin));
+  im_log((aIMCTX, 1, "i_gaussian2 yout=%p\n", yout));
+
+
+
 #/code
-  myfree(coeff);
+  if( co != NULL )
+    free_coeff(co);
+
   i_img_destroy(timg);
   
   return 1;
index f87fc2c..6a21765 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -178,7 +178,8 @@ undef_int i_flood_cfill_border(i_img *im, i_img_dim seedx, i_img_dim seedy, i_fi
 
 /* image processing functions */
 
-int i_gaussian    (i_img *im, double stdev);
+int i_gaussian    (i_img *im, double stddev);
+int i_gaussian2    (i_img *im, double stddevX, double stddevY);
 int i_conv        (i_img *im,const double *coeff,int len);
 void i_unsharp_mask(i_img *im, double stddev, double scale);
 
index f3d9e5e..d308e48 100644 (file)
@@ -101,6 +101,9 @@ that comes with the module source.
 
   gaussian        stddev
 
+  gaussian2       stddevX
+                  stddevY
+
   gradgen         xo yo colors 
                   dist         0
 
@@ -436,7 +439,7 @@ Really.  It even loads GIMP gradient files.
     or die $radial->errstr;
 
 =for stopwords Gaussian
-                           
+
 =item gaussian
 
 performs a Gaussian blur of the image, using C<stddev> as the standard
@@ -456,6 +459,26 @@ values around 5 provide a very strong blur.
   $img->filter(type=>"gaussian", stddev=>5)
     or die $img->errstr;
 
+=item gaussian2
+
+performs a Gaussian blur of the image, using C<stddevX>, C<stddevY> as the
+standard deviation of the curve used to combine pixels on the X and Y axis,
+respectively. Larger values give bigger blurs.  For a definition of Gaussian
+Blur, see:
+
+  http://www.maths.abdn.ac.uk/~igc/tch/mx4002/notes/node99.html
+
+Values of C<stddevX> or C<stddevY> around 0.5 provide a barely noticeable blur,
+values around 5 provide a very strong blur.
+
+  # only slightly blurred
+  $img->filter(type=>"gaussian2", stddevX=>0.5, stddevY=>0.5)
+    or die $img->errstr;
+
+  # blur an image in the Y axis
+  $img->filter(type=>"gaussian", stddevX=>0, stddevY=>5 )
+    or die $img->errstr;
+
 =item gradgen
 
 renders a gradient, with the given I<colors> at the corresponding
index 1be79b1..1ec6e19 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use Imager qw(:handy);
-use Test::More tests => 124;
+use Test::More tests => 136;
 
 -d "testout" or mkdir "testout";
 
@@ -67,6 +67,34 @@ test($imbase, {type=>'conv', coef=>[ 0.3, 1, 0.3, ], },
   is_image_similar($gauss, $gauss16, 250000, "8 and 16 gaussian match");
 }
 
+{
+  my $imbase = Imager->new( xsize=>150, ysize=>150 );
+  $imbase->box( filled=>1, color=>'white', box=>[ 70, 24, 80, 124 ] );
+  $imbase->box( filled=>1, color=>'red', box=>[ 70, 24, 124, 30 ] );
+  $imbase->write( file=>"testout/t61_gaussian2-base.ppm" );
+
+  my $gauss = test($imbase, {type=>'gaussian2', stddevY=>5, stddevX=>0 },
+                  'testout/t61_gaussianY.ppm');
+
+  my $imbase16 = $imbase->to_rgb16;
+  my $gauss16 = test($imbase16,  {type=>'gaussian2', stddevY=>5, stddevX=>0.1 },
+                    'testout/t61_gaussianY-16.ppm');
+  is_image_similar($gauss, $gauss16, 250000, "8 and 16 gaussian match");
+
+
+  test($imbase, {type=>'gaussian2', stddevX=>5, stddevY=>5 },
+                  'testout/t61_gaussian_both.ppm');
+
+
+  $gauss = test($imbase, {type=>'gaussian2', stddevX=>5, stddevY=>0 },
+                  'testout/t61_gaussianX.ppm');
+
+  $imbase16 = $imbase->to_rgb16;
+  $gauss16 = test($imbase16,  {type=>'gaussian2', stddevX=>5, stddevY=>0.1 },
+                    'testout/t61_gaussianX-16.ppm');
+  is_image_similar($gauss, $gauss16, 250000, "8 and 16 gaussian match");
+}
+
 
 test($imbase, { type=>'gradgen', dist=>1,
                    xo=>[ 10,  10, 120 ],