add the combine method
authorTony Cook <tony@develop=help.com>
Sun, 31 Oct 2010 12:58:45 +0000 (12:58 +0000)
committerTony Cook <tony@develop=help.com>
Sun, 31 Oct 2010 12:58:45 +0000 (12:58 +0000)
Changes
Imager.pm
Imager.xs
MANIFEST
Makefile.PL
combine.im [new file with mode: 0644]
imager.h
lib/Imager/Transformations.pod
t/t63combine.t [new file with mode: 0644]

diff --git a/Changes b/Changes
index a1adfa9..f077dcf 100644 (file)
--- a/Changes
+++ b/Changes
@@ -13,6 +13,10 @@ Imager 0.79 - unreleased
 
  - add wiggle.pl sample, as suggested by Dan Oppenheim.
 
+ - add the combine() method to combine channels from multiple source
+   images into a new image
+   https://rt.cpan.org/Ticket/Display.html?id=11872
+
 Bug fixes:
 
  - treat the co-efficients for convert() as doubles instead of floats.
index 89a3a9a..ad5ba70 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -3466,6 +3466,46 @@ sub convert {
   return $new;
 }
 
+# combine channels from multiple input images, a class method
+sub combine {
+  my ($class, %opts) = @_;
+
+  my $src = delete $opts{src};
+  unless ($src) {
+    $class->_set_error("src parameter missing");
+    return;
+  }
+  my @imgs;
+  my $index = 0;
+  for my $img (@$src) {
+    unless (eval { $img->isa("Imager") }) {
+      $class->_set_error("src must contain image objects");
+      return;
+    }
+    unless ($img->{IMG}) {
+      $class->_set_error("empty input image");
+      return;
+    }
+    push @imgs, $img->{IMG};
+  }
+  my $result;
+  if (my $channels = delete $opts{channels}) {
+    $result = i_combine(\@imgs, $channels);
+  }
+  else {
+    $result = i_combine(\@imgs);
+  }
+  unless ($result) {
+    $class->_set_error($class->_error_as_msg);
+    return;
+  }
+
+  my $img = $class->new;
+  $img->{IMG} = $result;
+
+  return $img;
+}
+
 
 # general function to map an image through lookup tables
 
@@ -4195,6 +4235,9 @@ circle() - L<Imager::Draw/circle> - draw a filled circle
 colorcount() - L<Imager::Draw/colorcount> - the number of colors in an
 image's palette (paletted images only)
 
+combine() - L<Imager::Transformations/combine> - combine channels from one or
+more images.
+
 combines() - L<Imager::Draw/combines> - return a list of the different
 combine type keywords
 
@@ -4399,6 +4442,8 @@ boxes, drawing - L<Imager::Draw/box>
 
 changes between image - L<Imager::Filters/"Image Difference">
 
+channels, combine into one image - L<Imager::Transformations/combine>
+
 color - L<Imager::Color>
 
 color names - L<Imager::Color>, L<Imager::Color::Table>
