From: Tony Cook Date: Mon, 26 Nov 2007 10:30:01 +0000 (+0000) Subject: merge in tiff re-work branch X-Git-Tag: Imager-0.63~50 X-Git-Url: http://git.imager.perl.org/imager.git/commitdiff_plain/bd8052a6c49bf23a2fb5965c59f18192b26bb8b4 merge in tiff re-work branch --- diff --git a/Changes b/Changes index 70aafab9..2bd66720 100644 --- a/Changes +++ b/Changes @@ -3,6 +3,24 @@ Imager release history. Older releases can be found in Changes.old Imager 0.62 - not yet released =========== + - major TIFF support re-work + http://rt.cpan.org/Ticket/Display.html?id=20329 + + - added a C level image interface for accessing samples from 1-32 + bits, exposed this at the perl level in getsamples() + + - the conv filter now works at floating point precision for high bit + images + + - added is_bilevel method to test whether an image should be written as + a bilevel image if the image format supports it. + + - added -log-stderr as an Imager import list option + + - added some important types to Imager::APIRef + + - added test_image_double() to Imager::Test + Bug fixes: - Imager::Fountain couldn't read GIMP gradient files with 10 or more diff --git a/Imager.pm b/Imager.pm index eac764eb..df530725 100644 --- a/Imager.pm +++ b/Imager.pm @@ -117,6 +117,7 @@ use Imager::Font; newcolour NC NF + NCF ); @EXPORT=qw( @@ -136,6 +137,7 @@ use Imager::Font; newcolor NF NC + NCF )], all => [@EXPORT_OK], default => [qw( @@ -426,13 +428,19 @@ BEGIN { # initlize Imager # NOTE: this might be moved to an import override later on -#sub import { -# my $pack = shift; -# (look through @_ for special tags, process, and remove them); -# use Data::Dumper; -# print Dumper($pack); -# print Dumper(@_); -#} +sub import { + my $i = 1; + while ($i < @_) { + if ($_[$i] eq '-log-stderr') { + init_log(undef, 4); + splice(@_, $i, 1); + } + else { + ++$i; + } + } + goto &Exporter::import; +} sub init_log { i_init_log($_[0],$_[1]); @@ -1025,6 +1033,14 @@ sub virtual { $self->{IMG} and i_img_virtual($self->{IMG}); } +sub is_bilevel { + my ($self) = @_; + + $self->{IMG} or return; + + return i_img_is_monochrome($self->{IMG}); +} + sub tags { my ($self, %opts) = @_; @@ -3041,7 +3057,7 @@ sub setscanline { sub getsamples { my $self = shift; - my %opts = ( type => '8bit', x=>0, @_); + my %opts = ( type => '8bit', x=>0, offset => 0, @_); defined $opts{width} or $opts{width} = $self->getwidth - $opts{x}; @@ -3054,18 +3070,103 @@ sub getsamples { $opts{channels} = [ 0 .. $self->getchannels()-1 ]; } - if ($opts{type} eq '8bit') { - return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, - $opts{y}, @{$opts{channels}}); - } - elsif ($opts{type} eq 'float') { - return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, - $opts{y}, @{$opts{channels}}); + if ($opts{target}) { + my $target = $opts{target}; + my $offset = $opts{offset}; + if ($opts{type} eq '8bit') { + my @samples = i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}) + or return; + @{$target}{$offset .. $offset + @samples - 1} = @samples; + return scalar(@samples); + } + elsif ($opts{type} eq 'float') { + my @samples = i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + @{$target}{$offset .. $offset + @samples - 1} = @samples; + return scalar(@samples); + } + elsif ($opts{type} =~ /^(\d+)bit$/) { + my $bits = $1; + + my @data; + my $count = i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, $bits, $target, + $offset, @{$opts{channels}}); + unless (defined $count) { + $self->_set_error(Imager->_error_as_msg); + return; + } + + return $count; + } + else { + $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + return; + } } else { - $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + if ($opts{type} eq '8bit') { + return i_gsamp($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + } + elsif ($opts{type} eq 'float') { + return i_gsampf($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, @{$opts{channels}}); + } + elsif ($opts{type} =~ /^(\d+)bit$/) { + my $bits = $1; + + my @data; + i_gsamp_bits($self->{IMG}, $opts{x}, $opts{x}+$opts{width}, + $opts{y}, $bits, \@data, 0, @{$opts{channels}}) + or return; + return @data; + } + else { + $self->_set_error("invalid type parameter - must be '8bit' or 'float'"); + return; + } + } +} + +sub setsamples { + my $self = shift; + my %opts = ( x => 0, offset => 0, @_ ); + + unless ($self->{IMG}) { + $self->_set_error('setsamples: empty input image'); + return; + } + + unless(defined $opts{data} && ref $opts{data}) { + $self->_set_error('setsamples: data parameter missing or invalid'); return; } + + unless ($opts{channels}) { + $opts{channels} = [ 0 .. $self->getchannels()-1 ]; + } + + unless ($opts{type} && $opts{type} =~ /^(\d+)bit$/) { + $self->_set_error('setsamples: type parameter missing or invalid'); + return; + } + my $bits = $1; + + unless (defined $opts{width}) { + $opts{width} = $self->getwidth() - $opts{x}; + } + + my $count = i_psamp_bits($self->{IMG}, $opts{x}, $opts{y}, $bits, + $opts{channels}, $opts{data}, $opts{offset}, + $opts{width}); + unless (defined $count) { + $self->_set_error(Imager->_error_as_msg); + return; + } + + return $count; } # make an identity matrix of the given size @@ -3446,6 +3547,7 @@ sub get_file_limits { sub newcolor { Imager::Color->new(@_); } sub newfont { Imager::Font->new(@_); } +sub NCF { Imager::Color::Float->new(@_) } *NC=*newcolour=*newcolor; *NF=*newfont; @@ -3840,6 +3942,8 @@ img_set() - L init() - L +is_bilevel - L + line() - L load_plugin() - L @@ -3855,6 +3959,8 @@ maxcolors() - L NC() - L +NCF() - L + new() - L newcolor() - L @@ -3910,6 +4016,8 @@ setmask() - L setpixel() - L +setsamples() - L + setscanline() - L settag() - L diff --git a/Imager.xs b/Imager.xs index 963277ad..92cf7514 100644 --- a/Imager.xs +++ b/Imager.xs @@ -1287,9 +1287,13 @@ i_sametype_chans(im, x, y, channels) int channels void -i_init_log(name,level) - char* name +i_init_log(name_sv,level) + SV* name_sv int level + PREINIT: + const char *name = SvOK(name_sv) ? SvPV_nolen(name_sv) : NULL; + CODE: + i_init_log(name, level); void i_log_entry(string,level) @@ -1343,6 +1347,25 @@ i_img_getdata(im) sv_2mortal(newSVpv((char *)im->idata, im->bytes)) : &PL_sv_undef); +void +i_img_is_monochrome(im) + Imager::ImgRaw im + PREINIT: + int zero_is_white; + int result; + PPCODE: + result = i_img_is_monochrome(im, &zero_is_white); + if (result) { + if (GIMME_V == G_ARRAY) { + EXTEND(SP, 2); + PUSHs(&PL_sv_yes); + PUSHs(sv_2mortal(newSViv(zero_is_white))); + } + else { + EXTEND(SP, 1); + PUSHs(&PL_sv_yes); + } + } void i_line(im,x1,y1,x2,y2,val,endp) @@ -2368,6 +2391,12 @@ i_writetiff_multi_wiol_faxable(ig, fine, ...) OUTPUT: RETVAL +const char * +i_tiff_libversion() + +bool +i_tiff_has_compression(name) + const char *name #endif /* HAVE_LIBTIFF */ @@ -3772,6 +3801,108 @@ i_gsamp(im, l, r, y, ...) } } +undef_neg_int +i_gsamp_bits(im, l, r, y, bits, target, offset, ...) + Imager::ImgRaw im + int l + int r + int y + int bits + AV *target + int offset + PREINIT: + int *chans; + int chan_count; + unsigned *data; + int count, i; + CODE: + i_clear_error(); + if (items < 8) + croak("No channel numbers supplied to g_samp()"); + if (l < r) { + chan_count = items - 7; + chans = mymalloc(sizeof(int) * chan_count); + for (i = 0; i < chan_count; ++i) + chans[i] = SvIV(ST(i+7)); + data = mymalloc(sizeof(unsigned) * (r-l) * chan_count); + count = i_gsamp_bits(im, l, r, y, data, chans, chan_count, bits); + myfree(chans); + for (i = 0; i < count; ++i) { + av_store(target, i+offset, newSVuv(data[i])); + } + myfree(data); + RETVAL = count; + } + else { + RETVAL = 0; + } + OUTPUT: + RETVAL + +undef_neg_int +i_psamp_bits(im, l, y, bits, channels_sv, data_av, data_offset = 0, pixel_count = -1) + Imager::ImgRaw im + int l + int y + int bits + SV *channels_sv + AV *data_av + int data_offset + int pixel_count + PREINIT: + int chan_count; + int *channels; + int data_count; + int data_used; + unsigned *data; + int i; + CODE: + i_clear_error(); + if (SvOK(channels_sv)) { + AV *channels_av; + if (!SvROK(channels_sv) || SvTYPE(SvRV(channels_sv)) != SVt_PVAV) { + croak("channels is not an array ref"); + } + channels_av = (AV *)SvRV(channels_sv); + chan_count = av_len(channels_av) + 1; + if (chan_count < 1) { + croak("i_psamp_bits: no channels provided"); + } + channels = mymalloc(sizeof(int) * chan_count); + for (i = 0; i < chan_count; ++i) + channels[i] = SvIV(*av_fetch(channels_av, i, 0)); + } + else { + chan_count = im->channels; + channels = NULL; + } + + data_count = av_len(data_av) + 1; + if (data_offset < 0) { + croak("data_offset must by non-negative"); + } + if (data_offset > data_count) { + croak("data_offset greater than number of samples supplied"); + } + if (pixel_count == -1 || + data_offset + pixel_count * chan_count > data_count) { + pixel_count = (data_count - data_offset) / chan_count; + } + + data_used = pixel_count * chan_count; + data = mymalloc(sizeof(unsigned) * data_count); + for (i = 0; i < data_used; ++i) + data[i] = SvUV(*av_fetch(data_av, data_offset + i, 0)); + + RETVAL = i_psamp_bits(im, l, l + pixel_count, y, data, channels, + chan_count, bits); + + if (data) + myfree(data); + if (channels) + myfree(channels); + OUTPUT: + RETVAL Imager::ImgRaw i_img_masked_new(targ, mask, x, y, w, h) diff --git a/MANIFEST b/MANIFEST index b1e78c76..b4d6dfdb 100644 --- a/MANIFEST +++ b/MANIFEST @@ -75,7 +75,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 -conv.c +conv.im convert.c doco.perl datatypes.c @@ -274,6 +274,7 @@ t/tr18561.t Regression tests t/tr18561b.t tags.c testimg/209_yonge.jpg Regression test: #17981 +testimg/alpha.tif Alpha scaling test image testimg/bad1oflow.bmp 1-bit/pixel, overflow integer on 32-bit machines testimg/bad1wid0.bmp 1-bit/pixel, zero width testimg/bad24comp.bmp 24-bit/pixel, bad compression @@ -302,11 +303,17 @@ testimg/base.jpg Base JPEG test image testimg/comp4.bmp Compressed 4-bit/pixel BMP testimg/comp4.tif 4-bit/pixel paletted TIFF testimg/comp4bad.tif corrupted 4-bit/pixel paletted TIFF +testimg/comp4t.tif 4-bit/pixel paletted TIFF (tiled) testimg/comp8.bmp Compressed 8-bit/pixel BMP testimg/comp8.tif 8-bit/pixel paletted TIFF testimg/exiftest.jpg Test image for EXIF parsing testimg/expected.gif testimg/gimpgrad A GIMP gradient file +testimg/gralpha.tif Grey alpha test image +testimg/grey16.tif 16-bit/sample greyscale TIFF +testimg/grey32.tif 32-bit/sample greyscale+alpha TIFF +testimg/imager.pbm Test bi-level +testimg/imager.tif Test bi-level testimg/junk.ppm testimg/loccmap.gif testimg/longid.tga Test TGA with a long id string @@ -321,13 +328,17 @@ testimg/nocmap.gif testimg/palette.png testimg/palette_out.png testimg/penguin-base.ppm +testimg/pengtile.tif Tiled tiff image, same as penguin-base.ppm testimg/pgm.pgm Simple pgm for testing the right sample is in the right place +testimg/rgb16.tif 16-bit/sample RGB image - strips +testimg/rgb16t.tif 16-bit/sample RGB image - tiled testimg/scale.gif testimg/scale.ppm testimg/scalei.gif testimg/scmyk.tif Simple CMYK TIFF image testimg/scmyk.jpg Simple CMYK JPEG image testimg/scmyka.tif CMYK with one alpha channel +testimg/scmyka16.tif CMYK with one alpha channel (16-bit) testimg/scmykaa.tif CMYK with 2 alpha channels testimg/screen2.gif testimg/screen3.gif @@ -349,6 +360,8 @@ testimg/simple.pbm testimg/slab.tif Lab color image testimg/srgb.tif Simple RGB image testimg/srgba.tif RGB with one alpha +testimg/srgba16.tif +testimg/srgba32.tif testimg/srgbaa.tif RGB with 2 alpha testimg/test_gimp_pal A simple GIMP palette file testimg/tiffwarn.tif Generates a warning while being read diff --git a/README b/README index f11a2872..602cf1d1 100644 --- a/README +++ b/README @@ -110,10 +110,11 @@ Stock libungif 4.1.4 or later seems to fix all of the bugs, if you have a problem that version of linungif (or later), let us know and we'll look into it. -Imager needs to have a libtiff version of at least 3.5.5. In the -future we might consider supporting older libtiff versions. For -now you can either configure Imager manually (by -setting the IM_MANUAL environment variable to 1, in sh: +Imager needs to have a libtiff version of at least 3.5.5, but you +should use a later version since some noticable bugs have been fixed. + +For now you can either configure Imager manually (by setting the +IM_MANUAL environment variable to 1, in sh: $ IM_MANUAL=1 perl Makefile.PL diff --git a/TODO b/TODO index 102ddb64..2afd2541 100644 --- a/TODO +++ b/TODO @@ -28,7 +28,7 @@ general alpha channel fixes (#29879, etc) For 0.62: -TIFF improvements (to be detailed) (#20329) +TIFF improvements (to be detailed) (#20329) (done) - read/write 16-bit RGB w/ and w/o alpha - read 16-bit CMYK w/ and w/o alpha - read/write 32-bit RGB w/ and w/o alpha diff --git a/apidocs.perl b/apidocs.perl index 2d36e311..dab9dbd8 100644 --- a/apidocs.perl +++ b/apidocs.perl @@ -11,7 +11,7 @@ my %funcs = map { $_ => 1 } @funcs; # look for files to parse my $mani = maniread; -my @files = grep /\.(c|im)$/, keys %$mani; +my @files = grep /\.(c|im|h)$/, keys %$mani; # scan each file for =item \b my $func; @@ -162,7 +162,7 @@ close OUT; sub make_func_list { - my $funcs; + my @funcs = qw(i_img i_color i_fcolor i_fill_t mm_log); open FUNCS, "< imexttypes.h" or die "Cannot open imexttypes.h: $!\n"; my $in_struct; diff --git a/conv.c b/conv.c deleted file mode 100644 index 033fb520..00000000 --- a/conv.c +++ /dev/null @@ -1,80 +0,0 @@ -#include "imager.h" - -/* - General convolution for 2d decoupled filters - end effects are acounted for by increasing - scaling the result with the sum of used coefficients - - coeff: (float array) coefficients for filter - len: length of filter.. number of coefficients - note that this has to be an odd number - (since the filter is even); -*/ - -void -i_conv(i_img *im,const float *coeff,int len) { - int i,l,c,ch,center; - float pc; - i_color rcolor; - float res[11]; - i_img timg; - - mm_log((1,"i_conv(im %p, coeff %p, len %d)\n",im,coeff,len)); - - i_img_empty_ch(&timg,im->xsize,im->ysize,im->channels); - - center=(len-1)/2; - - /* don't move the calculation of pc up here, it depends on which pixels - are readable */ - for(l=0;lysize;l++) { - for(i=0;ixsize;i++) { - pc=0.0; - for(ch=0;chchannels;ch++) res[ch]=0; - for(c=0;cchannels;ch++) - res[ch]+=(float)(rcolor.channel[ch])*coeff[c]; - pc+=coeff[c]; - } - for(ch=0;chchannels;ch++) { - double temp = res[ch]/pc; - rcolor.channel[ch] = - temp < 0 ? 0 : temp > 255 ? 255 : (unsigned char)temp; - } - i_ppix(&timg,i,l,&rcolor); - } - } - - for(l=0;lxsize;l++) - { - for(i=0;iysize;i++) - { - pc=0.0; - for(ch=0;chchannels;ch++) res[ch]=0; - for(c=0;cchannels;ch++) - res[ch]+=(float)(rcolor.channel[ch])*coeff[c]; - pc+=coeff[c]; - } - for(ch=0;chchannels;ch++) { - double temp = res[ch]/pc; - rcolor.channel[ch]= - temp < 0 ? 0 : temp > 255 ? 255 : (unsigned char)temp; - } - i_ppix(im,l,i,&rcolor); - } - } - i_img_exorcise(&timg); -} - - - - - - - - - diff --git a/conv.im b/conv.im new file mode 100644 index 00000000..986bbae7 --- /dev/null +++ b/conv.im @@ -0,0 +1,82 @@ +#include "imager.h" + +/* + General convolution for 2d decoupled filters + end effects are acounted for by increasing + scaling the result with the sum of used coefficients + + coeff: (float array) coefficients for filter + len: length of filter.. number of coefficients + note that this has to be an odd number + (since the filter is even); +*/ + +void +i_conv(i_img *im,const float *coeff,int len) { + int i,l,c,ch,center; + double pc; + double res[11]; + i_img *timg; + + mm_log((1,"i_conv(im %p, coeff %p, len %d)\n",im,coeff,len)); + + timg = i_sametype(im, im->xsize, im->ysize); + + center=(len-1)/2; + +#code im->bits <= 8 + IM_COLOR rcolor; + /* don't move the calculation of pc up here, it depends on which pixels + are readable */ + for(l=0;lysize;l++) { + for(i=0;ixsize;i++) { + pc=0.0; + for(ch=0;chchannels;ch++) + res[ch]=0; + for(c=0;cchannels;ch++) + res[ch] += (rcolor.channel[ch])*coeff[c]; + pc+=coeff[c]; + } + for(ch=0;chchannels;ch++) { + double temp = res[ch]/pc; + rcolor.channel[ch] = + temp < 0 ? 0 : temp > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : (IM_SAMPLE_T)temp; + } + IM_PPIX(timg,i,l,&rcolor); + } + } + + for(l=0;lxsize;l++) { + for(i=0;iysize;i++) { + pc=0.0; + for(ch=0;chchannels;ch++) res[ch]=0; + for(c=0;cchannels;ch++) + res[ch] += (rcolor.channel[ch])*coeff[c]; + pc+=coeff[c]; + } + } + for(ch=0;chchannels;ch++) { + double temp = res[ch]/pc; + rcolor.channel[ch]= + temp < 0 ? 0 : temp > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : (IM_SAMPLE_T)temp; + } + IM_PPIX(im,l,i,&rcolor); + } + } +#/code + + i_img_destroy(timg); +} + + + + + + + + + diff --git a/gaussian.im b/gaussian.im index 34fe8134..8489273a 100644 --- a/gaussian.im +++ b/gaussian.im @@ -72,7 +72,7 @@ i_gaussian(i_img *im, double stddev) { } for(ch=0;chchannels;ch++) { double value = res[ch] / pc; - rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : value; + rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : IM_ROUND(value); } IM_PPIX(timg,i,l,&rcolor); } @@ -91,7 +91,7 @@ i_gaussian(i_img *im, double stddev) { } for(ch=0;chchannels;ch++) { double value = res[ch]/pc; - rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : value; + rcolor.channel[ch] = value > IM_SAMPLE_MAX ? IM_SAMPLE_MAX : IM_ROUND(value); } IM_PPIX(im,l,i,&rcolor); } diff --git a/image.c b/image.c index 85cbb2bd..13b73d51 100644 --- a/image.c +++ b/image.c @@ -49,8 +49,58 @@ static int i_glinf_d(i_img *im, int l, int r, int y, i_fcolor *vals); static int i_plinf_d(i_img *im, int l, int r, int y, const i_fcolor *vals); static int i_gsamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, const int *chans, int chan_count); static int i_gsampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, const int *chans, int chan_count); -/*static int i_psamp_d(i_img *im, int l, int r, int y, i_sample_t *samps, int *chans, int chan_count); - static int i_psampf_d(i_img *im, int l, int r, int y, i_fsample_t *samps, int *chans, int chan_count);*/ + +/* +=item i_img_alloc() +=category Image Implementation + +Allocates a new i_img structure. + +When implementing a new image type perform the following steps in your +image object creation function: + +=over + +=item 1. + +allocate the image with i_img_alloc(). + +=item 2. + +initialize any function pointers or other data as needed, you can +overwrite the whole block if you need to. + +=item 3. + +initialize Imager's internal data by calling i_img_init() on the image +object. + +=back + +=cut +*/ + +i_img * +i_img_alloc(void) { + return mymalloc(sizeof(i_img)); +} + +/* +=item i_img_init(img) +=category Image Implementation + +Imager interal initialization of images. + +Currently this does very little, in the future it may be used to +support threads, or color profiles. + +=cut +*/ + +void +i_img_init(i_img *img) { + img->im_data = NULL; +} /* =item ICL_new_internal(r, g, b, a) @@ -232,6 +282,9 @@ static i_img IIM_base_8bit_direct = NULL, /* i_f_setcolors */ NULL, /* i_f_destroy */ + + i_gsamp_bits_fb, + NULL, /* i_f_psamp_bits */ }; /*static void set_8bit_direct(i_img *im) { @@ -301,8 +354,8 @@ i_img_new() { i_img *im; mm_log((1,"i_img_struct()\n")); - if ( (im=mymalloc(sizeof(i_img))) == NULL) - i_fatal(2,"malloc() error\n"); + + im = i_img_alloc(); *im = IIM_base_8bit_direct; im->xsize=0; @@ -311,6 +364,8 @@ i_img_new() { im->ch_mask=MAXINT; im->bytes=0; im->idata=NULL; + + i_img_init(im); mm_log((1,"(%p) <- i_img_struct\n",im)); return im; @@ -373,8 +428,7 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) { } if (im == NULL) - if ( (im=mymalloc(sizeof(i_img))) == NULL) - i_fatal(2,"malloc() error\n"); + im = i_img_alloc(); memcpy(im, &IIM_base_8bit_direct, sizeof(i_img)); i_tags_new(&im->tags); @@ -388,6 +442,8 @@ i_img_empty_ch(i_img *im,int x,int y,int ch) { memset(im->idata,0,(size_t)im->bytes); im->ext_data = NULL; + + i_img_init(im); mm_log((1,"(%p) <- i_img_empty_ch\n",im)); return im; @@ -705,20 +761,8 @@ i_copy(i_img *src) { } } else { - i_color temp; - int index; - int count; i_palidx *vals; - /* paletted image */ - i_img_pal_new_low(im, x1, y1, src->channels, i_maxcolors(src)); - /* copy across the palette */ - count = i_colorcount(src); - for (index = 0; index < count; ++index) { - i_getcolors(src, index, &temp, 1); - i_addcolors(im, &temp, 1); - } - vals = mymalloc(sizeof(i_palidx) * x1); for (y = 0; y < y1; ++y) { i_gpal(src, 0, x1, y, vals); @@ -1985,6 +2029,80 @@ int i_findcolor_forward(i_img *im, const i_color *color, i_palidx *entry) { /* =back +=head2 Fallback handler + +=over + +=item i_gsamp_bits_fb + +=cut +*/ + +int +i_gsamp_bits_fb(i_img *im, int l, int r, int y, unsigned *samps, + const int *chans, int chan_count, int bits) { + if (bits < 1 || bits > 32) { + i_push_error(0, "Invalid bits, must be 1..32"); + return -1; + } + + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + double scale; + int ch, count, i, w; + + if (bits == 32) + scale = 4294967295.0; + else + scale = (double)(1 << bits) - 1; + + if (r > im->xsize) + r = im->xsize; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return -1; + } + } + for (i = 0; i < w; ++i) { + i_fcolor c; + i_gpixf(im, l+i, y, &c); + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = (unsigned)(c.channel[ch] * scale + 0.5); + ++count; + } + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_error(0, "Invalid channel count"); + return -1; + } + for (i = 0; i < w; ++i) { + i_fcolor c; + i_gpixf(im, l+i, y, &c); + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = (unsigned)(c.channel[ch] * scale + 0.5); + ++count; + } + } + } + + return count; + } + else { + i_push_error(0, "Image position outside of image"); + return -1; + } +} + +/* +=back + =head2 Stream reading and writing wrapper functions =over @@ -2353,7 +2471,7 @@ i_img_is_monochrome(i_img *im, int *zero_is_white) { colors[1].rgb.r == 0 && colors[1].rgb.g == 0 && colors[1].rgb.b == 0) { - *zero_is_white = 0; + *zero_is_white = 1; return 1; } else if (colors[0].rgb.r == 0 && @@ -2362,19 +2480,19 @@ i_img_is_monochrome(i_img *im, int *zero_is_white) { colors[1].rgb.r == 255 && colors[1].rgb.g == 255 && colors[1].rgb.b == 255) { - *zero_is_white = 1; + *zero_is_white = 0; return 1; } } else if (im->channels == 1) { if (colors[0].channel[0] == 255 && - colors[1].channel[1] == 0) { - *zero_is_white = 0; + colors[1].channel[0] == 0) { + *zero_is_white = 1; return 1; } else if (colors[0].channel[0] == 0 && - colors[0].channel[0] == 255) { - *zero_is_white = 1; + colors[1].channel[0] == 255) { + *zero_is_white = 0; return 1; } } diff --git a/imager.h b/imager.h index 1c027548..dbec0790 100644 --- a/imager.h +++ b/imager.h @@ -56,6 +56,8 @@ i_img *i_img_empty(i_img *im,int x,int y); i_img *i_img_empty_ch(i_img *im,int x,int y,int ch); void i_img_exorcise(i_img *im); void i_img_destroy(i_img *im); +i_img *i_img_alloc(void); +void i_img_init(i_img *im); void i_img_info(i_img *im,int *info); @@ -112,6 +114,11 @@ extern int i_setcolors(i_img *im, int index, const i_color *colors, #define i_gsampf(im, l, r, y, samps, chans, count) \ (((im)->i_f_gsampf)((im), (l), (r), (y), (samps), (chans), (count))) +#define i_gsamp_bits(im, l, r, y, samps, chans, count, bits) \ + (((im)->i_f_gsamp_bits) ? ((im)->i_f_gsamp_bits)((im), (l), (r), (y), (samps), (chans), (count), (bits)) : -1) +#define i_psamp_bits(im, l, r, y, samps, chans, count, bits) \ + (((im)->i_f_psamp_bits) ? ((im)->i_f_psamp_bits)((im), (l), (r), (y), (samps), (chans), (count), (bits)) : -1) + #define i_findcolor(im, color, entry) \ (((im)->i_f_findcolor) ? ((im)->i_f_findcolor)((im), (color), (entry)) : 0) @@ -345,16 +352,13 @@ extern i_palidx *i_quant_translate(i_quantize *quant, i_img *img); extern void i_quant_transparent(i_quantize *quant, i_palidx *indices, i_img *img, i_palidx trans_index); extern i_img *i_img_pal_new(int x, int y, int channels, int maxpal); -extern i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal); extern i_img *i_img_to_pal(i_img *src, i_quantize *quant); extern i_img *i_img_to_rgb(i_img *src); extern i_img *i_img_masked_new(i_img *targ, i_img *mask, int x, int y, int w, int h); extern i_img *i_img_16_new(int x, int y, int ch); -extern i_img *i_img_16_new_low(i_img *im, int x, int y, int ch); extern i_img *i_img_to_rgb16(i_img *im); extern i_img *i_img_double_new(int x, int y, int ch); -extern i_img *i_img_double_new_low(i_img *im, int x, int y, int ch); extern int i_img_is_monochrome(i_img *im, int *zero_is_white); @@ -374,6 +378,8 @@ undef_int i_writetiff_wiol(i_img *im, io_glue *ig); undef_int i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count); undef_int i_writetiff_wiol_faxable(i_img *im, io_glue *ig, int fine); undef_int i_writetiff_multi_wiol_faxable(io_glue *ig, i_img **imgs, int count, int fine); +char const * i_tiff_libversion(void); +int i_tiff_has_compression(char const *name); #endif /* HAVE_LIBTIFF */ diff --git a/imageri.h b/imageri.h index df471cc8..613bd5d1 100644 --- a/imageri.h +++ b/imageri.h @@ -30,6 +30,10 @@ extern int i_findcolor_forward(i_img *im, const i_color *color, extern int i_setcolors_forward(i_img *im, int index, const i_color *colors, int count); +/* fallback handler for gsamp_bits */ +extern int i_gsamp_bits_fb(i_img *im, int x, int r, int y, unsigned *samp, + const int *chans, int chan_count, int bits); + #define SampleFTo16(num) ((int)((num) * 65535.0 + 0.01)) /* we add that little bit to avoid rounding issues */ #define Sample16ToF(num) ((num) / 65535.0) diff --git a/imdatatypes.h b/imdatatypes.h index 2c00b792..8e9cdd36 100644 --- a/imdatatypes.h +++ b/imdatatypes.h @@ -102,8 +102,110 @@ typedef int (*i_f_setcolors_t)(i_img *im, int index, const i_color *colors, typedef void (*i_f_destroy_t)(i_img *im); +typedef int (*i_f_gsamp_bits_t)(i_img *im, int x, int r, int y, unsigned *samp, + const int *chans, int chan_count, int bits); +typedef int (*i_f_psamp_bits_t)(i_img *im, int x, int r, int y, unsigned const *samp, + const int *chans, int chan_count, int bits); + typedef int i_img_dim; +/* +=item i_img +=category Data Types +=synopsis i_img *img; + +This is Imager's image type. + +It contains the following members: + +=over + +=item * + +channels - the number of channels in the image + +=item * + +xsize, ysize - the width and height of the image in pixels + +=item * + +bytes - the number of bytes used to store the image data. Undefined +where virtual is non-zero. + +=item * + +ch_mask - a mask of writable channels. eg. if this is 6 then only +channels 1 and 2 are writable. There may be bits set for which there +are no channels in the image. + +=item * + +bits - the number of bits stored per sample. Should be one of +i_8_bits, i_16_bits, i_double_bits. + +=item * + +type - either i_direct_type for direct color images, or i_palette_type +for paletted images. + +=item * + +virtual - if zero then this image is-self contained. If non-zero then +this image could be an interface to some other implementation. + +=item * + +idata - the image data. This should not be directly accessed. A new +image implementation can use this to store its image data. +i_img_destroy() will myfree() this pointer if it's non-null. + +=item * + +tags - a structure storing the image's tags. This should only be +accessed via the i_tags_*() functions. + +=item * + +ext_data - a pointer for use internal to an image implementation. +This should be freed by the image's destroy handler. + +=item * + +im_data - data internal to Imager. This is initialized by +i_img_init(). + +=item * + +i_f_ppix, i_f_ppixf, i_f_plin, i_f_plinf, i_f_gpix, i_f_gpixf, +i_f_glin, i_f_glinf, i_f_gsamp, i_f_gampf - implementations for each +of the required image functions. An image implementation should +initialize these between calling i_img_alloc() and i_img_init(). + +=item * + +i_f_gpal, i_f_ppal, i_f_addcolors, i_f_getcolors, i_f_colorcount, +i_f_maxcolors, i_f_findcolor, i_f_setcolors - implementations for each +paletted image function. + +=item * + +i_f_destroy - custom image destruction function. This should be used +to release memory if necessary. + +=item * + +i_f_gsamp_bits - implements i_gsamp_bits() for this image. + +=item * + +i_f_psamp_bits - implements i_psamp_bits() for this image. + +=back + +=cut +*/ + struct i_img_ { int channels; i_img_dim xsize,ysize; @@ -141,6 +243,12 @@ struct i_img_ { i_f_setcolors_t i_f_setcolors; i_f_destroy_t i_f_destroy; + + /* as of 0.61 */ + i_f_gsamp_bits_t i_f_gsamp_bits; + i_f_psamp_bits_t i_f_psamp_bits; + + void *im_data; }; /* ext_data for paletted images diff --git a/imext.c b/imext.c index 3e568ca3..39903745 100644 --- a/imext.c +++ b/imext.c @@ -112,7 +112,11 @@ im_ext_funcs imager_function_table = i_img_get_width, i_img_get_height, i_lhead, - i_loog + i_loog, + + /* IMAGER_API_LEVEL 4 functions */ + i_img_alloc, + i_img_init, }; /* in general these functions aren't called by Imager internally, but diff --git a/imext.h b/imext.h index 2fca3e44..3f0484b7 100644 --- a/imext.h +++ b/imext.h @@ -199,4 +199,7 @@ extern im_ext_funcs *imager_function_ext_table; #define i_lhead(file, line) ((im_extt->f_i_lhead)((file), (line))) #define i_loog (im_extt->f_i_loog) +#define i_img_alloc() ((im_extt->f_i_img_alloc)()) +#define i_img_init(img) ((im_extt->f_i_img_init)(img)) + #endif diff --git a/imexttypes.h b/imexttypes.h index e607d4a3..fc3dcf7a 100644 --- a/imexttypes.h +++ b/imexttypes.h @@ -18,7 +18,7 @@ will result in an increment of IMAGER_API_LEVEL. */ -#define IMAGER_API_LEVEL 3 +#define IMAGER_API_LEVEL 4 typedef struct { int version; @@ -152,6 +152,10 @@ typedef struct { void (*f_i_loog)(int level, const char *msg, ...); /* IMAGER_API_LEVEL 4 functions will be added here */ + i_img *(*f_i_img_alloc)(void); + void (*f_i_img_init)(i_img *); + + /* IMAGER_API_LEVEL 5 functions will be added here */ } im_ext_funcs; #define PERL_FUNCTION_TABLE_NAME "Imager::__ext_func_table" diff --git a/img16.c b/img16.c index e17d40d7..028539d2 100644 --- a/img16.c +++ b/img16.c @@ -35,6 +35,10 @@ static int i_gsamp_d16(i_img *im, int l, int r, int y, i_sample_t *samps, int const *chans, int chan_count); static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps, int const *chans, int chan_count); +static int i_gsamp_bits_d16(i_img *im, int l, int r, int y, unsigned *samps, + int const *chans, int chan_count, int bits); +static int i_psamp_bits_d16(i_img *im, int l, int r, int y, unsigned const *samps, + int const *chans, int chan_count, int bits); /* =item IIM_base_16bit_direct @@ -70,12 +74,17 @@ static i_img IIM_base_16bit_direct = NULL, /* i_f_gpal */ NULL, /* i_f_ppal */ - NULL, /* i_f_addcolor */ - NULL, /* i_f_getcolor */ + NULL, /* i_f_addcolors */ + NULL, /* i_f_getcolors */ NULL, /* i_f_colorcount */ + NULL, /* i_f_maxcolors */ NULL, /* i_f_findcolor */ + NULL, /* i_f_setcolors */ NULL, /* i_f_destroy */ + + i_gsamp_bits_d16, + i_psamp_bits_d16, }; /* it's possible some platforms won't have a 16-bit integer type, @@ -111,12 +120,9 @@ typedef unsigned short i_sample16_t; #define STORE16(bytes, offset, word) \ (((i_sample16_t *)(bytes))[offset] = (word)) #define STORE8as16(bytes, offset, byte) \ - (((i_sample16_t *)(bytes))[offset] = (byte) * 256) + (((i_sample16_t *)(bytes))[offset] = (byte) * 256 + (byte)) #define GET16(bytes, offset) \ (((i_sample16_t *)(bytes))[offset]) -#define GET16as8(bytes, offset) \ - (((i_sample16_t *)(bytes))[offset] / 256) - #else /* we have to do this the hard way */ @@ -125,25 +131,34 @@ typedef unsigned short i_sample16_t; (((unsigned char *)(bytes))[(offset)*2+1] = (word) & 0xFF)) #define STORE8as16(bytes, offset, byte) \ ((((unsigned char *)(bytes))[(offset)*2] = (byte)), \ - (((unsigned char *)(bytes))[(offset)*2+1] = 0)) + (((unsigned char *)(bytes))[(offset)*2+1] = (byte))) #define GET16(bytes, offset) \ (((unsigned char *)(bytes))[(offset)*2] * 256 \ + ((unsigned char *)(bytes))[(offset)*2+1]) -#define GET16as8(bytes, offset) \ - (((unsigned char *)(bytes))[(offset)*2] << 8) #endif +#define GET16as8(bytes, offset) \ + ((((i_sample16_t *)(bytes))[offset]+127) / 257) + /* -=item i_img_16_new_low(int x, int y, int ch) +=item i_img_16_new(x, y, ch) -Creates a new 16-bit per sample image. +=category Image creation +=synopsis i_img *img = i_img_16_new(width, height, channels); + +Create a new 16-bit/sample image. + +Returns the image on success, or NULL on failure. =cut */ -i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) { + +i_img *i_img_16_new(int x, int y, int ch) { + i_img *im; int bytes, line_bytes; + mm_log((1,"i_img_16_new(x %d, y %d, ch %d)\n", x, y, ch)); if (x < 1 || y < 1) { @@ -169,6 +184,7 @@ i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) { return NULL; } + im = i_img_alloc(); *im = IIM_base_16bit_direct; i_tags_new(&im->tags); im->xsize = x; @@ -177,45 +193,10 @@ i_img *i_img_16_new_low(i_img *im, int x, int y, int ch) { im->bytes = bytes; im->ext_data = NULL; im->idata = mymalloc(im->bytes); - if (im->idata) { - memset(im->idata, 0, im->bytes); - } - else { - i_tags_destroy(&im->tags); - im = NULL; - } - - return im; -} - -/* -=item i_img_16_new(x, y, ch) - -=category Image creation/destruction -=synopsis i_img *img = i_img_16_new(width, height, channels); - -Create a new 16-bit/sample image. - -Returns the image on success, or NULL on failure. - -=cut -*/ + memset(im->idata, 0, im->bytes); -i_img *i_img_16_new(int x, int y, int ch) { - i_img *im; - - i_clear_error(); + i_img_init(im); - im = mymalloc(sizeof(i_img)); - if (im) { - if (!i_img_16_new_low(im, x, y, ch)) { - myfree(im); - im = NULL; - } - } - - mm_log((1, "(%p) <- i_img_16_new\n", im)); - return im; } @@ -523,6 +504,121 @@ static int i_gsampf_d16(i_img *im, int l, int r, int y, i_fsample_t *samps, } } +static int +i_gsamp_bits_d16(i_img *im, int l, int r, int y, unsigned *samps, + int const *chans, int chan_count, int bits) { + int ch, count, i, w; + int off; + + if (bits != 16) { + return i_gsamp_bits_fb(im, l, r, y, samps, chans, chan_count, bits); + } + + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + off = (l+y*im->xsize) * im->channels; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return -1; + } + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = GET16(im->idata, off+chans[ch]); + ++count; + } + off += im->channels; + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_error(0, "Invalid channel count"); + return -1; + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + *samps++ = GET16(im->idata, off+ch); + ++count; + } + off += im->channels; + } + } + + return count; + } + else { + i_push_error(0, "Image position outside of image"); + return -1; + } +} + +static int +i_psamp_bits_d16(i_img *im, int l, int r, int y, unsigned const *samps, + int const *chans, int chan_count, int bits) { + int ch, count, i, w; + int off; + + if (bits != 16) { + i_push_error(0, "Invalid bits for 16-bit image"); + return -1; + } + + if (y >=0 && y < im->ysize && l < im->xsize && l >= 0) { + if (r > im->xsize) + r = im->xsize; + off = (l+y*im->xsize) * im->channels; + w = r - l; + count = 0; + + if (chans) { + /* make sure we have good channel numbers */ + for (ch = 0; ch < chan_count; ++ch) { + if (chans[ch] < 0 || chans[ch] >= im->channels) { + i_push_errorf(0, "No channel %d in this image", chans[ch]); + return -1; + } + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + if (im->ch_mask & (1 << ch)) + STORE16(im->idata, off+chans[ch], *samps); + ++samps; + ++count; + } + off += im->channels; + } + } + else { + if (chan_count <= 0 || chan_count > im->channels) { + i_push_error(0, "Invalid channel count"); + return -1; + } + for (i = 0; i < w; ++i) { + for (ch = 0; ch < chan_count; ++ch) { + if (im->ch_mask & (1 << ch)) + STORE16(im->idata, off+ch, *samps); + ++samps; + ++count; + } + off += im->channels; + } + } + + return count; + } + else { + i_push_error(0, "Image position outside of image"); + return -1; + } +} + /* =back diff --git a/imgdouble.c b/imgdouble.c index b9df1e30..a12b5ee6 100644 --- a/imgdouble.c +++ b/imgdouble.c @@ -70,17 +70,21 @@ static i_img IIM_base_double_direct = NULL, /* i_f_gpal */ NULL, /* i_f_ppal */ - NULL, /* i_f_addcolor */ - NULL, /* i_f_getcolor */ + NULL, /* i_f_addcolors */ + NULL, /* i_f_getcolors */ NULL, /* i_f_colorcount */ + NULL, /* i_f_maxcolors */ NULL, /* i_f_findcolor */ + NULL, /* i_f_setcolors */ NULL, /* i_f_destroy */ + + i_gsamp_bits_fb, + NULL, /* i_f_psamp_bits */ }; /* =item i_img_double_new(int x, int y, int ch) - =category Image creation/destruction =synopsis i_img *img = i_img_double_new(width, height, channels); @@ -88,8 +92,9 @@ Creates a new double per sample image. =cut */ -i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) { +i_img *i_img_double_new(int x, int y, int ch) { int bytes; + i_img *im; mm_log((1,"i_img_double_new(x %d, y %d, ch %d)\n", x, y, ch)); @@ -107,6 +112,7 @@ i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) { return NULL; } + im = i_img_alloc(); *im = IIM_base_double_direct; i_tags_new(&im->tags); im->xsize = x; @@ -115,31 +121,8 @@ i_img *i_img_double_new_low(i_img *im, int x, int y, int ch) { im->bytes = bytes; im->ext_data = NULL; im->idata = mymalloc(im->bytes); - if (im->idata) { - memset(im->idata, 0, im->bytes); - } - else { - i_tags_destroy(&im->tags); - im = NULL; - } - - return im; -} - -i_img *i_img_double_new(int x, int y, int ch) { - i_img *im; - - i_clear_error(); - - im = mymalloc(sizeof(i_img)); - if (im) { - if (!i_img_double_new_low(im, x, y, ch)) { - myfree(im); - im = NULL; - } - } - - mm_log((1, "(%p) <- i_img_double_new\n", im)); + memset(im->idata, 0, im->bytes); + i_img_init(im); return im; } diff --git a/lib/Imager/APIRef.pod b/lib/Imager/APIRef.pod index a7869147..31fd8b82 100644 --- a/lib/Imager/APIRef.pod +++ b/lib/Imager/APIRef.pod @@ -14,6 +14,9 @@ Imager::APIRef - Imager's C API. color.rgba.red = 255; color.rgba.green = 0; color.rgba.blue = 255; + # Data Types + i_img *img; + # Drawing i_arc(im, 50, 50, 20, 45, 135, &color); i_arc_aa(im, 50, 50, 35, 90, 135, &color); @@ -47,8 +50,10 @@ Imager::APIRef - Imager's C API. # Image - # Image creation/destruction + # Image creation i_img *img = i_img_16_new(width, height, channels); + + # Image creation/destruction i_img *img = i_img_8_new(width, height, channels); i_img_destroy(img) i_img *img = i_img_double_new(width, height, channels); @@ -56,14 +61,120 @@ Imager::APIRef - Imager's C API. i_img *img = i_sametype(src, width, height); i_img *img = i_sametype_chans(src, width, height, channels); + # Image Implementation + # Image quantization + # Logging + # Paletted images # Tags =head1 DESCRIPTION +=head2 Data Types + +=over + +=item i_img + +This is Imager's image type. + +It contains the following members: + +=over + +=item * + +channels - the number of channels in the image + +=item * + +xsize, ysize - the width and height of the image in pixels + +=item * + +bytes - the number of bytes used to store the image data. Undefined +where virtual is non-zero. + +=item * + +ch_mask - a mask of writable channels. eg. if this is 6 then only +channels 1 and 2 are writable. There may be bits set for which there +are no channels in the image. + +=item * + +bits - the number of bits stored per sample. Should be one of +i_8_bits, i_16_bits, i_double_bits. + +=item * + +type - either i_direct_type for direct color images, or i_palette_type +for paletted images. + +=item * + +virtual - if zero then this image is-self contained. If non-zero then +this image could be an interface to some other implementation. + +=item * + +idata - the image data. This should not be directly accessed. A new +image implementation can use this to store its image data. +i_img_destroy() will myfree() this pointer if it's non-null. + +=item * + +tags - a structure storing the image's tags. This should only be +accessed via the i_tags_*() functions. + +=item * + +ext_data - a pointer for use internal to an image implementation. +This should be freed by the image's destroy handler. + +=item * + +im_data - data internal to Imager. This is initialized by +i_img_init(). + +=item * + +i_f_ppix, i_f_ppixf, i_f_plin, i_f_plinf, i_f_gpix, i_f_gpixf, +i_f_glin, i_f_glinf, i_f_gsamp, i_f_gampf - implementations for each +of the required image functions. An image implementation should +initialize these between calling i_img_alloc() and i_img_init(). + +=item * + +i_f_gpal, i_f_ppal, i_f_addcolors, i_f_getcolors, i_f_colorcount, +i_f_maxcolors, i_f_findcolor, i_f_setcolors - implementations for each +paletted image function. + +=item * + +i_f_destroy - custom image destruction function. This should be used +to release memory if necessary. + +=item * + +i_f_gsamp_bits - implements i_gsamp_bits() for this image. + +=item * + +i_f_psamp_bits - implements i_psamp_bits() for this image. + +=back + + +=for comment +From: File imdatatypes.h + + +=back + =head2 Drawing =over @@ -659,7 +770,7 @@ From: File rubthru.im =back -=head2 Image creation/destruction +=head2 Image creation =over @@ -674,6 +785,13 @@ Returns the image on success, or NULL on failure. =for comment From: File img16.c + +=back + +=head2 Image creation/destruction + +=over + =item i_img_8_new(x, y, ch) @@ -696,7 +814,6 @@ From: File image.c =item i_img_double_new(int x, int y, int ch) - Creates a new double per sample image. @@ -739,6 +856,53 @@ For paletted images the equivalent direct type is returned. From: File image.c +=back + +=head2 Image Implementation + +=over + +=item i_img_alloc() + +Allocates a new i_img structure. + +When implementing a new image type perform the following steps in your +image object creation function: + +=over + +=item 1. + +allocate the image with i_img_alloc(). + +=item 2. + +initialize any function pointers or other data as needed, you can +overwrite the whole block if you need to. + +=item 3. + +initialize Imager's internal data by calling i_img_init() on the image +object. + +=back + + +=for comment +From: File image.c + +=item i_img_init(img) + +Imager interal initialization of images. + +Currently this does very little, in the future it may be used to +support threads, or color profiles. + + +=for comment +From: File image.c + + =back =head2 Image quantization @@ -788,6 +952,33 @@ The method used depends on the tr_* members of quant. From: File quant.c +=back + +=head2 Logging + +=over + +=item i_loog(level, format, ...) + +This is an internal function called by the mm_log() macro. + + +=for comment +From: File log.c + +=item mm_log((level, format, ...)) + +This is the main entry point to logging. Note that the extra set of +parentheses are required due to limitations in C89 macros. + +This will format a string with the current file and line number to the +log file if logging is enabled. + + +=for comment +From: File log.h + + =back =head2 Paletted images @@ -1150,11 +1341,19 @@ will change: =item * -B +B + +=item * + +B =item * -B +B + +=item * + +B diff --git a/lib/Imager/Draw.pod b/lib/Imager/Draw.pod index bd662952..3b273586 100644 --- a/lib/Imager/Draw.pod +++ b/lib/Imager/Draw.pod @@ -922,11 +922,22 @@ type - the type of sample data to return. Default: C<8bit>. Permited values are C<8bit> and C. +As of Imager 0.61 this can be C<16bit> only for 16 bit images. + =item * channels - a reference to an array of channels to return, where 0 is the first channel. Default: C< [ 0 .. $self->getchannels()-1 ] > +=item * + +target - if an array reference is supplied in target then the samples +will be stored here instead of being returned. + +=item * + +offset - the offset within the array referenced by I + =back In list context this will return a list of integers between 0 and 255 @@ -937,6 +948,9 @@ In scalar context this will return a string of packed bytes, as with C< pack("C*", ...) > when I is C<8bit> or a string of packed doubles as with C< pack("d*", ...) > when I is C. +If the I option is supplied then only a count of samples is +returned. + Example: Check if any pixels in an image have a non-zero alpha channel: @@ -960,6 +974,59 @@ Example: Convert a 2 channel grey image into a 4 channel RGBA image: $out->setscanline(y=>$y, pixels=>$data); } +Retrieve 16-bit samples: + + if ($img->bits == 16) { + my @samples; + $img->getsamples(x => 0, y => $y, target => \@samples, type => '16bit'); + } + +=item setsamples + +This allows writing of samples back to some images. Currently this is +only supported for 16-bit/sample images. + +Parameters: + +=over + +=item * + +y - vertical position of the scanline. This parameter is required. + +=item * + +x - position to start on the scanline. Default: 0 + +=item * + +width - number of pixels to write. Default: $img->getwidth - x. The +minimum of this and the number of pixels represented by the samples +provided will be written. + +=item * + +type - the type of sample data to write. This parameter is required. + +As of Imager 0.61 this can only be C<16bit> only for 16 bit images. + +=item * + +channels - a reference to an array of channels to return, where 0 is +the first channel. Default: C< [ 0 .. $self->getchannels()-1 ] > + +=item * + +data - a reference to an array of samples to write. Required. + +=item * + +offset - the starting offset within the array referenced by I + +=back + +Returns the number of samples written. + =back =head1 Packed Color Data diff --git a/lib/Imager/Files.pod b/lib/Imager/Files.pod index 57822057..288fd098 100644 --- a/lib/Imager/Files.pod +++ b/lib/Imager/Files.pod @@ -742,8 +742,7 @@ For greyscale images channel 0 is used, for color images channel 1 use $img->to_paletted() to product a bi-level image. This way you can use dithering: - my $bilevel = $img->to_paletted(colors=>[ NC(0,0,0), NC(255,255,255) ], - make_colors => 'none', + my $bilevel = $img->to_paletted(make_colors => 'mono', translate => 'errdiff', errdiff => 'stucki'); @@ -769,11 +768,78 @@ direct color images are read at 8-bits/sample. TIFF supports the spatial resolution tags. See the C tag for some extra options. +As of Imager 0.62 Imager reads: + +=over + +=item * + +16-bit grey, RGB, or CMYK image, including a possible alpha channel as +a 16-bit/sample image. + +=item * + +32-bit grey, RGB image, including a possible alpha channel as a +double/sample image. + +=item * + +bi-level images as paletted images containing only black and white, +which other formats will also write as bi-level. + +=item * + +tiled paletted images are now handled correctly + +=back + The following tags are set in a TIFF image when read, and can be set to control output: =over +=item tiff_compression + +When reading an image this is set to the numeric value of the TIFF +compression tag. + +On writing you can set this to either a numeric compression tag value, +or one of the following values: + + Ident Number Description + none 1 No compression + packbits 32773 Macintosh RLE + ccittrle 2 CCITT RLE + fax3 3 CCITT Group 3 fax encoding (T.4) + t4 3 As above + fax4 4 CCITT Group 4 fax encoding (T.6) + t6 4 As above + lzw 5 LZW + jpeg 7 JPEG + zip 8 Deflate (GZIP) Non-standard + deflate 8 As above. + oldzip 32946 Deflate with an older code. + ccittrlew 32771 Word aligned CCITT RLE + +In general a compression setting will be ignored where it doesn't make +sense, eg. C will be ignored for compression if the image is +being written as bilevel. + +Imager attempts to check that your build of libtiff supports the given +compression, and will fallback to C if it isn't enabled. +eg. older distributions didn't include LZW compression, and JPEG +compression is only available if libtiff is configured with libjpeg's +location. + + $im->write(file => 'foo.tif', tiff_compression => 'lzw') + or die $im->errstr; + +=item tiff_jpegquality + +If I if C then this can be a number from 1 to +100 giving the JPEG compression quality. High values are better +quality and larger files. + =item tiff_resolutionunit The value of the ResolutionUnit tag. This is ignored on writing if diff --git a/lib/Imager/Handy.pod b/lib/Imager/Handy.pod index 73eeaa4d..7bb2c7c6 100644 --- a/lib/Imager/Handy.pod +++ b/lib/Imager/Handy.pod @@ -35,6 +35,13 @@ new() method. my $font = NF(file => 'foo.ttf'); +=item NCF + +Create a new L object, supplying any parameter to +the new() method. + + my $colorf = NCF(1.0, 0, 0); + =back =head1 BUGS diff --git a/lib/Imager/ImageTypes.pod b/lib/Imager/ImageTypes.pod index 81d8cab7..71a0f08c 100644 --- a/lib/Imager/ImageTypes.pod +++ b/lib/Imager/ImageTypes.pod @@ -331,6 +331,42 @@ pixels, for example masked images. This may also be used for non-native Imager images in the future, for example, for an Imager object that draws on an SDL surface. +=item is_bilevel + +Tests if the image will be written as a monochrome or bi-level image +for formats that support that image organization. + +In scalar context, returns true if the image is bi-level. + +In list context returns a list: + + ($is_bilevel, $zero_is_white) = $img->is_bilevel; + +An image is considered bi-level, if all of the following are true: + +=over + +=item * + +the image is a paletted image + +=item * + +the image has 1 or 3 channels + +=item * + +the image has only 2 colors in the palette + +=item * + +those 2 colors are black and white, in either order. + +=back + +If a real bi-level organization image is ever added to Imager, this +function will return true for that too. + =back =head2 Direct Type Images diff --git a/lib/Imager/Test.pm b/lib/Imager/Test.pm index 0689736b..994ccc43 100644 --- a/lib/Imager/Test.pm +++ b/lib/Imager/Test.pm @@ -4,11 +4,13 @@ use Test::Builder; require Exporter; use vars qw(@ISA @EXPORT_OK); @ISA = qw(Exporter); -@EXPORT_OK = qw(diff_text_with_nul test_image_raw test_image_16 test_image +@EXPORT_OK = qw(diff_text_with_nul + test_image_raw test_image_16 test_image test_image_double is_color3 is_color1 is_color4 is_fcolor4 is_image is_image_similar - image_bounds_checks); + image_bounds_checks mask_tests + test_colorf_gpix test_color_gpix test_colorf_glin); sub diff_text_with_nul { my ($desc, $text1, $text2, @params) = @_; @@ -201,6 +203,19 @@ sub test_image_16 { $img; } +sub test_image_double { + my $green = Imager::Color->new(0, 255, 0, 255); + my $blue = Imager::Color->new(0, 0, 255, 255); + my $red = Imager::Color->new(255, 0, 0, 255); + my $img = Imager->new(xsize => 150, ysize => 150, bits => 'double'); + $img->box(filled => 1, color => $green, box => [ 70, 25, 130, 125 ]); + $img->box(filled => 1, color => $blue, box => [ 20, 25, 80, 125 ]); + $img->arc(x => 75, y => 75, r => 30, color => $red); + $img->filter(type => 'conv', coef => [ 0.1, 0.2, 0.4, 0.2, 0.1 ]); + + $img; +} + sub is_image_similar($$$$) { my ($left, $right, $limit, $comment) = @_; @@ -248,6 +263,22 @@ sub is_image_similar($$$$) { if ($diff > $limit) { $builder->ok(0, $comment); $builder->diag("image data difference > $limit - $diff"); + + if ($limit == 0) { + # find the first mismatch + CHECK: + for my $y (0 .. $left->getheight()-1) { + for my $x (0.. $left->getwidth()-1) { + my @lsamples = $left->getsamples(x => $x, y => $y, width => 1); + my @rsamples = $right->getsamples(x => $x, y => $y, width => 1); + if ("@lsamples" ne "@rsamples") { + $builder->diag("first mismatch at ($x, $y) - @lsamples vs @rsamples"); + last CHECK; + } + } + } + } + return; } @@ -288,6 +319,151 @@ sub image_bounds_checks { $builder->ok(!$im->setpixel(x => 0, y => 10, color => $blackf), 'bounds check set (0, 10) float'); } +sub test_colorf_gpix { + my ($im, $x, $y, $expected, $epsilon, $comment) = @_; + + my $builder = Test::Builder->new; + + defined $comment or $comment = ''; + + my $c = Imager::i_gpixf($im, $x, $y); + unless ($c) { + $builder->ok(0, "$comment - retrieve color at ($x,$y)"); + return; + } + unless ($builder->ok(_colorf_cmp($c, $expected, $epsilon) == 0, + "$comment - got right color ($x, $y)")) { + print "# got: (", join(",", ($c->rgba)[0,1,2]), ")\n"; + print "# expected: (", join(",", ($expected->rgba)[0,1,2]), ")\n"; + return; + } + 1; +} + +sub test_color_gpix { + my ($im, $x, $y, $expected, $comment) = @_; + + my $builder = Test::Builder->new; + + defined $comment or $comment = ''; + my $c = Imager::i_get_pixel($im, $x, $y); + unless ($c) { + $builder->ok(0, "$comment - retrieve color at ($x,$y)"); + return; + } + unless ($builder->ok(_color_cmp($c, $expected) == 0, + "got right color ($x, $y)")) { + print "# got: (", join(",", ($c->rgba)[0,1,2]), ")\n"; + print "# expected: (", join(",", ($expected->rgba)[0,1,2]), ")\n"; + return; + } + + return 1; +} + +sub test_colorf_glin { + my ($im, $x, $y, $pels, $comment) = @_; + + my $builder = Test::Builder->new; + + my @got = Imager::i_glinf($im, $x, $x+@$pels, $y); + @got == @$pels + or return $builder->is_num(scalar(@got), scalar(@$pels), "$comment - pixels retrieved"); + + return $builder->ok(!grep(_colorf_cmp($pels->[$_], $got[$_], 0.005), 0..$#got), + "$comment - check colors ($x, $y)"); +} + +sub _colorf_cmp { + my ($c1, $c2, $epsilon) = @_; + + defined $epsilon or $epsilon = 0; + + my @s1 = $c1->rgba; + my @s2 = $c2->rgba; + + # print "# (",join(",", @s1[0..2]),") <=> (",join(",", @s2[0..2]),")\n"; + return abs($s1[0]-$s2[0]) >= $epsilon && $s1[0] <=> $s2[0] + || abs($s1[1]-$s2[1]) >= $epsilon && $s1[1] <=> $s2[1] + || abs($s1[2]-$s2[2]) >= $epsilon && $s1[2] <=> $s2[2]; +} + +sub _color_cmp { + my ($c1, $c2) = @_; + + my @s1 = $c1->rgba; + my @s2 = $c2->rgba; + + return $s1[0] <=> $s2[0] + || $s1[1] <=> $s2[1] + || $s1[2] <=> $s2[2]; +} + +# these test the action of the channel mask on the image supplied +# which should be an OO image. +sub mask_tests { + my ($im, $epsilon) = @_; + + my $builder = Test::Builder->new; + + defined $epsilon or $epsilon = 0; + + # we want to check all four of ppix() and plin(), ppix() and plinf() + # basic test procedure: + # first using default/all 1s mask, set to white + # make sure we got white + # set mask to skip a channel, set to grey + # make sure only the right channels set + + print "# channel mask tests\n"; + # 8-bit color tests + my $white = Imager::NC(255, 255, 255); + my $grey = Imager::NC(128, 128, 128); + my $white_grey = Imager::NC(128, 255, 128); + + print "# with ppix\n"; + $builder->ok($im->setmask(mask=>~0), "set to default mask"); + $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$white), "set to white all channels"); + test_color_gpix($im->{IMG}, 0, 0, $white, "ppix"); + $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1"); + $builder->ok($im->setpixel(x=>0, 'y'=>0, color=>$grey), "set to grey, no channel 2"); + test_color_gpix($im->{IMG}, 0, 0, $white_grey, "ppix masked"); + + print "# with plin\n"; + $builder->ok($im->setmask(mask=>~0), "set to default mask"); + $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels => [$white]), + "set to white all channels"); + test_color_gpix($im->{IMG}, 0, 1, $white, "plin"); + $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1"); + $builder->ok($im->setscanline(x=>0, 'y'=>1, pixels=>[$grey]), + "set to grey, no channel 2"); + test_color_gpix($im->{IMG}, 0, 1, $white_grey, "plin masked"); + + # float color tests + my $whitef = Imager::NCF(1.0, 1.0, 1.0); + my $greyf = Imager::NCF(0.5, 0.5, 0.5); + my $white_greyf = Imager::NCF(0.5, 1.0, 0.5); + + print "# with ppixf\n"; + $builder->ok($im->setmask(mask=>~0), "set to default mask"); + $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$whitef), "set to white all channels"); + test_colorf_gpix($im->{IMG}, 0, 2, $whitef, $epsilon, "ppixf"); + $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1"); + $builder->ok($im->setpixel(x=>0, 'y'=>2, color=>$greyf), "set to grey, no channel 2"); + test_colorf_gpix($im->{IMG}, 0, 2, $white_greyf, $epsilon, "ppixf masked"); + + print "# with plinf\n"; + $builder->ok($im->setmask(mask=>~0), "set to default mask"); + $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels => [$whitef]), + "set to white all channels"); + test_colorf_gpix($im->{IMG}, 0, 3, $whitef, $epsilon, "plinf"); + $builder->ok($im->setmask(mask=>0xF-0x2), "set channel to exclude channel1"); + $builder->ok($im->setscanline(x=>0, 'y'=>3, pixels=>[$greyf]), + "set to grey, no channel 2"); + test_colorf_gpix($im->{IMG}, 0, 3, $white_greyf, $epsilon, "plinf masked"); + +} + 1; __END__ @@ -347,6 +523,10 @@ Returns a 150x150x3 8-bit/sample OO test image. Returns a 150x150x3 16-bit/sample OO test image. +=item test_image_double() + +Returns a 150x150x3 double/sample OO test image. + =item diff_text_with_nul($test_name, $text1, $text2, @options) Creates 2 test images and writes $text1 to the first image and $text2 @@ -365,6 +545,25 @@ image to ensure that it fails in those locations. Any new image type should pass these tests. Does 16 separate tests. +=item test_colorf_gpix($im, $x, $y, $expected, $epsilon, $comment) + +Retrieves the pixel ($x,$y) from the low-level image $im and compares +it to the floating point color $expected, with a tolerance of epsilon. + +=item test_color_gpix($im, $x, $y, $expected, $comment) + +Retrieves the pixel ($x,$y) from the low-level image $im and compares +it to the floating point color $expected. + +=item test_colorf_glin($im, $x, $y, $pels, $comment) + +Retrieves the floating point pixels ($x, $y)-[$x+@$pels, $y] from the +low level image $im and compares them against @$pels. + +=item mask_tests($im, $epsilon) + +Perform a standard set of mask tests on the OO image $im. + =back =head1 AUTHOR diff --git a/log.c b/log.c index fb08ffd4..2f043a95 100644 --- a/log.c +++ b/log.c @@ -67,6 +67,14 @@ void i_fatal(int exitcode,const char *fmt, ... ) { exit(exitcode); } #endif +/* +=item i_loog(level, format, ...) +=category Logging + +This is an internal function called by the mm_log() macro. + +=cut +*/ void i_loog(int level,const char *fmt, ... ) { diff --git a/log.h b/log.h index 12d3467b..5b1f4839 100644 --- a/log.h +++ b/log.h @@ -15,6 +15,18 @@ void i_init_log( const char *name, int onoff ); void i_loog(int level,const char *msg, ... ); void i_fatal ( int exitcode,const char *fmt, ... ); +/* +=item mm_log((level, format, ...)) +=category Logging + +This is the main entry point to logging. Note that the extra set of +parentheses are required due to limitations in C89 macros. + +This will format a string with the current file and line number to the +log file if logging is enabled. + +=cut +*/ #ifdef IMAGER_LOG #define mm_log(x) { i_lhead(__FILE__,__LINE__); i_loog x; } diff --git a/palimg.c b/palimg.c index 75c56cce..6bae01c3 100644 --- a/palimg.c +++ b/palimg.c @@ -71,18 +71,28 @@ static i_img IIM_base_8bit_pal = i_setcolors_p, /* i_f_setcolors */ i_destroy_p, /* i_f_destroy */ + + i_gsamp_bits_fb, + NULL, /* i_f_psamp_bits */ }; /* -=item i_img_pal_new_low(im, x, y, channels, maxpal) +=item i_img_pal_new(x, y, channels, maxpal) + +=category Image creation/destruction +=synopsis i_img *img = i_img_pal_new(width, height, channels, max_palette_size) + +Creates a new paletted image of the supplied dimensions. -Creates a new paletted image. +I is the maximum palette size and should normally be 256. -Currently 0 < maxpal <= 256 +Returns a new image or NULL on failure. =cut */ -i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) { +i_img * +i_img_pal_new(int x, int y, int channels, int maxpal) { + i_img *im; i_img_pal_ext *palext; int bytes, line_bytes; @@ -114,6 +124,7 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) { return NULL; } + im = i_img_alloc(); memcpy(im, &IIM_base_8bit_pal, sizeof(i_img)); palext = mymalloc(sizeof(i_img_pal_ext)); palext->pal = mymalloc(sizeof(i_color) * maxpal); @@ -128,34 +139,9 @@ i_img *i_img_pal_new_low(i_img *im, int x, int y, int channels, int maxpal) { memset(im->idata, 0, im->bytes); im->xsize = x; im->ysize = y; - - return im; -} - -/* -=item i_img_pal_new(x, y, channels, maxpal) - -=category Image creation/destruction -=synopsis i_img *img = i_img_pal_new(width, height, channels, max_palette_size) - -Creates a new paletted image of the supplied dimensions. - -I is the maximum palette size and should normally be 256. - -Returns a new image or NULL on failure. - -=cut -*/ - -i_img *i_img_pal_new(int x, int y, int channels, int maxpal) { - i_img *im; - mm_log((1, "i_img_pal_new(x %d, y %d, channels %d, maxpal %d)\n", x, y, channels, maxpal)); - im = mymalloc(sizeof(i_img)); - if (!i_img_pal_new_low(im, x, y, channels, maxpal)) { - myfree(im); - im = NULL; - } + i_img_init(im); + return im; } diff --git a/t/t01introvert.t b/t/t01introvert.t index f38f88af..c9ae822f 100644 --- a/t/t01introvert.t +++ b/t/t01introvert.t @@ -3,7 +3,7 @@ # to make sure we get expected values use strict; -use Test::More tests => 223; +use Test::More tests => 228; BEGIN { use_ok(Imager => qw(:handy :all)) } @@ -167,6 +167,15 @@ is($impal2->type, 'paletted', "check type"); is($impal3->type, 'paletted', "and is paletted"); } +{ # basic checks, 8-bit direct images + my $im = Imager->new(xsize => 2, ysize => 3); + ok($im, 'create 8-bit direct image'); + is($im->bits, 8, '8 bits'); + ok(!$im->virtual, 'not virtual'); + is($im->type, 'direct', 'direct image'); + ok(!$im->is_bilevel, 'not mono'); +} + ok(!Imager->new(xsize=>0, ysize=>1), "fail to create 0 height image"); cmp_ok(Imager->errstr, '=~', qr/Image sizes must be positive/, "0 height error message check"); diff --git a/t/t021sixteen.t b/t/t021sixteen.t index c04f31f2..abcfcd6b 100644 --- a/t/t021sixteen.t +++ b/t/t021sixteen.t @@ -1,15 +1,14 @@ #!perl -w use strict; -use Test::More tests => 103; +use Test::More tests => 104; BEGIN { use_ok(Imager=>qw(:all :handy)) } init_log("testout/t021sixteen.log", 1); -require "t/testtools.pl"; - use Imager::Color::Float; -use Imager::Test qw(test_image is_image image_bounds_checks); +use Imager::Test qw(test_image is_image image_bounds_checks test_colorf_gpix + test_colorf_glin mask_tests is_color3); my $im_g = Imager::i_img_16_new(100, 101, 1); @@ -42,22 +41,66 @@ for my $y (0..101) { } pass("fill with red"); # basic sanity -test_colorf_gpix($im_rgb, 0, 0, $redf); -test_colorf_gpix($im_rgb, 99, 0, $redf); -test_colorf_gpix($im_rgb, 0, 100, $redf); -test_colorf_gpix($im_rgb, 99, 100, $redf); -test_colorf_glin($im_rgb, 0, 0, ($redf) x 100); -test_colorf_glin($im_rgb, 0, 100, ($redf) x 100); +test_colorf_gpix($im_rgb, 0, 0, $redf, 0, "top-left"); +test_colorf_gpix($im_rgb, 99, 0, $redf, 0, "top-right"); +test_colorf_gpix($im_rgb, 0, 100, $redf, 0, "bottom left"); +test_colorf_gpix($im_rgb, 99, 100, $redf, 0, "bottom right"); +test_colorf_glin($im_rgb, 0, 0, [ ($redf) x 100 ], "first line"); +test_colorf_glin($im_rgb, 0, 100, [ ($redf) x 100 ], "last line"); Imager::i_plinf($im_rgb, 20, 1, ($greenf) x 60); test_colorf_glin($im_rgb, 0, 1, - ($redf) x 20, ($greenf) x 60, ($redf) x 20); + [ ($redf) x 20, ($greenf) x 60, ($redf) x 20 ], + "added some green in the middle"); +{ + my @samples; + is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0 .. 2), 12, + "i_gsamp_bits all channels - count") + or print "# ", Imager->_error_as_msg(), "\n"; + is_deeply(\@samples, [ 65535, 0, 0, 65535, 0, 0, + 0, 65535, 0, 0, 65535, 0 ], + "check samples retrieved"); + @samples = (); + is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0, 2), 8, + "i_gsamp_bits some channels - count") + or print "# ", Imager->_error_as_msg(), "\n"; + is_deeply(\@samples, [ 65535, 0, 65535, 0, + 0, 0, 0, 0 ], + "check samples retrieved"); + # fail gsamp + is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 16, \@samples, 0, 0, 3), undef, + "i_gsamp_bits fail bad channel"); + is(Imager->_error_as_msg(), 'No channel 3 in this image', 'check message'); + + is(Imager::i_gsamp_bits($im_rgb, 18, 22, 1, 17, \@samples, 0, 0, 2), 8, + "i_gsamp_bits succeed high bits"); + is($samples[0], 131071, "check correct with high bits"); + + # write some samples back + my @wr_samples = + ( + 0, 0, 65535, + 65535, 0, 0, + 0, 65535, 0, + 65535, 65535, 0 + ); + is(Imager::i_psamp_bits($im_rgb, 18, 2, 16, [ 0 .. 2 ], \@wr_samples), + 12, "write 16-bit samples") + or print "# ", Imager->_error_as_msg(), "\n"; + @samples = (); + is(Imager::i_gsamp_bits($im_rgb, 18, 22, 2, 16, \@samples, 0, 0 .. 2), 12, + "read them back") + or print "# ", Imager->_error_as_msg(), "\n"; + is_deeply(\@samples, \@wr_samples, "check they match"); + my $c = Imager::i_get_pixel($im_rgb, 18, 2); + is_color3($c, 0, 0, 255, "check it write to the right places"); +} # basic OO tests my $oo16img = Imager->new(xsize=>200, ysize=>201, bits=>16); ok($oo16img, "make a 16-bit oo image"); is($oo16img->bits, 16, "test bits"); - +isnt($oo16img->is_bilevel, "should not be considered mono"); # make sure of error handling ok(!Imager->new(xsize=>0, ysize=>1, bits=>16), "fail to create a 0 pixel wide image"); @@ -165,3 +208,16 @@ cmp_ok(Imager->errstr, '=~', qr/channels must be between 1 and 4/, my $im = Imager->new(xsize => 10, ysize => 10, bits => 16); image_bounds_checks($im); } + +{ + my $im = Imager->new(xsize => 10, ysize => 10, bits => 16, channels => 3); + my @wr_samples = map int(rand 65536), 1..30; + is($im->setsamples('y' => 1, data => \@wr_samples, type => '16bit'), + 30, "write 16-bit to OO image") + or print "# ", $im->errstr, "\n"; + my @samples; + is($im->getsamples(y => 1, target => \@samples, type => '16bit'), + 30, "read 16-bit from OO image") + or print "# ", $im->errstr, "\n"; + is_deeply(\@wr_samples, \@samples, "check it matches"); +} diff --git a/t/t022double.t b/t/t022double.t index 403aef66..e5234b12 100644 --- a/t/t022double.t +++ b/t/t022double.t @@ -1,6 +1,6 @@ #!perl -w use strict; -use Test::More tests => 97; +use Test::More tests => 98; BEGIN { use_ok(Imager => qw(:all :handy)) } require "t/testtools.pl"; @@ -64,6 +64,7 @@ test_colorf_glin($im_rgb, 0, 1, my $ooimg = Imager->new(xsize=>200, ysize=>201, bits=>'double'); ok($ooimg, "couldn't make double image"); is($ooimg->bits, 'double', "oo didn't give double image"); +ok(!$ooimg->is_bilevel, 'not monochrome'); # check that the image is copied correctly my $oocopy = $ooimg->copy; diff --git a/t/t023palette.t b/t/t023palette.t index ec464fb1..062e807e 100644 --- a/t/t023palette.t +++ b/t/t023palette.t @@ -1,7 +1,7 @@ #!perl -w # some of this is tested in t01introvert.t too use strict; -use Test::More tests => 107; +use Test::More tests => 121; BEGIN { use_ok("Imager"); } use Imager::Test qw(image_bounds_checks); @@ -280,6 +280,9 @@ cmp_ok(Imager->errstr, '=~', qr/Channels must be positive and <= 4/, translate => 'closest'); is($mono->type, 'paletted', "check we get right image type"); is($mono->colorcount, 2, "only 2 colors"); + my ($is_mono, $ziw) = $mono->is_bilevel; + ok($is_mono, "check monochrome check true"); + is($ziw, 0, "check ziw false"); my @colors = $mono->getcolors; iscolor($colors[0], $black, "check first entry"); iscolor($colors[1], $white, "check second entry"); @@ -289,6 +292,36 @@ cmp_ok(Imager->errstr, '=~', qr/Channels must be positive and <= 4/, is($pixels[2], 0, "check black pixel"); } +{ # check for the various mono images we accept + my $mono_8_bw_3 = Imager->new(xsize => 2, ysize => 2, channels => 3, + type => 'paletted'); + ok($mono_8_bw_3->addcolors(colors => [ qw/000000 FFFFFF/ ]), + "mono8bw3 - add colors"); + ok($mono_8_bw_3->is_bilevel, "it's mono"); + is(($mono_8_bw_3->is_bilevel)[1], 0, 'zero not white'); + + my $mono_8_wb_3 = Imager->new(xsize => 2, ysize => 2, channels => 3, + type => 'paletted'); + ok($mono_8_wb_3->addcolors(colors => [ qw/FFFFFF 000000/ ]), + "mono8wb3 - add colors"); + ok($mono_8_wb_3->is_bilevel, "it's mono"); + is(($mono_8_wb_3->is_bilevel)[1], 1, 'zero is white'); + + my $mono_8_bw_1 = Imager->new(xsize => 2, ysize => 2, channels => 1, + type => 'paletted'); + ok($mono_8_bw_1->addcolors(colors => [ qw/000000 FFFFFF/ ]), + "mono8bw - add colors"); + ok($mono_8_bw_1->is_bilevel, "it's mono"); + is(($mono_8_bw_1->is_bilevel)[1], 0, 'zero not white'); + + my $mono_8_wb_1 = Imager->new(xsize => 2, ysize => 2, channels => 1, + type => 'paletted'); + ok($mono_8_wb_1->addcolors(colors => [ qw/FFFFFF 000000/ ]), + "mono8wb - add colors"); + ok($mono_8_wb_1->is_bilevel, "it's mono"); + is(($mono_8_wb_1->is_bilevel)[1], 1, 'zero is white'); +} + { # check bounds checking my $im = Imager->new(xsize => 10, ysize => 10, type=>'paletted'); ok($im->addcolors(colors => [ $black ]), "add color of pixel bounds check writes"); diff --git a/t/t106tiff.t b/t/t106tiff.t index 0f6a0fa0..08727499 100644 --- a/t/t106tiff.t +++ b/t/t106tiff.t @@ -1,7 +1,8 @@ #!perl -w use strict; -use Test::More tests => 129; +use Test::More tests => 213; use Imager qw(:all); +use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double); $^W=1; # warnings during command-line tests $|=1; # give us some progress in the test harness init_log("testout/t106tiff.log",1); @@ -33,7 +34,17 @@ SKIP: cmp_ok($im->errstr, '=~', "format 'tiff' not supported", "check no tiff message"); ok(!grep($_ eq 'tiff', Imager->read_types), "check tiff not in read types"); ok(!grep($_ eq 'tiff', Imager->write_types), "check tiff not in write types"); - skip("no tiff support", 123); + skip("no tiff support", 209); + } + + my $ver_string = Imager::i_tiff_libversion(); + ok(my ($full, $major, $minor, $point) = + $ver_string =~ /Version +((\d+)\.(\d+).(\d+))/, + "extract library version"); + # make something we can compare + my $cmp_ver = sprintf("%03d%03d%03d", $major, $minor, $point); + if ($cmp_ver lt '003007000') { + diag("You have an old version of libtiff - $full, some tests will be skipped"); } Imager::i_tags_add($img, "i_xres", 0, "300", 0); @@ -44,7 +55,8 @@ SKIP: open(FH,">testout/t106.tiff") || die "cannot open testout/t106.tiff for writing\n"; binmode(FH); my $IO = Imager::io_new_fd(fileno(FH)); - ok(i_writetiff_wiol($img, $IO), "write low level"); + ok(i_writetiff_wiol($img, $IO), "write low level") + or print "# ", Imager->_error_as_msg, "\n"; close(FH); open(FH,"testout/t106.tiff") or die "cannot open testout/t106.tiff\n"; @@ -142,7 +154,8 @@ SKIP: # paletted reads my $img4 = Imager->new; - ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted"); + ok($img4->read(file=>'testimg/comp4.tif'), "reading 4-bit paletted") + or print "# ", $img4->errstr, "\n"; is($img4->type, 'paletted', "image isn't paletted"); print "# colors: ", $img4->colorcount,"\n"; cmp_ok($img4->colorcount, '<=', 16, "more than 16 colors!"); @@ -155,6 +168,10 @@ SKIP: my $diff = i_img_diff($img4->{IMG}, $bmp4->{IMG}); print "# diff $diff\n"; ok($diff == 0, "image mismatch"); + my $img4t = Imager->new; + ok($img4t->read(file => 'testimg/comp4t.tif'), "read 4-bit paletted, tiled") + or print "# ", $img4t->errstr, "\n"; + is_image($bmp4, $img4t, "check tiled version matches"); my $img8 = Imager->new; ok($img8->read(file=>'testimg/comp8.tif'), "reading 8-bit paletted"); is($img8->type, 'paletted', "image isn't paletted"); @@ -431,22 +448,34 @@ SKIP: my $photo_cielab = 8; my @alpha_images = ( - [ 'srgb.tif', 3, $photo_rgb ], - [ 'srgba.tif', 4, $photo_rgb ], - [ 'srgbaa.tif', 4, $photo_rgb ], - [ 'scmyk.tif', 3, $photo_cmyk ], - [ 'scmyka.tif', 4, $photo_cmyk ], - [ 'scmykaa.tif', 4, $photo_cmyk ], - [ 'slab.tif', 3, $photo_cielab ], + [ 'srgb.tif', 3, $photo_rgb, '003005005' ], + [ 'srgba.tif', 4, $photo_rgb, '003005005' ], + [ 'srgbaa.tif', 4, $photo_rgb, '003005005' ], + [ 'scmyk.tif', 3, $photo_cmyk, '003005005' ], + [ 'scmyka.tif', 4, $photo_cmyk, '003005005' ], + [ 'scmykaa.tif', 4, $photo_cmyk, '003005005' ], + [ 'slab.tif', 3, $photo_cielab, '003006001' ], ); + for my $test (@alpha_images) { - my $im = Imager->new; - ok($im->read(file => "testimg/$test->[0]"), - "read alpha test $test->[0]") + my ($input, $channels, $photo, $need_ver) = @$test; + + SKIP: { + my $skipped = $channels == 4 ? 4 : 3; + $need_ver le $cmp_ver + or skip("Your ancient tifflib is buggy/limited for this test", $skipped); + my $im = Imager->new; + ok($im->read(file => "testimg/$input"), + "read alpha test $input") or print "# ", $im->errstr, "\n"; - is($im->getchannels, $test->[1], "channels for $test->[0] match"); - is($im->tags(name=>'tiff_photometric'), $test->[2], - "photometric for $test->[0] match"); + is($im->getchannels, $channels, "channels for $input match"); + is($im->tags(name=>'tiff_photometric'), $photo, + "photometric for $input match"); + $channels == 4 + or next; + my $c = $im->getpixel(x => 0, 'y' => 7); + is(($c->rgba)[3], 0, "bottom row should have 0 alpha"); + } } } @@ -454,5 +483,256 @@ SKIP: ok(grep($_ eq 'tiff', Imager->read_types), "check tiff in read types"); ok(grep($_ eq 'tiff', Imager->write_types), "check tiff in write types"); } + + { # reading tile based images + my $im = Imager->new; + ok($im->read(file => 'testimg/pengtile.tif'), "read tiled image"); + # compare it + my $comp = Imager->new; + ok($comp->read(file => 'testimg/penguin-base.ppm'), 'read comparison image'); + is_image($im, $comp, 'compare them'); + } + + SKIP: + { # failing to read tile based images + # we grab our tiled image and patch a tile offset to nowhere + ok(open(TIFF, '< testimg/pengtile.tif'), 'open pengtile.tif') + or skip 'cannot open testimg/pengtile.tif', 4; + + $cmp_ver ge '0030057' + or skip("Your ancient tifflib has bad error handling", 4); + binmode TIFF; + my $data = do { local $/; ; }; + + # patch a tile offset + substr($data, 0x5AFE, 4) = pack("H*", "1F5C0000"); + + #open PIPE, "| bytedump -a | less" or die; + #print PIPE $data; + #close PIPE; + + my $allow = Imager->new; + ok($allow->read(data => $data, allow_incomplete => 1), + "read incomplete tiled"); + ok($allow->tags(name => 'i_incomplete'), 'i_incomplete set'); + is($allow->tags(name => 'i_lines_read'), 173, + 'check i_lines_read set appropriately'); + + my $fail = Imager->new; + ok(!$fail->read(data => $data), "read fail tiled"); + } + + { # read 16-bit/sample + my $im16 = Imager->new; + ok($im16->read(file => 'testimg/rgb16.tif'), "read 16-bit rgb"); + is($im16->bits, 16, 'got a 16-bit image'); + my $im16t = Imager->new; + ok($im16t->read(file => 'testimg/rgb16t.tif'), "ready 16-bit rgb tiled"); + is($im16t->bits, 16, 'got a 16-bit image'); + is_image($im16, $im16t, 'check they match'); + + my $grey16 = Imager->new; + ok($grey16->read(file => 'testimg/grey16.tif'), "read 16-bit grey") + or print "# ", $grey16->errstr, "\n"; + is($grey16->bits, 16, 'got a 16-bit image'); + is($grey16->getchannels, 1, 'and its grey'); + my $comp16 = $im16->convert(matrix => [ [ 0.299, 0.587, 0.114 ] ]); + is_image($grey16, $comp16, 'compare grey to converted'); + + my $grey32 = Imager->new; + ok($grey32->read(file => 'testimg/grey32.tif'), "read 32-bit grey") + or print "# ", $grey32->errstr, "\n"; + is($grey32->bits, 'double', 'got a double image'); + is($grey32->getchannels, 2, 'and its grey + alpha'); + is($grey32->tags(name => 'tiff_bitspersample'), 32, + "check bits per sample"); + my $base = test_image_double->convert(preset =>'grey') + ->convert(preset => 'addalpha'); + is_image($grey32, $base, 'compare to original'); + } + + { # read 16, 32-bit/sample and compare to the original + my $rgba = Imager->new; + ok($rgba->read(file => 'testimg/srgba.tif'), + "read base rgba image"); + my $rgba16 = Imager->new; + ok($rgba16->read(file => 'testimg/srgba16.tif'), + "read 16-bit/sample rgba image"); + is_image($rgba, $rgba16, "check they match"); + is($rgba16->bits, 16, 'check we got the right type'); + + my $rgba32 = Imager->new; + ok($rgba32->read(file => 'testimg/srgba32.tif'), + "read 32-bit/sample rgba image"); + is_image($rgba, $rgba32, "check they match"); + is($rgba32->bits, 'double', 'check we got the right type'); + + my $cmyka16 = Imager->new; + ok($cmyka16->read(file => 'testimg/scmyka16.tif'), + "read cmyk 16-bit") + or print "# ", $cmyka16->errstr, "\n"; + is($cmyka16->bits, 16, "check we got the right type"); + is_image_similar($rgba, $cmyka16, 10, "check image data"); + } + { # read bi-level + my $pbm = Imager->new; + ok($pbm->read(file => 'testimg/imager.pbm'), "read original pbm"); + my $tif = Imager->new; + ok($tif->read(file => 'testimg/imager.tif'), "read mono tif"); + is_image($pbm, $tif, "compare them"); + is($tif->type, 'paletted', 'check image type'); + is($tif->colorcount, 2, 'check we got a "mono" image'); + } + + { # check alpha channels scaled correctly for fallback handler + my $im = Imager->new; + ok($im->read(file=>'testimg/alpha.tif'), 'read alpha check image'); + my @colors = + ( + [ 0, 0, 0 ], + [ 255, 255, 255 ], + [ 127, 0, 127 ], + [ 127, 127, 0 ], + ); + my @alphas = ( 255, 191, 127, 63 ); + my $ok = 1; + my $msg = 'alpha check ok'; + CHECKER: + for my $y (0 .. 3) { + for my $x (0 .. 3) { + my $c = $im->getpixel(x => $x, 'y' => $y); + my @c = $c->rgba; + my $alpha = pop @c; + if ($alpha != $alphas[$y]) { + $ok = 0; + $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]"; + last CHECKER; + } + my $expect = $colors[$x]; + for my $ch (0 .. 2) { + if (abs($expect->[$ch]-$c[$ch]) > 3) { + $ok = 0; + $msg = "($x,$y)[$ch] color mismatch got $c[$ch] vs expected $expect->[$ch]"; + last CHECKER; + } + } + } + } + ok($ok, $msg); + } + + { # check alpha channels scaled correctly for greyscale + my $im = Imager->new; + ok($im->read(file=>'testimg/gralpha.tif'), 'read alpha check grey image'); + my @greys = ( 0, 255, 52, 112 ); + my @alphas = ( 255, 191, 127, 63 ); + my $ok = 1; + my $msg = 'alpha check ok'; + CHECKER: + for my $y (0 .. 3) { + for my $x (0 .. 3) { + my $c = $im->getpixel(x => $x, 'y' => $y); + my ($grey, $alpha) = $c->rgba; + if ($alpha != $alphas[$y]) { + $ok = 0; + $msg = "($x,$y) alpha mismatch $alpha vs $alphas[$y]"; + last CHECKER; + } + if (abs($greys[$x] - $grey) > 3) { + $ok = 0; + $msg = "($x,$y) grey mismatch $grey vs $greys[$x]"; + last CHECKER; + } + } + } + ok($ok, $msg); + } + + { # 16-bit writes + my $orig = test_image_16(); + my $data; + ok($orig->write(data => \$data, type => 'tiff', + tiff_compression => 'none'), "write 16-bit/sample"); + my $im = Imager->new; + ok($im->read(data => $data), "read it back"); + is_image($im, $orig, "check read data matches"); + is($im->tags(name => 'tiff_bitspersample'), 16, "correct bits"); + is($im->bits, 16, 'check image bits'); + is($im->tags(name => 'tiff_photometric'), 2, "correct photometric"); + is($im->tags(name => 'tiff_compression'), 'none', "no compression"); + is($im->getchannels, 3, 'correct channels'); + } + + { # 8-bit writes + # and check compression + my $compress = Imager::i_tiff_has_compression('lzw') ? 'lzw' : 'packbits'; + my $orig = test_image()->convert(preset=>'grey') + ->convert(preset => 'addalpha'); + my $data; + ok($orig->write(data => \$data, type => 'tiff', + tiff_compression=> $compress), + "write 8 bit"); + my $im = Imager->new; + ok($im->read(data => $data), "read it back"); + is_image($im, $orig, "check read data matches"); + is($im->tags(name => 'tiff_bitspersample'), 8, 'correct bits'); + is($im->bits, 8, 'check image bits'); + is($im->tags(name => 'tiff_photometric'), 1, 'correct photometric'); + is($im->tags(name => 'tiff_compression'), $compress, + "$compress compression"); + is($im->getchannels, 2, 'correct channels'); + } + + { # double writes + my $orig = test_image_double()->convert(preset=>'addalpha'); + my $data; + ok($orig->write(data => \$data, type => 'tiff', + tiff_compression => 'none'), + "write 32-bit/sample from double") + or print "# ", $orig->errstr, "\n"; + my $im = Imager->new; + ok($im->read(data => $data), "read it back"); + is_image($im, $orig, "check read data matches"); + is($im->tags(name => 'tiff_bitspersample'), 32, "correct bits"); + is($im->bits, 'double', 'check image bits'); + is($im->tags(name => 'tiff_photometric'), 2, "correct photometric"); + is($im->tags(name => 'tiff_compression'), 'none', "no compression"); + is($im->getchannels, 4, 'correct channels'); + } + + { # bilevel + my $im = test_image()->convert(preset => 'grey') + ->to_paletted(make_colors => 'mono', + translate => 'errdiff'); + my $faxdata; + + # fax compression is written as miniswhite + ok($im->write(data => \$faxdata, type => 'tiff', + tiff_compression => 'fax3'), + "write bilevel fax compressed"); + my $fax = Imager->new; + ok($fax->read(data => $faxdata), "read it back"); + ok($fax->is_bilevel, "got a bi-level image back"); + is($fax->tags(name => 'tiff_compression'), 'fax3', + "check fax compression used"); + is_image($fax, $im, "compare to original"); + + # other compresion written as minisblack + my $packdata; + ok($im->write(data => \$packdata, type => 'tiff', + tiff_compression => 'jpeg'), + "write bilevel packbits compressed"); + my $packim = Imager->new; + ok($packim->read(data => $packdata), "read it back"); + ok($packim->is_bilevel, "got a bi-level image back"); + is($packim->tags(name => 'tiff_compression'), 'packbits', + "check fallback compression used"); + is_image($packim, $im, "compare to original"); + } + + { # fallback handling of tiff + is(Imager::i_tiff_has_compression('none'), 1, "can always do uncompresed"); + is(Imager::i_tiff_has_compression('xxx'), '', "can't do xxx compression"); + } } diff --git a/t/t61filters.t b/t/t61filters.t index 90382222..061eee39 100644 --- a/t/t61filters.t +++ b/t/t61filters.t @@ -27,7 +27,7 @@ test($imbase, {type=>'conv', coef=>[ -0.5, 1, -0.5, ], }, my $imbase16 = $imbase->to_rgb16; my $gauss16 = test($imbase16, {type=>'gaussian', stddev=>5 }, 'testout/t61_gaussian16.ppm'); - is_image_similar($gauss, $gauss16, 200000, "8 and 16 gaussian match"); + is_image_similar($gauss, $gauss16, 250000, "8 and 16 gaussian match"); } diff --git a/t/testtools.pl b/t/testtools.pl index a7a79cef..497edc1e 100644 --- a/t/testtools.pl +++ b/t/testtools.pl @@ -175,6 +175,3 @@ sub mask_tests { } -sub NCF { - return Imager::Color::Float->new(@_); -} diff --git a/testimg/alpha.tif b/testimg/alpha.tif new file mode 100644 index 00000000..b56a9a0b Binary files /dev/null and b/testimg/alpha.tif differ diff --git a/testimg/comp4t.tif b/testimg/comp4t.tif new file mode 100644 index 00000000..0140a274 Binary files /dev/null and b/testimg/comp4t.tif differ diff --git a/testimg/gralpha.tif b/testimg/gralpha.tif new file mode 100644 index 00000000..7fa6def8 Binary files /dev/null and b/testimg/gralpha.tif differ diff --git a/testimg/grey16.tif b/testimg/grey16.tif new file mode 100755 index 00000000..9dc3a21a Binary files /dev/null and b/testimg/grey16.tif differ diff --git a/testimg/grey32.tif b/testimg/grey32.tif new file mode 100644 index 00000000..a3844b84 Binary files /dev/null and b/testimg/grey32.tif differ diff --git a/testimg/imager.pbm b/testimg/imager.pbm new file mode 100644 index 00000000..610caf3e Binary files /dev/null and b/testimg/imager.pbm differ diff --git a/testimg/imager.tif b/testimg/imager.tif new file mode 100644 index 00000000..8c2bfed4 Binary files /dev/null and b/testimg/imager.tif differ diff --git a/testimg/pengtile.tif b/testimg/pengtile.tif new file mode 100644 index 00000000..dd4229f6 Binary files /dev/null and b/testimg/pengtile.tif differ diff --git a/testimg/rgb16.tif b/testimg/rgb16.tif new file mode 100644 index 00000000..19fa32b0 Binary files /dev/null and b/testimg/rgb16.tif differ diff --git a/testimg/rgb16t.tif b/testimg/rgb16t.tif new file mode 100644 index 00000000..4ae3247e Binary files /dev/null and b/testimg/rgb16t.tif differ diff --git a/testimg/scmyka16.tif b/testimg/scmyka16.tif new file mode 100644 index 00000000..e24fbd43 Binary files /dev/null and b/testimg/scmyka16.tif differ diff --git a/testimg/srgba16.tif b/testimg/srgba16.tif new file mode 100644 index 00000000..e6d8e5f5 Binary files /dev/null and b/testimg/srgba16.tif differ diff --git a/testimg/srgba32.tif b/testimg/srgba32.tif new file mode 100644 index 00000000..7725f5fc Binary files /dev/null and b/testimg/srgba32.tif differ diff --git a/testimg/srgbaa.tif b/testimg/srgbaa.tif index afa7d87e..3214858e 100644 Binary files a/testimg/srgbaa.tif and b/testimg/srgbaa.tif differ diff --git a/tiff.c b/tiff.c index 8406ca6b..7c5548e8 100644 --- a/tiff.c +++ b/tiff.c @@ -1,8 +1,14 @@ #include "imager.h" -#include "tiffio.h" +#include #include "iolayer.h" #include "imageri.h" +/* needed to implement our substitute TIFFIsCODECConfigured */ +#if TIFFLIB_VERSION < 20031121 +#include "tiffconf.h" +static int TIFFIsCODECConfigured(uint16 scheme); +#endif + /* =head1 NAME @@ -35,11 +41,17 @@ Some of these functions are internal. ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#define CLAMP8(x) ((x) < 0 ? 0 : (x) > 255 ? 255 : (x)) +#define CLAMP16(x) ((x) < 0 ? 0 : (x) > 65535 ? 65535 : (x)) + struct tag_name { char *name; uint32 tag; }; +static i_img *read_one_rgb_tiled(TIFF *tif, int width, int height, int allow_incomplete); +static i_img *read_one_rgb_lines(TIFF *tif, int width, int height, int allow_incomplete); + static struct tag_name text_tag_names[] = { { "tiff_documentname", TIFFTAG_DOCUMENTNAME, }, @@ -53,10 +65,102 @@ static struct tag_name text_tag_names[] = { "tiff_hostcomputer", TIFFTAG_HOSTCOMPUTER, }, }; +static struct tag_name +compress_values[] = + { + { "none", COMPRESSION_NONE }, + { "ccittrle", COMPRESSION_CCITTRLE }, + { "fax3", COMPRESSION_CCITTFAX3 }, + { "t4", COMPRESSION_CCITT_T4 }, + { "fax4", COMPRESSION_CCITTFAX4 }, + { "t6", COMPRESSION_CCITT_T6 }, + { "lzw", COMPRESSION_LZW }, + { "jpeg", COMPRESSION_JPEG }, + { "packbits", COMPRESSION_PACKBITS }, + { "deflate", COMPRESSION_ADOBE_DEFLATE }, + { "zip", COMPRESSION_ADOBE_DEFLATE }, + { "oldzip", COMPRESSION_DEFLATE }, + { "ccittrlew", COMPRESSION_CCITTRLEW }, + }; + +static const int compress_value_count = + sizeof(compress_values) / sizeof(*compress_values); + +typedef struct read_state_tag read_state_t; +/* the setup function creates the image object, allocates the line buffer */ +typedef int (*read_setup_t)(read_state_t *state); + +/* the putter writes the image data provided by the getter to the + image, x, y, width, height describe the target area of the image, + extras is the extra number of pixels stored for each scanline in + the raster buffer, (for tiles against the right side of the + image) */ + +typedef int (*read_putter_t)(read_state_t *state, int x, int y, int width, + int height, int extras); + +/* reads from a tiled or strip image and calls the putter. + This may need a second type for handling non-contiguous images + at some point */ +typedef int (*read_getter_t)(read_state_t *state, read_putter_t putter); + +struct read_state_tag { + TIFF *tif; + i_img *img; + void *raster; + unsigned long pixels_read; + int allow_incomplete; + void *line_buf; + uint32 width, height; + uint16 bits_per_sample; + uint16 photometric; + + /* the total number of channels (samples per pixel) */ + int samples_per_pixel; + + /* if non-zero, which channel is the alpha channel, typically 3 for rgb */ + int alpha_chan; + + /* whether or not to scale the color channels based on the alpha + channel. TIFF has 2 types of alpha channel, if the alpha channel + we use is EXTRASAMPLE_ASSOCALPHA then the color data will need to + be scaled to match Imager's conventions */ + int scale_alpha; +}; + +static int tile_contig_getter(read_state_t *state, read_putter_t putter); +static int strip_contig_getter(read_state_t *state, read_putter_t putter); + +static int setup_paletted(read_state_t *state); +static int paletted_putter8(read_state_t *, int, int, int, int, int); +static int paletted_putter4(read_state_t *, int, int, int, int, int); + +static int setup_16_rgb(read_state_t *state); +static int setup_16_grey(read_state_t *state); +static int putter_16(read_state_t *, int, int, int, int, int); + +static int setup_8_rgb(read_state_t *state); +static int setup_8_grey(read_state_t *state); +static int putter_8(read_state_t *, int, int, int, int, int); + +static int setup_32_rgb(read_state_t *state); +static int setup_32_grey(read_state_t *state); +static int putter_32(read_state_t *, int, int, int, int, int); + +static int setup_bilevel(read_state_t *state); +static int putter_bilevel(read_state_t *, int, int, int, int, int); + +static int setup_cmyk8(read_state_t *state); +static int putter_cmyk8(read_state_t *, int, int, int, int, int); + +static int setup_cmyk16(read_state_t *state); +static int putter_cmyk16(read_state_t *, int, int, int, int, int); + static const int text_tag_count = sizeof(text_tag_names) / sizeof(*text_tag_names); static void error_handler(char const *module, char const *fmt, va_list ap) { + mm_log((1, "tiff error fmt %s\n", fmt)); i_push_errorvf(0, fmt, ap); } @@ -73,6 +177,8 @@ static void warn_handler(char const *module, char const *fmt, va_list ap) { #else vsprintf(buf, fmt, ap); #endif + mm_log((1, "tiff warning %s\n", buf)); + if (!warn_buffer || strlen(warn_buffer)+strlen(buf)+2 > warn_buffer_size) { int new_size = warn_buffer_size + strlen(buf) + 2; char *old_buffer = warn_buffer; @@ -91,9 +197,8 @@ static void warn_handler(char const *module, char const *fmt, va_list ap) { static int save_tiff_tags(TIFF *tif, i_img *im); -static void expand_4bit_hl(unsigned char *buf, int count); - -static void pack_4bit_hl(unsigned char *buf, int count); +static void +pack_4bit_to(unsigned char *dest, const unsigned char *src, int count); static toff_t sizeproc(thandle_t x) { @@ -154,56 +259,151 @@ comp_munmap(thandle_t h, tdata_t p, toff_t off) { static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { i_img *im; uint32 width, height; - uint16 channels; - uint32* raster = NULL; + uint16 samples_per_pixel; int tiled, error; float xres, yres; uint16 resunit; int gotXres, gotYres; uint16 photometric; uint16 bits_per_sample; + uint16 planar_config; + uint16 inkset; + uint16 compress; int i; - int ch; + read_state_t state; + read_setup_t setupf = NULL; + read_getter_t getterf = NULL; + read_putter_t putterf = NULL; error = 0; TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width); TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height); - TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &channels); + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel); tiled = TIFFIsTiled(tif); TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample); + TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); + TIFFGetFieldDefaulted(tif, TIFFTAG_INKSET, &inkset); - mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, channels)); + mm_log((1, "i_readtiff_wiol: width=%d, height=%d, channels=%d\n", width, height, samples_per_pixel)); mm_log((1, "i_readtiff_wiol: %stiled\n", tiled?"":"not ")); mm_log((1, "i_readtiff_wiol: %sbyte swapped\n", TIFFIsByteSwapped(tif)?"":"not ")); - /* separated defaults to CMYK, but if the user is using some strange - ink system we can't work out the color anyway */ - if (photometric == PHOTOMETRIC_SEPARATED && channels >= 4) { - /* TIFF can have more than one alpha channel on an image, - but Imager can't, only store the first one */ - - channels = channels == 4 ? 3 : 4; - - /* unfortunately the RGBA functions don't try to deal with the alpha - channel on CMYK images, at some point I'm planning on expanding - TIFF support to handle 16-bit/sample images and I'll deal with - it then */ + /* yes, this if() is horrible */ + if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) { + setupf = setup_paletted; + if (bits_per_sample == 8) + putterf = paletted_putter8; + else if (bits_per_sample == 4) + putterf = paletted_putter4; + else + mm_log((1, "unsupported paletted bits_per_sample %d\n", bits_per_sample)); } + else if (bits_per_sample == 16 + && photometric == PHOTOMETRIC_RGB + && samples_per_pixel >= 3) { + setupf = setup_16_rgb; + putterf = putter_16; + } + else if (bits_per_sample == 16 + && photometric == PHOTOMETRIC_MINISBLACK) { + setupf = setup_16_grey; + putterf = putter_16; + } + else if (bits_per_sample == 8 + && photometric == PHOTOMETRIC_MINISBLACK) { + setupf = setup_8_grey; + putterf = putter_8; + } + else if (bits_per_sample == 8 + && photometric == PHOTOMETRIC_RGB) { + setupf = setup_8_rgb; + putterf = putter_8; + } + else if (bits_per_sample == 32 + && photometric == PHOTOMETRIC_RGB + && samples_per_pixel >= 3) { + setupf = setup_32_rgb; + putterf = putter_32; + } + else if (bits_per_sample == 32 + && photometric == PHOTOMETRIC_MINISBLACK) { + setupf = setup_32_grey; + putterf = putter_32; + } + else if (bits_per_sample == 1 + && (photometric == PHOTOMETRIC_MINISBLACK + || photometric == PHOTOMETRIC_MINISWHITE) + && samples_per_pixel == 1) { + setupf = setup_bilevel; + putterf = putter_bilevel; + } + else if (bits_per_sample == 8 + && photometric == PHOTOMETRIC_SEPARATED + && inkset == INKSET_CMYK + && samples_per_pixel >= 4) { + setupf = setup_cmyk8; + putterf = putter_cmyk8; + } + else if (bits_per_sample == 16 + && photometric == PHOTOMETRIC_SEPARATED + && inkset == INKSET_CMYK + && samples_per_pixel >= 4) { + setupf = setup_cmyk16; + putterf = putter_cmyk16; + } + if (tiled) { + if (planar_config == PLANARCONFIG_CONTIG) + getterf = tile_contig_getter; + } + else { + if (planar_config == PLANARCONFIG_CONTIG) + getterf = strip_contig_getter; + } + if (setupf && getterf && putterf) { + unsigned long total_pixels = (unsigned long)width * height; + memset(&state, 0, sizeof(state)); + state.tif = tif; + state.allow_incomplete = allow_incomplete; + state.width = width; + state.height = height; + state.bits_per_sample = bits_per_sample; + state.samples_per_pixel = samples_per_pixel; + state.photometric = photometric; + + if (!setupf(&state)) + return NULL; + if (!getterf(&state, putterf) || !state.pixels_read) { + if (state.img) + i_img_destroy(state.img); + if (state.raster) + _TIFFfree(state.raster); + if (state.line_buf) + myfree(state.line_buf); + + return NULL; + } - /* TIFF images can have more than one alpha channel, but Imager can't - this ignores the possibility of 2 channel images with 2 alpha, - but there's not much I can do about that */ - if (channels > 4) - channels = 4; - - if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) { - channels = 3; - im = i_img_pal_new(width, height, channels, 256); + if (allow_incomplete && state.pixels_read < total_pixels) { + i_tags_setn(&(state.img->tags), "i_incomplete", 1); + i_tags_setn(&(state.img->tags), "i_lines_read", + state.pixels_read / width); + } + im = state.img; + + if (state.raster) + _TIFFfree(state.raster); + if (state.line_buf) + myfree(state.line_buf); } else { - im = i_img_empty_ch(NULL, width, height, channels); + if (tiled) { + im = read_one_rgb_tiled(tif, width, height, allow_incomplete); + } + else { + im = read_one_rgb_lines(tif, width, height, allow_incomplete); + } } if (!im) @@ -212,6 +412,7 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { /* general metadata */ i_tags_addn(&im->tags, "tiff_bitspersample", 0, bits_per_sample); i_tags_addn(&im->tags, "tiff_photometric", 0, photometric); + TIFFGetFieldDefaulted(tif, TIFFTAG_COMPRESSION, &compress); /* resolution tags */ TIFFGetFieldDefaulted(tif, TIFFTAG_RESOLUTIONUNIT, &resunit); @@ -262,172 +463,14 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { i_tags_add(&im->tags, "i_warning", 0, warn_buffer, -1, 0); *warn_buffer = '\0'; } - - /* TIFFPrintDirectory(tif, stdout, 0); good for debugging */ - - if (photometric == PHOTOMETRIC_PALETTE && - (bits_per_sample == 4 || bits_per_sample == 8)) { - uint16 *maps[3]; - char used[256]; - int maxused; - uint32 row, col; - unsigned char *buffer; - - if (!TIFFGetField(tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) { - i_push_error(0, "Cannot get colormap for paletted image"); - i_img_destroy(im); - return NULL; - } - buffer = (unsigned char *)_TIFFmalloc(width+2); - if (!buffer) { - i_push_error(0, "out of memory"); - i_img_destroy(im); - return NULL; - } - row = 0; - memset(used, 0, sizeof(used)); - while (row < height && TIFFReadScanline(tif, buffer, row, 0) > 0) { - if (bits_per_sample == 4) - expand_4bit_hl(buffer, (width+1)/2); - for (col = 0; col < width; ++col) { - used[buffer[col]] = 1; - } - i_ppal(im, 0, width, row, buffer); - ++row; - } - if (row < height) { - if (allow_incomplete) { - i_tags_setn(&im->tags, "i_lines_read", row); - } - else { - i_img_destroy(im); - _TIFFfree(buffer); - return NULL; - } - error = 1; - } - /* Ideally we'd optimize the palette, but that could be expensive - since we'd have to re-index every pixel. - - Optimizing the palette (even at this level) might not - be what the user wants, so I don't do it. - - We'll add a function to optimize a paletted image instead. - */ - maxused = (1 << bits_per_sample)-1; - if (!error) { - while (maxused >= 0 && !used[maxused]) - --maxused; - } - for (i = 0; i < 1 << bits_per_sample; ++i) { - i_color c; - for (ch = 0; ch < 3; ++ch) { - c.channel[ch] = Sample16To8(maps[ch][i]); - } - i_addcolors(im, &c, 1); + + for (i = 0; i < compress_value_count; ++i) { + if (compress_values[i].tag == compress) { + i_tags_add(&im->tags, "tiff_compression", 0, compress_values[i].name, -1, 0); + break; } - _TIFFfree(buffer); } - else { - if (tiled) { - int ok = 1; - uint32 row, col; - uint32 tile_width, tile_height; - - TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width); - TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height); - mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height)); - - raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32)); - if (!raster) { - i_img_destroy(im); - i_push_error(0, "No space for raster buffer"); - return NULL; - } - - for( row = 0; row < height; row += tile_height ) { - for( col = 0; ok && col < width; col += tile_width ) { - uint32 i_row, x, newrows, newcols; - - /* Read the tile into an RGBA array */ - if (!TIFFReadRGBATile(tif, col, row, raster)) { - ok = 0; - break; - } - newrows = (row+tile_height > height) ? height-row : tile_height; - mm_log((1, "i_readtiff_wiol: newrows=%d\n", newrows)); - newcols = (col+tile_width > width ) ? width-row : tile_width; - for( i_row = 0; i_row < tile_height; i_row++ ) { - for(x = 0; x < newcols; x++) { - i_color val; - uint32 temp = raster[x+tile_width*(tile_height-i_row-1)]; - val.rgba.r = TIFFGetR(temp); - val.rgba.g = TIFFGetG(temp); - val.rgba.b = TIFFGetB(temp); - val.rgba.a = TIFFGetA(temp); - i_ppix(im, col+x, row+i_row, &val); - } - } - } - } - } else { - uint32 rowsperstrip, row; - int rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); - mm_log((1, "i_readtiff_wiol: rowsperstrip=%d rc = %d\n", rowsperstrip, rc)); - if (rc != 1 || rowsperstrip==-1) { - rowsperstrip = height; - } - - raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32)); - if (!raster) { - i_img_destroy(im); - i_push_error(0, "No space for raster buffer"); - return NULL; - } - - for( row = 0; row < height; row += rowsperstrip ) { - uint32 newrows, i_row; - - if (!TIFFReadRGBAStrip(tif, row, raster)) { - if (allow_incomplete) { - i_tags_setn(&im->tags, "i_lines_read", row); - error++; - break; - } - else { - i_push_error(0, "could not read TIFF image strip"); - _TIFFfree(raster); - i_img_destroy(im); - return NULL; - } - } - - newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip; - mm_log((1, "newrows=%d\n", newrows)); - - for( i_row = 0; i_row < newrows; i_row++ ) { - uint32 x; - for(x = 0; xtags, "i_incomplete", 1); - } - if (raster) - _TIFFfree( raster ); - return im; } @@ -673,180 +716,92 @@ i_writetiff_low_faxable(TIFF *tif, i_img *im, int fine) { return 1; } -undef_int -i_writetiff_low(TIFF *tif, i_img *im) { - uint32 width, height; - uint16 channels; - uint16 predictor = 0; - int quality = 75; - int jpegcolormode = JPEGCOLORMODE_RGB; - uint16 compression = COMPRESSION_PACKBITS; - i_color val; - uint16 photometric; - uint32 rowsperstrip = (uint32) -1; /* Let library pick default */ - unsigned char *linebuf = NULL; - uint32 y; - tsize_t linebytes; - int ch, ci, rc; - uint32 x; - int got_xres, got_yres, aspect_only, resunit; - double xres, yres; - uint16 bitspersample = 8; - uint16 samplesperpixel; - uint16 *colors = NULL; - - width = im->xsize; - height = im->ysize; - channels = im->channels; +static uint16 +find_compression(char const *name, uint16 *compress) { + int i; - switch (channels) { - case 1: - photometric = PHOTOMETRIC_MINISBLACK; - break; - case 3: - photometric = PHOTOMETRIC_RGB; - if (compression == COMPRESSION_JPEG && jpegcolormode == JPEGCOLORMODE_RGB) photometric = PHOTOMETRIC_YCBCR; - else if (im->type == i_palette_type) { - photometric = PHOTOMETRIC_PALETTE; + for (i = 0; i < compress_value_count; ++i) { + if (strcmp(compress_values[i].name, name) == 0) { + *compress = (uint16)compress_values[i].tag; + return 1; } - break; - default: - /* This means a colorspace we don't handle yet */ - mm_log((1, "i_writetiff_wiol: don't handle %d channel images.\n", channels)); - return 0; } + *compress = COMPRESSION_NONE; - /* Add code to get the filename info from the iolayer */ - /* Also add code to check for mmapped code */ - - /*io_glue_commit_types(ig);*/ - /*mm_log((1, "i_writetiff_wiol(im 0x%p, ig 0x%p)\n", im, ig));*/ + return 0; +} - mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d\n", width, height, channels)); - - if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width) ) { - mm_log((1, "i_writetiff_wiol: TIFFSetField width=%d failed\n", width)); - return 0; +static uint16 +get_compression(i_img *im, uint16 def_compress) { + int entry; + int value; + + if (i_tags_find(&im->tags, "tiff_compression", 0, &entry) + && im->tags.tags[entry].data) { + uint16 compress; + if (find_compression(im->tags.tags[entry].data, &compress) + && TIFFIsCODECConfigured(compress)) + return compress; } - if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height) ) { - mm_log((1, "i_writetiff_wiol: TIFFSetField length=%d failed\n", height)); - return 0; + if (i_tags_get_int(&im->tags, "tiff_compression", 0, &value)) { + if ((uint16)value == value + && TIFFIsCODECConfigured((uint16)value)) + return (uint16)value; } - if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField Orientation=topleft\n")); - return 0; + + return def_compress; +} + +int +i_tiff_has_compression(const char *name) { + uint16 compress; + + if (!find_compression(name, &compress)) + return 0; + + return TIFFIsCODECConfigured(compress); +} + +static int +set_base_tags(TIFF *tif, i_img *im, uint16 compress, uint16 photometric, + uint16 bits_per_sample, uint16 samples_per_pixel) { + double xres, yres; + int resunit; + int got_xres, got_yres; + int aspect_only; + + if (!TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, im->xsize)) { + i_push_error(0, "write TIFF: setting width tag"); + return 0; } - if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField planarconfig\n")); - return 0; + if (!TIFFSetField(tif, TIFFTAG_IMAGELENGTH, im->ysize)) { + i_push_error(0, "write TIFF: setting length tag"); + return 0; } - if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField photometric=%d\n", photometric)); - return 0; + if (!TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT)) { + i_push_error(0, "write TIFF: setting orientation tag"); + return 0; } - if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compression)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField compression=%d\n", compression)); - return 0; + if (!TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)) { + i_push_error(0, "write TIFF: setting planar configuration tag"); + return 0; } - samplesperpixel = channels; - if (photometric == PHOTOMETRIC_PALETTE) { - uint16 *out[3]; - i_color c; - int count = i_colorcount(im); - int size; - int ch, i; - - samplesperpixel = 1; - if (count > 16) - bitspersample = 8; - else - bitspersample = 4; - size = 1 << bitspersample; - colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size); - out[0] = colors; - out[1] = colors + size; - out[2] = colors + 2 * size; - - for (i = 0; i < count; ++i) { - i_getcolors(im, i, &c, 1); - for (ch = 0; ch < 3; ++ch) - out[ch][i] = c.channel[ch] * 257; - } - for (; i < size; ++i) { - for (ch = 0; ch < 3; ++ch) - out[ch][i] = 0; - } - if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitspersample)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n", - bitspersample)); - return 0; - } - if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) { - mm_log((1, "i_writetiff_wiol: TIFFSetField colormap\n")); - return 0; - } + if (!TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photometric)) { + i_push_error(0, "write TIFF: setting photometric tag"); + return 0; } - else { - if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitspersample)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField bitpersample=%d\n", - bitspersample)); - return 0; - } + if (!TIFFSetField(tif, TIFFTAG_COMPRESSION, compress)) { + i_push_error(0, "write TIFF: setting compression tag"); + return 0; } - if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField samplesperpixel=%d failed\n", samplesperpixel)); + if (!TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample)) { + i_push_error(0, "write TIFF: setting bits per sample tag"); return 0; } - - switch (compression) { - case COMPRESSION_JPEG: - mm_log((1, "i_writetiff_wiol: jpeg compression\n")); - if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, quality) ) { - mm_log((1, "i_writetiff_wiol: TIFFSetField jpegquality=%d\n", quality)); - return 0; - } - if (!TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, jpegcolormode)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField jpegcolormode=%d\n", jpegcolormode)); - return 0; - } - break; - case COMPRESSION_LZW: - mm_log((1, "i_writetiff_wiol: lzw compression\n")); - break; - case COMPRESSION_DEFLATE: - mm_log((1, "i_writetiff_wiol: deflate compression\n")); - if (predictor != 0) - if (!TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)) { - mm_log((1, "i_writetiff_wiol: TIFFSetField predictor=%d\n", predictor)); - return 0; - } - break; - case COMPRESSION_PACKBITS: - mm_log((1, "i_writetiff_wiol: packbits compression\n")); - break; - default: - mm_log((1, "i_writetiff_wiol: unknown compression %d\n", compression)); + if (!TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel)) { + i_push_error(0, "write TIFF: setting samples per pixel tag"); return 0; } - - linebytes = channels * width; - linebytes = TIFFScanlineSize(tif) > linebytes ? linebytes - : TIFFScanlineSize(tif); - /* working space for the scanlines - we go from 8-bit/pixel to 4 */ - if (photometric == PHOTOMETRIC_PALETTE && bitspersample == 4) - linebytes += linebytes + 1; - linebuf = (unsigned char *)_TIFFmalloc(linebytes); - - if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, rowsperstrip))) { - mm_log((1, "i_writetiff_wiol: TIFFSetField rowsperstrip=%d\n", rowsperstrip)); return 0; } - - TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); - TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rc); - - mm_log((1, "i_writetiff_wiol: TIFFGetField rowsperstrip=%d\n", rowsperstrip)); - mm_log((1, "i_writetiff_wiol: TIFFGetField scanlinesize=%d\n", TIFFScanlineSize(tif) )); - mm_log((1, "i_writetiff_wiol: TIFFGetField planarconfig=%d == %d\n", rc, PLANARCONFIG_CONTIG)); - mm_log((1, "i_writetiff_wiol: bitspersample = %d\n", bitspersample)); got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres); got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres); @@ -872,113 +827,482 @@ i_writetiff_low(TIFF *tif, i_img *im) { } } if (!TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)xres)) { - i_push_error(0, "cannot set TIFFTAG_XRESOLUTION tag"); + i_push_error(0, "write TIFF: setting xresolution tag"); return 0; } if (!TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)yres)) { - i_push_error(0, "cannot set TIFFTAG_YRESOLUTION tag"); + i_push_error(0, "write TIFF: setting yresolution tag"); return 0; } if (!TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, (uint16)resunit)) { - i_push_error(0, "cannot set TIFFTAG_RESOLUTIONUNIT tag"); + i_push_error(0, "write TIFF: setting resolutionunit tag"); return 0; } } - if (!save_tiff_tags(tif, im)) { - return 0; + return 1; +} + +static int +write_one_bilevel(TIFF *tif, i_img *im, int zero_is_white) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + uint16 photometric; + unsigned char *in_row; + unsigned char *out_row; + unsigned out_size; + int x, y; + int invert; + + mm_log((1, "tiff - write_one_bilevel(tif %p, im %p, zero_is_white %d)\n", + tif, im, zero_is_white)); + + /* ignore a silly choice */ + if (compress == COMPRESSION_JPEG) + compress = COMPRESSION_PACKBITS; + + switch (compress) { + case COMPRESSION_CCITTRLE: + case COMPRESSION_CCITTFAX3: + case COMPRESSION_CCITTFAX4: + /* natural fax photometric */ + photometric = PHOTOMETRIC_MINISWHITE; + break; + + default: + /* natural for most computer images */ + photometric = PHOTOMETRIC_MINISBLACK; + break; } - if (photometric == PHOTOMETRIC_PALETTE) { - for (y = 0; y < height; ++y) { - i_gpal(im, 0, width, y, linebuf); - if (bitspersample == 4) - pack_4bit_hl(linebuf, width); - if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { - mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n")); - if (linebuf) _TIFFfree(linebuf); - if (colors) _TIFFfree(colors); - return 0; - } - } + if (!set_base_tags(tif, im, compress, photometric, 1, 1)) + return 0; + + if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) { + i_push_error(0, "write TIFF: setting rows per strip tag"); + return 0; } - else { - for (y=0; yxsize); + + invert = (photometric == PHOTOMETRIC_MINISWHITE) != (zero_is_white != 0); + + for (y = 0; y < im->ysize; ++y) { + int mask = 0x80; + unsigned char *outp = out_row; + memset(out_row, 0, out_size); + i_gpal(im, 0, im->xsize, y, in_row); + for (x = 0; x < im->xsize; ++x) { + if (invert ? !in_row[x] : in_row[x]) { + *outp |= mask; } - if (TIFFWriteScanline(tif, linebuf, y, 0) < 0) { - mm_log((1, "i_writetiff_wiol: TIFFWriteScanline failed.\n")); - if (linebuf) _TIFFfree(linebuf); - if (colors) _TIFFfree(colors); - return 0; + mask >>= 1; + if (!mask) { + ++outp; + mask = 0x80; } } + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + _TIFFfree(out_row); + myfree(in_row); + i_push_error(0, "write TIFF: write scan line failed"); + return 0; + } } - if (linebuf) _TIFFfree(linebuf); - if (colors) _TIFFfree(colors); - return 1; -} - -/* -=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode) -Stores an image in the iolayer object. + _TIFFfree(out_row); + myfree(in_row); - ig - io_object that defines source to write to - imgs,count - the images to write + return 1; +} -=cut -*/ +static int +set_palette(TIFF *tif, i_img *im, int size) { + int count; + uint16 *colors; + uint16 *out[3]; + i_color c; + int i, ch; + + colors = (uint16 *)_TIFFmalloc(sizeof(uint16) * 3 * size); + out[0] = colors; + out[1] = colors + size; + out[2] = colors + 2 * size; + + count = i_colorcount(im); + for (i = 0; i < count; ++i) { + i_getcolors(im, i, &c, 1); + for (ch = 0; ch < 3; ++ch) + out[ch][i] = c.channel[ch] * 257; + } + for (; i < size; ++i) { + for (ch = 0; ch < 3; ++ch) + out[ch][i] = 0; + } + if (!TIFFSetField(tif, TIFFTAG_COLORMAP, out[0], out[1], out[2])) { + _TIFFfree(colors); + i_push_error(0, "write TIFF: setting color map"); + return 0; + } + _TIFFfree(colors); + + return 1; +} -undef_int -i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) { - TIFF* tif; - TIFFErrorHandler old_handler; - int i; +static int +write_one_paletted8(TIFF *tif, i_img *im) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + unsigned char *out_row; + unsigned out_size; + int y; - old_handler = TIFFSetErrorHandler(error_handler); + mm_log((1, "tiff - write_one_paletted8(tif %p, im %p)\n", tif, im)); - io_glue_commit_types(ig); - i_clear_error(); - mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n", - ig, imgs, count)); + /* ignore a silly choice */ + if (compress == COMPRESSION_JPEG || + compress == COMPRESSION_CCITTRLE || + compress == COMPRESSION_CCITTFAX3 || + compress == COMPRESSION_CCITTFAX4) + compress = COMPRESSION_PACKBITS; - /* FIXME: Enable the mmap interface */ - - tif = TIFFClientOpen("No name", - "wm", - (thandle_t) ig, - (TIFFReadWriteProc) ig->readcb, - (TIFFReadWriteProc) ig->writecb, - (TIFFSeekProc) comp_seek, - (TIFFCloseProc) ig->closecb, - ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc, - (TIFFMapFileProc) comp_mmap, - (TIFFUnmapFileProc) comp_munmap); - + if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) { + i_push_error(0, "write TIFF: setting rows per strip tag"); + return 0; + } + if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 8, 1)) + return 0; - if (!tif) { - mm_log((1, "i_writetiff_multi_wiol: Unable to open tif file for writing\n")); - i_push_error(0, "Could not create TIFF object"); - TIFFSetErrorHandler(old_handler); + if (!set_palette(tif, im, 256)) return 0; - } - for (i = 0; i < count; ++i) { - if (!i_writetiff_low(tif, imgs[i])) { - TIFFClose(tif); - TIFFSetErrorHandler(old_handler); + out_size = TIFFScanlineSize(tif); + out_row = (unsigned char *)_TIFFmalloc( out_size ); + + for (y = 0; y < im->ysize; ++y) { + i_gpal(im, 0, im->xsize, y, out_row); + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + _TIFFfree(out_row); + i_push_error(0, "write TIFF: write scan line failed"); return 0; } + } - if (!TIFFWriteDirectory(tif)) { - i_push_error(0, "Cannot write TIFF directory"); - TIFFClose(tif); + _TIFFfree(out_row); + + return 1; +} + +static int +write_one_paletted4(TIFF *tif, i_img *im) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + unsigned char *in_row; + unsigned char *out_row; + unsigned out_size; + int y; + + mm_log((1, "tiff - write_one_paletted4(tif %p, im %p)\n", tif, im)); + + /* ignore a silly choice */ + if (compress == COMPRESSION_JPEG || + compress == COMPRESSION_CCITTRLE || + compress == COMPRESSION_CCITTFAX3 || + compress == COMPRESSION_CCITTFAX4) + compress = COMPRESSION_PACKBITS; + + if (!set_base_tags(tif, im, compress, PHOTOMETRIC_PALETTE, 4, 1)) + return 0; + + if (!set_palette(tif, im, 16)) + return 0; + + if (!TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(tif, -1))) { + i_push_error(0, "write TIFF: setting rows per strip tag"); + return 0; + } + + in_row = mymalloc(im->xsize); + out_size = TIFFScanlineSize(tif); + out_row = (unsigned char *)_TIFFmalloc( out_size ); + + for (y = 0; y < im->ysize; ++y) { + i_gpal(im, 0, im->xsize, y, in_row); + memset(out_row, 0, out_size); + pack_4bit_to(out_row, in_row, im->xsize); + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + _TIFFfree(out_row); + i_push_error(0, "write TIFF: write scan line failed"); + return 0; + } + } + + myfree(in_row); + _TIFFfree(out_row); + + return 1; +} + +static int +set_direct_tags(TIFF *tif, i_img *im, uint16 compress, + uint16 bits_per_sample) { + uint16 extras = EXTRASAMPLE_ASSOCALPHA; + uint16 extra_count = im->channels == 2 || im->channels == 4; + uint16 photometric = im->channels >= 3 ? + PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; + + if (!set_base_tags(tif, im, compress, photometric, bits_per_sample, + im->channels)) { + return 0; + } + + if (extra_count) { + if (!TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, extra_count, &extras)) { + i_push_error(0, "write TIFF: setting extra samples tag"); + return 0; + } + } + + if (compress == COMPRESSION_JPEG) { + int jpeg_quality; + if (i_tags_get_int(&im->tags, "tiff_jpegquality", 0, &jpeg_quality) + && jpeg_quality >= 0 && jpeg_quality <= 100) { + if (!TIFFSetField(tif, TIFFTAG_JPEGQUALITY, jpeg_quality)) { + i_push_error(0, "write TIFF: setting jpeg quality pseudo-tag"); + return 0; + } + } + } + + return 1; +} + +static int +write_one_32(TIFF *tif, i_img *im) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + unsigned *in_row; + size_t out_size; + uint32 *out_row; + int y; + size_t sample_count = im->xsize * im->channels; + size_t sample_index; + + mm_log((1, "tiff - write_one_32(tif %p, im %p)\n", tif, im)); + + /* only 8 and 12 bit samples are supported by jpeg compression */ + if (compress == COMPRESSION_JPEG) + compress = COMPRESSION_PACKBITS; + + if (!set_direct_tags(tif, im, compress, 32)) + return 0; + + in_row = mymalloc(sample_count * sizeof(unsigned)); + out_size = TIFFScanlineSize(tif); + out_row = (uint32 *)_TIFFmalloc( out_size ); + + for (y = 0; y < im->ysize; ++y) { + if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 32) <= 0) { + i_push_error(0, "Cannot read 32-bit samples"); + return 0; + } + for (sample_index = 0; sample_index < sample_count; ++sample_index) + out_row[sample_index] = in_row[sample_index]; + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + myfree(in_row); + _TIFFfree(out_row); + i_push_error(0, "write TIFF: write scan line failed"); + return 0; + } + } + + myfree(in_row); + _TIFFfree(out_row); + + return 1; +} + +static int +write_one_16(TIFF *tif, i_img *im) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + unsigned *in_row; + size_t out_size; + uint16 *out_row; + int y; + size_t sample_count = im->xsize * im->channels; + size_t sample_index; + + mm_log((1, "tiff - write_one_16(tif %p, im %p)\n", tif, im)); + + /* only 8 and 12 bit samples are supported by jpeg compression */ + if (compress == COMPRESSION_JPEG) + compress = COMPRESSION_PACKBITS; + + if (!set_direct_tags(tif, im, compress, 16)) + return 0; + + in_row = mymalloc(sample_count * sizeof(unsigned)); + out_size = TIFFScanlineSize(tif); + out_row = (uint16 *)_TIFFmalloc( out_size ); + + for (y = 0; y < im->ysize; ++y) { + if (i_gsamp_bits(im, 0, im->xsize, y, in_row, NULL, im->channels, 16) <= 0) { + i_push_error(0, "Cannot read 16-bit samples"); + return 0; + } + for (sample_index = 0; sample_index < sample_count; ++sample_index) + out_row[sample_index] = in_row[sample_index]; + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + myfree(in_row); + _TIFFfree(out_row); + i_push_error(0, "write TIFF: write scan line failed"); + return 0; + } + } + + myfree(in_row); + _TIFFfree(out_row); + + return 1; +} + +static int +write_one_8(TIFF *tif, i_img *im) { + uint16 compress = get_compression(im, COMPRESSION_PACKBITS); + size_t out_size; + unsigned char *out_row; + int y; + size_t sample_count = im->xsize * im->channels; + + mm_log((1, "tiff - write_one_8(tif %p, im %p)\n", tif, im)); + + if (!set_direct_tags(tif, im, compress, 8)) + return 0; + + out_size = TIFFScanlineSize(tif); + if (out_size < sample_count) + out_size = sample_count; + out_row = (unsigned char *)_TIFFmalloc( out_size ); + + for (y = 0; y < im->ysize; ++y) { + if (i_gsamp(im, 0, im->xsize, y, out_row, NULL, im->channels) <= 0) { + i_push_error(0, "Cannot read 8-bit samples"); + return 0; + } + if (TIFFWriteScanline(tif, out_row, y, 0) < 0) { + _TIFFfree(out_row); + i_push_error(0, "write TIFF: write scan line failed"); + return 0; + } + } + _TIFFfree(out_row); + + return 1; +} + +static int +i_writetiff_low(TIFF *tif, i_img *im) { + uint32 width, height; + uint16 channels; + int zero_is_white; + + width = im->xsize; + height = im->ysize; + channels = im->channels; + + mm_log((1, "i_writetiff_low: width=%d, height=%d, channels=%d, bits=%d\n", width, height, channels, im->bits)); + if (im->type == i_palette_type) { + mm_log((1, "i_writetiff_low: paletted, colors=%d\n", i_colorcount(im))); + } + + if (i_img_is_monochrome(im, &zero_is_white)) { + if (!write_one_bilevel(tif, im, zero_is_white)) + return 0; + } + else if (im->type == i_palette_type) { + if (i_colorcount(im) <= 16) { + if (!write_one_paletted4(tif, im)) + return 0; + } + else { + if (!write_one_paletted8(tif, im)) + return 0; + } + } + else if (im->bits > 16) { + if (!write_one_32(tif, im)) + return 0; + } + else if (im->bits > 8) { + if (!write_one_16(tif, im)) + return 0; + } + else { + if (!write_one_8(tif, im)) + return 0; + } + + if (!save_tiff_tags(tif, im)) + return 0; + + return 1; +} + +/* +=item i_writetiff_multi_wiol(ig, imgs, count, fine_mode) + +Stores an image in the iolayer object. + + ig - io_object that defines source to write to + imgs,count - the images to write + +=cut +*/ + +undef_int +i_writetiff_multi_wiol(io_glue *ig, i_img **imgs, int count) { + TIFF* tif; + TIFFErrorHandler old_handler; + int i; + + old_handler = TIFFSetErrorHandler(error_handler); + + io_glue_commit_types(ig); + i_clear_error(); + mm_log((1, "i_writetiff_multi_wiol(ig 0x%p, imgs 0x%p, count %d)\n", + ig, imgs, count)); + + /* FIXME: Enable the mmap interface */ + + tif = TIFFClientOpen("No name", + "wm", + (thandle_t) ig, + (TIFFReadWriteProc) ig->readcb, + (TIFFReadWriteProc) ig->writecb, + (TIFFSeekProc) comp_seek, + (TIFFCloseProc) ig->closecb, + ig->sizecb ? (TIFFSizeProc) ig->sizecb : (TIFFSizeProc) sizeproc, + (TIFFMapFileProc) comp_mmap, + (TIFFUnmapFileProc) comp_munmap); + + + + if (!tif) { + mm_log((1, "i_writetiff_multi_wiol: Unable to open tif file for writing\n")); + i_push_error(0, "Could not create TIFF object"); + TIFFSetErrorHandler(old_handler); + return 0; + } + + for (i = 0; i < count; ++i) { + if (!i_writetiff_low(tif, imgs[i])) { + TIFFClose(tif); + TIFFSetErrorHandler(old_handler); + return 0; + } + + if (!TIFFWriteDirectory(tif)) { + i_push_error(0, "Cannot write TIFF directory"); + TIFFClose(tif); TIFFSetErrorHandler(old_handler); return 0; } @@ -1194,40 +1518,1039 @@ static int save_tiff_tags(TIFF *tif, i_img *im) { } -/* -=item expand_4bit_hl(buf, count) +static void +unpack_4bit_to(unsigned char *dest, const unsigned char *src, + int src_byte_count) { + while (src_byte_count > 0) { + *dest++ = *src >> 4; + *dest++ = *src++ & 0xf; + --src_byte_count; + } +} -Expands 4-bit/entry packed data into 1 byte/entry. +static void pack_4bit_to(unsigned char *dest, const unsigned char *src, + int pixel_count) { + int i = 0; + while (i < pixel_count) { + if ((i & 1) == 0) { + *dest = *src++ << 4; + } + else { + *dest++ |= *src++; + } + ++i; + } +} -buf must contain count bytes to be expanded and have 2*count bytes total -space. +static i_img * +make_rgb(TIFF *tif, int width, int height, int *alpha_chan) { + uint16 photometric; + uint16 channels, in_channels; + uint16 extra_count; + uint16 *extras; -The data is expanded in place. + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &in_channels); + TIFFGetFieldDefaulted(tif, TIFFTAG_PHOTOMETRIC, &photometric); -=cut -*/ + switch (photometric) { + case PHOTOMETRIC_SEPARATED: + channels = 3; + break; + + case PHOTOMETRIC_MINISWHITE: + case PHOTOMETRIC_MINISBLACK: + /* the TIFF RGBA functions expand single channel grey into RGB, + so reduce it, we move the alpha channel into the right place + if needed */ + channels = 1; + break; -static void expand_4bit_hl(unsigned char *buf, int count) { - while (--count >= 0) { - buf[count*2+1] = buf[count] & 0xF; - buf[count*2] = buf[count] >> 4; + default: + channels = 3; + break; + } + /* TIFF images can have more than one alpha channel, but Imager can't + this ignores the possibility of 2 channel images with 2 alpha, + but there's not much I can do about that */ + *alpha_chan = 0; + if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras) + && extra_count) { + *alpha_chan = channels++; } + + return i_img_8_new(width, height, channels); } -static void pack_4bit_hl(unsigned char *buf, int count) { - int i = 0; - while (i < count) { - buf[i/2] = (buf[i] << 4) + buf[i+1]; - i += 2; +static i_img * +read_one_rgb_lines(TIFF *tif, int width, int height, int allow_incomplete) { + i_img *im; + uint32* raster = NULL; + uint32 rowsperstrip, row; + i_color *line_buf; + int alpha_chan; + + im = make_rgb(tif, width, height, &alpha_chan); + if (!im) + return NULL; + + int rc = TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rowsperstrip); + mm_log((1, "i_readtiff_wiol: rowsperstrip=%d rc = %d\n", rowsperstrip, rc)); + + if (rc != 1 || rowsperstrip==-1) { + rowsperstrip = height; + } + + raster = (uint32*)_TIFFmalloc(width * rowsperstrip * sizeof (uint32)); + if (!raster) { + i_img_destroy(im); + i_push_error(0, "No space for raster buffer"); + return NULL; + } + + line_buf = mymalloc(sizeof(i_color) * width); + + for( row = 0; row < height; row += rowsperstrip ) { + uint32 newrows, i_row; + + if (!TIFFReadRGBAStrip(tif, row, raster)) { + if (allow_incomplete) { + i_tags_setn(&im->tags, "i_lines_read", row); + i_tags_setn(&im->tags, "i_incomplete", 1); + break; + } + else { + i_push_error(0, "could not read TIFF image strip"); + _TIFFfree(raster); + i_img_destroy(im); + return NULL; + } + } + + newrows = (row+rowsperstrip > height) ? height-row : rowsperstrip; + mm_log((1, "newrows=%d\n", newrows)); + + for( i_row = 0; i_row < newrows; i_row++ ) { + uint32 x; + i_color *outp = line_buf; + + for(x = 0; xrgba.r = TIFFGetR(temp); + outp->rgba.g = TIFFGetG(temp); + outp->rgba.b = TIFFGetB(temp); + + if (alpha_chan) { + /* the libtiff RGBA code expands greyscale into RGBA, so put the + alpha in the right place and scale it */ + int ch; + outp->channel[alpha_chan] = TIFFGetA(temp); + if (outp->channel[alpha_chan]) { + for (ch = 0; ch < alpha_chan; ++ch) { + outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan]; + } + } + } + + outp++; + } + i_plin(im, 0, width, i_row+row, line_buf); + } } + + myfree(line_buf); + _TIFFfree(raster); + + return im; +} + +/* adapted from libtiff + + libtiff's TIFFReadRGBATile succeeds even when asked to read an + invalid tile, which means we have no way of knowing whether the data + we received from it is valid or not. + + So the caller here has set stoponerror to 1 so that + TIFFRGBAImageGet() will fail. + + read_one_rgb_tiled() then takes that into account for i_incomplete + or failure. + */ +static int +myTIFFReadRGBATile(TIFFRGBAImage *img, uint32 col, uint32 row, uint32 * raster) + +{ + int ok; + uint32 tile_xsize, tile_ysize; + uint32 read_xsize, read_ysize; + uint32 i_row; + + /* + * Verify that our request is legal - on a tile file, and on a + * tile boundary. + */ + + TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILEWIDTH, &tile_xsize); + TIFFGetFieldDefaulted(img->tif, TIFFTAG_TILELENGTH, &tile_ysize); + if( (col % tile_xsize) != 0 || (row % tile_ysize) != 0 ) + { + i_push_errorf(0, "Row/col passed to myTIFFReadRGBATile() must be top" + "left corner of a tile."); + return 0; + } + + /* + * The TIFFRGBAImageGet() function doesn't allow us to get off the + * edge of the image, even to fill an otherwise valid tile. So we + * figure out how much we can read, and fix up the tile buffer to + * a full tile configuration afterwards. + */ + + if( row + tile_ysize > img->height ) + read_ysize = img->height - row; + else + read_ysize = tile_ysize; + + if( col + tile_xsize > img->width ) + read_xsize = img->width - col; + else + read_xsize = tile_xsize; + + /* + * Read the chunk of imagery. + */ + + img->row_offset = row; + img->col_offset = col; + + ok = TIFFRGBAImageGet(img, raster, read_xsize, read_ysize ); + + /* + * If our read was incomplete we will need to fix up the tile by + * shifting the data around as if a full tile of data is being returned. + * + * This is all the more complicated because the image is organized in + * bottom to top format. + */ + + if( read_xsize == tile_xsize && read_ysize == tile_ysize ) + return( ok ); + + for( i_row = 0; i_row < read_ysize; i_row++ ) { + memmove( raster + (tile_ysize - i_row - 1) * tile_xsize, + raster + (read_ysize - i_row - 1) * read_xsize, + read_xsize * sizeof(uint32) ); + _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize+read_xsize, + 0, sizeof(uint32) * (tile_xsize - read_xsize) ); + } + + for( i_row = read_ysize; i_row < tile_ysize; i_row++ ) { + _TIFFmemset( raster + (tile_ysize - i_row - 1) * tile_xsize, + 0, sizeof(uint32) * tile_xsize ); + } + + return (ok); } +static i_img * +read_one_rgb_tiled(TIFF *tif, int width, int height, int allow_incomplete) { + i_img *im; + uint32* raster = NULL; + int ok = 1; + uint32 row, col; + uint32 tile_width, tile_height; + unsigned long pixels = 0; + char emsg[1024] = ""; + TIFFRGBAImage img; + i_color *line; + int alpha_chan; + + im = make_rgb(tif, width, height, &alpha_chan); + if (!im) + return NULL; + + if (!TIFFRGBAImageOK(tif, emsg) + || !TIFFRGBAImageBegin(&img, tif, 1, emsg)) { + i_push_error(0, emsg); + i_img_destroy(im); + return( 0 ); + } + + TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height); + mm_log((1, "i_readtiff_wiol: tile_width=%d, tile_height=%d\n", tile_width, tile_height)); + + raster = (uint32*)_TIFFmalloc(tile_width * tile_height * sizeof (uint32)); + if (!raster) { + i_img_destroy(im); + i_push_error(0, "No space for raster buffer"); + TIFFRGBAImageEnd(&img); + return NULL; + } + line = mymalloc(tile_width * sizeof(i_color)); + + for( row = 0; row < height; row += tile_height ) { + for( col = 0; col < width; col += tile_width ) { + + /* Read the tile into an RGBA array */ + if (myTIFFReadRGBATile(&img, col, row, raster)) { + uint32 i_row, x; + uint32 newrows = (row+tile_height > height) ? height-row : tile_height; + uint32 newcols = (col+tile_width > width ) ? width-col : tile_width; + + mm_log((1, "i_readtiff_wiol: tile(%d, %d) newcols=%d newrows=%d\n", col, row, newcols, newrows)); + for( i_row = 0; i_row < newrows; i_row++ ) { + i_color *outp = line; + for(x = 0; x < newcols; x++) { + uint32 temp = raster[x+tile_width*(tile_height-i_row-1)]; + outp->rgba.r = TIFFGetR(temp); + outp->rgba.g = TIFFGetG(temp); + outp->rgba.b = TIFFGetB(temp); + outp->rgba.a = TIFFGetA(temp); + + if (alpha_chan) { + /* the libtiff RGBA code expands greyscale into RGBA, so put the + alpha in the right place and scale it */ + int ch; + outp->channel[alpha_chan] = TIFFGetA(temp); + + if (outp->channel[alpha_chan]) { + for (ch = 0; ch < alpha_chan; ++ch) { + outp->channel[ch] = outp->channel[ch] * 255 / outp->channel[alpha_chan]; + } + } + } + + ++outp; + } + i_plin(im, col, col+newcols, row+i_row, line); + } + pixels += newrows * newcols; + } + else { + if (allow_incomplete) { + ok = 0; + } + else { + goto error; + } + } + } + } + + if (!ok) { + if (pixels == 0) { + i_push_error(0, "TIFF: No image data could be read from the image"); + goto error; + } + + /* incomplete image */ + i_tags_setn(&im->tags, "i_incomplete", 1); + i_tags_setn(&im->tags, "i_lines_read", pixels / width); + } + + myfree(line); + TIFFRGBAImageEnd(&img); + _TIFFfree(raster); + + return im; + + error: + myfree(line); + _TIFFfree(raster); + TIFFRGBAImageEnd(&img); + i_img_destroy(im); + return NULL; +} + +char const * +i_tiff_libversion(void) { + return TIFFGetVersion(); +} + +static int +setup_paletted(read_state_t *state) { + uint16 *maps[3]; + int i, ch; + int color_count = 1 << state->bits_per_sample; + + state->img = i_img_pal_new(state->width, state->height, 3, 256); + if (!state->img) + return 0; + + /* setup the color map */ + if (!TIFFGetField(state->tif, TIFFTAG_COLORMAP, maps+0, maps+1, maps+2)) { + i_push_error(0, "Cannot get colormap for paletted image"); + i_img_destroy(state->img); + return 0; + } + for (i = 0; i < color_count; ++i) { + i_color c; + for (ch = 0; ch < 3; ++ch) { + c.channel[ch] = Sample16To8(maps[ch][i]); + } + i_addcolors(state->img, &c, 1); + } + + return 1; +} + +static int +tile_contig_getter(read_state_t *state, read_putter_t putter) { + uint32 tile_width, tile_height; + uint32 this_tile_height, this_tile_width; + uint32 rows_left, cols_left; + uint32 x, y; + + state->raster = _TIFFmalloc(TIFFTileSize(state->tif)); + if (!state->raster) { + i_push_error(0, "tiff: Out of memory allocating tile buffer"); + return 0; + } + + TIFFGetField(state->tif, TIFFTAG_TILEWIDTH, &tile_width); + TIFFGetField(state->tif, TIFFTAG_TILELENGTH, &tile_height); + rows_left = state->height; + for (y = 0; y < state->height; y += this_tile_height) { + this_tile_height = rows_left > tile_height ? tile_height : rows_left; + + cols_left = state->width; + for (x = 0; x < state->width; x += this_tile_width) { + this_tile_width = cols_left > tile_width ? tile_width : cols_left; + + if (TIFFReadTile(state->tif, + state->raster, + x, y, 0, 0) < 0) { + if (!state->allow_incomplete) { + return 0; + } + } + else { + putter(state, x, y, this_tile_width, this_tile_height, tile_width - this_tile_width); + } + + cols_left -= this_tile_width; + } + + rows_left -= this_tile_height; + } + + return 1; +} + +static int +strip_contig_getter(read_state_t *state, read_putter_t putter) { + uint32 rows_per_strip; + tsize_t strip_size = TIFFStripSize(state->tif); + uint32 y, strip_rows, rows_left; + + state->raster = _TIFFmalloc(strip_size); + if (!state->raster) { + i_push_error(0, "tiff: Out of memory allocating strip buffer"); + return 0; + } + + TIFFGetFieldDefaulted(state->tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + rows_left = state->height; + for (y = 0; y < state->height; y += strip_rows) { + strip_rows = rows_left > rows_per_strip ? rows_per_strip : rows_left; + if (TIFFReadEncodedStrip(state->tif, + TIFFComputeStrip(state->tif, y, 0), + state->raster, + strip_size) < 0) { + if (!state->allow_incomplete) + return 0; + } + else { + putter(state, 0, y, state->width, strip_rows, 0); + } + rows_left -= strip_rows; + } + + return 1; +} + +static int +paletted_putter8(read_state_t *state, int x, int y, int width, int height, int extras) { + unsigned char *p = state->raster; + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + i_ppal(state->img, x, x + width, y, p); + p += width + extras; + --height; + ++y; + } + + return 1; +} + +static int +paletted_putter4(read_state_t *state, int x, int y, int width, int height, int extras) { + uint32 img_line_size = (width + 1) / 2; + uint32 skip_line_size = (width + extras + 1) / 2; + unsigned char *p = state->raster; + + if (!state->line_buf) + state->line_buf = mymalloc(state->width); + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + unpack_4bit_to(state->line_buf, p, img_line_size); + i_ppal(state->img, x, x + width, y, state->line_buf); + p += skip_line_size; + --height; + ++y; + } + + return 1; +} + +static void +rgb_channels(read_state_t *state, int *out_channels) { + uint16 extra_count; + uint16 *extras; + + /* safe defaults */ + *out_channels = 3; + state->alpha_chan = 0; + state->scale_alpha = 0; + + /* plain RGB */ + if (state->samples_per_pixel == 3) + return; + + if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) { + mm_log((1, "tiff: samples != 3 but no extra samples tag\n")); + return; + } + + if (!extra_count) { + mm_log((1, "tiff: samples != 3 but no extra samples listed")); + return; + } + + ++*out_channels; + state->alpha_chan = 3; + switch (*extras) { + case EXTRASAMPLE_UNSPECIFIED: + case EXTRASAMPLE_ASSOCALPHA: + state->scale_alpha = 1; + break; + + case EXTRASAMPLE_UNASSALPHA: + state->scale_alpha = 0; + break; + + default: + mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n", + *extras)); + state->scale_alpha = 1; + break; + } + mm_log((1, "tiff alpha channel %d scale %d\n", state->alpha_chan, state->scale_alpha)); +} + +static void +grey_channels(read_state_t *state, int *out_channels) { + uint16 extra_count; + uint16 *extras; + + /* safe defaults */ + *out_channels = 1; + state->alpha_chan = 0; + state->scale_alpha = 0; + + /* plain grey */ + if (state->samples_per_pixel == 1) + return; + + if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) { + mm_log((1, "tiff: samples != 1 but no extra samples tag\n")); + return; + } + + if (!extra_count) { + mm_log((1, "tiff: samples != 1 but no extra samples listed")); + return; + } + + ++*out_channels; + state->alpha_chan = 1; + switch (*extras) { + case EXTRASAMPLE_UNSPECIFIED: + case EXTRASAMPLE_ASSOCALPHA: + state->scale_alpha = 1; + break; + + case EXTRASAMPLE_UNASSALPHA: + state->scale_alpha = 0; + break; + + default: + mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n", + *extras)); + state->scale_alpha = 1; + break; + } +} + +static int +setup_16_rgb(read_state_t *state) { + int out_channels; + + rgb_channels(state, &out_channels); + + state->img = i_img_16_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels); + + return 1; +} + +static int +setup_16_grey(read_state_t *state) { + int out_channels; + + grey_channels(state, &out_channels); + + state->img = i_img_16_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels); + + return 1; +} + +static int +putter_16(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + uint16 *p = state->raster; + int out_chan = state->img->channels; + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + int ch; + unsigned *outp = state->line_buf; + + for (i = 0; i < width; ++i) { + for (ch = 0; ch < out_chan; ++ch) { + outp[ch] = p[ch]; + } + if (state->alpha_chan && state->scale_alpha && outp[state->alpha_chan]) { + for (ch = 0; ch < state->alpha_chan; ++ch) { + int result = 0.5 + (outp[ch] * 65535.0 / outp[state->alpha_chan]); + outp[ch] = CLAMP16(result); + } + } + p += state->samples_per_pixel; + outp += out_chan; + } + + i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16); + + p += row_extras * state->samples_per_pixel; + --height; + ++y; + } + + return 1; +} + +static int +setup_8_rgb(read_state_t *state) { + int out_channels; + + rgb_channels(state, &out_channels); + + state->img = i_img_8_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(unsigned) * state->width * out_channels); + + return 1; +} + +static int +setup_8_grey(read_state_t *state) { + int out_channels; + + grey_channels(state, &out_channels); + + state->img = i_img_8_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(i_color) * state->width * out_channels); + + return 1; +} + +static int +putter_8(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + unsigned char *p = state->raster; + int out_chan = state->img->channels; + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + int ch; + i_color *outp = state->line_buf; + + for (i = 0; i < width; ++i) { + for (ch = 0; ch < out_chan; ++ch) { + outp->channel[ch] = p[ch]; + } + if (state->alpha_chan && state->scale_alpha + && outp->channel[state->alpha_chan]) { + for (ch = 0; ch < state->alpha_chan; ++ch) { + int result = (outp->channel[ch] * 255 + 127) / outp->channel[state->alpha_chan]; + + outp->channel[ch] = CLAMP8(result); + } + } + p += state->samples_per_pixel; + outp++; + } + + i_plin(state->img, x, x + width, y, state->line_buf); + + p += row_extras * state->samples_per_pixel; + --height; + ++y; + } + + return 1; +} + +static int +setup_32_rgb(read_state_t *state) { + int out_channels; + + rgb_channels(state, &out_channels); + + state->img = i_img_double_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(i_fcolor) * state->width); + + return 1; +} + +static int +setup_32_grey(read_state_t *state) { + int out_channels; + + grey_channels(state, &out_channels); + + state->img = i_img_double_new(state->width, state->height, out_channels); + if (!state->img) + return 0; + state->line_buf = mymalloc(sizeof(i_fcolor) * state->width); + + return 1; +} + +static int +putter_32(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + uint32 *p = state->raster; + int out_chan = state->img->channels; + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + int ch; + i_fcolor *outp = state->line_buf; + + for (i = 0; i < width; ++i) { + for (ch = 0; ch < out_chan; ++ch) { + outp->channel[ch] = p[ch] / 4294967295.0; + } + if (state->alpha_chan && state->scale_alpha && outp->channel[state->alpha_chan]) { + for (ch = 0; ch < state->alpha_chan; ++ch) + outp->channel[ch] /= outp->channel[state->alpha_chan]; + } + p += state->samples_per_pixel; + outp++; + } + + i_plinf(state->img, x, x + width, y, state->line_buf); + + p += row_extras * state->samples_per_pixel; + --height; + ++y; + } + + return 1; +} + +static int +setup_bilevel(read_state_t *state) { + i_color black, white; + state->img = i_img_pal_new(state->width, state->height, 1, 256); + if (!state->img) + return 0; + black.channel[0] = black.channel[1] = black.channel[2] = + black.channel[3] = 0; + white.channel[0] = white.channel[1] = white.channel[2] = + white.channel[3] = 255; + if (state->photometric == PHOTOMETRIC_MINISBLACK) { + i_addcolors(state->img, &black, 1); + i_addcolors(state->img, &white, 1); + } + else { + i_addcolors(state->img, &white, 1); + i_addcolors(state->img, &black, 1); + } + state->line_buf = mymalloc(state->width); + + return 1; +} + +static int +putter_bilevel(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + unsigned char *line_in = state->raster; + size_t line_size = (width + row_extras + 7) / 8; + + /* tifflib returns the bits in MSB2LSB order even when the file is + in LSB2MSB, so we only need to handle MSB2LSB */ + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + unsigned char *outp = state->line_buf; + unsigned char *inp = line_in; + unsigned mask = 0x80; + + for (i = 0; i < width; ++i) { + *outp++ = *inp & mask ? 1 : 0; + mask >>= 1; + if (!mask) { + ++inp; + mask = 0x80; + } + } + + i_ppal(state->img, x, x + width, y, state->line_buf); + + line_in += line_size; + --height; + ++y; + } + + return 1; +} + +static void +cmyk_channels(read_state_t *state, int *out_channels) { + uint16 extra_count; + uint16 *extras; + + /* safe defaults */ + *out_channels = 3; + state->alpha_chan = 0; + state->scale_alpha = 0; + + /* plain CMYK */ + if (state->samples_per_pixel == 4) + return; + + if (!TIFFGetField(state->tif, TIFFTAG_EXTRASAMPLES, &extra_count, &extras)) { + mm_log((1, "tiff: CMYK samples != 4 but no extra samples tag\n")); + return; + } + + if (!extra_count) { + mm_log((1, "tiff: CMYK samples != 4 but no extra samples listed")); + return; + } + + ++*out_channels; + state->alpha_chan = 4; + switch (*extras) { + case EXTRASAMPLE_UNSPECIFIED: + case EXTRASAMPLE_ASSOCALPHA: + state->scale_alpha = 1; + break; + + case EXTRASAMPLE_UNASSALPHA: + state->scale_alpha = 0; + break; + + default: + mm_log((1, "tiff: unknown extra sample type %d, treating as assoc alpha\n", + *extras)); + state->scale_alpha = 1; + break; + } +} + +static int +setup_cmyk8(read_state_t *state) { + int channels; + + cmyk_channels(state, &channels); + state->img = i_img_8_new(state->width, state->height, channels); + + state->line_buf = mymalloc(sizeof(i_color) * state->width); + + return 1; +} + +static int +putter_cmyk8(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + unsigned char *p = state->raster; + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + int ch; + i_color *outp = state->line_buf; + + for (i = 0; i < width; ++i) { + unsigned char c, m, y, k; + c = p[0]; + m = p[1]; + y = p[2]; + k = 255 - p[3]; + outp->rgba.r = (k * (255 - c)) / 255; + outp->rgba.g = (k * (255 - m)) / 255; + outp->rgba.b = (k * (255 - y)) / 255; + if (state->alpha_chan) { + outp->rgba.a = p[state->alpha_chan]; + if (state->scale_alpha + && outp->rgba.a) { + for (ch = 0; ch < 3; ++ch) { + int result = (outp->channel[ch] * 255 + 127) / outp->rgba.a; + outp->channel[ch] = CLAMP8(result); + } + } + } + p += state->samples_per_pixel; + outp++; + } + + i_plin(state->img, x, x + width, y, state->line_buf); + + p += row_extras * state->samples_per_pixel; + --height; + ++y; + } + + return 1; +} + +static int +setup_cmyk16(read_state_t *state) { + int channels; + + cmyk_channels(state, &channels); + state->img = i_img_16_new(state->width, state->height, channels); + + state->line_buf = mymalloc(sizeof(unsigned) * state->width * channels); + + return 1; +} + +static int +putter_cmyk16(read_state_t *state, int x, int y, int width, int height, + int row_extras) { + uint16 *p = state->raster; + int out_chan = state->img->channels; + + mm_log((4, "putter_cmyk16(%p, %d, %d, %d, %d, %d)\n", x, y, width, height, row_extras)); + + state->pixels_read += (unsigned long) width * height; + while (height > 0) { + int i; + int ch; + unsigned *outp = state->line_buf; + + for (i = 0; i < width; ++i) { + unsigned c, m, y, k; + c = p[0]; + m = p[1]; + y = p[2]; + k = 65535 - p[3]; + outp[0] = (k * (65535U - c)) / 65535U; + outp[1] = (k * (65535U - m)) / 65535U; + outp[2] = (k * (65535U - y)) / 65535U; + if (state->alpha_chan) { + outp[3] = p[state->alpha_chan]; + if (state->scale_alpha + && outp[3]) { + for (ch = 0; ch < 3; ++ch) { + int result = (outp[ch] * 65535 + 32767) / outp[3]; + outp[3] = CLAMP16(result); + } + } + } + p += state->samples_per_pixel; + outp += out_chan; + } + + i_psamp_bits(state->img, x, x + width, y, state->line_buf, NULL, out_chan, 16); + + p += row_extras * state->samples_per_pixel; + --height; + ++y; + } + + return 1; +} + +/* + + Older versions of tifflib we support don't define this, so define it + ourselves. + + */ +#if TIFFLIB_VERSION < 20031121 + +int TIFFIsCODECConfigured(uint16 scheme) { + switch (scheme) { + case COMPRESSION_NONE: +#if PACKBITS_SUPPORT + case COMPRESSION_PACKBITS: +#endif + +#if CCITT_SUPPORT + case COMPRESSION_CCITTRLE: + case COMPRESSION_CCITTRLEW: + case COMPRESSION_CCITTFAX3: + case COMPRESSION_CCITTFAX4: +#endif + +#if JPEG_SUPPORT + case COMPRESSION_JPEG: +#endif + +#if LZW_SUPPORT + case COMPRESSION_LZW: +#endif + +#if ZIP_SUPPORT + case COMPRESSION_DEFLATE: + case COMPRESSION_ADOBE_DEFLATE: +#endif + return 1; + + default: + return 0; + } +} + +#endif + /* =back =head1 AUTHOR -Arnar M. Hrafnkelsson +Arnar M. Hrafnkelsson , Tony Cook =head1 SEE ALSO