image based fills
authorTony Cook <tony@develop=help.com>
Wed, 19 Sep 2001 02:15:08 +0000 (02:15 +0000)
committerTony Cook <tony@develop=help.com>
Wed, 19 Sep 2001 02:15:08 +0000 (02:15 +0000)
Changes
Imager.xs
fills.c
image.h
lib/Imager/Fill.pm
t/t20fill.t

diff --git a/Changes b/Changes
index 1b1d1d2..e94d7bc 100644 (file)
--- a/Changes
+++ b/Changes
@@ -513,6 +513,7 @@ Revision history for Perl extension Imager.
           if it's small enough
         - $img->arc() now calls i_circle_aa() if a complete circle is
           being drawn in a plain color
+        - image based fills
 
 =================================================================
 
index 01d146e..ea122a1 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -3189,3 +3189,38 @@ i_new_fill_hatchf(fg, bg, combine, hatch, cust_hatch, dx, dy)
       OUTPUT:
         RETVAL
 
+Imager::FillHandle
+i_new_fill_image(src, matrix, xoff, yoff, combine)
+        Imager::ImgRaw src
+        int xoff
+        int yoff
+        int combine
+      PREINIT:
+        double matrix[9];
+        double *matrixp;
+        AV *av;
+        IV len;
+        SV *sv1;
+        int i;
+      CODE:
+        if (!SvOK(ST(1))) {
+          matrixp = NULL;
+        }
+        else {
+          if (!SvROK(ST(1)) || SvTYPE(SvRV(ST(1))) != SVt_PVAV)
+            croak("i_new_fill_image: parameter must be an arrayref");
+         av=(AV*)SvRV(ST(1));
+         len=av_len(av)+1;
+          if (len > 9)
+            len = 9;
+          for (i = 0; i < len; ++i) {
+           sv1=(*(av_fetch(av,i,0)));
+           matrix[i] = SvNV(sv1);
+          }
+          for (; i < 9; ++i)
+            matrix[i] = 0;
+          matrixp = matrix;
+        }
+        RETVAL = i_new_fill_image(src, matrixp, xoff, yoff, combine);
+      OUTPUT:
+        RETVAL
diff --git a/fills.c b/fills.c
index d91fb98..196d91c 100644 (file)
--- a/fills.c
+++ b/fills.c
@@ -16,6 +16,7 @@ fills.c - implements the basic general fills
   fill = i_new_fill_solid(&c1, combine);
   fill = i_new_fill_hatchf(&fc1, &fc2, combine, hatch, cust_hash, dx, dy);
   fill = i_new_fill_hatch(&c1, &c2, combine, hatch, cust_hash, dx, dy);
+  fill = i_new_fill_image(im, matrix, xoff, yoff, combine);
   i_fill_destroy(fill);
 
 =head1 DESCRIPTION
@@ -462,6 +463,58 @@ i_new_fill_hatchf(i_fcolor *fg, i_fcolor *bg, int combine, int hatch,
                          dx, dy);
 }
 
