the rubthrough() method now supports destination images with an alpha
authorTony Cook <tony@develop=help.com>
Tue, 29 Aug 2006 00:42:46 +0000 (00:42 +0000)
committerTony Cook <tony@develop=help.com>
Tue, 29 Aug 2006 00:42:46 +0000 (00:42 +0000)
channel.

Also added a statement on the relationship between the alpha channel
and color data in Imager.

The new rubthrough() code uses a new pre-processor that reduces source
code duplication between 8-bit/sample and double/sample processing.

MANIFEST
MANIFEST.SKIP
Makefile.PL
apidocs.perl
image.c
imtoc.perl [new file with mode: 0644]
lib/Imager/APIRef.pod
lib/Imager/ImageTypes.pod
rubthru.im [new file with mode: 0644]
t/t69rubthru.t

index a6b3315..8005f8a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -117,6 +117,7 @@ img16.c
 imgdouble.c     Implements double/sample images
 imio.h
 imperl.h
+imtoc.perl      Sample size adapter pre-processor
 io.c
 iolayer.c
 iolayer.h
@@ -173,6 +174,7 @@ regmach.h
 regops.perl
 rgb.c           Reading and writing SGI rgb files
 rotate.c
+rubthru.im
 samples/README
 samples/align-string.pl Demonstrate align_string method.
 samples/anaglyph.pl
index eb2e76c..64b7f0d 100644 (file)
@@ -43,6 +43,7 @@ Makefile\.old
 ^imconfig\.h$
 ^ICO/ICO\.c$
 ^ICO/testout
+^rubthru\.c$
 
 # trash from profiling
 \.gcno$
index a7e6ee6..8cdb5ef 100644 (file)
@@ -155,7 +155,7 @@ my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o
               regmach.o trans2.o quant.o error.o convert.o
               map.o tags.o palimg.o maskimg.o img16.o rotate.o
               bmp.o tga.o rgb.o color.o fills.o imgdouble.o limits.o hlines.o
-              imext.o scale.o);
+              imext.o scale.o rubthru.o);
 
 $Recommends{Imager} =
   { 'Parse::RecDescent' => 0 };
@@ -194,6 +194,9 @@ exit;
 sub MY::postamble {
     my $self = shift;
     my $perl = $self->{PERLRUN} ? '$(PERLRUN)' : '$(PERL)';
+    my $mani = maniread;
+
+    my @ims = grep /\.im$/, keys %$mani;
 '
 dyntest.$(MYEXTLIB) : dynfilt/Makefile
        cd dynfilt && $(MAKE) $(PASTHRU)
@@ -209,7 +212,20 @@ imconfig.h : Makefile.PL
 lib/Imager/APIRef.pod : \$(C_FILES) apidocs.perl
        $perl apidocs.perl lib/Imager/APIRef.pod
 
-!;
+!.join('', map _im_rule($perl, $_), @ims)
+
+}
+
+sub _im_rule {
+  my ($perl, $im) = @_;
+
+  (my $c = $im) =~ s/\.im$/.c/;
+  return <<MAKE;
+
+$c: $im imtoc.perl
+       $perl imtoc.perl $im $c
+
+MAKE
 
 }
 
index 2674289..3559870 100644 (file)
@@ -1,5 +1,6 @@
 #!perl -w
 use strict;
+use ExtUtils::Manifest 'maniread';
 
 my $outname = shift || '-';
 
@@ -9,7 +10,8 @@ my %funcs = map { $_ => 1 } @funcs;
 
 # look for files to parse
 
-my @files = grep $_ ne 'Imager.xs', glob '*.c';
+my $mani = maniread;
+my @files = grep /\.(c|im)$/, keys %$mani;
 
 # scan each file for =item <func>\b
 my $func;
diff --git a/image.c b/image.c
index 18a9796..316982a 100644 (file)
--- a/image.c
+++ b/image.c
@@ -697,104 +697,6 @@ i_copy(i_img *src) {
 }
 
 