index 3a9ee1b..4a82c12 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1757,6 +1757,47 @@ i_compose_mask(out, src, mask, out_left, out_top, src_left, src_top, mask_left,
        int combine
        double opacity
 
+Imager::ImgRaw
+i_combine(src_av, channels_av = NULL)
+       AV *src_av
+       AV *channels_av
+  PREINIT:
+       i_img **imgs = NULL;
+       STRLEN in_count;
+       int *channels = NULL;
+       int i;
+       SV **psv;
+       IV tmp;
+  CODE:
+       in_count = av_len(src_av) + 1;
+       if (in_count > 0) {
+         imgs = mymalloc(sizeof(i_img*) * in_count);
+         channels = mymalloc(sizeof(int) * in_count);
+         for (i = 0; i < in_count; ++i) {
+           psv = av_fetch(src_av, i, 0);
+           if (!psv || !*psv || !sv_derived_from(*psv, "Imager::ImgRaw")) {
+             myfree(imgs);
+             myfree(channels);
+             croak("imgs must contain only images");
+           }
+           tmp = SvIV((SV*)SvRV(*psv));
+           imgs[i] = INT2PTR(i_img*, tmp);
+           if (channels_av &&
+               (psv = av_fetch(channels_av, i, 0)) != NULL &&
+               *psv) {
+             channels[i] = SvIV(*psv);
+           }
+           else {
+             channels[i] = 0;
+           }
+         }
+       }
+       RETVAL = i_combine(imgs, channels, in_count);
+       myfree(imgs);
+       myfree(channels);
+  OUTPUT:
+       RETVAL
+
 undef_int
 i_flipxy(im, direction)
     Imager::ImgRaw     im
index ce6501d..08f408a 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -170,6 +170,7 @@ apidocs.perl    Build lib/Imager/APIRef.pm
 bigtest.perl   Library selection tester
 bmp.c           Reading and writing Windows BMP files
 color.c         Color translation and handling
+combine.im     Channel combine
 compose.im
 conv.im
 convert.im
@@ -311,6 +312,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
+samples/wiggle.pl      "Wiggle" stereoscopy
 scale.im       Newer scaling code
 spot.perl      For making an ordered dither matrix from a spot function
 stackmach.c
@@ -349,6 +351,7 @@ t/t57infix.t
 t/t58trans2.t
 t/t59assem.t
 t/t61filters.t
+t/t63combine.t         Test combine() method
 t/t64copyflip.t         Test copy, flip, rotate, matrix_transform
 t/t65crop.t
 t/t66paste.t
index ecc0582..42d20ae 100644 (file)
@@ -157,7 +157,7 @@ if ($^O eq 'hpux')                { $OSLIBS .= ' -ldld'; }
 if (defined $Config{'d_dlsymun'}) { $OSDEF  .= ' -DDLSYMUN'; }
 
 my @objs = qw(Imager.o draw.o polygon.o image.o io.o iolayer.o
-              log.o gaussian.o conv.o pnm.o raw.o feat.o font.o
+              log.o gaussian.o conv.o pnm.o raw.o feat.o font.o combine.o
               filters.o dynaload.o stackmach.o datatypes.o
               regmach.o trans2.o quant.o error.o convert.o
               map.o tags.o palimg.o maskimg.o img16.o rotate.o
diff --git a/combine.im b/combine.im
new file mode 100644 (file)
index 0000000..37e174d
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+=head1 NAME
+
+combine.im - combining channels into an image
+
+=head1 SYNOPSIS
+
+  out = i_combine(imgs, channels, count);
+
+=head1 DESCRIPTION
+
+Combines channels from the input images into an output image.
+
+=over
+
+=cut
+*/
+
+#include "imager.h"
+
+i_img *
+i_combine(i_img **imgs, const int *channels, int in_count) {
+  i_img *out = NULL;
+  int maxbits = 0;
+  i_img *maximg = NULL;
+  int i;
+  i_img_dim width, height;
+  i_img_dim x, y;
+
+  i_clear_error();
+  if (in_count <= 0) {
+    i_push_error(0, "At least one image must be supplied");
+    return NULL;
+  }
+  if (in_count > MAXCHANNELS) {
+    i_push_errorf(0, "Maximum of %d channels, you supplied %d",
+                 MAXCHANNELS, in_count);
+    return NULL;
+  }
+
+  width = imgs[0]->xsize;
+  height = imgs[0]->ysize;
+  for (i = 0; i < in_count; ++i) {
+    if (imgs[i]->bits > maxbits) {
+      maximg = imgs[i];
+      maxbits = maximg->bits;
+    }
+    if (imgs[i]->xsize < width)
+      width = imgs[i]->xsize;
+    if (imgs[i]->ysize < height)
+      height = imgs[i]->ysize;
+    if (channels[i] < 0) {
+      i_push_error(0, "Channel numbers must be zero or positive");
+      return NULL;
+    }
+    if (channels[i] >= imgs[i]->channels) {
+      i_push_errorf(0, "Channel %d for image %d is too high (%d channels)",
+                   channels[i], i, imgs[i]->channels);
+      return NULL;
+    }
+  }
+
+  out = i_sametype_chans(maximg, width, height, in_count);
+  if (!out)
+    return NULL;
+#code maxbits <= i_8_bits
+  IM_SAMPLE_T *in_row = mymalloc(sizeof(IM_SAMPLE_T) * width);
+  IM_COLOR *out_row = mymalloc(sizeof(IM_COLOR) * width);
+
+  for (y = 0; y < height; ++y) {
+    for (i = 0; i < in_count; ++i) {
+      IM_GSAMP(imgs[i], 0, width, y, in_row, channels + i, 1);
+      for (x = 0; x < width; ++x)
+       out_row[x].channel[i] = in_row[x];
+    }
+    IM_PLIN(out, 0, width, y, out_row);
+  }
+#/code
+
+  return out;
+}
index 085fea7..a803382 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -190,6 +190,9 @@ i_compose(i_img *out, i_img *src,
               int out_left, int out_top, int src_left, int src_top,
               int width, int height, int combine, double opacity);
 
+extern i_img *
+i_combine(i_img **src, const int *channels, int in_count);
+
 undef_int i_flipxy (i_img *im, int direction);
 extern i_img *i_rotate90(i_img *im, int degrees);
 extern i_img *i_rotate_exact(i_img *im, double amount);
index 4a6557c..39aea2a 100644 (file)
@@ -48,6 +48,11 @@ Imager::Transformations - Simple transformations of one image into another.
                                  [ 1, 0, 0 ],
                                  [ 0, 0, 1 ] ]);
 
+  # build an image using channels from multiple input images
+  $new = $img->combine(src => [ $im1, $im2, $im3 ]);
+  $new = $img->combine(src => [ $im1, $im2, $im3 ],
+                       channels => [ 2, 1, 0 ]);
+
   # limit the range of red channel from 0..255 to 0..127
   @map = map { int( $_/2 } 0..255;
   $img->map( red=>\@map );
@@ -867,6 +872,41 @@ alpha channel:
                                            [ 0, 0, 0, 0.5 ],
                                          ]);
 