+static void fill_image(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_color *data, i_color *work);
+static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_fcolor *data, i_fcolor *work);
+struct i_fill_image_t {
+  i_fill_t base;
+  i_img *src;
+  int xoff, yoff;
+  int has_matrix;
+  double matrix[9];
+};
+
+/*
+=item i_new_fill_image(im, matrix, xoff, yoff, combine)
+
+Create an image based fill.
+
+=cut
+*/
+i_fill_t *
+i_new_fill_image(i_img *im, double *matrix, int xoff, int yoff, int combine) {
+  struct i_fill_image_t *fill = mymalloc(sizeof(*fill));
+
+  fill->base.fill_with_color = fill_image;
+  fill->base.fill_with_fcolor = fill_imagef;
+  fill->base.destroy = NULL;
+
+  if (combine) {
+    i_get_combine(combine, &fill->base.combine, &fill->base.combinef);
+  }
+  else {
+    fill->base.combine = NULL;
+    fill->base.combinef = NULL;
+  }
+  fill->src = im;
+  if (xoff < 0)
+    xoff += im->xsize;
+  fill->xoff = xoff;
+  if (yoff < 0)
+    yoff += im->ysize;
+  fill->yoff = yoff;
+  if (matrix) {
+    fill->has_matrix = 1;
+    memcpy(fill->matrix, matrix, sizeof(fill->matrix));
+  }
+  else
+    fill->has_matrix = 0;
+
+  return &fill->base;
+}
+
+
 #define T_SOLID_FILL(fill) ((i_fill_solid_t *)(fill))
 
 /*
@@ -654,6 +707,208 @@ static void fill_hatchf(i_fill_t *fill, int x, int y, int width, int channels,
   }
 }
 
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_color interp_i_color(i_color before, i_color after, double pos,
+                              int channels) {
+  i_color out;
+  int ch;
+
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+  if (out.channel[3])
+    for (ch = 0; ch < channels; ++ch)
+      if (ch != 3) {
+        int temp = out.channel[ch] * 255 / out.channel[3];
+        if (temp > 255)
+          temp = 255;
+        out.channel[ch] = temp;
+      }
+
+  return out;
+}
+
+/* hopefully this will be inlined  (it is with -O3 with gcc 2.95.4) */
+/* linear interpolation */
+static i_fcolor interp_i_fcolor(i_fcolor before, i_fcolor after, double pos,
+                                int channels) {
+  i_fcolor out;
+  int ch;
+
+  pos -= floor(pos);
+  for (ch = 0; ch < channels; ++ch)
+    out.channel[ch] = (1-pos) * before.channel[ch] + pos * after.channel[ch];
+  if (out.channel[3])
+    for (ch = 0; ch < channels; ++ch)
+      if (ch != 3) {
+        int temp = out.channel[ch] / out.channel[3];
+        if (temp > 1.0)
+          temp = 1.0;
+        out.channel[ch] = temp;
+      }
+
+  return out;
+}
+
+/*
+=item fill_image(fill, x, y, width, channels, data, work)
+
+=cut
+*/
+static void fill_image(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_color *data, i_color *work) {
+  struct i_fill_image_t *f = (struct i_fill_image_t *)fill;
+  i_color *out = fill->combine ? work : data;
+  int i = 0;
+  i_color c;
+  
+  if (f->has_matrix) {
+    /* the hard way */
+    while (i < width) {
+      double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2];
+      double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5];
+      double ix = floor(rx / f->src->xsize);
+      double iy = floor(ry / f->src->ysize);
+      i_color c[2][2];
+      i_color c2[2];
+      int dy;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = floor(rx / f->src->xsize);
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = floor(ry / f->src->ysize);
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+
+      for (dy = 0; dy < 2; ++dy) {
+        if ((int)rx == f->src->xsize-1) {
+          i_gpix(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]);
+          i_gpix(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]);
+        }
+        else {
+          i_glin(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, 
+                 c[dy]);
+        }
+        c2[dy] = interp_i_color(c[dy][0], c[dy][1], rx, f->src->channels);
+      }
+      *out++ = interp_i_color(c2[0], c2[1], ry, f->src->channels);
+      ++i;
+    }
+  }
+  else {
+    /* the easy way */
+    /* this should be possible to optimize to use i_glin() */
+    while (i < width) {
+      int rx = x+i;
+      int ry = y;
+      int ix = rx / f->src->xsize;
+      int iy = ry / f->src->ysize;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = rx / f->src->xsize;
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = ry / f->src->xsize;
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+      i_gpix(f->src, rx, ry, out);
+      ++out;
+      ++i;
+    }
+  }
+
+  if (fill->combine) {
+    (fill->combine)(data, work, channels, width);
+  }
+}
+
+/*
+=item fill_image(fill, x, y, width, channels, data, work)
+
+=cut
+*/
+static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_fcolor *data, i_fcolor *work) {
+  struct i_fill_image_t *f = (struct i_fill_image_t *)fill;
+  i_fcolor *out = fill->combine ? work : data;
+  int i = 0;
+  i_fcolor c;
+  
+  if (f->has_matrix) {
+    /* the hard way */
+    while (i < width) {
+      double rx = f->matrix[0] * (x+i) + f->matrix[1] * y + f->matrix[2];
+      double ry = f->matrix[3] * (x+i) + f->matrix[4] * y + f->matrix[5];
+      double ix = floor(rx / f->src->xsize);
+      double iy = floor(ry / f->src->ysize);
+      i_fcolor c[2][2];
+      i_fcolor c2[2];
+      int dy;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = floor(rx / f->src->xsize);
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = floor(ry / f->src->ysize);
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+
+      for (dy = 0; dy < 2; ++dy) {
+        if ((int)rx == f->src->xsize-1) {
+          i_gpixf(f->src, f->src->xsize-1, ((int)ry+dy) % f->src->ysize, &c[dy][0]);
+          i_gpixf(f->src, 0, ((int)ry+dy) % f->src->xsize, &c[dy][1]);
+        }
+        else {
+          i_glinf(f->src, (int)rx, (int)rx+2, ((int)ry+dy) % f->src->ysize, 
+                 c[dy]);
+        }
+        c2[dy] = interp_i_fcolor(c[dy][0], c[dy][1], rx, f->src->channels);
+      }
+      *out++ = interp_i_fcolor(c2[0], c2[1], ry, f->src->channels);
+      ++i;
+    }
+  }
+  else {
+    /* the easy way */
+    /* this should be possible to optimize to use i_glin() */
+    while (i < width) {
+      int rx = x+i;
+      int ry = y;
+      int ix = rx / f->src->xsize;
+      int iy = ry / f->src->ysize;
+
+      if (f->xoff) {
+        rx += iy * f->xoff;
+        ix = rx / f->src->xsize;
+      }
+      else if (f->yoff) {
+        ry += ix * f->yoff;
+        iy = ry / f->src->xsize;
+      }
+      rx -= ix * f->src->xsize;
+      ry -= iy * f->src->ysize;
+      i_gpixf(f->src, rx, ry, out);
+      ++out;
+      ++i;
+    }
+  }
+
+  if (fill->combinef) {
+    (fill->combinef)(data, work, channels, width);
+  }
+}
+
 static void combine_replace(i_color *, i_color *, int, int);
 static void combine_replacef(i_fcolor *, i_fcolor *, int, int);
 static void combine_alphablend(i_color *, i_color *, int, int);
