add opacity adaption fills
authorTony Cook <tony@develop=help.com>
Fri, 6 Nov 2009 09:42:03 +0000 (09:42 +0000)
committerTony Cook <tony@develop=help.com>
Fri, 6 Nov 2009 09:42:03 +0000 (09:42 +0000)
Imager.xs
fills.c
imager.h
lib/Imager/Fill.pm
lib/Imager/Test.pm
t/t20fill.t

index 5968c30..539836a 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -3379,6 +3379,11 @@ i_new_fill_fount(xa, ya, xb, yb, type, repeat, combine, super_sample, ssample_pa
       OUTPUT:
         RETVAL
 
+Imager::FillHandle
+i_new_fill_opacity(other_fill, alpha_mult)
+    Imager::FillHandle other_fill
+    double alpha_mult
+
 void
 i_errors()
       PREINIT:
diff --git a/fills.c b/fills.c
index 9bb2cb1..e386d8a 100644 (file)
--- a/fills.c
+++ b/fills.c
@@ -17,6 +17,7 @@ fills.c - implements the basic general fills
   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);
+  fill = i_new_fill_opacity(fill, alpha_mult);
   i_fill_destroy(fill);
 
 =head1 DESCRIPTION
@@ -528,6 +529,38 @@ i_new_fill_image(i_img *im, const double *matrix, int xoff, int yoff, int combin
   return &fill->base;
 }
 
+static void fill_opacity(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_color *data);
+static void fill_opacityf(i_fill_t *fill, int x, int y, int width, int channels,
+                       i_fcolor *data);
+
+struct i_fill_opacity_t {
+  i_fill_t base;
+  i_fill_t *other_fill;
+  double alpha_mult;
+};
+
+static struct i_fill_opacity_t
+opacity_fill_proto =
+  {
+    fill_opacity,
+    fill_opacityf,
+    NULL
+  };
+
+i_fill_t *
+i_new_fill_opacity(i_fill_t *base_fill, double alpha_mult) {
+  struct i_fill_opacity_t *fill = mymalloc(sizeof(*fill));
+  *fill = opacity_fill_proto;
+
+  fill->base.combine = base_fill->combine;
+  fill->base.combinef = base_fill->combinef;
+
+  fill->other_fill = base_fill;
+  fill->alpha_mult = alpha_mult;
+
+  return &fill->base;
+}
 
 #define T_SOLID_FILL(fill) ((i_fill_solid_t *)(fill))
 
@@ -901,6 +934,45 @@ static void fill_imagef(i_fill_t *fill, int x, int y, int width, int channels,
     i_adapt_fcolors(want_channels, f->src->channels, data, width);
 }
 
