1package PDF::Builder::Resource::XObject::Image::TIFF::File;
2
3use strict;
4use warnings;
5
6our $VERSION = '3.023'; # VERSION
7our $LAST_UPDATE = '3.023'; # manually update whenever code is changed
8
9use IO::File;
10
11=head1 NAME
12
13PDF::Builder::Resource::XObject::Image::TIFF::File - support routines for TIFF image library
14
15=cut
16
17sub new {
18    my ($class, $file) = @_;
19
20    my $self = {};
21    bless ($self, $class);
22    if (ref($file)) {
23        $self->{'fh'} = $file;
24        seek($self->{'fh'}, 0, 0);
25    } else {
26        $self->{'fh'} = IO::File->new();
27        open($self->{'fh'}, '<', $file) or die "$!: $file";
28    }
29    binmode($self->{'fh'}, ':raw');
30    my $fh = $self->{'fh'};
31
32    $self->{'offset'} = 0;
33    $fh->seek($self->{'offset'}, 0);
34
35    # checking byte order of data
36    $fh->read($self->{'byteOrder'}, 2);
37    $self->{'byte'} = 'C';
38    $self->{'short'} = (($self->{'byteOrder'} eq 'MM')? 'n': 'v');
39    $self->{'long'} = (($self->{'byteOrder'} eq 'MM')? 'N': 'V');
40    $self->{'rational'} = (($self->{'byteOrder'} eq 'MM')? 'NN': 'VV');
41
42    # get/check version id
43    $fh->read($self->{'version'}, 2);
44    $self->{'version'} = unpack($self->{'short'}, $self->{'version'});
45    die "Wrong TIFF Id '" . $self->{'version'} . "' (should be 42)." if $self->{'version'} != 42;
46
47    # get the offset to the first tag directory.
48    $fh->read($self->{'ifdOffset'}, 4);
49    $self->{'ifdOffset'} = unpack($self->{'long'}, $self->{'ifdOffset'});
50
51    $self->readTags();
52
53    return $self;
54}
55
56sub readTag {
57    my $self = shift;
58
59    my $fh = $self->{'fh'};
60    my $buf;
61    $fh->read($buf, 12);
62    my $tag = unpack($self->{'short'}, substr($buf, 0, 2));
63    my $type = unpack($self->{'short'}, substr($buf, 2, 2));
64    my $count = unpack($self->{'long'}, substr($buf, 4, 4));
65    my $len = 0;
66
67    $len = ($type == 1? $count  : # byte
68            $type == 2? $count  : # char2
69            $type == 3? $count*2: # int16
70            $type == 4? $count*4: # int32
71            $type == 5? $count*8: # rational: 2 * int32
72            $count);
73
74    my $off = substr($buf, 8, 4);
75
76    if ($len > 4) {
77        $off = unpack($self->{'long'}, $off);
78    } else {
79        $off = ($type == 1? unpack($self->{'byte'},  $off):
80                $type == 2? unpack($self->{'long'},  $off):
81                $type == 3? unpack($self->{'short'}, $off):
82                $type == 4? unpack($self->{'long'},  $off):
83                unpack($self->{'short'}, $off) );
84    }
85
86    return ($tag, $type, $count, $len, $off);
87}
88
89sub close { ## no critic
90    my $self = shift;
91
92    return $self->{'fh'}->close();
93}
94
95sub readTags {
96    my $self = shift;
97
98    my $fh = $self->{'fh'};
99    $self->{'fillOrder'} = 1;
100    $self->{'ifd'} = $self->{'ifdOffset'};
101
102    while ($self->{'ifd'} > 0) {
103        $fh->seek($self->{'ifd'}, 0);
104        $fh->read($self->{'ifdNum'}, 2);
105        $self->{'ifdNum'} = unpack($self->{'short'}, $self->{'ifdNum'});
106        $self->{'bitsPerSample'} = 1;
107        foreach (1 .. $self->{'ifdNum'}) {
108            my ($valTag, $valType, $valCount, $valLen, $valOffset) = $self->readTag();
109            # print "tag=$valTag type=$valType count=$valCount len=$valLen off=$valOffset\n";
110            if      ($valTag == 0) {
111	        # no-op
112            } elsif ($valTag == 256) {
113                $self->{'imageWidth'} = $valOffset;
114            } elsif ($valTag == 257) {
115                $self->{'imageHeight'} = $valOffset;
116            } elsif ($valTag == 258) {
117                # bits per sample
118                if ($valCount > 1) {
119                    my $here = $fh->tell();
120                    my $val;
121                    $fh->seek($valOffset, 0);
122                    $fh->read($val, 2);
123                    $self->{'bitsPerSample'} = unpack($self->{'short'}, $val);
124                    $fh->seek($here, 0);
125                } else {
126                    $self->{'bitsPerSample'} = $valOffset;
127                }
128            } elsif ($valTag == 259) {
129                # compression
130                $self->{'filter'} = $valOffset;
131                if      ($valOffset == 1) {
132                    delete $self->{'filter'};
133                } elsif ($valOffset == 3 || $valOffset == 4) {
134                    $self->{'filter'} = 'CCITTFaxDecode';
135                    $self->{'ccitt'} = $valOffset;
136                } elsif ($valOffset == 5) {
137                    $self->{'filter'} = 'LZWDecode';
138                } elsif ($valOffset == 6 || $valOffset == 7) {
139                    $self->{'filter'} = 'DCTDecode';
140                } elsif ($valOffset == 8 || $valOffset == 0x80b2) {
141                    $self->{'filter'} = 'FlateDecode';
142                } elsif ($valOffset == 32773) {
143                    $self->{'filter'} = 'RunLengthDecode';
144                } else {
145                    die "unknown/unsupported TIFF compression method with id '" . $self->{'filter'} . "'.";
146                }
147            } elsif ($valTag == 262) {
148                # photometric interpretation
149                $self->{'colorSpace'} = $valOffset;
150                if ($valOffset == 0) {
151                    $self->{'colorSpace'} = 'DeviceGray';
152                    $self->{'whiteIsZero'} = 1;
153                } elsif ($valOffset == 1) {
154                    $self->{'colorSpace'} = 'DeviceGray';
155                    $self->{'blackIsZero'} = 1;
156                } elsif ($valOffset == 2) {
157                    $self->{'colorSpace'} = 'DeviceRGB';
158                } elsif ($valOffset == 3) {
159                    $self->{'colorSpace'} = 'Indexed';
160		# } elsif ($valOffset == 4) {
161                #     $self->{'colorSpace'} = 'TransMask';
162                } elsif ($valOffset == 5) {
163                    $self->{'colorSpace'} = 'DeviceCMYK';
164                } elsif ($valOffset == 6) {
165                    $self->{'colorSpace'} = 'DeviceRGB';
166                } elsif ($valOffset == 8) {
167                    $self->{'colorSpace'} = 'Lab';
168                } else {
169                    die "unknown/unsupported TIFF photometric interpretation with id '" . $self->{'colorSpace'} . "'.";
170                }
171            } elsif ($valTag == 266) {
172                $self->{'fillOrder'} = $valOffset;
173            } elsif ($valTag == 270) {
174                # ImageDescription
175                my $here = $fh->tell();
176                $fh->seek($valOffset, 0);
177                $fh->read($self->{'imageDescription'}, $valLen);
178                $fh->seek($here, 0);
179            } elsif ($valTag == 282) {
180                # xRes
181                my $here = $fh->tell();
182                $fh->seek($valOffset, 0);
183                $fh->read($self->{'xRes'}, $valLen);
184                $fh->seek($here, 0);
185                $self->{'xRes'} = [unpack($self->{'rational'}, $self->{'xRes'})];
186                $self->{'xRes'} = ($self->{'xRes'}->[0] / $self->{'xRes'}->[1]);
187            } elsif ($valTag == 283) {
188                # yRes
189                my $here = $fh->tell();
190                $fh->seek($valOffset, 0);
191                $fh->read($self->{'yRes'}, $valLen);
192                $fh->seek($here, 0);
193                $self->{'yRes'} = [unpack($self->{'rational'}, $self->{'yRes'})];
194                $self->{'yRes'} = ($self->{'yRes'}->[0] / $self->{'yRes'}->[1]);
195            } elsif ($valTag == 296) {
196                # resolution Unit
197                $self->{'resUnit'} = $valOffset;
198            } elsif ($valTag == 273) {
199                # image data offset/strip offsets
200                if ($valCount == 1) {
201                    $self->{'imageOffset'} = $valOffset;
202                } else {
203                    my $here = $fh->tell();
204                    my $val;
205                    $fh->seek($valOffset, 0);
206                    $fh->read($val, $valLen);
207                    $fh->seek($here, 0);
208                    $self->{'imageOffset'} = [unpack($self->{'long'} . '*', $val)];
209                }
210            } elsif ($valTag == 277) {
211                $self->{'samplesPerPixel'} = $valOffset;
212            } elsif ($valTag == 278) {
213                $self->{'RowsPerStrip'} = $valOffset;
214            } elsif ($valTag == 279) {
215                # image data length/strip lengths
216                if ($valCount == 1) {
217                    $self->{'imageLength'} = $valOffset;
218                } else {
219                    my $here = $fh->tell();
220                    my $val;
221                    $fh->seek($valOffset, 0);
222                    $fh->read($val, $valLen);
223                    $fh->seek($here, 0);
224                    $self->{'imageLength'} = [unpack($self->{'long'} . '*', $val)];
225                }
226            } elsif ($valTag == 292) {
227                $self->{'g3Options'} = $valOffset;
228            } elsif ($valTag == 293) {
229                $self->{'g4Options'} = $valOffset;
230            } elsif ($valTag == 320) {
231                # color map
232                $self->{'colorMapOffset'} = $valOffset;
233                $self->{'colorMapSamples'} = $valCount;
234                $self->{'colorMapLength'} = $valCount*2; # shorts!
235            } elsif ($valTag == 317) {
236                $self->{'Predictor'} = $valOffset;
237            } elsif ($valTag == 0x800d) {
238                # imageID
239                my $here = $fh->tell();
240                $fh->seek($valOffset, 0);
241                $fh->read($self->{'imageId'}, $valLen);
242                $fh->seek($here, 0);
243            # } else {
244            #     print "tag=$valTag, type=$valType, len=$valLen\n";
245            }
246        } # end foreach loop
247
248	$fh->read($self->{'ifd'}, 4);
249        $self->{'ifd'} = unpack($self->{'long'}, $self->{'ifd'});
250    } # end while loop
251
252    return $self;
253}
254
2551;
256