diff --git a/image.h b/image.h
index 3e74e40..d3ed9c5 100644 (file)
--- a/image.h
+++ b/image.h
@@ -181,6 +181,8 @@ i_new_fill_hatch(i_color *fg, i_color *bg, int combine, int hatch,
 extern i_fill_t *
 i_new_fill_hatchf(i_fcolor *fg, i_fcolor *bg, int combine, int hatch, 
                   unsigned char *cust_hatch, int dx, int dy);
+extern i_fill_t *
+i_new_fill_image(i_img *im, double *matrix, int xoff, int yoff, int combine);
 extern void i_fill_destroy(i_fill_t *fill);
 
 float i_gpix_pch(i_img *im,int x,int y,int ch);
index 229fbd2..037f0d6 100644 (file)
@@ -149,6 +149,14 @@ sub new {
                   $hsh{ftype}, $hsh{repeat}, $hsh{combine}, $hsh{super_sample},
                   $hsh{ssample_param}, $hsh{segments});
   }
+  elsif (defined $hsh{image}) {
+    $hsh{xoff} ||= 0;
+    $hsh{yoff} ||= 0;
+    $self->{fill} =
+      Imager::i_new_fill_image($hsh{image}{IMG}, $hsh{matrix}, $hsh{xoff}, 
+                               $hsh{yoff}, $hsh{combine});
+    $self->{DEPS} = [ $hsh{image}{IMG} ];
+  }
   else {
     $Imager::ERRSTR = "No fill type specified";
     warn "No fill type!";
@@ -177,6 +185,8 @@ sub combines {
   my $fill1 = Imager::Fill->new(solid=>$color, combine=>$combine);
   my $fill2 = Imager::Fill->new(hatch=>'vline2', fg=>$color1, bg=>$color2,
                                 dx=>$dx, dy=>$dy);
+  my $fill3 = Imager::Fill->new(fountain=>$type, ...);
+  my $fill4 = Imager::Fill->new(image=>$img, ...);
 
 =head1 DESCRIPTION 
 
@@ -402,6 +412,22 @@ same fill as the C<fountain> filter, but is restricted to the shape
 you are drawing, and the fountain parameter supplies the fill type,
 and is required.
 
+=head2 Image Fills
+
+  my $fill = Imager::Fill->new(image=>$src, xoff=>$xoff, yoff=>$yoff,
+                               matrix=>$matrix, $combine);
+
+Fills the given image with a tiled version of the given image.  The
+first non-zero value of xoff or yoff will provide an offset along the
+given axis between rows or columns of tiles respectively.
+
+The matrix parameter performs a co-ordinate transformation from the
+co-ordinates in the target image to the fill image co-ordinates.
+Linear interpolation is used to determine the fill pixel.  You can use
+the L<Imager::Matrix2d> class to create transformation matrices.
+
+The matrix parameter will significantly slow down the fill.
+
 =head1 OTHER METHODS
 
 =over
@@ -422,10 +448,6 @@ I'm planning on adding the following types of fills:
 
 =over
 
-=item image
-
-tiled image fill
-
 =item checkerboard
 
 combines 2 other fills in a checkerboard
index d47a450..ae84303 100644 (file)
@@ -1,7 +1,7 @@
 #!perl -w
 use strict;
 
-print "1..35\n";
+print "1..37\n";
 
 use Imager ':handy';
 use Imager::Fill;
@@ -198,6 +198,38 @@ for my $comb (Imager::Fill->combines) {
 ok($testnum++, $ffim->arc(r=>45, color=>$blue, aa=>1), "aa circle");
 $ffim->write(file=>"testout/t20_aacircle.ppm");
 
+# image based fills
+my $green = NC(0, 255, 0);
+my $fillim = Imager->new(xsize=>40, ysize=>40, channels=>4);
+$fillim->box(filled=>1, xmin=>5, ymin=>5, xmax=>35, ymax=>35, 
+             color=>NC(0, 0, 255, 128));
+$fillim->arc(filled=>1, r=>10, color=>$green, aa=>1);
+my $ooim = Imager->new(xsize=>150, ysize=>150);
+$ooim->box(filled=>1, color=>$green, xmin=>70, ymin=>25, xmax=>130, ymax=>125);
+$ooim->box(filled=>1, color=>$blue, xmin=>20, ymin=>25, xmax=>80, ymax=>125);
+$ooim->arc(r=>30, color=>$red, aa=>1);
+
+my $oocopy = $ooim->copy();
+ok($testnum++, 
+   $oocopy->arc(fill=>{image=>$fillim, 
+                       combine=>'normal',
+                       xoff=>5}, r=>40),
+   "image based fill");
+$oocopy->write(file=>'testout/t20_image.ppm');
+
+# a more complex version
+use Imager::Matrix2d ':handy';
+$oocopy = $ooim->copy;
+ok($testnum++,
+   $oocopy->arc(fill=>{
+                       image=>$fillim,
+                       combine=>'normal',
+                       matrix=>m2d_rotate(degrees=>30),
+                       xoff=>5
+                       }, r=>40),
+   "transformed image based fill");
+$oocopy->write(file=>'testout/t20_image_xform.ppm');
+
 sub ok ($$$) {
   my ($num, $test, $desc) = @_;