File Coverage

blib/lib/Image/TIFF.pm
Criterion Covered Total %
statement 150 174 86.2
branch 75 98 76.5
condition 29 38 76.3
subroutine 16 16 100.0
pod 0 10 0.0
total 270 336 80.3


line stmt bran cond sub pod time code
1             package Image::TIFF;
2              
3             # Copyright 1999-2001, Gisle Aas.
4             # Copyright 2006, 2007 Tels
5             #
6             # This library is free software; you can redistribute it and/or
7             # modify it under the same terms as Perl v5.8.8 itself.
8              
9 12     12   70 use strict;
  12         20  
  12         14546  
10              
11             our $VERSION = '1.12';
12              
13             my @types = (
14             [ "BYTE", "C1", 1],
15             [ "ASCII", "A1", 1],
16             [ "SHORT", "n1", 2],
17             [ "LONG", "N1", 4],
18             [ "RATIONAL", "N2", 8],
19             [ "SBYTE", "c1", 1],
20             [ "UNDEFINED", "a1", 1],
21             [ "SSHORT", "n1", 2],
22             [ "SLONG", "N1", 4],
23             [ "SRATIONAL", "N2", 8],
24             [ "FLOAT", "f1", 4], # XXX 4-byte IEEE format
25             [ "DOUBLE", "d1", 8], # XXX 8-byte IEEE format
26             # XXX TODO:
27             # [ "IFD", "??", ?], # See ExifTool
28             );
29              
30             my %nikon1_tags = (
31             0x0003 => "Quality",
32             0x0004 => "ColorMode",
33             0x0005 => "ImageAdjustment",
34             0x0006 => "CCDSensitivity",
35             0x0007 => "Whitebalance",
36             0x0008 => "Focus",
37             0x000A => "DigitalZoom",
38             0x000B => "Converter",
39             );
40              
41             my %nikon2_tags = (
42             0x0001 => "NikonVersion",
43             0x0002 => "ISOSetting",
44             0x0003 => "ColorMode",
45             0x0004 => "Quality",
46             0x0005 => "Whitebalance",
47             0x0006 => "ImageSharpening",
48             0x0007 => "FocusMode",
49             0x0008 => "FlashSetting",
50             0x0009 => "FlashMetering",
51             0x000B => "WBAdjustment",
52             0x000F => "ISOSelection",
53             0x0080 => "ImageAdjustment",
54             0x0082 => "AuxiliaryLens",
55             0x0084 => "Lens",
56             0x0085 => "ManualFocusDistance",
57             0x0086 => "DigitalZoom",
58             0x0088 => { __TAG__ => "AFFocusPosition",
59             pack("xCxx",0) => "Center",
60             pack("xCxx",1) => "Top",
61             pack("xCxx",2) => "Bottom",
62             pack("xCxx",3) => "Left",
63             pack("xCxx",4) => "Right",
64             },
65             0x008d => "ColorMode",
66             0x0090 => "FlashType",
67             0x0095 => "NoiseReduction",
68             0x0010 => "DataDump",
69             );
70              
71             my %olympus_tags = (
72             0x0200 => "SpecialMode",
73             0x0201 => { __TAG__ => "JpegQual", 0 => "SQ", 1 => "HQ", 2 => "SHQ" },
74             0x0202 => { __TAG__ => "Macro", 0 => "Normal", 1 => "Macro" },
75             0x0204 => "DigiZoom",
76             0x0207 => "SoftwareRelease",
77             0x0208 => "PictInfo",
78             0x0209 => "CameraID",
79             0x0f00 => "DataDump",
80             );
81              
82             my %fujifilm_tags = (
83             0x0000 => "Version",
84             0x1000 => "Quality",
85             0x1001 => { __TAG__ => "Sharpness",
86             1 => "Very Soft",
87             2 => "Soft",
88             3 => "Normal",
89             4 => "Hard",
90             5 => "Very Hard",
91             },
92             0x1002 => { __TAG__ => "WhiteBalance",
93             0 => "Auto",
94             256 => "Daylight",
95             512 => "Cloudy",
96             768 => "DaylightColor-fluorescence",
97             769 => "DaywhiteColor-fluorescence",
98             770 => "White-fluorescence",
99             1024 => "Incandenscense",
100             3840 => "Custom white balance",
101             },
102             0x1003 => { __TAG__ => "Color", 0 => "Normal", 256 => "High", 512 => "Low" },
103             0x1004 => { __TAG__ => "Tone" , 0 => "Normal", 256 => "High", 512 => "Low" },
104             0x1010 => { __TAG__ => "FlashMode", 0 => "Auto", 1 => "On", 2 => "Off", 3 => "Red-eye reduction" },
105             0x1011 => "FlashStrength",
106             0x1020 => { __TAG__ => "Macro", 0 => "Off", 1 => "On"},
107             0x1021 => { __TAG__ => "FocusMode", 0 => "Auto", 1 => "Manual" },
108             0x1030 => { __TAG__ => "SlowSync", 0 => "Off", 1 => "On"},
109             0x1031 => { __TAG__ => "PictureMode",
110             0 => "Auto",
111             1 => "Portrait",
112             2 => "Landscape",
113             4 => "Sports",
114             5 => "Night",
115             6 => "Program AE",
116             256 => "Aperture priority",
117             512 => "Shutter priority",
118             768 => "Manual",
119             },
120             0x1100 => { __TAG__ => "AutoBracketing", 0 => "Off", 1 => "On"},
121             0x1300 => { __TAG__ => "BlurWarning", 0 => "No", 1 => "Yes"},
122             0x1301 => { __TAG__ => "FocusWarning", 0 => "No", 1 => "Yes"},
123             0x1302 => { __TAG__ => "AEWarning", 0 => "No", 1 => "Yes"},
124             );
125              
126             my %casio_tags = (
127             0x0001 => { __TAG__ => "RecordingMode",
128             1 => "SingleShutter",
129             2 => "Panorama",
130             3 => "Night scene",
131             4 => "Portrait",
132             5 => "Landscape",
133             },
134             0x0002 => { __TAG__ => "Quality", 1 => "Economy", 2 => "Normal", 3 => "Fine" },
135             0x0003 => { __TAG__ => "FocusingMode",
136             2 => "Macro",
137             3 => "Auto",
138             4 => "Manual",
139             5 => "Infinity",
140             },
141             0x0004 => { __TAG__ => "FlashMode", 1 => "Auto", 2 => "On", 3 => "Off", 4 => "Red-eye reduction" },
142             0x0005 => { __TAG__ => "FlashIntensity", 11 => "Weak", 13 => "Normal", 15 => "Strong" },
143             0x0006 => "ObjectDistance",
144             0x0007 => { __TAG__ => "WhiteBalance",
145             1 => "Auto",
146             2 => "Tungsten",
147             3 => "Daylight",
148             4 => "Fluorescent",
149             5 => "Shade",
150             129 => "Manual",
151             },
152             0x000a => { __TAG__ => "DigitalZoom", 65536 => "Off", 65537 => "2X" },
153             0x000b => { __TAG__ => "Sharpness", 0 => "Normal", 1 => "Soft", 2 => "Hard" },
154             0x000c => { __TAG__ => "Contrast" , 0 => "Normal", 1 => "Low", 2 => "High" },
155             0x000d => { __TAG__ => "Saturation", 0 => "Normal", 1 => "Low", 2 => "High" },
156             0x0014 => { __TAG__ => "CCDSensitivity",
157             64 => "Normal",
158             125 => "+1.0",
159             250 => "+2.0",
160             244 => "+3.0",
161             80 => "Normal",
162             100 => "High",
163             },
164             );
165              
166             my %canon_0x0001_tags = (
167             0 => { __TAG__ => "MacroMode", 1 => "Macro", 2 => "Normal" },
168             1 => "SelfTimer",
169             2 => { __TAG__ => "Quality", 2 => "Normal", 3 => "Fine", 5 => "SuperFine" },
170             3 => 'Tag-0x0001-03',
171             4 => { __TAG__ => 'FlashMode',
172             0 => 'Flash Not Fired',
173             1 => 'Auto',
174             2 => 'On',
175             3 => 'Red-Eye Reduction',
176             4 => 'Slow Synchro',
177             5 => 'Auto + Red-Eye Reduction',
178             6 => 'On + Red-Eye Reduction',
179             16 => 'External Flash'
180             },
181             5 => { __TAG__ => 'ContinuousDriveMode', 0 => 'Single Or Timer', 1 => 'Continuous' },
182             6 => 'Tag-0x0001-06',
183             7 => { __TAG__ => 'FocusMode',
184             0 => 'One-Shot',
185             1 => 'AI Servo',
186             2 => 'AI Focus',
187             3 => 'MF',
188             4 => 'Single',
189             5 => 'Continuous',
190             6 => 'MF'
191             },
192             8 => 'Tag-0x0001-08',
193             9 => 'Tag-0x0001-09',
194             10 => { __TAG__ => 'ImageSize', 0 => 'Large', 1 => 'Medium', 2 => 'Small' },
195             11 => { __TAG__ => 'EasyShootingMode',
196             0 => 'Full Auto',
197             1 => 'Manual',
198             2 => 'Landscape',
199             3 => 'Fast Shutter',
200             4 => 'Slow Shutter',
201             5 => 'Night',
202             6 => 'B&W',
203             7 => 'Sepia',
204             8 => 'Portrait',
205             9 => 'Sports',
206             10 => 'Macro/Close-Up',
207             11 => 'Pan Focus'
208             },
209             12 => { __TAG__ => 'DigitalZoom', 0 => 'None', 1 => '2x', 2 => '4x' },
210             13 => { __TAG__ => 'Contrast', 0xFFFF => 'Low', 0 => 'Normal', 1 => 'High' },
211             14 => { __TAG__ => 'Saturation', 0xFFFF => 'Low', 0 => 'Normal', 1 => 'High' },
212             15 => { __TAG__ => 'Sharpness', 0xFFFF => 'Low', 0 => 'Normal', 1 => 'High' },
213             16 => { __TAG__ => 'ISO',
214             0 => 'See ISOSpeedRatings Tag',
215             15 => 'Auto',
216             16 => '50',
217             17 => '100',
218             18 => '200',
219             19 => '400'
220             },
221             17 => { __TAG__ => 'MeteringMode', 3 => 'Evaluative', 4 => 'Partial', 5 => 'Center-Weighted' },
222             18 => { __TAG__ => 'FocusType',
223             0 => 'Manual',
224             1 => 'Auto',
225             3 => 'Close-Up (Macro)',
226             8 => 'Locked (Pan Mode)'
227             },
228             19 => { __TAG__ => 'AFPointSelected',
229             0x3000 => 'None { __TAG__ => MF)',
230             0x3001 => 'Auto-Selected',
231             0x3002 => 'Right',
232             0x3003 => 'Center',
233             0x3004 => 'Left'
234             },
235             20 => { __TAG__ => 'ExposureMode',
236             0 => 'Easy Shooting',
237             1 => 'Program',
238             2 => 'Tv-priority',
239             3 => 'Av-priority',
240             4 => 'Manual',
241             5 => 'A-DEP'
242             },
243             21 => 'Tag-0x0001-21',
244             22 => 'Tag-0x0001-22',
245             23 => 'LongFocalLengthOfLensInFocalUnits',
246             24 => 'ShortFocalLengthOfLensInFocalUnits',
247             25 => 'FocalUnitsPerMM',
248             26 => 'Tag-0x0001-26',
249             27 => 'Tag-0x0001-27',
250             28 => { __TAG__ => 'FlashActivity', 0 => 'Did Not Fire', 1 => 'Fired' },
251             29 => { __TAG__ => 'FlashDetails',
252             14 => 'External E-TTL',
253             13 => 'Internal Flash',
254             11 => 'FP Sync Used',
255             7 => '2nd ("Rear")-Curtain Sync Used',
256             4 => 'FP Sync Enabled'
257             },
258             30 => 'Tag-0x0001-30',
259             31 => 'Tag-0x0001-31',
260             32 => { __TAG__ => 'FocusMode', 0 => 'Single', 1 => 'Continuous' },
261             );
262              
263             my %canon_0x0004_tags = (
264             7 => { __TAG__ => 'WhiteBalance',
265             0 => 'Auto',
266             1 => 'Sunny',
267             2 => 'Cloudy',
268             3 => 'Tungsten',
269             4 => 'Fluorescent',
270             5 => 'Flash',
271             6 => 'Custom'
272             },
273             9 => 'SequenceNumber',
274             14 => 'AFPointUsed',
275             15 => { __TAG__ => 'FlashBias',
276             0xFFC0 => '-2 EV',
277             0xFFCC => '-1.67 EV',
278             0xFFD0 => '-1.50 EV',
279             0xFFD4 => '-1.33 EV',
280             0xFFE0 => '-1 EV',
281             0xFFEC => '-0.67 EV',
282             0xFFF0 => '-0.50 EV',
283             0xFFF4 => '-0.33 EV',
284             0x0000 => '0 EV',
285             0x000C => '0.33 EV',
286             0x0010 => '0.50 EV',
287             0x0014 => '0.67 EV',
288             0x0020 => '1 EV',
289             0x002C => '1.33 EV',
290             0x0030 => '1.50 EV',
291             0x0034 => '1.67 EV',
292             0x0040 => '2 EV',
293             },
294             19 => 'SubjectDistance'
295             );
296              
297              
298             my %canon_tags = (
299             0x0001 => { __TAG__ => "Custom_0x0001", __ARRAYOFFSET__ => \%canon_0x0001_tags },
300             0x0004 => { __TAG__ => "Custom_0x0004", __ARRAYOFFSET__ => \%canon_0x0004_tags },
301             0x0006 => "ImageType",
302             0x0007 => "FirmwareVersion",
303             0x0008 => "ImageNumber",
304             0x0009 => "OwnerName",
305             0x000c => "SerialNumber",
306             );
307              
308             # see http://www.compton.nu/panasonic.html
309              
310             my %panasonic_tags = (
311             0x0001 => { __TAG__ => "ImageQuality",
312             2 => 'High',
313             3 => 'Normal',
314             6 => 'Very High', #3 (Leica)
315             7 => 'Raw', #3 (Leica)
316             },
317             0x0002 => "FirmwareVersion",
318             0x0003 => { __TAG__ => "WhiteBalance",
319             1 => 'Auto',
320             2 => 'Daylight',
321             3 => 'Cloudy',
322             4 => 'Halogen',
323             5 => 'Manual',
324             8 => 'Flash',
325             10 => 'Black & White', #3 (Leica)
326             },
327             0x0007 => { __TAG__ => "FocusMode",
328             1 => 'Auto',
329             2 => 'Manual',
330             5 => 'Auto, Continuous',
331             4 => 'Auto, Focus button',
332             },
333             0x000f => { __TAG__ => "SpotMode",
334             # XXX TODO: does not decode properly
335             "0,1" => 'On',
336             "0,16" => 'Off',
337             },
338             0x001a => { __TAG__ => "ImageStabilizer",
339             2 => 'On, Mode 1',
340             3 => 'Off',
341             4 => 'On, Mode 2',
342             },
343             0x001c => { __TAG__ => "MacroMode",
344             1 => 'On',
345             2 => 'Off',
346             },
347             0x001f => { __TAG__ => "ShootingMode",
348             1 => 'Normal',
349             2 => 'Portrait',
350             3 => 'Scenery',
351             4 => 'Sports',
352             5 => 'Night Portrait',
353             6 => 'Program',
354             7 => 'Aperture Priority',
355             8 => 'Shutter Priority',
356             9 => 'Macro',
357             11 => 'Manual',
358             13 => 'Panning',
359             18 => 'Fireworks',
360             19 => 'Party',
361             20 => 'Snow',
362             21 => 'Night Scenery',
363             },
364             0x0020 => { __TAG__ => "Audio",
365             1 => 'Yes',
366             2 => 'No',
367             },
368             0x0021 => "DataDump",
369             0x0022 => "Panasonic 0x0022",
370             0x0023 => "WhiteBalanceBias",
371             0x0024 => "FlashBias",
372             0x0025 => "SerialNumber",
373             0x0026 => "Panasonic 0x0026",
374             0x0027 => "Panasonic 0x0027",
375             0x0028 => { __TAG__ => "ColorEffect",
376             1 => 'Off',
377             2 => 'Warm',
378             3 => 'Cool',
379             4 => 'Black & White',
380             5 => 'Sepia',
381             },
382             0x0029 => "Panasonic 0x0029",
383             0x002a => { __TAG__ => "BurstMode",
384             0 => 'Off',
385             1 => 'Low/High Quality',
386             2 => 'Infinite',
387             },
388             0x002b => "ImageSequenceNumber",
389             0x002c => { __TAG__ => "Contrast",
390             0 => 'Normal',
391             1 => 'Low',
392             2 => 'High',
393             0x100 => 'Low', # Leica
394             0x110 => 'Normal', # Leica
395             0x120 => 'High', # Leica
396             },
397             0x002d => { __TAG__ => "NoiseReduction",
398             0 => 'Standard',
399             1 => 'Low',
400             2 => 'High',
401             },
402             0x002e => { __TAG__ => "SelfTimer",
403             1 => 'Off',
404             2 => '10s',
405             3 => '2s',
406             },
407             0x002f => "Panasonic 0x002f",
408             0x0030 => "Panasonic 0x0030",
409             0x0031 => "Panasonic 0x0031",
410             0x0032 => "Panasonic 0x0032",
411             );
412              
413             # format:
414             # "Make Model" => [ Offset, 'Tag_prefix', ptr to tags ]
415             # Offset is either 0, or a positive number of Bytes.
416             # Offset -1 or -2 means a kludge for Fuji or Nikon
417              
418             my %makernotes = (
419             "NIKON CORPORATION NIKON D1" => [0, 'NikonD1', \%nikon2_tags],
420             "NIKON CORPORATION NIKON D70" => [-2, 'NikonD1', \%nikon2_tags],
421             "NIKON CORPORATION NIKON D100" => [-2, 'NikonD1', \%nikon2_tags],
422              
423             # For the following manufacturers we simple discard the model and always
424             # decode the MakerNote in the same manner. This makes it work with all
425             # models, even yet unreleased ones. (That's better than the very limited
426             # list of models we previously had)
427              
428             "CANON" => [0, 'Canon', \%canon_tags],
429             'PANASONIC' => [12, 'Panasonic', \%panasonic_tags],
430             "FUJIFILM" => [-1, 'FinePix', \%fujifilm_tags],
431             "CASIO" => [0, 'Casio', \%casio_tags],
432             "OLYMPUS" => [8, 'Olympus', \%olympus_tags],
433             );
434              
435             BEGIN
436             {
437             # add some Nikon cameras
438 12     12   54 for my $model (qw/E700 E800 E900 E900S E910 E950/)
439             {
440 72         251 $makernotes{'NIKON ' . $model} = [8, 'CoolPix', \%nikon1_tags];
441             }
442 12         22 for my $model (qw/E880 E990 E995/)
443             {
444 36         34921 $makernotes{'NIKON ' . $model} = [0, 'CoolPix', \%nikon2_tags];
445             }
446             }
447              
448             my %exif_intr_tags = (
449             0x1 => "InteroperabilityIndex",
450             0x2 => "InteroperabilityVersion",
451             0x1000 => "RelatedImageFileFormat",
452             0x1001 => "RelatedImageWidth",
453             0x1002 => "RelatedImageLength",
454             );
455              
456             # Tag decode helpers
457             sub components_configuration_decoder;
458             sub file_source_decoder;
459             sub scene_type_decoder;
460              
461             my %exif_tags = (
462             0x828D => "CFARepeatPatternDim",
463             0x828E => "CFAPattern",
464             0x828F => "BatteryLevel",
465             0x8298 => "Copyright",
466             0x829A => "ExposureTime",
467             0x829D => "FNumber",
468             0x83BB => "IPTC/NAA",
469             0x8769 => "ExifOffset",
470             0x8773 => "InterColorProfile",
471             0x8822 => { __TAG__ => "ExposureProgram",
472             0 => "unknown",
473             1 => "Manual",
474             2 => "Program",
475             3 => "Aperture priority",
476             4 => "Shutter priority",
477             5 => "Program creative",
478             6 => "Program action",
479             7 => "Portrait",
480             8 => "Landscape",
481             # 9 .. 255 reserved
482             },
483             0x8824 => "SpectralSensitivity",
484             0x8827 => "ISOSpeedRatings",
485             0x8828 => "OECF",
486             0x9000 => "ExifVersion",
487             0x9003 => "DateTimeOriginal",
488             0x9004 => "DateTimeDigitized",
489             0x9101 => { __TAG__ => "ComponentsConfiguration",
490             DECODER => \&components_configuration_decoder,
491             },
492             0x9102 => "CompressedBitsPerPixel",
493             0x9201 => "ShutterSpeedValue",
494             0x9202 => "ApertureValue",
495             0x9203 => "BrightnessValue",
496             0x9204 => "ExposureBiasValue",
497             0x9205 => "MaxApertureValue",
498             0x9206 => "SubjectDistance",
499             0x9207 => { __TAG__ => "MeteringMode",
500             0 => "unknown",
501             1 => "Average",
502             2 => "CenterWeightedAverage",
503             3 => "Spot",
504             4 => "MultiSpot",
505             5 => "Pattern",
506             6 => "Partial",
507             # 7 .. 254 reserved in EXIF 1.2
508             255 => "other",
509             },
510             0x9208 => { __TAG__ => "LightSource",
511             0 => "unknown",
512             1 => "Daylight",
513             2 => "Fluorescent",
514             3 => "Tungsten",
515             4 => "Flash",
516             # 5 .. 8 reserved in EXIF 2.2
517             9 => "Fine weather",
518             10 => "Cloudy weather",
519             11 => "Shade",
520             12 => "Daylight fluorescent (D 5700-7100K)",
521             13 => "Day white fluorescent (N 4600-5400K)",
522             14 => "Cool white fluorescent (W 3900-4500K)",
523             15 => "White fluorescent (WW 3200-3700K)",
524             17 => "Standard light A",
525             18 => "Standard light B",
526             19 => "Standard light C",
527             20 => "D55",
528             21 => "D65",
529             22 => "D75",
530             23 => "D50",
531             24 => "ISO studio tungesten",
532             # 25 .. 254 reserved in EXIF 2.2
533             255 => "other light source",
534             },
535             0x9209 => { __TAG__ => "Flash",
536             0x0000 => "Flash did not fire",
537             0x0001 => "Flash fired",
538             0x0005 => "Strobe return light not detected",
539             0x0007 => "Strobe return light detected",
540             0x0009 => "Flash fired, compulsory flash mode",
541             0x000D => "Flash fired, compulsory flash mode, return light not detected",
542             0x000F => "Flash fired, compulsory flash mode, return light detected",
543             0x0010 => "Flash did not fire, compulsory flash mode",
544             0x0018 => "Flash did not fire, auto mode",
545             0x0019 => "Flash fired, auto mode",
546             0x001D => "Flash fired, auto mode, return light not detected",
547             0x001F => "Flash fired, auto mode, return light detected",
548             0x0020 => "No flash function",
549             0x0041 => "Flash fired, red-eye reduction mode",
550             0x0045 => "Flash fired, red-eye reduction mode, return light not detected",
551             0x0047 => "Flash fired, red-eye reduction mode, return light detected",
552             0x0049 => "Flash fired, compulsory flash mode, red-eye reduction mode",
553             0x004D => "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
554             0x004F => "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
555             0x0059 => "Flash fired, auto mode, red-eye reduction mode",
556             0x005D => "Flash fired, auto mode, return light not detected, red-eye reduction mode",
557             0x005F => "Flash fired, auto mode, return light detected, red-eye reduction mode"
558             },
559             0x920A => "FocalLength",
560             0x9214 => "SubjectArea",
561             0x927C => "MakerNote",
562             0x9286 => "UserComment",
563             0x9290 => "SubSecTime",
564             0x9291 => "SubSecTimeOriginal",
565             0x9292 => "SubSecTimeDigitized",
566             0xA000 => "FlashPixVersion",
567             0xA001 => "ColorSpace",
568             0xA002 => "ExifImageWidth",
569             0xA003 => "ExifImageLength",
570             0xA004 => "RelatedAudioFile",
571             0xA005 => {__TAG__ => "InteroperabilityOffset",
572             __SUBIFD__ => \%exif_intr_tags,
573             },
574             0xA20B => "FlashEnergy", # 0x920B in TIFF/EP
575             0xA20C => "SpatialFrequencyResponse", # 0x920C - -
576             0xA20E => "FocalPlaneXResolution", # 0x920E - -
577             0xA20F => "FocalPlaneYResolution", # 0x920F - -
578             0xA210 => { __TAG__ => "FocalPlaneResolutionUnit", # 0x9210 - -
579             1 => "pixels",
580             2 => "dpi",
581             3 => "dpcm",
582             },
583             0xA214 => "SubjectLocation", # 0x9214 - -
584             0xA215 => "ExposureIndex", # 0x9215 - -
585             0xA217 => {__TAG__ => "SensingMethod",
586             1 => "Not defined",
587             2 => "One-chip color area sensor",
588             3 => "Two-chip color area sensor",
589             4 => "Three-chip color area sensor",
590             5 => "Color sequential area sensor",
591             7 => "Trilinear sensor",
592             8 => "Color sequential linear sensor"
593             },
594             0xA300 => {__TAG__ => "FileSource",
595             DECODER => \&file_source_decoder,
596             },
597             0xA301 => {__TAG__ => "SceneType",
598             DECODER => \&scene_type_decoder,
599             },
600             0xA302 => "CFAPattern",
601             0xA401 => {__TAG__ => "CustomRendered",
602             0 => "Normal process",
603             1 => "Custom process"
604             },
605             0xA402 => {__TAG__ => "ExposureMode",
606             0 => "Auto exposure",
607             1 => "Manual exposure",
608             2 => "Auto bracket"
609             },
610             0xA403 => {__TAG__ => "WhiteBalance",
611             0 => "Auto white balance",
612             1 => "Manual white balance"
613             },
614             0xA404 => "DigitalZoomRatio",
615             0xA405 => "FocalLengthIn35mmFilm",
616             0xA406 => {__TAG__ => "SceneCaptureType",
617             0 => "Standard",
618             1 => "Landscape",
619             2 => "Portrait",
620             3 => "Night Scene"
621             },
622             0xA407 => {__TAG__ => "GainControl",
623             0 => "None",
624             1 => "Low gain up",
625             2 => "High gain up",
626             3 => "Low gain down",
627             4 => "High gain down"
628             },
629             0xA408 => {__TAG__ => "Contrast",
630             0 => "Normal",
631             1 => "Soft",
632             2 => "Hard"
633             },
634             0xA409 => {__TAG__ => "Saturation",
635             0 => "Normal",
636             1 => "Low saturation",
637             2 => "High saturation"
638             },
639             0xA40A => {__TAG__ => "Sharpness",
640             0 => "Normal",
641             1 => "Soft",
642             2 => "Hard"
643             },
644             0xA40B => "DeviceSettingDescription",
645             0xA40C => {__TAG__ => "SubjectDistanceRange",
646             0 => "Unknown",
647             1 => "Macro",
648             2 => "Close view",
649             3 => "Distant view"
650             },
651             0xA420 => "ImageUniqueID",
652             );
653              
654             my %gps_tags = (
655             0x0000 => 'GPSVersionID',
656             0x0001 => 'GPSLatitudeRef',
657             0x0002 => 'GPSLatitude',
658             0x0003 => 'GPSLongitudeRef',
659             0x0004 => 'GPSLongitude',
660             0x0005 => 'GPSAltitudeRef',
661             0x0006 => 'GPSAltitude',
662             0x0007 => 'GPSTimeStamp',
663             0x0008 => 'GPSSatellites',
664             0x0009 => 'GPSStatus',
665             0x000A => 'GPSMeasureMode',
666             0x000B => 'GPSDOP',
667             0x000C => 'GPSSpeedRef',
668             0x000D => 'GPSSpeed',
669             0x000E => 'GPSTrackRef',
670             0x000F => 'GPSTrack',
671             0x0010 => 'GPSImgDirectionRef',
672             0x0011 => 'GPSImgDirection',
673             0x0012 => 'GPSMapDatum',
674             0x0013 => 'GPSDestLatitudeRef',
675             0x0014 => 'GPSDestLatitude',
676             0x0015 => 'GPSDestLongitudeRef',
677             0x0016 => 'GPSDestLongitude',
678             0x0017 => 'GPSDestBearingRef',
679             0x0018 => 'GPSDestBearing',
680             0x0019 => 'GPSDestDistanceRef',
681             0x001A => 'GPSDestDistance',
682             0x001B => 'GPSProcessingMethod',
683             0x001C => 'GPSAreaInformation',
684             0x001D => 'GPSDateStamp',
685             0x001E => 'GPSDifferential',
686             );
687              
688             my %tiff_tags = (
689             254 => { __TAG__ => "NewSubfileType",
690             1 => "ReducedResolution",
691             2 => "SinglePage",
692             4 => "TransparencyMask",
693             },
694             255 => { __TAG__ => "SubfileType",
695             1 => "FullResolution",
696             2 => "ReducedResolution",
697             3 => "SinglePage",
698             },
699             256 => "width",
700             257 => "height",
701             258 => "BitsPerSample",
702             259 => { __TAG__ => "Compression",
703             1 => "PackBytes",
704             2 => "CCITT Group3",
705             3 => "CCITT T4",
706             4 => "CCITT T6",
707             5 => "LZW",
708             6 => "JPEG",
709             7 => "JPEG DCT",
710             8 => "Deflate (Adobe)",
711             32766 => "NeXT 2-bit RLE",
712             32771 => "#1 w/ word alignment",
713             32773 => "PackBits (Macintosh RLE)",
714             32809 => "ThunderScan RLE",
715             32895 => "IT8 CT w/padding",
716             32896 => "IT8 Linework RLE",
717             32897 => "IT8 Monochrome picture",
718             32898 => "IT8 Binary line art",
719             32908 => "Pixar companded 10bit LZW",
720             32909 => "Pixar companded 11bit ZIP",
721             32946 => "Deflate",
722             },
723             262 => { __TAG__ => "PhotometricInterpretation",
724             0 => "WhiteIsZero",
725             1 => "BlackIsZero",
726             2 => "RGB",
727             3 => "RGB Palette",
728             4 => "Transparency Mask",
729             5 => "CMYK",
730             6 => "YCbCr",
731             8 => "CIELab",
732             },
733             263 => { __TAG__ => "Threshholding",
734             1 => "NoDithering",
735             2 => "OrderedDither",
736             3 => "Randomized",
737             },
738             266 => { __TAG__ => "FillOrder",
739             1 => "LowInHigh",
740             2 => "HighInLow",
741             },
742             269 => "DocumentName",
743             270 => "ImageDescription",
744             271 => "Make",
745             272 => "Model",
746             273 => "StripOffsets",
747             274 => { __TAG__ => "Orientation",
748             1 => "top_left",
749             2 => "top_right",
750             3 => "bot_right",
751             4 => "bot_left",
752             5 => "left_top",
753             6 => "right_top",
754             7 => "right_bot",
755             8 => "left_bot",
756             },
757             277 => "SamplesPerPixel",
758             278 => "RowsPerStrip",
759             279 => "StripByteCounts",
760             282 => "XResolution",
761             283 => "YResolution",
762             284 => {__TAG__ => "PlanarConfiguration",
763             1 => "Chunky", 2 => "Planar",
764             },
765             296 => {__TAG__ => "ResolutionUnit",
766             1 => "pixels", 2 => "dpi", 3 => "dpcm",
767             },
768             297 => "PageNumber",
769             301 => "TransferFunction",
770             305 => "Software",
771             306 => "DateTime",
772             315 => "Artist",
773             316 => "Host",
774             318 => "WhitePoint",
775             319 => "PrimaryChromaticities",
776             320 => "ColorMap",
777             513 => "JPEGInterchangeFormat",
778             514 => "JPEGInterchangeFormatLength",
779             529 => "YCbCrCoefficients",
780             530 => "YCbCrSubSampling",
781             531 => "YCbCrPositioning",
782             532 => "ReferenceBlackWhite",
783             33432 => "Copyright",
784             34665 => { __TAG__ => "ExifOffset",
785             __SUBIFD__ => \%exif_tags,
786             },
787             34853 => { __TAG__ => "GPSInfo",
788             __SUBIFD__ => \%gps_tags,
789             },
790             );
791              
792              
793             sub new
794             {
795 18     18 0 45 my $class = shift;
796 18         27 my $source = shift;
797              
798 18 50       54 if (!ref($source)) {
799 0         0 local(*F);
800 0 0       0 open(F, $source) || return;
801 0         0 binmode(F);
802 0         0 $source = \*F;
803             }
804              
805 18 50       55 if (ref($source) ne "SCALAR") {
806             # XXX should really only read the file on demand
807 0         0 local($/); # slurp mode
808 0         0 my $data = <$source>;
809 0         0 $source = \$data;
810             }
811              
812 18         50 my $self = bless { source => $source }, $class;
813              
814 18         46 for ($$source) {
815 18         61 my $byte_order = substr($_, 0, 2);
816 18         82 $self->{little_endian} = ($byte_order eq "II");
817 18         65 $self->{version} = $self->unpack("n", substr($_, 2, 2));
818              
819 18         55 my $ifd = $self->unpack("N", substr($_, 4, 4));
820 18         72 while ($ifd) {
821 33         37 push(@{$self->{ifd}}, $ifd);
  33         63  
822 33         94 my($num_fields) = $self->unpack("x$ifd n", $_);
823              
824 33         93 my $substr_ifd = substr($_, $ifd + 2 + $num_fields*12, 4);
825 33 100       108 last unless defined $substr_ifd; # bad TIFF header, eg: substr idx > length($substr_ifd)
826              
827 32         45 my $next_ifd = $self->unpack("N", $substr_ifd);
828              
829             # guard against (bug #26127)
830 32 50       61 $next_ifd = 0 if $next_ifd > length($_);
831             # guard against looping ifd (bug #26130)
832 32 100       72 if ($next_ifd <= $ifd) {
833             # bad TIFF header - would cause a loop or strange results
834 17         37 last;
835             }
836 15         32 $ifd = $next_ifd;
837             }
838             }
839              
840 18         48 $self;
841             }
842              
843             sub unpack
844             {
845 1836     1836 0 1928 my $self = shift;
846 1836         4700 my $template = shift;
847 1836 100       2733 if ($self->{little_endian}) {
848 1803         2028 $template =~ tr/nN/vV/;
849             }
850             #print "UNPACK $template\n";
851 1836         4423 CORE::unpack($template, $_[0]);
852             }
853              
854             sub num_ifds
855             {
856 18     18 0 23 my $self = shift;
857 18         22 scalar @{$self->{ifd}};
  18         219  
858             }
859              
860             sub ifd
861             {
862 33     33 0 50 my $self = shift;
863 33   100     119 my $num = shift || 0;
864 33         41 my @ifd;
865              
866 33         150 $self->add_fields($self->{ifd}[$num], \@ifd);
867             }
868              
869             sub tagname
870             {
871 71 50   71 0 355 $tiff_tags{$_[1]} || sprintf "Tag-0x%04x",$_[1];
872             }
873              
874             sub exif_tagname
875             {
876 203 100 66 203 0 733 $tiff_tags{$_[1]} || $exif_tags{$_[1]} || sprintf "Tag-0x%04x",$_[1];
877             }
878              
879             sub add_fields
880             {
881 71     71 0 144 my($self, $offset, $ifds, $tags, $voff_plus) = @_;
882 71 50       123 return unless $offset;
883             # guard against finding the same offset twice (bug #29088)
884 71 50       246 return if $self->{seen_offset}{$offset}++;
885 71   100     188 $tags ||= \%tiff_tags;
886              
887 71         85 for (${$self->{source}}) { # alias as $_
  71         137  
888 71 100       152 last if $offset > length($_) - 2; # bad offset
889 70         176 my $entries = $self->unpack("x$offset n", $_);
890 70         184 my $max_entries = int((length($_) - $offset - 2) / 12);
891             # print "ENTRIES $entries $max_entries\n";
892 70 100       128 if ($entries > $max_entries) {
893             # Hmmm, something smells bad here... parsing garbage
894 8         10 $entries = $max_entries;
895 8         13 last;
896             }
897             FIELD:
898 62         128 for my $i (0 .. $entries-1) {
899 813         994 my $entry_offset = 2 + $offset + $i*12;
900 813         1507 my($tag, $type, $count, $voff) =
901             $self->unpack("nnNN", substr($_, $entry_offset, 12));
902             #print STDERR "TAG $tag $type $count $voff\n";
903              
904 813 100 100     2376 if ($type == 0 || $type > @types) {
905             # unknown type code might indicate that we are parsing garbage
906 2         282 print STDERR "# Ignoring unknown type code $type\n";
907 2         12 next;
908             }
909              
910             # extract type information
911 811         899 my($tmpl, $vlen);
912 811         846 ($type, $tmpl, $vlen) = @{$types[$type-1]};
  811         1285  
913              
914 811 50       1211 die "Undefined type while parsing" unless $type;
915              
916 811 100       1319 if ($count * $vlen <= 4) {
    50          
917 439         511 $voff = $entry_offset + 8;
918             }
919             elsif ($voff + $count * $vlen > length($_)) {
920             # offset points outside of string, corrupt entry ignore
921 0         0 print STDERR "# ignoring offset pointer outside of string\n";
922 0         0 next;
923             }
924             else {
925 372   50     754 $voff += $voff_plus || 0;
926             }
927              
928 811         2684 $tmpl =~ s/(\d+)$/$count*$1/e;
  811         1811  
929              
930 811         1678 my @v = $self->unpack("x$voff $tmpl", $_);
931              
932 811 100       1635 if ($type =~ /^S(SHORT|LONG|RATIONAL)/) {
933 45 100       116 my $max = 2 ** ($1 eq "SHORT" ? 15 : 31);
934 45 100       101 $v[0] -= ($max * 2) if $v[0] >= $max;
935             }
936              
937 811 100       1227 my $val = (@v > 1) ? \@v : $v[0];
938 811 100       1526 if ($type =~ /^S?RATIONAL$/) {
939 193 50       265 if (ref $val) {
940 193         302 bless $val, "Image::TIFF::Rational";
941             } else {
942 0         0 print STDERR "# invalid rational value\n";
943 0         0 $val = undef;
944             }
945             }
946              
947 811 100 100     5103 if ($type eq 'ASCII' || $type eq 'UNDEFINED')
948             {
949 236 100       1310 if ($val =~ /\0[^\0]+\z/)
950             {
951             # cut out the first "ASCII\0" and take the remaining text
952             # (fix bug #29243 parsing of UserComment)
953             # XXX TODO: this needs to handle UNICODE, too:
954 14         63 $val =~ /\0([^\0]+)/;
955 14   50     56 $val = '' . ($1 || '');
956             }
957             else
958             {
959             # avoid things like "Foo\x00\x00\x00" by removing trailing nulls
960             # this needs to handle UNICODE, too:
961 222         465 $val =~ /^([^\0]*)/;
962 222   100     636 $val = '' . ($1 || '');
963             }
964             }
965              
966 811   66     5855 $tag = $tags->{$tag} || $self->tagname($tag);
967              
968 811 100       1333 if ($tag eq 'MakerNote')
969             {
970 16   100     88 my $maker = uc($self->{Make} || '');
971 16   50     62 $maker =~ /^([A-Z]+)/; $maker = $1 || ''; # "OLYMPUS ..." > "OLYMPUS"
  16         68  
972              
973             # if 'Panasonic' doesn't exist, try 'Panasonic DMC-FZ5'
974 6 50       17 $maker = join " ", grep { defined && m/\S/ } $self->{Make}, $self->{Model}
975 16 100       48 unless exists $makernotes{$maker};
976              
977 16 100       53 if (exists $makernotes{$maker}) {
978 13         26 my ($ifd_off, $tag_prefix, $sub) = @{$makernotes{$maker}};
  13         35  
979              
980             #print STDERR "# Decoding Makernotes from $maker\n";
981              
982 13         37 $self->{tag_prefix} = $tag_prefix;
983 13 50 66     66 if ($ifd_off == -1 && length($val) >= 12) {
    50          
984             # fuji kludge - http://www.butaman.ne.jp/~tsuruzoh/Computer/Digicams/exif-e.html#APP4
985 0         0 my $save_endian = $self->{little_endian};
986 0         0 $self->{little_endian} = 1;
987 0         0 $ifd_off = $self->unpack("N", substr($val, 8, 4));
988 0         0 $self->add_fields($voff+$ifd_off, $ifds, $sub, $voff);
989 0         0 $self->{little_endian} = $save_endian;
990             } elsif ($ifd_off == -2) {
991             # Nikon D70/D100 kludge -- word "Nikon" and 5
992             # bytes of data is tacked to the front of MakerNote;
993             # all EXIF offsets are relative to MakerNote section
994 0         0 my ($nikon_voff);
995 0         0 $nikon_voff = 0;
996 0 0       0 if (substr($val, 0, 5) eq 'Nikon') {
997 0         0 $nikon_voff = $voff+10;
998             }
999             #print "IFD_OFF $ifd_off NIKON_VOFF $nikon_voff\n";
1000 0         0 $self->add_fields($voff+18, $ifds, $sub, $nikon_voff);
1001             } else {
1002 13         78 $self->add_fields($voff+$ifd_off, $ifds, $sub)
1003             }
1004 13         50 delete $self->{tag_prefix};
1005 13         43 next FIELD;
1006             }
1007             }
1008              
1009 798 100       1143 if (ref($tag)) {
1010 233 50       440 die "Assert" unless ref($tag) eq "HASH";
1011 233 100       435 if (my $sub = $tag->{__SUBIFD__}) {
1012 25 50 33     114 next if $val < 0 || $val > length($_);
1013             #print "SUBIFD $tag->{__TAG__} $val ", length($_), "\n";
1014 25         193 $self->add_fields($val, $ifds, $sub);
1015 25         72 next FIELD;
1016             }
1017 208 100       379 if (my $sub = $tag->{__ARRAYOFFSET__}) {
1018 2 50       4 my $prefix; $prefix = $tag = $self->{tag_prefix} . '-' if $self->{tag_prefix};
  2         8  
1019 2         8 for (my $i=0; $i < @$val; $i++) {
1020 82 100       207 if ( exists($sub->{$i}) )
1021 38 100 66     114 { if ( ref($sub->{$i}) eq "HASH" && exists($sub->{$i}->{__TAG__}) )
1022 21 100       53 { if ( exists($sub->{$i}->{$val->[$i]}) )
1023             {
1024             push @$ifds, [ $prefix . $sub->{$i}->{__TAG__}, $type, $count,
1025 13         54 $sub->{$i}->{$val->[$i]} ];
1026             }
1027             else
1028             {
1029 8         36 push @$ifds, [ $prefix . $sub->{$i}->{__TAG__}, $type, $count,
1030             "Unknown (" . $val->[$i] . ")" ];
1031             }
1032             }
1033             else
1034 17         61 { push @$ifds, [ $prefix . $sub->{$i}, $type, $count, $val->[$i] ]; }
1035             }
1036             }
1037 2         14 next FIELD;
1038             }
1039             #hack for UNDEFINED values, they all have different
1040             #meanings depending on tag
1041 206 100       389 $val = &{$tag->{DECODER}}($self,$val) if defined($tag->{DECODER});
  41         84  
1042 206 100       467 $val = $tag->{$val} if exists $tag->{$val};
1043 206         284 $tag = $tag->{__TAG__};
1044             }
1045              
1046 771 100       1201 $tag = $self->{tag_prefix} . '-' . $tag if $self->{tag_prefix};
1047              
1048             #if ( $val =~ m/ARRAY/ ) { $val = join(', ',@$val); }
1049 771         1957 push @$ifds, [ $tag, $type, $count, $val ];
1050              
1051 771 100 100     2683 $self->{$tag} = $val if ($tag eq 'Make' or $tag eq 'Model');
1052             }
1053             }
1054              
1055 71         151 $ifds;
1056             }
1057              
1058             sub components_configuration_decoder
1059             {
1060 16     16 0 25 my $self = shift;
1061 16         26 my $val = shift;
1062 16         21 my $rv = "";
1063 16         107 my %v = (
1064             0 => undef,
1065             1 => 'Y',
1066             2 => 'Cb',
1067             3 => 'Cr',
1068             4 => 'R',
1069             5 => 'G',
1070             6 => 'B',
1071             );
1072 16 50       38 return join ( '', map { $v{$_} if defined($v{$_}) } $self->unpack('c4',$val) );
  48         275  
1073             }
1074              
1075             sub file_source_decoder
1076             {
1077 13     13 0 31 my $self = shift;
1078 13         24 my $val = shift;
1079 13         49 my %v = (
1080             3 => "(DSC) Digital Still Camera",
1081             );
1082 13         51 $val = $self->unpack('c',$val);
1083 13 50       62 return $v{$val} if $v{$val};
1084 0         0 "Other";
1085             }
1086              
1087             sub scene_type_decoder
1088             {
1089 12     12 0 14 my $self = shift;
1090 12         17 my $val = shift;
1091 12         28 my %v = (
1092             1 => "Directly Photographed Image",
1093             );
1094 12         23 $val = $self->unpack('c',$val);
1095 12 50       36 return $v{$val} if $v{$val};
1096 0         0 "Other";
1097             }
1098              
1099             package Image::TIFF::Rational;
1100              
1101 12         119 use overload '""' => \&as_string,
1102             '0+' => \&as_float,
1103 12     12   104 fallback => 1;
  12         18  
1104              
1105             sub new {
1106 22     22   42 my($class, $a, $b) = @_;
1107 22         77 bless [$a, $b], $class;
1108             }
1109              
1110             sub as_string {
1111 89     89   21807 my $self = shift;
1112             #warn "@$self";
1113 89         1382 "$self->[0]/$self->[1]";
1114             }
1115              
1116             sub as_float {
1117 106     106   156 my $self = shift;
1118              
1119             # We check here because some stupid cameras (Samsung Digimax 200)
1120             # use rationals with 0 denominator (found in thumbnail resolution spec).
1121 106 50       215 if ($self->[1]) {
1122 106         337 return $self->[0] / $self->[1];
1123             }
1124             else {
1125 0           return $self->[0];
1126             }
1127             }
1128              
1129             1;