+=item combine
+X<combine>
+
+Combine channels from one or more input images into a new image.
+
+Parameters:
+
+=over
+
+=item *
+
+C<src> - a reference to an array of input images.  There must be at least
+one input image.  A given image may appear more than once in C<src>.
+
+=item *
+
+C<channels> - a reference to an array of channels corresponding to the
+source images.  If C<channels> is not supplied then the first channel
+from each input image is used.  If the array referenced by C<channels>
+is shorter than that referenced by C<src> then the first channel is
+used from the extra images.
+
+=back
+
+  # make an rgb image from red, green, and blue images
+  my $rgb = Imager->combine(src => [ $red, $green, $blue ]);
+
+  # convert a BGR image into RGB
+  my $rgb = Imager->combine(src => [ $bgr, $bgr, $bgr ],
+                            channels => [ 2, 1, 0 ]);
+
+  # add an alpha channel from another image
+  my $rgba = Imager->combine(src => [ $rgb, $rgb, $rgb, $alpha ],
+                     channels => [ 0, 1, 2, 0 ]);
+
 =back
 
 =head2 Color Mappings
diff --git a/t/t63combine.t b/t/t63combine.t
new file mode 100644 (file)
index 0000000..b94b97a
--- /dev/null
@@ -0,0 +1,101 @@
+#!perl -w
+use strict;
+use Imager;
+use Test::More tests => 31;
+use Imager::Test qw/test_image test_image_double is_image/;
+
+my $test_im = test_image;
+my $test_im_dbl = test_image_double;
+
+{
+  # split out channels and put it back together
+  my $red = Imager->combine(src => [ $test_im ]);
+  ok($red, "extracted the red channel");
+  is($red->getchannels, 1, "red should be a single channel");
+  my $green = Imager->combine(src => [ $test_im ], channels => [ 1 ]);
+  ok($green, "extracted the green channel");
+  is($green->getchannels, 1, "green should be a single channel");
+  my $blue = $test_im->convert(preset => "blue");
+  ok($blue, "extracted blue (via convert)");
+
+  # put them back together
+  my $combined = Imager->combine(src => [ $red, $green, $blue ]);
+  is($combined->getchannels, 3, "check we got a three channel image");
+  is_image($combined, $test_im, "presto! check it's the same");
+}
+
+{
+  # no src
+  ok(!Imager->combine(), "no src");
+  is(Imager->errstr, "src parameter missing", "check message");
+}
+
+{
+  # bad image error
+  my $im = Imager->new;
+  ok(!Imager->combine(src => [ $im ]), "empty image");
+  is(Imager->errstr, "empty input image", "check message");
+}
+
+{
+  # not an image
+  my $im = {};
+  ok(!Imager->combine(src => [ $im ]), "not an image");
+  is(Imager->errstr, "src must contain image objects", "check message");
+}
+
+{
+  # no images
+  ok(!Imager->combine(src => []), "no images");
+  is(Imager->errstr, "At least one image must be supplied",
+     "check message");
+}
+
+{
+  # too many images
+  ok(!Imager->combine(src => [ ($test_im) x 5 ]), "too many source images");
+  is(Imager->errstr, "Maximum of 4 channels, you supplied 5",
+     "check message");
+}
+
+{
+  # negative channel
+  ok(!Imager->combine(src => [ $test_im ], channels => [ -1 ]),
+     "negative channel");
+  is(Imager->errstr, "Channel numbers must be zero or positive",
+     "check message");
+}
+
+{
+  # channel too high
+  ok(!Imager->combine(src => [ $test_im ], channels => [ 3 ]),
+     "too high channel");
+  is(Imager->errstr, "Channel 3 for image 0 is too high (3 channels)",
+     "check message");
+}
+
+{
+  # make sure we get the higher of the bits
+  my $out = Imager->combine(src => [ $test_im, $test_im_dbl ]);
+  ok($out, "make from 8 and double/sample images");
+  is($out->bits, "double", "check output bits");
+}
+
+{
+  # check high-bit processing
+  # split out channels and put it back together
+  my $red = Imager->combine(src => [ $test_im_dbl ]);
+  ok($red, "extracted the red channel");
+  is($red->getchannels, 1, "red should be a single channel");
+  my $green = Imager->combine(src => [ $test_im_dbl ], channels => [ 1 ]);
+  ok($green, "extracted the green channel");
+  is($green->getchannels, 1, "green should be a single channel");
+  my $blue = $test_im_dbl->convert(preset => "blue");
+  ok($blue, "extracted blue (via convert)");
+
+  # put them back together
+  my $combined = Imager->combine(src => [ $red, $green, $blue ]);
+  is($combined->getchannels, 3, "check we got a three channel image");
+  is_image($combined, $test_im_dbl, "presto! check it's the same");
+  is($combined->bits, "double", "and we got a double image output");
+}