From fe415ad2caee20c8d87fda35959174becb733f6f Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Tue, 29 Aug 2006 00:42:46 +0000 Subject: [PATCH 1/1] the rubthrough() method now supports destination images with an alpha 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 | 2 + MANIFEST.SKIP | 1 + Makefile.PL | 20 +++- apidocs.perl | 4 +- image.c | 98 ------------------ imtoc.perl | 207 ++++++++++++++++++++++++++++++++++++++ lib/Imager/APIRef.pod | 2 +- lib/Imager/ImageTypes.pod | 4 + rubthru.im | 152 ++++++++++++++++++++++++++++ t/t69rubthru.t | 73 +++++++++++++- 10 files changed, 459 insertions(+), 104 deletions(-) create mode 100644 imtoc.perl create mode 100644 rubthru.im diff --git a/MANIFEST b/MANIFEST index a6b33159..8005f8a1 100644 --- 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 diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index eb2e76c5..64b7f0d5 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -43,6 +43,7 @@ Makefile\.old ^imconfig\.h$ ^ICO/ICO\.c$ ^ICO/testout +^rubthru\.c$ # trash from profiling \.gcno$ diff --git a/Makefile.PL b/Makefile.PL index a7e6ee6c..8cdb5ef5 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -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 < 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 \b my $func; diff --git a/image.c b/image.c index 18a9796b..316982af 100644 --- 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 and -overlays it at (I,I) on the image object. - -The alpha channel of each pixel in I is used to control how much -the existing colour in I 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 index 00000000..0d59f474 --- /dev/null +++ b/imtoc.perl @@ -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 = )) { + 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 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 + +=cut diff --git a/lib/Imager/APIRef.pod b/lib/Imager/APIRef.pod index e92e4c49..39927738 100644 --- a/lib/Imager/APIRef.pod +++ b/lib/Imager/APIRef.pod @@ -655,7 +655,7 @@ unmodified. =for comment -From: File image.c +From: File rubthru.im =back diff --git a/lib/Imager/ImageTypes.pod b/lib/Imager/ImageTypes.pod index cb67145c..b888e37c 100644 --- a/lib/Imager/ImageTypes.pod +++ b/lib/Imager/ImageTypes.pod @@ -127,6 +127,10 @@ C methods. The coordinate system in Imager has the origin in the upper left corner, see L 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 index 00000000..fdc23d3e --- /dev/null +++ b/rubthru.im @@ -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 and +overlays it at (I,I) on the image object. + +The alpha channel of each pixel in I is used to control how much +the existing colour in I 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); +} diff --git a/t/t69rubthru.t b/t/t69rubthru.t index b968b84d..9f71293c 100644 --- a/t/t69rubthru.t +++ b/t/t69rubthru.t @@ -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(<