-/*
-=item i_rubthru(im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy )
-
-=category Image
-
-Takes the sub image I<src[src_minx, src_maxx)[src_miny, src_maxy)> and
-overlays it at (I<tx>,I<ty>) on the image object.
-
-The alpha channel of each pixel in I<src> is used to control how much
-the existing colour in I<im> is replaced, if it is 255 then the colour
-is completely replaced, if it is 0 then the original colour is left 
-unmodified.
-
-=cut
-*/
-
-int
-i_rubthru(i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny,
-         int src_maxx, int src_maxy) {
-  int x, y, ttx, tty;
-  int chancount;
-  int chans[3];
-  int alphachan;
-  int ch;
-
-  mm_log((1,"i_rubthru(im %p, src %p, tx %d, ty %d, src_minx %d, "
-         "src_miny %d, src_maxx %d, src_maxy %d)\n",
-         im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy));
-  i_clear_error();
-
-  if (im->channels == 3 && src->channels == 4) {
-    chancount = 3;
-    chans[0] = 0; chans[1] = 1; chans[2] = 2;
-    alphachan = 3;
-  }
-  else if (im->channels == 3 && src->channels == 2) {
-    chancount = 3;
-    chans[0] = chans[1] = chans[2] = 0;
-    alphachan = 1;
-  }
-  else if (im->channels == 1 && src->channels == 2) {
-    chancount = 1;
-    chans[0] = 0;
-    alphachan = 1;
-  }
-  else {
-    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (3,2) or (1,2)");
-    return 0;
-  }
-
-  if (im->bits <= 8) {
-    /* if you change this code, please make sure the else branch is
-       changed in a similar fashion - TC */
-    int alpha;
-    i_color pv, orig, dest;
-    tty = ty;
-    for(y = src_miny; y < src_maxy; y++) {
-      ttx = tx;
-      for(x = src_minx; x < src_maxx; x++) {
-        i_gpix(src, x,   y,   &pv);
-        i_gpix(im,  ttx, tty, &orig);
-        alpha = pv.channel[alphachan];
-        for (ch = 0; ch < chancount; ++ch) {
-          dest.channel[ch] = (alpha * pv.channel[chans[ch]]
-                              + (255 - alpha) * orig.channel[ch])/255;
-        }
-        i_ppix(im, ttx, tty, &dest);
-       ttx++;
-      }
-      tty++;
-    }
-  }
-  else {
-    double alpha;
-    i_fcolor pv, orig, dest;
-
-    tty = ty;
-    for(y = src_miny; y < src_maxy; y++) {
-      ttx = tx;
-      for(x = src_minx; x < src_maxx; x++) {
-        i_gpixf(src, x,   y,   &pv);
-        i_gpixf(im,  ttx, tty, &orig);
-        alpha = pv.channel[alphachan];
-        for (ch = 0; ch < chancount; ++ch) {
-          dest.channel[ch] = alpha * pv.channel[chans[ch]]
-           + (1 - alpha) * orig.channel[ch];
-        }
-        i_ppixf(im, ttx, tty, &dest);
-        ttx++;
-      }
-      tty++;
-    }
-  }
-
-  return 1;
-}
-
-
 /*
 =item i_flipxy(im, axis)
 
diff --git a/imtoc.perl b/imtoc.perl
new file mode 100644 (file)
index 0000000..0d59f47
--- /dev/null
@@ -0,0 +1,207 @@
+#!perl -w
+use strict;
+
+my $src = shift;
+my $dest = shift
+  or usage();
+
+open SRC, "< $src"
+  or die "Cannot open $src: $!\n";
+
+my $cond;
+my $cond_line;
+my $save_code;
+my @code;
+my $code_line;
+my @out;
+my $failed;
+
+push @out, "#line 1 \"$src\"\n";
+while (defined(my $line = <SRC>)) {
+  if ($line =~ /^\#code (.+)$/) {
+    $save_code
+      and do { warn "$src:$code_line:Unclosed #code block\n"; ++$failed; };
+
+    $cond = $1;
+    $cond_line = $.;
+    $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";
+    push @out, "#line $code_line \"$src\"\n";
+    push @out, byte_samples(@code);
+    push @out, "  }\n", "  else {\n";
+    push @out, "#line $code_line \"$src\"\n";
+    push @out, double_samples(@code);
+    push @out, "  }\n";
+    push @out, "#line $. \"$src\"\n";
+    @code = ();
+    $save_code = 0;
+  }
+  elsif ($save_code) {
+    push @code, $line;
+  }
+  else {
+    push @out, $line;
+  }
+}
+
+if ($save_code) {
+  warn "$src:$code_line:#code block not closed by EOF\n";
+  ++$failed;
+}
+
+close SRC;
+
+$failed 
+  and die "Errors during parsing, aborting\n";
+
+open DEST, "> $dest"
+  or die "Cannot open $dest: $!\n";
+print DEST @out;
+close DEST;
+
+sub byte_samples {
+  # important we make a copy
+  my @lines = @_;
+
+  for (@lines) {
+    s/\bIM_GPIX\b/i_gpix/g;
+    s/\bIM_GLIN\b/i_glin/g;
+    s/\bIM_PPIX\b/i_ppix/g;
+    s/\bIM_PLIN\b/i_plin/g;
+    s/\bIM_GSAMP\b/i_gsamp/g;
+    s/\bIM_SAMPLE_MAX\b/255/g;
+    s/\bIM_SAMPLE_T/i_sample_t/g;
+    s/\bIM_COLOR\b/i_color/g;
+    s/\bIM_WORK_T\b/int/g;
+    s/\bIM_Sf\b/"%d"/g;
+    s/\bIM_Wf\b/"%d"/g;
+  }
+
+  @lines;
+}
+
+sub double_samples {
+  # important we make a copy
+  my @lines = @_;
+
+  for (@lines) {
+    s/\bIM_GPIX\b/i_gpixf/g;
+    s/\bIM_GLIN\b/i_glinf/g;
+    s/\bIM_PPIX\b/i_ppixf/g;
+    s/\bIM_PLIN\b/i_plinf/g;
+    s/\bIM_GSAMP\b/i_gsampf/g;
+    s/\bIM_SAMPLE_MAX\b/1.0/g;
+    s/\bIM_SAMPLE_T/i_fsample_t/g;
+    s/\bIM_COLOR\b/i_fcolor/g;
+    s/\bIM_WORK_T\b/double/g;
+    s/\bIM_Sf\b/"%f"/g;
+    s/\bIM_Wf\b/"%f"/g;
+  }
+
+  @lines;
+}
+
+=head1 NAME
+
+imtoc.perl - simple preprocessor for handling multiple sample sizes
+
+=head1 SYNOPSIS
+
+  /* in the source: */
+  #code condition true to work with 8-bit samples
+  ... code using preprocessor types/values ...
+  #/code
+
+  perl imtoc.perl foo.im foo.c
+
+=head1 DESCRIPTION
+
+This is a simple preprocessor that aims to reduce duplication of
+source code when implementing an algorithm both for 8-bit samples and
+double samples in Imager.
+
+Imager's Makefile.PL currently scans the MANIFEST for .im files and
+adds Makefile files to convert these to .c files.
+
+The beginning of a sample-independent section of code is preceded by:
+
+  #code expression
+
+where I<expression> should return true if processing should be done at
+8-bits/sample.
+
+The end of a sample-independent section of code is terminated by:
+
+  #/code
+
+#code sections cannot be nested.
+
+#/code without a starting #code is an error.
+
+The following types and values are defined in a #code section:
+
+=over
+
+=item *
+
+IM_GPIX(im, x, y, &col)
+
+=item *
+
+IM_GLIN(im, l, r, y, colors)
+
+=item *
+
+IM_PPIX(im, x, y, &col)
+
+=item *
+
+IM_PLIN(im, x, y, colors)
+
+=item *
+
+IM_GSAMP(im, l, r, y, samples, chans, chan_count)
+
+These correspond to the appropriate image function, eg. IM_GPIX()
+becomes i_gpix() or i_gpixf() as appropriate.
+
+=item *
+
+IM_SAMPLE_MAX - maximum value for a sample
+
+=item *
+
+IM_SAMPLE_T - type of a sample (i_sample_t or i_fsample_t)
+
+=item *
+
+IM_COLOR - color type, either i_color or i_fcolor.
+
+=item *
+
+IM_WORK_T - working sample type, either int or double.
+
+=item *
+
+IM_Sf - format string for the sample type, C<"%d"> or C<"%f">.
+
+=item *
+
+IM_Wf - format string for the work type, C<"%d"> or C<"%f">.
+
+=back
+
+Other types, functions and values may be added in the future.
+
+=head1 AUTHOR
+
+Tony Cook <tony@imager.perl.org>
+
+=cut
index e92e4c4..3992773 100644 (file)
@@ -655,7 +655,7 @@ unmodified.
 
 
 =for comment