+static void 
+fill_opacity(i_fill_t *fill, int x, int y, int width, int channels,
+            i_color *data) {
+  struct i_fill_opacity_t *f = (struct i_fill_opacity_t *)fill;
+  int alpha_chan = channels-1; /* channels is always 2 or 4 */
+  i_color *datap = data;
+  
+  (f->other_fill->f_fill_with_color)(f->other_fill, x, y, width, channels, data);
+  while (width--) {
+    double new_alpha = datap->channel[alpha_chan] * f->alpha_mult;
+    if (new_alpha < 0) 
+      datap->channel[alpha_chan] = 0;
+    else if (new_alpha > 255)
+      datap->channel[alpha_chan] = 255;
+    else datap->channel[alpha_chan] = (int)(new_alpha + 0.5);
+
+    ++datap;
+  }
+}
+static void 
+fill_opacityf(i_fill_t *fill, int x, int y, int width, int channels,
+           i_fcolor *data) {
+  struct i_fill_opacity_t *f = (struct i_fill_alpha_t *)fill;
+  int alpha_chan = channels-1; /* channels is always 2 or 4 */
+  i_fcolor *datap = data;
+  
+  (f->other_fill->f_fill_with_fcolor)(f->other_fill, x, y, width, channels, data);
+  
+  while (width--) {
+    double new_alpha = datap->channel[alpha_chan] * f->alpha_mult;
+    if (new_alpha < 0) 
+      datap->channel[alpha_chan] = 0;
+    else if (new_alpha > 1.0)
+      datap->channel[alpha_chan] = 1.0;
+    else datap->channel[alpha_chan] = new_alpha;
+
+    ++datap;
+  }
+}
 
 /*
 =back
index 2508b86..7c3d22f 100644 (file)
--- a/imager.h
+++ b/imager.h
@@ -155,6 +155,7 @@ i_new_fill_hatchf(const i_fcolor *fg, const i_fcolor *bg, int combine, int hatch
                   const unsigned char *cust_hatch, int dx, int dy);
 extern i_fill_t *
 i_new_fill_image(i_img *im, const double *matrix, int xoff, int yoff, int combine);
+extern i_fill_t *i_new_fill_opacity(i_fill_t *, double alpha_mult);
 extern void i_fill_destroy(i_fill_t *fill);
 
 float i_gpix_pch(i_img *im,int x,int y,int ch);
index b4e4fe3..3901fbe 100644 (file)
@@ -125,6 +125,24 @@ sub new {
                                $hsh{yoff}, $hsh{combine});
     $self->{DEPS} = [ $hsh{image}{IMG} ];
   }
+  elsif (defined $hsh{type} && $hsh{type} eq "opacity") {
+    my $other_fill = delete $hsh{other};
+    unless (defined $other_fill) {
+      Imager->_set_error("'other' parameter required to create alpha fill");
+      return;
+    }
+    unless (eval { $other_fill->isa("Imager::Fill") }) {
+      Imager->_set_error("'other' parameter must be an Imager::Fill object to create an alpha fill");
+      return;
+    }
+
+    my $raw_fill = $other_fill->{fill};
+    my $opacity = delete $hsh{opacity};
+    defined $opacity or $opacity = 0.5; # some sort of default
+    $self->{fill} = 
+      Imager::i_new_fill_opacity($raw_fill, $opacity);
+    $self->{DEPS} = [ $other_fill ]; # keep reference to old fill and its deps
+  }
   else {
     $Imager::ERRSTR = "No fill type specified";
     warn "No fill type!";
@@ -158,6 +176,7 @@ sub combines {
                                 dx=>$dx, dy=>$dy);
   my $fill3 = Imager::Fill->new(fountain=>$type, ...);
   my $fill4 = Imager::Fill->new(image=>$img, ...);
+  my $fill5 = Imager::Fill->new(type => "alpha", other => $fill, alpha => ...);
 
 =head1 DESCRIPTION 
 
@@ -341,6 +360,35 @@ the L<Imager::Matrix2d> class to create transformation matrices.
 
 The matrix parameter will significantly slow down the fill.
 
+=head2 Opacity modification fill
+
+  my $fill = Imager::Fill->new(type => "opacity"
+
+This can be used to make a fill that is a more translucent of opaque
+version of an existing fill.  This is intended for use where you
+receive a fill object as a parameter and need to change the opacity.
+
+Parameters:
+
+=over
+
+=item *
+
+type => "alpha" - Required
+
+=item *
+
+other - the fill to produce a modified version of.  This must be an
+Imager::Fill object.  Required.
+
+=item *
+
+opacity - multiplier for the source fill opacity.  Default: 0.5.
+
+=back
+
+The source fill's combine mode is used.
+
 =head1 OTHER METHODS
 
 =over
index e2e2756..2fd6408 100644 (file)
@@ -127,10 +127,10 @@ sub is_color4($$$$$$) {
                       && $ca == $alpha, $comment)) {
     $builder->diag(<<END_DIAG);
 Color mismatch:
-  Red: $red vs $cr
-Green: $green vs $cg
- Blue: $blue vs $cb
-Alpha: $alpha vs $ca
+  Red: $cr vs $red
+Green: $cg vs $green
+ Blue: $cb vs $blue
+Alpha: $ca vs $alpha
 END_DIAG
     return;
   }
@@ -168,10 +168,10 @@ sub is_fcolor4($$$$$$;$) {
                       && abs($ca - $alpha) <= $mindiff, $comment)) {
     $builder->diag(<<END_DIAG);
 Color mismatch:
-  Red: $red vs $cr
-Green: $green vs $cg
- Blue: $blue vs $cb
-Alpha: $alpha vs $ca
+  Red: $cr vs $red
+Green: $cg vs $green
+ Blue: $cb vs $blue
+Alpha: $ca vs $alpha
 END_DIAG
     return;
   }
index ff6471f..0b2118d 100644 (file)
@@ -1,11 +1,11 @@
 #!perl -w
 use strict;
-use Test::More tests => 129;
+use Test::More tests => 143;
 
 use Imager ':handy';
 use Imager::Fill;
 use Imager::Color::Float;
-use Imager::Test qw(is_image);
+use Imager::Test qw(is_image is_color4 is_fcolor4);
 use Config;
 
 Imager::init_log("testout/t20fill.log", 1);
@@ -466,6 +466,108 @@ SKIP:
   }
 }
 
+{ # alpha modifying fills
+  { # 8-bit/sample
+    my $base_img = Imager->new(xsize => 4, ysize => 2, channels => 4);
+    $base_img->setscanline
+      (
+       x => 0, 
+       y => 0, 
+       pixels => 
+       [
+       map Imager::Color->new($_),
+       qw/FF000020 00FF0080 00008040 FFFF00FF/,
+       ],
+      );
+    $base_img->setscanline
+      (
+       x => 0, 
+       y => 1, 
+       pixels => 
+       [
+       map Imager::Color->new($_),
+       qw/FFFF00FF FF000000 00FF0080 00008040/
+       ]
+      );
+    my $base_fill = Imager::Fill->new
+      (
+       image => $base_img,
+       combine => "normal",
+      );
+    ok($base_fill, "make the base image fill");
+    my $fill50 = Imager::Fill->new(type => "opacity", opacity => 0.5, other => $base_fill)
+      or print "# ", Imager->errstr, "\n";
+    ok($fill50, "make 50% alpha translation fill");
+    my $out = Imager->new(xsize => 10, ysize => 10, channels => 4);
+    $out->box(fill => $fill50);
+    is_color4($out->getpixel(x => 0, y => 0),
+             255, 0, 0, 16, "check alpha output");
+    is_color4($out->getpixel(x => 2, y => 1),
+             0, 255, 0, 64, "check alpha output");
+    $out->box(filled => 1, color => "000000");
+    is_color4($out->getpixel(x => 0, y => 0),
+             0, 0, 0, 255, "check after clear");
+    $out->box(fill => $fill50);
+    is_color4($out->getpixel(x => 4, y => 2),
+             16, 0, 0, 255, "check drawn against background");
+    is_color4($out->getpixel(x => 6, y => 3),
+             0, 64, 0, 255, "check drawn against background");
+  }
+  { # double/sample
+    use Imager::Color::Float;
+    my $base_img = Imager->new(xsize => 4, ysize => 2, channels => 4, bits => "double");
+    $base_img->setscanline
+      (
+       x => 0, 
+       y => 0, 
+       pixels => 
+       [
+       map Imager::Color::Float->new(@$_),
+       [ 1, 0, 0, 0.125 ],
+       [ 0, 1, 0, 0.5 ],
+       [ 0, 0, 0.5, 0.25 ],
+       [ 1, 1, 0, 1 ],
+       ],
+      );
+    $base_img->setscanline
+      (
+       x => 0, 
+       y => 1, 
+       pixels => 
+       [
+       map Imager::Color::Float->new(@$_),
+       [ 1, 1, 0, 1 ],
+       [ 1, 0, 0, 0 ],
+       [ 0, 1, 0, 0.5 ],
+       [ 0, 0, 0.5, 0.25 ],
+       ]
+      );
+    my $base_fill = Imager::Fill->new
+      (
+       image => $base_img,
+       combine => "normal",
+      );
+    ok($base_fill, "make the base image fill");
+    my $fill50 = Imager::Fill->new(type => "opacity", opacity => 0.5, other => $base_fill)
+      or print "# ", Imager->errstr, "\n";
+    ok($fill50, "make 50% alpha translation fill");
+    my $out = Imager->new(xsize => 10, ysize => 10, channels => 4, bits => "double");
+    $out->box(fill => $fill50);
+    is_fcolor4($out->getpixel(x => 0, y => 0, type => "float"),
+             1, 0, 0, 0.0625, "check alpha output at 0,0");
+    is_fcolor4($out->getpixel(x => 2, y => 1, type => "float"),
+             0, 1, 0, 0.25, "check alpha output at 2,1");
+    $out->box(filled => 1, color => "000000");
+    is_fcolor4($out->getpixel(x => 0, y => 0, type => "float"),
+             0, 0, 0, 1, "check after clear");
+    $out->box(fill => $fill50);
+    is_fcolor4($out->getpixel(x => 4, y => 2, type => "float"),
+             0.0625, 0, 0, 1, "check drawn against background at 4,2");
+    is_fcolor4($out->getpixel(x => 6, y => 3, type => "float"),
+             0, 0.25, 0, 1, "check drawn against background at 6,3");
+  }
+}
+
 sub color_close {
   my ($c1, $c2) = @_;