convert scale.c to scale.im so we have 8 bit/sample and double/sample
authorTony Cook <tony@develop=help.com>
Wed, 30 Aug 2006 06:47:48 +0000 (06:47 +0000)
committerTony Cook <tony@develop=help.com>
Wed, 30 Aug 2006 06:47:48 +0000 (06:47 +0000)
implementations of mixing scaling.

modified imtoc.perl to allow non-conditional #code sections to allow
creation ofr 8 and double/sample versions of support functions.

fixed a bug in an optimization that avoids vertically scaling when the
vertical size stays the same.

The change from double/sample only to both saved about 20% on
scalebench time (which also loads/saves the images)

MANIFEST
bench/scale.perl
imtoc.perl
lib/Imager/Transformations.pod
scale.c [deleted file]
scale.im [new file with mode: 0644]
t/t40scale.t

index 8005f8a..864702c 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -191,7 +191,7 @@ samples/samp-tags.cgi   Demonstrate image upload via a HTML form
 samples/samp-tags.html  Form for samp-tags.cgi
 samples/slant_text.pl   Using $font->transform() to slant text
 samples/tk-photo.pl
-scale.c                Newer scaling code
+scale.im       Newer scaling code
 spot.perl      For making an ordered dither matrix from a spot function
 stackmach.c
 stackmach.h
index 0966808..ceb3dbe 100644 (file)
@@ -6,8 +6,8 @@ print "$0\n";
 my @allfiles = (@ARGV) x 120;
 my $srcdir = '.';
 