-From: File image.c
+From: File rubthru.im
 
 
 =back
index cb67145..b888e37 100644 (file)
@@ -127,6 +127,10 @@ C<virtual()> methods.
 The coordinate system in Imager has the origin in the upper left
 corner, see L<Imager::Draw> for details.
 
+The alpha channel when one is present is considered unassociated -
+ie. the color data has not been scaled by the alpha channel.  Note
+that not all code follows this (recent) rule, but will over time.
+
 =head2 Creating Imager Objects
 
 =over
diff --git a/rubthru.im b/rubthru.im
new file mode 100644 (file)
index 0000000..fdc23d3
--- /dev/null
@@ -0,0 +1,152 @@
+#include "imager.h"
+
+static int
+rubthru_targ_noalpha(i_img *im, i_img *src,
+                     int tx, int ty, 
+                     int src_minx, int src_miny,
+                     int src_maxx, int src_maxy) {
+  int x, y, ttx, tty;
+  int chancount;
+  int chans[3];
+  int alphachan;
+  int ch;
+
+  i_clear_error();
+
+  if (im->channels == 3 && src->channels == 4) {
+    chancount = 3;
+    chans[0] = 0; chans[1] = 1; chans[2] = 2;
+    alphachan = 3;
+  }
+  else if (im->channels == 3 && src->channels == 2) {
+    chancount = 3;
+    chans[0] = chans[1] = chans[2] = 0;
+    alphachan = 1;
+  }
+  else if (im->channels == 1 && src->channels == 2) {
+    chancount = 1;
+    chans[0] = 0;
+    alphachan = 1;
+  }
+  else {
+    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)");
+    return 0;
+  }
+
+#code im->bits <= 8 && src->bits <= 8
+  IM_WORK_T alpha;
+  IM_COLOR pv, orig, dest;
+
+  tty = ty;
+  for(y = src_miny; y < src_maxy; y++) {
+    ttx = tx;
+    for(x = src_minx; x < src_maxx; x++) {
+      IM_GPIX(src, x,   y,   &pv);
+      IM_GPIX(im,  ttx, tty, &orig);
+      alpha = pv.channel[alphachan];
+      for (ch = 0; ch < chancount; ++ch) {
+        dest.channel[ch] = (alpha * pv.channel[chans[ch]]
+                            + (IM_SAMPLE_MAX - alpha) * orig.channel[ch])/IM_SAMPLE_MAX;
+      }
+      IM_PPIX(im, ttx, tty, &dest);
+      ttx++;
+    }
+    tty++;
+  }
+#/code
+  return 1;
+}
+
+static int
+rubthru_targ_alpha(i_img *im, i_img *src, int tx, int ty, 
+                   int src_minx, int src_miny,
+                   int src_maxx, int src_maxy) {
+  int x, y, ttx, tty;
+  int chancount;
+  int chans[3];
+  int alphachan;
+  int ch;
+  int targ_alpha_chan;
+  
+  if (im->channels == 4 && src->channels == 4) {
+    chancount = 3;
+    chans[0] = 0; chans[1] = 1; chans[2] = 2;
+    alphachan = 3;
+  }
+  else if (im->channels == 4 && src->channels == 2) {
+    chancount = 3;
+    chans[0] = chans[1] = chans[2] = 0;
+    alphachan = 1;
+  }
+  else if (im->channels == 2 && src->channels == 2) {
+    chancount = 1;
+    chans[0] = 0;
+    alphachan = 1;
+  }
+  else {
+    i_push_error(0, "rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)");
+    return 0;
+  }
+
+  targ_alpha_chan = im->channels - 1;
+
+#code im->bits <= 8 && src->bits <= 8
+  IM_WORK_T src_alpha, orig_alpha, dest_alpha, remains;
+  IM_COLOR pv, orig, dest;
+
+  tty = ty;
+  for(y = src_miny; y < src_maxy; y++) {
+    ttx = tx;
+    for(x = src_minx; x < src_maxx; x++) {
+      IM_GPIX(src, x,   y,   &pv);
+      src_alpha = pv.channel[alphachan];
+      if (src_alpha) {
+        remains = IM_SAMPLE_MAX - src_alpha;
+        IM_GPIX(im,  ttx, tty, &orig);
+        orig_alpha = orig.channel[targ_alpha_chan];
+        dest_alpha = src_alpha + (remains * orig_alpha) / IM_SAMPLE_MAX;
+
+        for (ch = 0; ch < chancount; ++ch) {
+          dest.channel[ch] = 
+            ( src_alpha * pv.channel[chans[ch]]
+              + remains * orig.channel[ch] * orig_alpha / IM_SAMPLE_MAX 
+              ) / dest_alpha;
+        }
+        /* dest's alpha */
+        dest.channel[targ_alpha_chan] = dest_alpha;
+        IM_PPIX(im, ttx, tty, &dest);
+      }
+      ttx++;
+    }
+    tty++;
+  }
+#/code
+  return 1;
+}
+
+/*
+=item i_rubthru(im, src, tx, ty, src_minx, src_miny, src_maxx, src_maxy )
+
+=category Image
+
+Takes the sub image I<src[src_minx, src_maxx)[src_miny, src_maxy)> and
+overlays it at (I<tx>,I<ty>) on the image object.
+
+The alpha channel of each pixel in I<src> is used to control how much
+the existing colour in I<im> is replaced, if it is 255 then the colour
+is completely replaced, if it is 0 then the original colour is left 
+unmodified.
+
+=cut
+*/
+
+int
+i_rubthru(i_img *im, i_img *src, int tx, int ty, int src_minx, int src_miny,
+         int src_maxx, int src_maxy) {
+  if (im->channels == 1 || im->channels == 3)
+    return rubthru_targ_noalpha(im, src, tx, ty, src_minx, src_miny, 
+                                src_maxx, src_maxy);
+  else
+    return rubthru_targ_alpha(im, src, tx, ty, src_minx, src_miny,
+                              src_maxx, src_maxy);
+}
index b968b84..9f71293 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 use lib 't';
-use Test::More tests => 28;
+use Test::More tests => 38;
 BEGIN { use_ok(Imager => qw(:all :handy)); }
 
 init_log("testout/t69rubthru.log", 1);
