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.
imgdouble.c Implements double/sample images
imio.h
imperl.h
+imtoc.perl Sample size adapter pre-processor
io.c
iolayer.c
iolayer.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
^imconfig\.h$
^ICO/ICO\.c$
^ICO/testout
+^rubthru\.c$
# trash from profiling
\.gcno$
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 };
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)
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
}
#!perl -w
use strict;
+use ExtUtils::Manifest 'maniread';
my $outname = shift || '-';
# 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;
}
-/*
-=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)
--- /dev/null
+#!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
=for comment
-From: File image.c
+From: File rubthru.im
=back
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
--- /dev/null
+#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);
+}
#!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);
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
"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;
|| $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
+ }
+}