-my %opts1 = (scalefactor=>.333334);
-my %opts2 = (scalefactor=>.25);
+my %opts1 = (scalefactor=>.333334, qtype => 'mixing');
+my %opts2 = (scalefactor=>.25, qtype => 'mixing');
 my %exopts=();
 for my $file (@allfiles) {
   # print STDERR "reading $srcdir/$file\n";
index 0d59f47..0332de7 100644 (file)
@@ -18,7 +18,7 @@ my $failed;
 
 push @out, "#line 1 \"$src\"\n";
 while (defined(my $line = <SRC>)) {
-  if ($line =~ /^\#code (.+)$/) {
+  if ($line =~ /^\#code\s+(\S.+)$/) {
     $save_code
       and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
 
@@ -27,18 +27,34 @@ while (defined(my $line = <SRC>)) {
     $code_line = $. + 1;
     $save_code = 1;
   }
+  elsif ($line =~ /^\#code\s*$/) {
+    $save_code
+      and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
+
+    $cond = '';
+    $cond_line = 0;
+    $code_line = $. + 1;
+    $save_code = 1;
+  }
   elsif ($line =~ /^\#\/code$/) {
     $save_code
       or do { warn "$src:$.:#/code without #code\n"; ++$failed; next; };
 
-    push @out, "#line $cond_line \"$src\"\n";
-    push @out, "  if ($cond) {\n";
+    if ($cond) {
+      push @out, "#line $cond_line \"$src\"\n";
+      push @out, "  if ($cond) {\n";
+    }
+    push @out, "#undef IM_EIGHT_BIT\n",
+      "#define IM_EIGHT_BIT 1\n";
     push @out, "#line $code_line \"$src\"\n";
     push @out, byte_samples(@code);
-    push @out, "  }\n", "  else {\n";
+    push @out, "  }\n", "  else {\n"
+      if $cond;
+    push @out, "#undef IM_EIGHT_BIT\n";
     push @out, "#line $code_line \"$src\"\n";
     push @out, double_samples(@code);
-    push @out, "  }\n";
+    push @out, "  }\n"
+      if $cond;
     push @out, "#line $. \"$src\"\n";
     @code = ();
     $save_code = 0;
@@ -82,6 +98,7 @@ sub byte_samples {
     s/\bIM_WORK_T\b/int/g;
     s/\bIM_Sf\b/"%d"/g;
     s/\bIM_Wf\b/"%d"/g;
+    s/\bIM_SUFFIX\((\w+)\)/$1_8/g;
   }
 
   @lines;
@@ -103,6 +120,7 @@ sub double_samples {
     s/\bIM_WORK_T\b/double/g;
     s/\bIM_Sf\b/"%f"/g;
     s/\bIM_Wf\b/"%f"/g;
+    s/\bIM_SUFFIX\((\w+)\)/$1_double/g;
   }
 
   @lines;
@@ -137,6 +155,11 @@ The beginning of a sample-independent section of code is preceded by:
 where I<expression> should return true if processing should be done at
 8-bits/sample.
 
+You can also use a #code block around a function definition to produce
+8-bit and double sample versions of a function.  In this case #code
+has no expression and you will need to use IM_SUFFIX() to produce
+different function names.
+
 The end of a sample-independent section of code is terminated by:
 
   #/code
@@ -196,6 +219,14 @@ IM_Sf - format string for the sample type, C<"%d"> or C<"%f">.
 
 IM_Wf - format string for the work type, C<"%d"> or C<"%f">.
 
+=item *
+
+IM_SUFFIX(identifier) - adds _8 or _double onto the end of identifier.
+
+=item *
+
+IM_EIGHT_BIT - this is a macro defined only in 8-bit/sample code.
+
 =back
 
 Other types, functions and values may be added in the future.
index 23adf0b..07381a5 100644 (file)
@@ -8,7 +8,7 @@ Imager::Transformations - Simple transformations of one image into another.
 
   $newimg = $img->copy();
 
-  $newimg = $img->scale(xpixels=>400);
+  $newimg = $img->scale(xpixels=>400, qtype => 'mixing');
   $newimg = $img->scale(xpixels=>400, ypixels=>400);
   $newimg = $img->scale(xpixels=>400, ypixels=>400, type=>'min');
   $newimg = $img->scale(scalefactor=>0.25);
diff --git a/scale.c b/scale.c
deleted file mode 100644 (file)
index 2c51c91..0000000
--- a/scale.c
+++ /dev/null
@@ -1,221 +0,0 @@
-#include "imager.h"
-
-/*
- * i_scale_mixing() is based on code contained in pnmscale.c, part of
- * the netpbm distribution.  No code was copied from pnmscale but
- * the algorthm was and for this I thank the netpbm crew.
- *
- * Tony
- */
-
-/* pnmscale.c - read a portable anymap and scale it
-**
-** Copyright (C) 1989, 1991 by Jef Poskanzer.
-**
-** Permission to use, copy, modify, and distribute this software and its
-** documentation for any purpose and without fee is hereby granted, provided
-** that the above copyright notice appear in all copies and that both that
-** copyright notice and this permission notice appear in supporting
-** documentation.  This software is provided "as is" without express or
-** implied warranty.
-**
-*/
-
-
-static void
-zero_row(i_fcolor *row, int width, int channels);
-static void
-accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
-                int width, int channels);
-static void
-horizontal_scale(i_fcolor *out, int out_width, 
-                i_fcolor const *in, int in_width,
-                int channels);
-
-/*
-=item i_scale_mixing
-
-Returns a new image scaled to the given size.
-
-Unlike i_scale_axis() this does a simple coverage of pixels from
-source to target and doesn't resample.
-
-Adapted from pnmscale.
-
-=cut
-*/
-i_img *
-i_scale_mixing(i_img *src, int x_out, int y_out) {
-  i_img *result;
-  i_fcolor *in_row = NULL;
-  i_fcolor *xscale_row = NULL;
-  i_fcolor *accum_row = NULL;
-  int y;
-  int in_row_bytes, out_row_bytes;
-  double rowsleft, fracrowtofill;
-  int rowsread;
-  double y_scale;
-
-  mm_log((1, "i_scale_mixing(src %p, x_out %d, y_out %d)\n", 
-         src, x_out, y_out));
-
-  i_clear_error();
-
-  if (x_out <= 0) {
-    i_push_errorf(0, "output width %d invalid", x_out);
-    return NULL;
-  }
-  if (y_out <= 0) {
-    i_push_errorf(0, "output height %d invalid", y_out);
-    return NULL;
-  }
-
-  in_row_bytes = sizeof(i_fcolor) * src->xsize;
-  if (in_row_bytes / sizeof(i_fcolor) != src->xsize) {
-    i_push_error(0, "integer overflow allocating input row buffer");
-    return NULL;
-  }
-  out_row_bytes = sizeof(i_fcolor) * x_out;
-  if (out_row_bytes / sizeof(i_fcolor) != x_out) {
-    i_push_error(0, "integer overflow allocating output row buffer");
-    return NULL;
-  }
-
-  if (x_out == src->xsize && y_out == src->ysize) {
-    return i_copy(src);
-  }
-
-  y_scale = y_out / (double)src->ysize;
-
-  result = i_sametype_chans(src, x_out, y_out, src->channels);
-  if (!result)
-    return NULL;
-
-  in_row     = mymalloc(in_row_bytes);
-  accum_row  = mymalloc(in_row_bytes);
-  xscale_row = mymalloc(out_row_bytes);
-
-  rowsread = 0;
-  rowsleft = 0.0;
-  for (y = 0; y < y_out; ++y) {
-    if (y_out == src->ysize) {
-      i_glinf(src, 0, src->xsize, y, accum_row);
-    }
-    else {
-      fracrowtofill = 1.0;
-      zero_row(accum_row, src->xsize, src->channels);
-      while (fracrowtofill > 0) {
-       if (rowsleft <= 0) {
-         if (rowsread < src->ysize) {
-           i_glinf(src, 0, src->xsize, rowsread, in_row);
-           ++rowsread;
-         }
-         /* else just use the last row read */
-
-         rowsleft = y_scale;
-       }
-       if (rowsleft < fracrowtofill) {
-         accum_output_row(accum_row, rowsleft, in_row, src->xsize, 
-                          src->channels);
-         fracrowtofill -= rowsleft;
-         rowsleft = 0;
-       }
-       else {
-         accum_output_row(accum_row, fracrowtofill, in_row, src->xsize, 
-                          src->channels);
-         rowsleft -= fracrowtofill;
-         fracrowtofill = 0;
-       }
-      }
-      /* we've accumulated a vertically scaled row */
-      if (x_out == src->xsize) {
-       /* no need to scale */
-       i_plinf(result, 0, x_out, y, accum_row);
-      }
-      else {
-       horizontal_scale(xscale_row, x_out, accum_row, src->xsize, 
-                        src->channels);
-       i_plinf(result, 0, x_out, y, xscale_row);
-      }
-    }
-  }
-
-  myfree(in_row);
-  myfree(accum_row);
-  myfree(xscale_row);
-
-  return result;
-}
-
-static void
-zero_row(i_fcolor *row, int width, int channels) {
-  int x;
-  int ch;
-
-  /* with IEEE floats we could just use memset() but that's not
-     safe in general under ANSI C */
-  for (x = 0; x < width; ++x) {
-    for (ch = 0; ch < channels; ++ch)
-      row[x].channel[ch] = 0.0;
-  }
-}
-
-static void
-accum_output_row(i_fcolor *accum, double fraction, i_fcolor const *in,
-                int width, int channels) {
-  int x, ch;
-
-  for (x = 0; x < width; ++x) {
-    for (ch = 0; ch < channels; ++ch) {
-      accum[x].channel[ch] += in[x].channel[ch] * fraction;
-    }
-  }
-}
-
-static void
-horizontal_scale(i_fcolor *out, int out_width, 
-                i_fcolor const *in, int in_width,
-                int channels) {
-  double frac_col_to_fill, frac_col_left;
-  int in_x;
-  int out_x;
-  double x_scale = (double)out_width / in_width;
-  int ch;
-  double accum[MAXCHANNELS] = { 0 };
-  
-  frac_col_to_fill = 1.0;
-  out_x = 0;
-  for (in_x = 0; in_x < in_width; ++in_x) {
-    frac_col_left = x_scale;
-    while (frac_col_left >= frac_col_to_fill) {
-      for (ch = 0; ch < channels; ++ch)
-       accum[ch] += frac_col_to_fill * in[in_x].channel[ch];
-
-      for (ch = 0; ch < channels; ++ch) {
-       out[out_x].channel[ch] = accum[ch];
-       accum[ch] = 0;
-      }
-      frac_col_left -= frac_col_to_fill;
-      frac_col_to_fill = 1.0;
-      ++out_x;
-    }
-
-    if (frac_col_left > 0) {
-      for (ch = 0; ch < channels; ++ch) {
-       accum[ch] += frac_col_left * in[in_x].channel[ch];
-      }
-      frac_col_to_fill -= frac_col_left;
-    }
-  }
-
-  if (out_x < out_width-1 || out_x > out_width) {
-    i_fatal(3, "Internal error: out_x %d out of range (width %d)", out_x, out_width);
-  }
-  
-  if (out_x < out_width) {
-    for (ch = 0; ch < channels; ++ch) {
-      accum[ch] += frac_col_to_fill * in[in_width-1].channel[ch];
-      out[out_x].channel[ch] = accum[ch];
-    }
-  }
-}
diff --git a/scale.im b/scale.im
new file mode 100644 (file)
index 0000000..26ea790
--- /dev/null
+++ b/scale.im
@@ -0,0 +1,263 @@
+#include "imager.h"
+
+/*
+ * i_scale_mixing() is based on code contained in pnmscale.c, part of
+ * the netpbm distribution.  No code was copied from pnmscale but
+ * the algorthm was and for this I thank the netpbm crew.
+ *
+ * Tony
+ */
+
+/* pnmscale.c - read a portable anymap and scale it
+**
+** Copyright (C) 1989, 1991 by Jef Poskanzer.
+**
+** Permission to use, copy, modify, and distribute this software and its
+** documentation for any purpose and without fee is hereby granted, provided
+** that the above copyright notice appear in all copies and that both that
+** copyright notice and this permission notice appear in supporting
+** documentation.  This software is provided "as is" without express or
+** implied warranty.
+**
+*/
+
+
+static void
+zero_row(i_fcolor *row, int width, int channels);
+
+#code
+static void
+IM_SUFFIX(accum_output_row)(i_fcolor *accum, double fraction, IM_COLOR const *in,
+                int width, int channels);
+static void
+IM_SUFFIX(horizontal_scale)(IM_COLOR *out, int out_width, 
+                            i_fcolor const *in, int in_width,
+                            int channels);
+#/code
+
+/*
+=item i_scale_mixing
+
+Returns a new image scaled to the given size.
+
+Unlike i_scale_axis() this does a simple coverage of pixels from
+source to target and doesn't resample.
+
+Adapted from pnmscale.
+
+=cut
+*/
+i_img *
+i_scale_mixing(i_img *src, int x_out, int y_out) {
+  i_img *result;
+  i_fcolor *accum_row = NULL;
+  int y;
+  int accum_row_bytes;
+  double rowsleft, fracrowtofill;
+  int rowsread;
+  double y_scale;
+
+  mm_log((1, "i_scale_mixing(src %p, x_out %d, y_out %d)\n", 
+         src, x_out, y_out));
+
+  i_clear_error();
+
+  if (x_out <= 0) {
+    i_push_errorf(0, "output width %d invalid", x_out);
+    return NULL;
+  }
+  if (y_out <= 0) {
+    i_push_errorf(0, "output height %d invalid", y_out);
+    return NULL;
+  }
+
+  if (x_out == src->xsize && y_out == src->ysize) {
+    return i_copy(src);
+  }
+
+  y_scale = y_out / (double)src->ysize;
+
+  result = i_sametype_chans(src, x_out, y_out, src->channels);
+  if (!result)
+    return NULL;
+
+  accum_row_bytes = sizeof(i_fcolor) * src->xsize;
+  if (accum_row_bytes / sizeof(i_fcolor) != src->xsize) {
+    i_push_error(0, "integer overflow allocating accumulator row buffer");
+    return NULL;
+  }
+
+  accum_row  = mymalloc(accum_row_bytes);
+
+#code src->bits <= 8
+  IM_COLOR *in_row = NULL;
+  IM_COLOR *xscale_row = NULL;
+  int in_row_bytes, out_row_bytes;
+
+  in_row_bytes = sizeof(IM_COLOR) * src->xsize;
+  if (in_row_bytes / sizeof(IM_COLOR) != src->xsize) {
+    i_push_error(0, "integer overflow allocating input row buffer");
+    return NULL;
+  }
+  out_row_bytes = sizeof(IM_COLOR) * x_out;
+  if (out_row_bytes / sizeof(IM_COLOR) != x_out) {
+    i_push_error(0, "integer overflow allocating output row buffer");
+    return NULL;
+  }
+
+  in_row     = mymalloc(in_row_bytes);
+  xscale_row = mymalloc(out_row_bytes);
+
+  rowsread = 0;
+  rowsleft = 0.0;
+  for (y = 0; y < y_out; ++y) {
+    if (y_out == src->ysize) {
+      /* no vertical scaling, just load it */
+      int x, ch;
+#ifdef IM_EIGHT_BIT
+      /* load and convert to doubles */
+      IM_GLIN(src, 0, src->xsize, y, in_row);
+      for (x = 0; x < src->xsize; ++x) {
+        for (ch = 0; ch < src->channels; ++ch) {
+          accum_row[x].channel[ch] = in_row[x].channel[ch];
+        }
+      }
+#else
+      IM_GLIN(src, 0, src->xsize, y, accum_row);
+#endif
+    }
+    else {
+      fracrowtofill = 1.0;
+      zero_row(accum_row, src->xsize, src->channels);
+      while (fracrowtofill > 0) {
+       if (rowsleft <= 0) {
+         if (rowsread < src->ysize) {
+           IM_GLIN(src, 0, src->xsize, rowsread, in_row);
+           ++rowsread;
+         }
+         /* else just use the last row read */
+
+         rowsleft = y_scale;
+       }
+       if (rowsleft < fracrowtofill) {
+         IM_SUFFIX(accum_output_row)(accum_row, rowsleft, in_row, 
+                                      src->xsize, src->channels);
+         fracrowtofill -= rowsleft;
+         rowsleft = 0;
+       }
+       else {
+         IM_SUFFIX(accum_output_row)(accum_row, fracrowtofill, in_row, 
+                                      src->xsize, src->channels);
+         rowsleft -= fracrowtofill;
+         fracrowtofill = 0;
+       }
+      }
+    }
+    /* we've accumulated a vertically scaled row */
+    if (x_out == src->xsize) {
+      int x, ch;
+#if IM_EIGHT_BIT
+      /* no need to scale, but we need to convert it */
+      for (x = 0; x < x_out; ++x) {
+        for (ch = 0; ch < result->channels; ++ch)
+          xscale_row[x].channel[ch] = accum_row[x].channel[ch];
+      }
+      IM_PLIN(result, 0, x_out, y, xscale_row);
+#else
+      IM_PLIN(result, 0, x_out, y, accum_row);
+#endif
+    }
+    else {
+      IM_SUFFIX(horizontal_scale)(xscale_row, x_out, accum_row, 
+                                  src->xsize, src->channels);
+      IM_PLIN(result, 0, x_out, y, xscale_row);
+    }
+  }
+  myfree(in_row);
+  myfree(xscale_row);
+#/code
+  myfree(accum_row);
+
+  return result;
+}
+
+static void
+zero_row(i_fcolor *row, int width, int channels) {
+  int x;
+  int ch;
+
+  /* with IEEE floats we could just use memset() but that's not
+     safe in general under ANSI C.
+     memset() is slightly faster.
+  */
+  for (x = 0; x < width; ++x) {
+    for (ch = 0; ch < channels; ++ch)
+      row[x].channel[ch] = 0.0;
+  }
+}
+
+#code
+
+static void
+IM_SUFFIX(accum_output_row)(i_fcolor *accum, double fraction, IM_COLOR const *in,
+                int width, int channels) {
+  int x, ch;
+
+  /* it's tempting to change this into a pointer iteration loop but
+     modern CPUs do the indexing as part of the instruction */
+  for (x = 0; x < width; ++x) {
+    for (ch = 0; ch < channels; ++ch) {
+      accum[x].channel[ch] += in[x].channel[ch] * fraction;
+    }
+  }
+}
+
+static void
+IM_SUFFIX(horizontal_scale)(IM_COLOR *out, int out_width, 
+                i_fcolor const *in, int in_width,
+                int channels) {
+  double frac_col_to_fill, frac_col_left;
+  int in_x;
+  int out_x;
+  double x_scale = (double)out_width / in_width;
+  int ch;
+  double accum[MAXCHANNELS] = { 0 };
+  
+  frac_col_to_fill = 1.0;
+  out_x = 0;
+  for (in_x = 0; in_x < in_width; ++in_x) {
+    frac_col_left = x_scale;
+    while (frac_col_left >= frac_col_to_fill) {
+      for (ch = 0; ch < channels; ++ch)
+       accum[ch] += frac_col_to_fill * in[in_x].channel[ch];
+
+      for (ch = 0; ch < channels; ++ch) {
+       out[out_x].channel[ch] = accum[ch];
+       accum[ch] = 0;
+      }
+      frac_col_left -= frac_col_to_fill;
+      frac_col_to_fill = 1.0;
+      ++out_x;
+    }
+
+    if (frac_col_left > 0) {
+      for (ch = 0; ch < channels; ++ch) {
+       accum[ch] += frac_col_left * in[in_x].channel[ch];
+      }
+      frac_col_to_fill -= frac_col_left;
+    }
+  }
+
+  if (out_x < out_width-1 || out_x > out_width) {
+    i_fatal(3, "Internal error: out_x %d out of range (width %d)", out_x, out_width);
+  }
+  
+  if (out_x < out_width) {
+    for (ch = 0; ch < channels; ++ch) {
+      accum[ch] += frac_col_to_fill * in[in_width-1].channel[ch];
+      out[out_x].channel[ch] = accum[ch];
+    }
+  }
+}
+
+#/code
index cd34411..509f500 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use lib 't';
-use Test::More tests => 213;
+use Test::More tests => 223;
 
 BEGIN { use_ok(Imager=>':all') }
 
@@ -31,6 +31,35 @@ ok($scaleimg, "scale it (mixing)") or print "# ", $img->errstr, "\n";
 ok($scaleimg->write(file=>'testout/t40scale3.ppm', type=>'pnm'),
    "write mixing scaled image") or print "# ", $img->errstr, "\n";
 
+{ # double image scaling with mixing, since it has code to handle it
+  my $dimg = Imager->new(xsize => $img->getwidth, ysize => $img->getheight,
+                         channels => $img->getchannels,
+                         bits => 'double');
+  ok($dimg, "create double/sample image");
+  $dimg->paste(src => $img);
+  $scaleimg = $dimg->scale(scalefactor => 0.25, qtype => 'mixing');
+  ok($scaleimg, "scale it (mixing, double)");
+  ok($scaleimg->write(file => 'testout/t40mixdbl.ppm', type => 'pnm'),
+     "write double/mixing scaled image");
+  is($scaleimg->bits, 'double', "got the right image type as output");
+
+  # hscale only, mixing
+  $scaleimg = $dimg->scale(xscalefactor => 0.33, yscalefactor => 1.0,
+                           qtype => 'mixing');
+  ok($scaleimg, "scale it (hscale, mixing, double)");
+  is($scaleimg->getheight, $dimg->getheight, "same height");
+  ok($scaleimg->write(file => 'testout/t40hscdmix.ppm', type => 'pnm'),
+     "save it");
+
+  # vscale only, mixing
+  $scaleimg = $dimg->scale(xscalefactor => 1.0, yscalefactor => 0.33,
+                           qtype => 'mixing');
+  ok($scaleimg, "scale it (vscale, mixing, double)");
+  is($scaleimg->getwidth, $dimg->getwidth, "same width");
+  ok($scaleimg->write(file => 'testout/t40vscdmix.ppm', type => 'pnm'),
+     "save it");
+}
+
 {
   # check for a warning when scale() is called in void context
   my $warning;