From 05c9b356afb0704cdb8b7df551d78bb92e96eff0 Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Mon, 20 May 2013 23:31:38 +1000 Subject: [PATCH] [rt #74540] handle the TIFF SampleFormat tag --- Changes | 14 + MANIFEST | 2 + MANIFEST.SKIP | 1 + TIFF/Changes | 17 ++ TIFF/MANIFEST | 2 + TIFF/Makefile.PL | 9 +- TIFF/TIFF.pm | 2 +- TIFF/TIFF.xs | 8 + TIFF/imtiff.c | 116 +++++++- TIFF/t/t10tiff.t | 36 ++- TIFF/testimg/grey16sg.tif | Bin 0 -> 5133 bytes TIFF/testimg/srgba32f.tif | Bin 0 -> 1652 bytes fileformatdocs/tiffsigned.pl | 527 +++++++++++++++++++++++++++++++++++ 13 files changed, 720 insertions(+), 14 deletions(-) create mode 100644 TIFF/testimg/grey16sg.tif create mode 100644 TIFF/testimg/srgba32f.tif create mode 100644 fileformatdocs/tiffsigned.pl diff --git a/Changes b/Changes index 52f9bdd8..11a887a2 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,19 @@ Imager release history. Older releases can be found in Changes.old + - TIFF: handle SampleFormat = 2 by translating the signed integer + values to unsigned by flipping their sign bits. + + Handle SampleFormat = 3 where possible. + + SampleFormat is ignored for paletted images. + + Mixed SampleFormat are be handled incorrectly, since libtiff + returns only the first SampleFormat value, and an image with both + an alpha channel and SampleFormat = 2 for color channels probably + has a different SampleFormat for the alpha channel. + + https://rt.cpan.org/Ticket/Display.html?id=74540 + Imager 0.96 - 19 May 2013 =========== diff --git a/MANIFEST b/MANIFEST index a0ae7de8..a6757d6c 100644 --- a/MANIFEST +++ b/MANIFEST @@ -483,6 +483,7 @@ TIFF/testimg/comp8.bmp Compressed 8-bit/pixel BMP TIFF/testimg/comp8.tif 8-bit/pixel paletted TIFF TIFF/testimg/gralpha.tif Grey alpha test image TIFF/testimg/grey16.tif 16-bit/sample greyscale TIFF +TIFF/testimg/grey16sg.tif signed 16-bit/sample greyscale TIFF TIFF/testimg/grey32.tif 32-bit/sample greyscale+alpha TIFF TIFF/testimg/imager.pbm Test bi-level TIFF/testimg/imager.tif Test bi-level @@ -500,6 +501,7 @@ TIFF/testimg/srgb.tif Simple RGB image TIFF/testimg/srgba.tif RGB with one alpha TIFF/testimg/srgba16.tif TIFF/testimg/srgba32.tif +TIFF/testimg/srgba32f.tif floating point sample RGBA TIFF/testimg/srgbaa.tif RGB with 2 alpha TIFF/testimg/tiffwarn.tif Generates a warning while being read TIFF/TIFF.pm diff --git a/MANIFEST.SKIP b/MANIFEST.SKIP index 96d847e0..9743d397 100644 --- a/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -27,6 +27,7 @@ # editor trash ~$ (^|/)\#.*\#$ +(^|/)\.\# # stuff we don't distribute ^TODO$ diff --git a/TIFF/Changes b/TIFF/Changes index eb1609e9..f005596e 100644 --- a/TIFF/Changes +++ b/TIFF/Changes @@ -1,3 +1,20 @@ +Imager-File-TIFF 0.88 +===================== + + - handle SampleFormat = 2 by translating the signed integer values to + unsigned by flipping their sign bits. + + Handle SampleFormat = 3 where possible. + + SampleFormat is ignored for paletted images. + + Mixed SampleFormat are be handled incorrectly, since libtiff + returns only the first SampleFormat value, and an image with both + an alpha channel and SampleFormat = 2 for color channels probably + has a different SampleFormat for the alpha channel. + + https://rt.cpan.org/Ticket/Display.html?id=74540 + Imager-File-TIFF 0.87 ===================== diff --git a/TIFF/MANIFEST b/TIFF/MANIFEST index ceabaa80..4c1d84da 100644 --- a/TIFF/MANIFEST +++ b/TIFF/MANIFEST @@ -16,6 +16,7 @@ testimg/comp8.bmp Compressed 8-bit/pixel BMP testimg/comp8.tif 8-bit/pixel paletted TIFF testimg/gralpha.tif Grey alpha test image testimg/grey16.tif 16-bit/sample greyscale TIFF +testimg/grey16sg.tif signed 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 @@ -33,6 +34,7 @@ testimg/srgb.tif Simple RGB image testimg/srgba.tif RGB with one alpha testimg/srgba16.tif testimg/srgba32.tif +testimg/srgba32f.tif floating point sample RGBA testimg/srgbaa.tif RGB with 2 alpha testimg/tiffwarn.tif Generates a warning while being read TIFF.pm diff --git a/TIFF/Makefile.PL b/TIFF/Makefile.PL index c2924420..c8a8c385 100644 --- a/TIFF/Makefile.PL +++ b/TIFF/Makefile.PL @@ -17,11 +17,18 @@ our %IMAGER_LIBS; my $MM_ver = eval $ExtUtils::MakeMaker::VERSION; +my $define = ""; +my $fp_rep = unpack("H*", pack("f<", 1.25)); +if ($fp_rep eq "0000a03f") { + $define = "-DIEEEFP_TYPES"; +} + my %opts = ( NAME => 'Imager::File::TIFF', VERSION_FROM => 'TIFF.pm', OBJECT => 'TIFF.o imtiff.o', + DEFINE => $define, clean => { FILES => 'testout' }, ); @@ -86,7 +93,7 @@ if ($probe_res) { push @inc, $probe_res->{INC}; $opts{LIBS} = $probe_res->{LIBS}; - $opts{DEFINE} = $probe_res->{DEFINE}; + $opts{DEFINE} .= " $probe_res->{DEFINE}"; $opts{INC} = "@inc"; if ($MM_ver > 6.06) { diff --git a/TIFF/TIFF.pm b/TIFF/TIFF.pm index 9dd37f8a..204ebcc8 100644 --- a/TIFF/TIFF.pm +++ b/TIFF/TIFF.pm @@ -4,7 +4,7 @@ use Imager; use vars qw($VERSION @ISA); BEGIN { - $VERSION = "0.87"; + $VERSION = "0.88"; require XSLoader; XSLoader::load('Imager::File::TIFF', $VERSION); diff --git a/TIFF/TIFF.xs b/TIFF/TIFF.xs index c57486ce..43be5af8 100644 --- a/TIFF/TIFF.xs +++ b/TIFF/TIFF.xs @@ -11,6 +11,12 @@ extern "C" { DEFINE_IMAGER_CALLBACKS; +#ifdef IEEEFP_TYPES +#define i_tiff_ieeefp() &PL_sv_yes +#else +#define i_tiff_ieeefp() &PL_sv_no +#endif + MODULE = Imager::File::TIFF PACKAGE = Imager::File::TIFF Imager::ImgRaw @@ -140,6 +146,8 @@ bool i_tiff_has_compression(name) const char *name +SV * +i_tiff_ieeefp() BOOT: PERL_INITIALIZE_IMAGER_CALLBACKS; diff --git a/TIFF/imtiff.c b/TIFF/imtiff.c index 0bce6b21..ba1cc1f3 100644 --- a/TIFF/imtiff.c +++ b/TIFF/imtiff.c @@ -87,6 +87,18 @@ compress_values[] = static const int compress_value_count = sizeof(compress_values) / sizeof(*compress_values); +static struct tag_name +sample_format_values[] = + { + "uint", SAMPLEFORMAT_UINT, + "int", SAMPLEFORMAT_INT, + "ieeefp", SAMPLEFORMAT_IEEEFP, + "undefined", SAMPLEFORMAT_VOID, + }; + +static const int sample_format_value_count = + sizeof(sample_format_values) / sizeof(*sample_format_values); + static int myTIFFIsCODECConfigured(uint16 scheme); @@ -130,6 +142,14 @@ struct read_state_tag { we use is EXTRASAMPLE_ASSOCALPHA then the color data will need to be scaled to match Imager's conventions */ int scale_alpha; + + /* number of color samples (not including alpha) */ + int color_channels; + + /* SampleFormat is 2 */ + int sample_signed; + + int sample_format; }; static int tile_contig_getter(read_state_t *state, read_putter_t putter); @@ -362,6 +382,7 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { uint16 planar_config; uint16 inkset; uint16 compress; + uint16 sample_format; int i; read_state_t state; read_setup_t setupf = NULL; @@ -370,6 +391,7 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { int channels = MAXCHANNELS; size_t sample_size = ~0; /* force failure if some code doesn't set it */ i_img_dim total_pixels; + int samples_integral; error = 0; @@ -382,6 +404,13 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { TIFFGetFieldDefaulted(tif, TIFFTAG_PLANARCONFIG, &planar_config); TIFFGetFieldDefaulted(tif, TIFFTAG_INKSET, &inkset); + if (samples_per_pixel == 0) { + i_push_error(0, "invalid image: SamplesPerPixel is 0"); + return NULL; + } + + TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &sample_format); + 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 ")); @@ -395,9 +424,16 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { state.bits_per_sample = bits_per_sample; state.samples_per_pixel = samples_per_pixel; state.photometric = photometric; + state.sample_signed = sample_format == SAMPLEFORMAT_INT; + state.sample_format = sample_format; + + samples_integral = sample_format == SAMPLEFORMAT_UINT + || sample_format == SAMPLEFORMAT_INT + || sample_format == SAMPLEFORMAT_VOID; /* sample as UINT */ /* yes, this if() is horrible */ - if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8) { + if (photometric == PHOTOMETRIC_PALETTE && bits_per_sample <= 8 + && samples_integral) { setupf = setup_paletted; if (bits_per_sample == 8) putterf = paletted_putter8; @@ -411,28 +447,32 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { } else if (bits_per_sample == 16 && photometric == PHOTOMETRIC_RGB - && samples_per_pixel >= 3) { + && samples_per_pixel >= 3 + && samples_integral) { setupf = setup_16_rgb; putterf = putter_16; sample_size = 2; rgb_channels(&state, &channels); } else if (bits_per_sample == 16 - && photometric == PHOTOMETRIC_MINISBLACK) { + && photometric == PHOTOMETRIC_MINISBLACK + && samples_integral) { setupf = setup_16_grey; putterf = putter_16; sample_size = 2; grey_channels(&state, &channels); } else if (bits_per_sample == 8 - && photometric == PHOTOMETRIC_MINISBLACK) { + && photometric == PHOTOMETRIC_MINISBLACK + && samples_integral) { setupf = setup_8_grey; putterf = putter_8; sample_size = 1; grey_channels(&state, &channels); } else if (bits_per_sample == 8 - && photometric == PHOTOMETRIC_RGB) { + && photometric == PHOTOMETRIC_RGB + && samples_integral) { setupf = setup_8_rgb; putterf = putter_8; sample_size = 1; @@ -465,7 +505,8 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { else if (bits_per_sample == 8 && photometric == PHOTOMETRIC_SEPARATED && inkset == INKSET_CMYK - && samples_per_pixel >= 4) { + && samples_per_pixel >= 4 + && samples_integral) { setupf = setup_cmyk8; putterf = putter_cmyk8; sample_size = 1; @@ -474,7 +515,8 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { else if (bits_per_sample == 16 && photometric == PHOTOMETRIC_SEPARATED && inkset == INKSET_CMYK - && samples_per_pixel >= 4) { + && samples_per_pixel >= 4 + && samples_integral) { setupf = setup_cmyk16; putterf = putter_cmyk16; sample_size = 2; @@ -607,12 +649,25 @@ static i_img *read_one_tiff(TIFF *tif, int allow_incomplete) { break; } } - + + if (TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sample_format)) { + /* only set the tag if the the TIFF tag is present */ + i_tags_setn(&im->tags, "tiff_sample_format", sample_format); + + for (i = 0; i < sample_format_value_count; ++i) { + if (sample_format_values[i].tag == sample_format) { + i_tags_set(&im->tags, "tiff_sample_format_name", + sample_format_values[i].name, -1); + break; + } + } + } + return im; } /* -=item i_readtiff_wiol(im, ig) +=item i_readtiff_wiol(ig, allow_incomplete, page) =cut */ @@ -2278,6 +2333,7 @@ rgb_channels(read_state_t *state, int *out_channels) { *out_channels = 3; state->alpha_chan = 0; state->scale_alpha = 0; + state->color_channels = 3; /* plain RGB */ if (state->samples_per_pixel == 3) @@ -2323,6 +2379,7 @@ grey_channels(read_state_t *state, int *out_channels) { *out_channels = 1; state->alpha_chan = 0; state->scale_alpha = 0; + state->color_channels = 1; /* plain grey */ if (state->samples_per_pixel == 1) @@ -2402,6 +2459,10 @@ putter_16(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_ for (ch = 0; ch < out_chan; ++ch) { outp[ch] = p[ch]; } + if (state->sample_signed) { + for (ch = 0; ch < state->color_channels; ++ch) + outp[ch] ^= 0x8000; + } 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]); @@ -2466,6 +2527,10 @@ putter_8(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_d for (ch = 0; ch < out_chan; ++ch) { outp->channel[ch] = p[ch]; } + if (state->sample_signed) { + for (ch = 0; ch < state->color_channels; ++ch) + outp->channel[ch] ^= 0x80; + } if (state->alpha_chan && state->scale_alpha && outp->channel[state->alpha_chan]) { for (ch = 0; ch < state->alpha_chan; ++ch) { @@ -2529,9 +2594,25 @@ putter_32(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_img_ 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; +#ifdef IEEEFP_TYPES + if (state->sample_format == SAMPLEFORMAT_IEEEFP) { + const float *pv = (const float *)p; + for (ch = 0; ch < out_chan; ++ch) { + outp->channel[ch] = pv[ch]; + } + } + else { +#endif + for (ch = 0; ch < out_chan; ++ch) { + if (state->sample_signed && ch < state->color_channels) + outp->channel[ch] = (p[ch] ^ 0x80000000UL) / 4294967295.0; + else + outp->channel[ch] = p[ch] / 4294967295.0; + } +#ifdef IEEEFP_TYPES } +#endif + 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]; @@ -2616,6 +2697,7 @@ cmyk_channels(read_state_t *state, int *out_channels) { *out_channels = 3; state->alpha_chan = 0; state->scale_alpha = 0; + state->color_channels = 3; /* plain CMYK */ if (state->samples_per_pixel == 4) @@ -2680,6 +2762,12 @@ putter_cmyk8(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_i m = p[1]; y = p[2]; k = 255 - p[3]; + if (state->sample_signed) { + c ^= 0x80; + m ^= 0x80; + y ^= 0x80; + k ^= 0x80; + } outp->rgba.r = (k * (255 - c)) / 255; outp->rgba.g = (k * (255 - m)) / 255; outp->rgba.b = (k * (255 - y)) / 255; @@ -2741,6 +2829,12 @@ putter_cmyk16(read_state_t *state, i_img_dim x, i_img_dim y, i_img_dim width, i_ m = p[1]; y = p[2]; k = 65535 - p[3]; + if (state->sample_signed) { + c ^= 0x8000; + m ^= 0x8000; + y ^= 0x8000; + k ^= 0x8000; + } outp[0] = (k * (65535U - c)) / 65535U; outp[1] = (k * (65535U - m)) / 65535U; outp[2] = (k * (65535U - y)) / 65535U; diff --git a/TIFF/t/t10tiff.t b/TIFF/t/t10tiff.t index 40d15aeb..698f8a49 100644 --- a/TIFF/t/t10tiff.t +++ b/TIFF/t/t10tiff.t @@ -1,6 +1,6 @@ #!perl -w use strict; -use Test::More tests => 239; +use Test::More tests => 247; use Imager qw(:all); use Imager::Test qw(is_image is_image_similar test_image test_image_16 test_image_double test_image_raw); @@ -846,3 +846,37 @@ HEX my @im = Imager->read_multi(type => "tiff", data => $ifdloop); is(@im, 2, "should be only 2 images"); } + +SKIP: +{ # sample format + Imager::File::TIFF::i_tiff_has_compression("lzw") + or skip "No LZW support", 8; + Imager::File::TIFF::i_tiff_ieeefp() + or skip "No IEEE FP type", 8; + + SKIP: + { # signed + my $cmp = Imager->new(file => "testimg/grey16.tif", filetype => "tiff") + or skip "Cannot read grey16.tif: ". Imager->errstr, 4; + my $im = Imager->new(file => "testimg/grey16sg.tif", filetype => "tiff"); + ok($im, "read image with SampleFormat = signed int") + or skip "Couldn't read the file", 3; + is_image($im, $cmp, "check the images match"); + my %tags = map @$_, $im->tags; + is($tags{tiff_sample_format}, 2, "check sample format"); + is($tags{tiff_sample_format_name}, "int", "check sample format name"); + } + + SKIP: + { # float + my $cmp = Imager->new(file => "testimg/srgba32.tif", filetype => "tiff") + or skip "Cannot read srgaba32f.tif: ". Imager->errstr, 4; + my $im = Imager->new(file => "testimg/srgba32f.tif", filetype => "tiff"); + ok($im, "read image with SampleFormat = float") + or skip "Couldn't read the file", 3; + is_image($im, $cmp, "check the images match"); + my %tags = map @$_, $im->tags; + is($tags{tiff_sample_format}, 3, "check sample format"); + is($tags{tiff_sample_format_name}, "ieeefp", "check sample format name"); + } +} diff --git a/TIFF/testimg/grey16sg.tif b/TIFF/testimg/grey16sg.tif new file mode 100644 index 0000000000000000000000000000000000000000..1ca614fef1f71aa04626176ea1db2e6bcac8ae21 GIT binary patch literal 5133 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 literal 0 HcmV?d00001 diff --git a/fileformatdocs/tiffsigned.pl b/fileformatdocs/tiffsigned.pl new file mode 100644 index 00000000..657ac663 --- /dev/null +++ b/fileformatdocs/tiffsigned.pl @@ -0,0 +1,527 @@ +#!perl -w +# Make a signed or floating point version of an uncompressed TIFF +use strict; +use Getopt::Long; + +my $mode = "int"; +GetOptions("m|mode=s" => \$mode); + +use constant TIFFTAG_BITSPERSAMPLE => 258; +use constant TIFFTAG_SAMPLEFORMAT => 339; +use constant SAMPLEFORMAT_UINT => 1; +use constant SAMPLEFORMAT_INT => 2; +use constant SAMPLEFORMAT_IEEEFP => 3; +use constant SAMPLEFORMAT_VOID => 4; +use constant TIFFTAG_COMPRESSION => 259; +use constant COMPRESSION_NONE => 1; +use constant TIFFTAG_SAMPLESPERPIXEL => 277; + +my $inname = shift; +my $outname = shift + or die < }; + +close $fh; + +my $tiff = TIFFPP->new($data); + +$tiff->compression == COMPRESSION_NONE + or die "TIFF must be uncompressed\n"; + +my $sample_count = $tiff->samples_per_pixel; + +if ($mode eq "int") { + $tiff->each_strip + ( + sub { + my ($data, $tiff) = @_; + my ($values, $bits, $format) = $tiff->unpack_samples($data); + my $i = 0; + for my $value (@$values) { + my $limit= 1 << ($bits->[$i++] - 1); + $value -= $limit; + $i == @$bits and $i = 0; + } + return $tiff->pack_samples($values, undef, [ (SAMPLEFORMAT_INT) x $sample_count ]); + } + ); + $tiff->add_tag + ( + tag => TIFFTAG_SAMPLEFORMAT, + type => "SHORT", + value => [ (SAMPLEFORMAT_INT) x $sample_count ], + ); +} +elsif ($mode eq "float") { + $tiff->each_strip + ( + sub { + my ($data, $tiff) = @_; + my ($values, $bits, $format) = $tiff->unpack_samples($data); + my $i = 0; + for my $value (@$values) { + my $limit = 2 ** ($bits->[$i++]) - 1; + $value /= $limit; + $i == @$bits and $i = 0; + } + return $tiff->pack_samples($values, [ (32) x $sample_count ], [ (SAMPLEFORMAT_IEEEFP) x $sample_count ]); + } + ); + $tiff->add_tag + ( + tag => TIFFTAG_SAMPLEFORMAT, + type => "SHORT", + value => [ (SAMPLEFORMAT_IEEEFP) x $sample_count ], + ); + $tiff->add_tag + ( + tag => TIFFTAG_BITSPERSAMPLE, + type => "SHORT", + value => [ ( 32 ) x $sample_count ] + ); +} +elsif ($mode eq "double") { + $tiff->each_strip + ( + sub { + my ($data, $tiff) = @_; + my ($values, $bits, $format) = $tiff->unpack_samples($data); + my $i = 0; + for my $value (@$values) { + my $limit= 2 ** ($bits->[$i++] - 1) - 1; + $value /= $limit; + $i == @$bits and $i = 0; + } + return $tiff->pack_samples($values, [ (64) x $sample_count ], [ (SAMPLEFORMAT_IEEEFP) x $sample_count ]); + } + ); + $tiff->add_tag + ( + tag => TIFFTAG_SAMPLEFORMAT, + type => "SHORT", + value => [ (SAMPLEFORMAT_IEEEFP) x $sample_count ], + ); + $tiff->add_tag + ( + tag => TIFFTAG_BITSPERSAMPLE, + type => "SHORT", + value => [ ( 64 ) x $sample_count ] + ); +} + +$tiff->save_ifd; + +open my $ofh, ">", $outname; +binmode $ofh; + +print $ofh $tiff->data; +close $ofh or die; + +package TIFFPP; + +use constant TIFFTAG_STRIPOFFSETS => 273; +use constant TIFFTAG_STRIPBYTECOUNTS => 279; +use constant TIFFTAG_SAMPLESPERPIXEL => 277; +use constant TIFFTAG_BITSPERSAMPLE => 258; +use constant TIFFTAG_SAMPLEFORMAT => 339; +use constant TIFFTAG_COMPRESSION => 259; +use constant COMPRESSION_NONE => 1; + +use constant TYPE_SHORT => 3; +use constant TYPE_LONG => 4; + +use constant SAMPLEFORMAT_UINT => 1; +use constant SAMPLEFORMAT_INT => 2; +use constant SAMPLEFORMAT_IEEEFP => 3; +use constant SAMPLEFORMAT_VOID => 4; + +my %types; +my %type_names; + +my %bit_types; + +BEGIN { + %types = + ( + 1 => + { + name => "BYTE", + size => 1, + pack => sub { pack("C*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "C*", $_[0] ] }, + }, + 2 => + { + name => "ASCII", + size => 1, + }, + 3 => + { + name => "SHORT", + size => 2, + pack => sub { pack("$_[1]{SHORT}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{SHORT}*", $_[0] ] }, + }, + 4 => + { + name => "LONG", + size => 4, + pack => sub { pack("$_[1]{LONG}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{LONG}*", $_[0] ] }, + }, + 5 => + { + name => "RATIONAL", + size => 8, + pack => sub { pack("$_[1]{LONG}*", map @$_, @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { + my @raw = unpack("$_[1]{LONG}*", $_[0]); + return [ map [ @raw[$_*2, $_*2+1] ], 0 .. $#raw/2 ]; + }, + }, + 6 => + { + name => "SBYTE", + size => 1, + pack => sub { pack("c*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "c*", $_[0] ] }, + }, + 7 => + { + name => "UNDEFINED", + size => 1, + pack => sub { $_[0], length $_[0] }, + unpack => sub { $_[0] }, + }, + 8 => + { + name => "SSHORT", + size => 2, + pack => sub { pack("$_[1]{SSHORT}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{SSHORT}*", $_[0] ] }, + }, + 9 => + { + name => "SLONG", + size => 4, + pack => sub { pack("$_[1]{SLONG}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{SLONG}*", $_[0] ] }, + }, + 10 => + { + name => "SRATIONAL", + size => 8, + pack => sub { pack("($_[1]{SLONG}$_[1]{LONG})*", map @$_, @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { + my @raw = unpack("($_[1]{SLONG}$_[1]{LONG})*", $_[0]); + return [ map [ @raw[$_*2, $_*2+1] ], 0 .. $#raw/2 ]; + }, + }, + 11 => + { + name => "FLOAT", + size => 4, + pack => sub { pack("$_[1]{FLOAT}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{FLOAT}*", $_[0] ] }, + }, + 12 => + { + name => "DOUBLE", + size => 8, + pack => sub { pack("$_[1]{DOUBLE}*", @{$_[0]}), scalar @{$_[0]} }, + unpack => sub { [ unpack "$_[1]{DOUBLE}*", $_[0] ] }, + }, + ); + + %type_names = map { $types{$_}->{name} => $_ } keys %types; + + %bit_types = + ( + 8 => 'BYTE', + 16 => 'SHORT', + 32 => 'LONG', + ); +} + +sub new { + my ($class, $data) = @_; + + my %opts = + ( + data => $data, + ); + + if (substr($data, 0, 2) eq "II") { + $opts{LONG} = "V"; + $opts{SHORT} = "v"; + $opts{SLONG} = "l<"; + $opts{SSHORT} = "s<"; + $opts{FLOAT} = "f<"; + $opts{DOUBLE} = "d<"; + } + elsif (substr($data, 0, 2) eq "MM") { + $opts{LONG} = "N"; + $opts{SHORT} = "n"; + $opts{SLONG} = "l>"; + $opts{SSHORT} = "s>"; + $opts{FLOAT} = "f>"; + $opts{DOUBLE} = "d>"; + } + else { + die "Not a TIFF file (bad byte-order)\n"; + } + substr($data, 2, 2) eq "\x2A\0" + or die "Not a TIFF file (bad TIFF marker)\n"; + my $ifd_off = unpack($opts{LONG}, substr($data, 4, 4)); + $ifd_off < length $data + or die "Invalid TIFF - IFD offset too long\n"; + + my $self = bless \%opts, $class; + $self->_load_ifd(4, $ifd_off); + + $self; +} + +sub data { + $_[0]{data}; +} + +sub _load_ifd { + my ($self, $off_ptr, $off) = @_; + + $self->{off_ptr} = $off_ptr; + $self->{ifd_off} = $off; + my $count = unpack($self->{SHORT}, substr($self->{data}, $off, 2)); + $self->{ifd_size} = $count; + $off += 2; + my @ifds; + my ($short, $long) = ($self->{SHORT}, $self->{LONG}); + for my $index (1 .. $count) { + my ($tag, $type, $count, $value) = + unpack("$short$short${long}a4", substr($self->{data}, $off, 12)); + $types{$type} + or die "Unknown type $type in IFD\n"; + my $size = $types{$type}{size} * $count; + + my $item_off = $size > 4 ? unpack($long, $value) : $off + 8; + my $data = substr($self->{data}, $item_off, $size); + push @ifds, + { + tag => $tag, + type => $type, + count => $count, + offset => $item_off, + data => $data, + original => 1, + }; + $off += 12; + } + my %ifd = map { $_->{tag} => $_ } @ifds; + $self->{ifd} = \@ifds; + $self->{ifdh} = \%ifd; + + $self->{next_ifd} = unpack($long, substr($self->{data}, $off, 4)); +} + +sub save_ifd { + my ($self) = @_; + + my @ifd = sort { $a->{tag} <=> $b->{tag} } @{$self->{ifd}}; + my $ifd = pack($self->{SHORT}, scalar(@ifd)); + my ($short, $long) = ($self->{SHORT}, $self->{LONG}); + for my $entry (@ifd) { + my %entry = %$entry; + if (!$entry{original} && length $entry{data} > 4) { + $entry{offset} = length $self->{data}; + $self->{data} .= $entry{data}; + } + if (length $entry{data} > 4) { + $ifd .= pack("$short$short$long$long", @entry{qw(tag type count offset)}); + } + else { + $ifd .= pack("$short$short${long}a4", @entry{qw(tag type count data)}); + } + } + $ifd .= pack($long, $self->{next_ifd}); + if (scalar(@ifd) <= $self->{ifd_size}) { + substr($self->{data}, $self->{ifd_off}, length $ifd, $ifd); + } + else { + $self->{ifd_off} = length $self->{data}; + $self->{data} .= $ifd; + substr($self->{data}, $self->{off_ptr}, 4, pack($long, $self->{ifd_off})); + } +} + +sub remove_tag { + my ($self, $tag) = @_; + + if (delete $self->{ifdh}{$tag}) { + $self->{ifd} = [ grep $_->{tag} != $tag, @{$self->{ifd}} ]; + } +} + +sub add_tag { + my ($self, %opts) = @_; + + unless ($opts{type} =~ /[0-9]/) { + $opts{type} = $type_names{$opts{type}} + or die "add_tag: Invalid type\n"; + } + + if ($opts{value} && !exists $opts{data}) { + @opts{qw(data count)} = $types{$opts{type}}{pack}->($opts{value}, $self); + } + + if ($self->{ifdh}{$opts{tag}}) { + $self->remove_tag($opts{tag}); + } + push @{$self->{ifd}}, \%opts; + $self->{ifdh}{$opts{tag}} = \%opts; +} + +sub tag_value { + my ($self, $tag) = @_; + + my $val = $self->{ifdh}{$tag} + or return; + + return $types{$val->{type}}{unpack}->($val->{data}, $self); +} + +sub each_strip { + my ($self, $cb) = @_; + + my $offsets = $self->tag_value(TIFFTAG_STRIPOFFSETS); + my $sizes = $self->tag_value(TIFFTAG_STRIPBYTECOUNTS); + @$offsets == @$sizes + or die "Strip offset and byte counts do not match\n"; + for my $i (0 .. $#$offsets) { + my $bytes = substr($self->{data}, $offsets->[$i], $sizes->[$i]); + $bytes = $cb->($bytes, $self); + if (length $bytes > $sizes->[$i]) { + $offsets->[$i] = length $self->{data}; + $self->{data} .= $bytes; + } + else { + substr($self->{data}, $offsets->[$i], length $bytes, $bytes); + } + $sizes->[$i] = length $bytes; + } + my $off_type = TYPE_SHORT; + my $count_type = TYPE_SHORT; + $_ > 0xFFFF and $off_type = TYPE_LONG for @$offsets; + $_ > 0xFFFF and $count_type = TYPE_LONG for @$sizes; + + $self->add_tag + ( + tag => TIFFTAG_STRIPOFFSETS, + type => $off_type, + value => $offsets, + ); + $self->add_tag + ( + tag => TIFFTAG_STRIPBYTECOUNTS, + type => $count_type, + value => $sizes, + ); +} + +sub _pack_format { + my ($self, $bits, $formats) = @_; + + $bits ||= $self->_bitspersample; + $formats ||= $self->_sampleformat; + @$bits == @$formats + or die "Mismatch between bitsperlsample and sampleformat counts\n"; + my $pack = ''; + for my $i (0 .. $#$bits) { + my $type; + if ($formats->[$i] == SAMPLEFORMAT_IEEEFP) { + if ($bits->[$i] == 32) { + $type = "FLOAT"; + } + elsif ($bits->[$i] == 64) { + $type = "DOUBLE"; + } + else { + die "No IEEEFP format for bits $bits->[$i]\n"; + } + } + else { + $type = $bit_types{$bits->[$i]} + or die "Can't pack $bits->[$i] bits\n"; + $type = "S$type" if $formats->[$i] == SAMPLEFORMAT_INT; + } + $pack .= $self->{$type}; + } + + wantarray ? ($pack, $bits, $formats) : $pack; +} + +sub samples_per_pixel { + my ($self) = @_; + + my $spp = $self->tag_value(TIFFTAG_SAMPLESPERPIXEL); + $spp or return 1; + + return $spp->[0]; +} + +sub compression { + my ($self) = @_; + + my $comp = $self->tag_value(TIFFTAG_COMPRESSION); + $comp or return COMPRESSION_NONE; + + return $comp->[0]; +} + +sub _bitspersample { + my ($self) = @_; + + my $bps = $self->tag_value(TIFFTAG_BITSPERSAMPLE); + $bps or $bps = [ ( 1 ) x $self->samples_per_pixel ]; + + return $bps; +} + +sub _sampleformat { + my ($self) = @_; + + my $formats = $self->tag_value(TIFFTAG_SAMPLEFORMAT); + unless ($formats) { + $formats = [ ( SAMPLEFORMAT_UINT ) x $self->samples_per_pixel ]; + } + + return $formats; +} + +sub unpack_samples { + my ($self, $data, $bits, $formats) = @_; + + my ($pack, $rbits, $rformats) = $self->_pack_format($bits, $formats); + + my $values = [ unpack "($pack)*", $data ]; + + wantarray ? ( $values, $rbits, $rformats) : $values; +} + +sub pack_samples { + my ($self, $data, $bits, $formats) = @_; + + my $pack = $self->_pack_format($bits, $formats); + + pack "($pack)*", @$data; +} -- 2.39.2