@@ -77,7 +77,7 @@ my $oogtarg = Imager->new(xsize=>100, ysize=>100, channels=>1);
 ok(!$oogtarg->rubthrough(src=>$oosrc), "check oo fails correctly");
 
 is($oogtarg->errstr, 
-   'rubthru can only work where (dest, src) channels are (3,4), (3,2) or (1,2)',
+   'rubthru can only work where (dest, src) channels are (3,4), (4,4), (3,2), (4,2), (1,2) or (2,2)',
    "check error message");
 
 { # check empty image errors
@@ -89,6 +89,60 @@ is($oogtarg->errstr,
      "check error message");
 }
 
+{
+  # alpha source and target
+  my $src = Imager->new(xsize => 10, ysize => 1, channels => 4);
+  my $targ = Imager->new(xsize => 10, ysize => 2, channels => 4);
+
+  # simple initialization
+  $targ->setscanline('y' => 1, x => 1,
+                     pixels =>
+                     [
+                      NC(255, 128, 0, 255),
+                      NC(255, 128, 0, 128),
+                      NC(255, 128, 0, 0),
+                      NC(255, 128, 0, 255),
+                      NC(255, 128, 0, 128),
+                      NC(255, 128, 0, 0),
+                      NC(255, 128, 0, 255),
+                      NC(255, 128, 0, 128),
+                      NC(255, 128, 0, 0),
+                     ]);
+  $src->setscanline('y' => 0,
+                    pixels =>
+                    [
+                     NC(0, 128, 255, 0),
+                     NC(0, 128, 255, 0),
+                     NC(0, 128, 255, 0),
+                     NC(0, 128, 255, 128),
+                     NC(0, 128, 255, 128),
+                     NC(0, 128, 255, 128),
+                     NC(0, 128, 255, 255),
+                     NC(0, 128, 255, 255),
+                     NC(0, 128, 255, 255),
+                    ]);
+  ok($targ->rubthrough(src => $src,
+                       tx => 1, ty => 1), "do 4 on 4 rubthrough");
+  iscolora($targ->getpixel(x => 1, y => 1), NC(255, 128, 0, 255),
+           "check at zero source coverage on full targ coverage");
+  iscolora($targ->getpixel(x => 2, y => 1), NC(255, 128, 0, 128),
+           "check at zero source coverage on half targ coverage");
+  iscolora($targ->getpixel(x => 3, y => 1), NC(255, 128, 0, 0),
+           "check at zero source coverage on zero targ coverage");
+  iscolora($targ->getpixel(x => 4, y => 1), NC(127, 128, 128, 255),
+           "check at half source_coverage on full targ coverage");
+  iscolora($targ->getpixel(x => 5, y => 1), NC(85, 128, 170, 191),
+           "check at half source coverage on half targ coverage");
+  iscolora($targ->getpixel(x => 6, y => 1), NC(0, 128, 255, 128),
+           "check at half source coverage on zero targ coverage");
+  iscolora($targ->getpixel(x => 7, y => 1), NC(0, 128, 255, 255),
+           "check at full source_coverage on full targ coverage");
+  iscolora($targ->getpixel(x => 8, y => 1), NC(0, 128, 255, 255),
+           "check at full source coverage on half targ coverage");
+  iscolora($targ->getpixel(x => 9, y => 1), NC(0, 128, 255, 255),
+           "check at full source coverage on zero targ coverage");
+}
+
 sub color_cmp {
   my ($l, $r) = @_;
   my @l = $l->rgba;
@@ -99,4 +153,19 @@ sub color_cmp {
       || $l[2] <=> $r[2];
 }
 
+sub iscolora {
+  my ($c1, $c2, $msg) = @_;
+
+  my $builder = Test::Builder->new;
+  my @c1 = $c1->rgba;
+  my @c2 = $c2->rgba;
+  if (!$builder->ok($c1[0] == $c2[0] && $c1[1] == $c2[1] && $c1[2] == $c2[2]
+                    && $c1[3] == $c2[3],
+                    $msg)) {
+    $builder->diag(<<DIAG);
+      got color: [ @c1 ]
+ expected color: [ @c2 ]
+DIAG
+  }
+}