File Coverage

blib/lib/Image/ExifTool/Photoshop.pm
Criterion Covered Total %
statement 154 319 48.2
branch 58 230 25.2
condition 19 69 27.5
subroutine 8 10 80.0
pod 0 6 0.0
total 239 634 37.7


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Photoshop.pm
3             #
4             # Description: Read/write Photoshop IRB meta information
5             #
6             # Revisions: 02/06/2004 - P. Harvey Created
7             # 02/25/2004 - P. Harvey Added hack for problem with old photoshops
8             # 10/04/2004 - P. Harvey Added a bunch of tags (ref Image::MetaData::JPEG)
9             # but left most of them commented out until I have enough
10             # information to write PrintConv routines for them to
11             # display something useful
12             # 07/08/2005 - P. Harvey Added support for reading PSD files
13             # 01/07/2006 - P. Harvey Added PSD write support
14             # 11/04/2006 - P. Harvey Added handling of resource name
15             #
16             # References: 1) http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf
17             # 2) http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html
18             # 3) Matt Mueller private communication (tests with PS CS2)
19             # 4) http://www.fileformat.info/format/psd/egff.htm
20             # 5) http://www.telegraphics.com.au/svn/psdparse/trunk/resources.c
21             # 6) http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf
22             # 7) http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/
23             #------------------------------------------------------------------------------
24              
25             package Image::ExifTool::Photoshop;
26              
27 23     23   4605 use strict;
  23         46  
  23         1281  
28 23     23   121 use vars qw($VERSION $AUTOLOAD $iptcDigestInfo);
  23         50  
  23         1264  
29 23     23   128 use Image::ExifTool qw(:DataAccess :Utils);
  23         41  
  23         105872  
30              
31             $VERSION = '1.65';
32              
33             sub ProcessPhotoshop($$$);
34             sub WritePhotoshop($$$);
35             sub ProcessLayers($$$);
36              
37             # map of where information is stored in PSD image
38             my %psdMap = (
39             IPTC => 'Photoshop',
40             XMP => 'Photoshop',
41             EXIFInfo => 'Photoshop',
42             IFD0 => 'EXIFInfo',
43             IFD1 => 'IFD0',
44             ICC_Profile => 'Photoshop',
45             ExifIFD => 'IFD0',
46             GPS => 'IFD0',
47             SubIFD => 'IFD0',
48             GlobParamIFD => 'IFD0',
49             PrintIM => 'IFD0',
50             InteropIFD => 'ExifIFD',
51             MakerNotes => 'ExifIFD',
52             );
53              
54             # tag information for PhotoshopThumbnail and PhotoshopBGRThumbnail
55             my %thumbnailInfo = (
56             Writable => 'undef',
57             Protected => 1,
58             RawConv => 'my $img=substr($val,0x1c); $self->ValidateImage(\$img,$tag)',
59             ValueConvInv => q{
60             my $et = new Image::ExifTool;
61             my @tags = qw{ImageWidth ImageHeight FileType};
62             my $info = $et->ImageInfo(\$val, @tags);
63             my ($w, $h, $type) = @$info{@tags};
64             $w and $h and $type eq 'JPEG' or warn("Not a valid JPEG image\n"), return undef;
65             my $wbytes = int(($w * 24 + 31) / 32) * 4;
66             return pack('N6n2', 1, $w, $h, $wbytes, $wbytes * $h, length($val), 24, 1) . $val;
67             },
68             );
69              
70             # tag info to decode Photoshop Unicode string
71             my %unicodeString = (
72             ValueConv => sub {
73             my ($val, $et) = @_;
74             return '' if length($val) < 4;
75             my $len = unpack('N', $val) * 2;
76             return '' if length($val) < 4 + $len;
77             return $et->Decode(substr($val, 4, $len), 'UCS2', 'MM');
78             },
79             ValueConvInv => sub {
80             my ($val, $et) = @_;
81             return pack('N', length $val) . $et->Encode($val, 'UCS2', 'MM');
82             },
83             );
84              
85             # Photoshop APP13 tag table
86             # (set Unknown flag for information we don't want to display normally)
87             %Image::ExifTool::Photoshop::Main = (
88             GROUPS => { 2 => 'Image' },
89             PROCESS_PROC => \&ProcessPhotoshop,
90             WRITE_PROC => \&WritePhotoshop,
91             0x03e8 => { Unknown => 1, Name => 'Photoshop2Info' },
92             0x03e9 => { Unknown => 1, Name => 'MacintoshPrintInfo' },
93             0x03ea => { Unknown => 1, Name => 'XMLData', Binary => 1 }, #PH
94             0x03eb => { Unknown => 1, Name => 'Photoshop2ColorTable' },
95             0x03ed => {
96             Name => 'ResolutionInfo',
97             SubDirectory => {
98             TagTable => 'Image::ExifTool::Photoshop::Resolution',
99             },
100             },
101             0x03ee => {
102             Name => 'AlphaChannelsNames',
103             ValueConv => 'Image::ExifTool::Photoshop::ConvertPascalString($self,$val)',
104             },
105             0x03ef => { Unknown => 1, Name => 'DisplayInfo' },
106             0x03f0 => { Unknown => 1, Name => 'PStringCaption' },
107             0x03f1 => { Unknown => 1, Name => 'BorderInformation' },
108             0x03f2 => { Unknown => 1, Name => 'BackgroundColor' },
109             0x03f3 => { Unknown => 1, Name => 'PrintFlags', Format => 'int8u' },
110             0x03f4 => { Unknown => 1, Name => 'BW_HalftoningInfo' },
111             0x03f5 => { Unknown => 1, Name => 'ColorHalftoningInfo' },
112             0x03f6 => { Unknown => 1, Name => 'DuotoneHalftoningInfo' },
113             0x03f7 => { Unknown => 1, Name => 'BW_TransferFunc' },
114             0x03f8 => { Unknown => 1, Name => 'ColorTransferFuncs' },
115             0x03f9 => { Unknown => 1, Name => 'DuotoneTransferFuncs' },
116             0x03fa => { Unknown => 1, Name => 'DuotoneImageInfo' },
117             0x03fb => { Unknown => 1, Name => 'EffectiveBW', Format => 'int8u' },
118             0x03fc => { Unknown => 1, Name => 'ObsoletePhotoshopTag1' },
119             0x03fd => { Unknown => 1, Name => 'EPSOptions' },
120             0x03fe => { Unknown => 1, Name => 'QuickMaskInfo' },
121             0x03ff => { Unknown => 1, Name => 'ObsoletePhotoshopTag2' },
122             0x0400 => { Unknown => 1, Name => 'TargetLayerID', Format => 'int16u' }, # (LayerStateInfo)
123             0x0401 => { Unknown => 1, Name => 'WorkingPath' },
124             0x0402 => { Unknown => 1, Name => 'LayersGroupInfo', Format => 'int16u' },
125             0x0403 => { Unknown => 1, Name => 'ObsoletePhotoshopTag3' },
126             0x0404 => {
127             Name => 'IPTCData',
128             SubDirectory => {
129             DirName => 'IPTC',
130             TagTable => 'Image::ExifTool::IPTC::Main',
131             },
132             },
133             0x0405 => { Unknown => 1, Name => 'RawImageMode' },
134             0x0406 => { #2
135             Name => 'JPEG_Quality',
136             SubDirectory => {
137             TagTable => 'Image::ExifTool::Photoshop::JPEG_Quality',
138             },
139             },
140             0x0408 => { Unknown => 1, Name => 'GridGuidesInfo' },
141             0x0409 => {
142             Name => 'PhotoshopBGRThumbnail',
143             Notes => 'this is a JPEG image, but in BGR format instead of RGB',
144             %thumbnailInfo,
145             Groups => { 2 => 'Preview' },
146             },
147             0x040a => {
148             Name => 'CopyrightFlag',
149             Writable => 'int8u',
150             Groups => { 2 => 'Author' },
151             ValueConv => 'join(" ",unpack("C*", $val))',
152             ValueConvInv => 'pack("C*",split(" ",$val))',
153             PrintConv => { #3
154             0 => 'False',
155             1 => 'True',
156             },
157             },
158             0x040b => {
159             Name => 'URL',
160             Writable => 'string',
161             Groups => { 2 => 'Author' },
162             },
163             0x040c => {
164             Name => 'PhotoshopThumbnail',
165             %thumbnailInfo,
166             Groups => { 2 => 'Preview' },
167             },
168             0x040d => {
169             Name => 'GlobalAngle',
170             Writable => 'int32u',
171             ValueConv => 'unpack("N",$val)',
172             ValueConvInv => 'pack("N",$val)',
173             },
174             0x040e => { Unknown => 1, Name => 'ColorSamplersResource' },
175             0x040f => {
176             Name => 'ICC_Profile',
177             SubDirectory => {
178             TagTable => 'Image::ExifTool::ICC_Profile::Main',
179             },
180             },
181             0x0410 => { Unknown => 1, Name => 'Watermark', Format => 'int8u' },
182             0x0411 => { Unknown => 1, Name => 'ICC_Untagged', Format => 'int8u' },
183             0x0412 => { Unknown => 1, Name => 'EffectsVisible', Format => 'int8u' },
184             0x0413 => { Unknown => 1, Name => 'SpotHalftone' },
185             0x0414 => { Unknown => 1, Name => 'IDsBaseValue', Description => 'IDs Base Value', Format => 'int32u' },
186             0x0415 => { Unknown => 1, Name => 'UnicodeAlphaNames' },
187             0x0416 => { Unknown => 1, Name => 'IndexedColorTableCount', Format => 'int16u' },
188             0x0417 => { Unknown => 1, Name => 'TransparentIndex', Format => 'int16u' },
189             0x0419 => {
190             Name => 'GlobalAltitude',
191             Writable => 'int32u',
192             ValueConv => 'unpack("N",$val)',
193             ValueConvInv => 'pack("N",$val)',
194             },
195             0x041a => {
196             Name => 'SliceInfo',
197             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::SliceInfo' },
198             },
199             0x041b => { Name => 'WorkflowURL', %unicodeString },
200             0x041c => { Unknown => 1, Name => 'JumpToXPEP' },
201             0x041d => { Unknown => 1, Name => 'AlphaIdentifiers' },
202             0x041e => {
203             Name => 'URL_List',
204             List => 1,
205             Writable => 1,
206             ValueConv => sub {
207             my ($val, $et) = @_;
208             return '' if length($val) < 4;
209             my $num = unpack('N', $val);
210             my ($i, @vals);
211             my $pos = 4;
212             for ($i=0; $i<$num; ++$i) {
213             $pos += 8; # (skip word and ID)
214             last if length($val) < $pos + 4;
215             my $len = unpack("x${pos}N", $val) * 2;
216             last if length($val) < $pos + 4 + $len;
217             push @vals, $et->Decode(substr($val,$pos+4,$len), 'UCS2', 'MM');
218             $pos += 4 + $len;
219             }
220             return \@vals;
221             },
222             # (this is tricky to make writable)
223             },
224             0x0421 => {
225             Name => 'VersionInfo',
226             SubDirectory => {
227             TagTable => 'Image::ExifTool::Photoshop::VersionInfo',
228             },
229             },
230             0x0422 => {
231             Name => 'EXIFInfo', #PH (Found in EPS and PSD files)
232             SubDirectory => {
233             TagTable=> 'Image::ExifTool::Exif::Main',
234             ProcessProc => \&Image::ExifTool::ProcessTIFF,
235             WriteProc => \&Image::ExifTool::WriteTIFF,
236             },
237             },
238             0x0423 => { Unknown => 1, Name => 'ExifInfo2', Binary => 1 }, #5
239             0x0424 => {
240             Name => 'XMP',
241             SubDirectory => {
242             TagTable => 'Image::ExifTool::XMP::Main',
243             },
244             },
245             0x0425 => {
246             Name => 'IPTCDigest',
247             Writable => 'string',
248             Protected => 1,
249             Notes => q{
250             this tag indicates provides a way for XMP-aware applications to indicate
251             that the XMP is synchronized with the IPTC. The MWG recommendation is to
252             ignore the XMP if IPTCDigest exists and doesn't match the CurrentIPTCDigest.
253             When writing, special values of "new" and "old" represent the digests of the
254             IPTC from the edited and original files respectively, and are undefined if
255             the IPTC does not exist in the respective file. Set this to "new" as an
256             indication that the XMP is synchronized with the IPTC
257             },
258             # also note the 'new' feature requires that the IPTC comes before this tag is written
259             ValueConv => 'unpack("H*", $val)',
260             ValueConvInv => q{
261             if (lc($val) eq 'new' or lc($val) eq 'old') {
262             {
263             local $SIG{'__WARN__'} = sub { };
264             return lc($val) if eval { require Digest::MD5 };
265             }
266             warn "Digest::MD5 must be installed\n";
267             return undef;
268             }
269             return pack('H*', $val) if $val =~ /^[0-9a-f]{32}$/i;
270             warn "Value must be 'new', 'old' or 32 hexadecimal digits\n";
271             return undef;
272             }
273             },
274             0x0426 => {
275             Name => 'PrintScaleInfo',
276             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PrintScaleInfo' },
277             },
278             0x0428 => {
279             Name => 'PixelInfo',
280             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PixelInfo' },
281             },
282             0x0429 => { Unknown => 1, Name => 'LayerComps' }, #5
283             0x042a => { Unknown => 1, Name => 'AlternateDuotoneColors' }, #5
284             0x042b => { Unknown => 1, Name => 'AlternateSpotColors' }, #5
285             0x042d => { #7
286             Name => 'LayerSelectionIDs',
287             Description => 'Layer Selection IDs',
288             Unknown => 1,
289             ValueConv => q{
290             my ($n, @a) = unpack("nN*",$val);
291             $#a = $n - 1 if $n > @a;
292             return join(' ', @a);
293             },
294             },
295             0x042e => { Unknown => 1, Name => 'HDRToningInfo' }, #7
296             0x042f => { Unknown => 1, Name => 'PrintInfo' }, #7
297             0x0430 => { Unknown => 1, Name => 'LayerGroupsEnabledID', Format => 'int8u' }, #7
298             0x0431 => { Unknown => 1, Name => 'ColorSamplersResource2' }, #7
299             0x0432 => { Unknown => 1, Name => 'MeasurementScale' }, #7
300             0x0433 => { Unknown => 1, Name => 'TimelineInfo' }, #7
301             0x0434 => { Unknown => 1, Name => 'SheetDisclosure' }, #7
302             0x0435 => { Unknown => 1, Name => 'DisplayInfo' }, #7
303             0x0436 => { Unknown => 1, Name => 'OnionSkins' }, #7
304             0x0438 => { Unknown => 1, Name => 'CountInfo' }, #7
305             0x043a => { Unknown => 1, Name => 'PrintInfo2' }, #7
306             0x043b => { Unknown => 1, Name => 'PrintStyle' }, #7
307             0x043c => { Unknown => 1, Name => 'MacintoshNSPrintInfo' }, #7
308             0x043d => { Unknown => 1, Name => 'WindowsDEVMODE' }, #7
309             0x043e => { Unknown => 1, Name => 'AutoSaveFilePath' }, #7
310             0x043f => { Unknown => 1, Name => 'AutoSaveFormat' }, #7
311             0x0440 => { Unknown => 1, Name => 'PathSelectionState' }, #7
312             # 0x07d0-0x0bb6 Path information
313             0x0bb7 => {
314             Name => 'ClippingPathName',
315             # convert from a Pascal string (ignoring 6 bytes of unknown data after string)
316             ValueConv => q{
317             my $len = ord($val);
318             $val = substr($val, 0, $len+1) if $len < length($val);
319             return Image::ExifTool::Photoshop::ConvertPascalString($self,$val);
320             },
321             },
322             0x0bb8 => { Unknown => 1, Name => 'OriginPathInfo' }, #7
323             # 0x0fa0-0x1387 - plug-in resources (ref 7)
324             0x1b58 => { Unknown => 1, Name => 'ImageReadyVariables' }, #7
325             0x1b59 => { Unknown => 1, Name => 'ImageReadyDataSets' }, #7
326             0x1f40 => { Unknown => 1, Name => 'LightroomWorkflow' }, #7
327             0x2710 => { Unknown => 1, Name => 'PrintFlagsInfo' },
328             );
329              
330             # Photoshop JPEG quality record (ref 2)
331             %Image::ExifTool::Photoshop::JPEG_Quality = (
332             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
333             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
334             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
335             FORMAT => 'int16s',
336             GROUPS => { 2 => 'Image' },
337             0 => {
338             Name => 'PhotoshopQuality',
339             Writable => 1,
340             PrintConv => '$val + 4',
341             PrintConvInv => '$val - 4',
342             },
343             1 => {
344             Name => 'PhotoshopFormat',
345             PrintConv => {
346             0x0000 => 'Standard',
347             0x0001 => 'Optimized',
348             0x0101 => 'Progressive',
349             },
350             },
351             2 => {
352             Name => 'ProgressiveScans',
353             PrintConv => {
354             1 => '3 Scans',
355             2 => '4 Scans',
356             3 => '5 Scans',
357             },
358             },
359             );
360              
361             # Photoshop Slices
362             %Image::ExifTool::Photoshop::SliceInfo = (
363             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
364             20 => { Name => 'SlicesGroupName', Format => 'var_ustr32' },
365             24 => { Name => 'NumSlices', Format => 'int32u' },
366             );
367              
368             # Photoshop resolution information #PH
369             %Image::ExifTool::Photoshop::Resolution = (
370             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
371             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
372             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
373             FORMAT => 'int16u',
374             FIRST_ENTRY => 0,
375             WRITABLE => 1,
376             GROUPS => { 2 => 'Image' },
377             0 => {
378             Name => 'XResolution',
379             Format => 'int32u',
380             Priority => 0,
381             ValueConv => '$val / 0x10000',
382             ValueConvInv => 'int($val * 0x10000 + 0.5)',
383             PrintConv => 'int($val * 100 + 0.5) / 100',
384             PrintConvInv => '$val',
385             },
386             2 => {
387             Name => 'DisplayedUnitsX',
388             PrintConv => {
389             1 => 'inches',
390             2 => 'cm',
391             },
392             },
393             4 => {
394             Name => 'YResolution',
395             Format => 'int32u',
396             Priority => 0,
397             ValueConv => '$val / 0x10000',
398             ValueConvInv => 'int($val * 0x10000 + 0.5)',
399             PrintConv => 'int($val * 100 + 0.5) / 100',
400             PrintConvInv => '$val',
401             },
402             6 => {
403             Name => 'DisplayedUnitsY',
404             PrintConv => {
405             1 => 'inches',
406             2 => 'cm',
407             },
408             },
409             );
410              
411             # Photoshop version information
412             %Image::ExifTool::Photoshop::VersionInfo = (
413             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
414             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
415             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
416             FIRST_ENTRY => 0,
417             GROUPS => { 2 => 'Image' },
418             # (always 1) 0 => { Name => 'PhotoshopVersion', Format => 'int32u' },
419             4 => { Name => 'HasRealMergedData', Format => 'int8u', PrintConv => { 0 => 'No', 1 => 'Yes' } },
420             5 => { Name => 'WriterName', Format => 'var_ustr32' },
421             9 => { Name => 'ReaderName', Format => 'var_ustr32' },
422             # (always 1) 13 => { Name => 'FileVersion', Format => 'int32u' },
423             );
424              
425             # Print Scale
426             %Image::ExifTool::Photoshop::PrintScaleInfo = (
427             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
428             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
429             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
430             FIRST_ENTRY => 0,
431             GROUPS => { 2 => 'Image' },
432             0 => {
433             Name => 'PrintStyle',
434             Format => 'int16u',
435             PrintConv => {
436             0 => 'Centered',
437             1 => 'Size to Fit',
438             2 => 'User Defined',
439             },
440             },
441             2 => { Name => 'PrintPosition', Format => 'float[2]' },
442             10 => { Name => 'PrintScale', Format => 'float' },
443             );
444              
445             # Pixel Aspect Ratio
446             %Image::ExifTool::Photoshop::PixelInfo = (
447             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
448             WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
449             CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
450             FIRST_ENTRY => 0,
451             GROUPS => { 2 => 'Image' },
452             # 0 - version
453             4 => { Name => 'PixelAspectRatio', Format => 'double' },
454             );
455              
456             # Photoshop PSD file header
457             %Image::ExifTool::Photoshop::Header = (
458             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
459             FORMAT => 'int16u',
460             GROUPS => { 2 => 'Image' },
461             NOTES => 'This information is found in the PSD file header.',
462             6 => 'NumChannels',
463             7 => { Name => 'ImageHeight', Format => 'int32u' },
464             9 => { Name => 'ImageWidth', Format => 'int32u' },
465             11 => 'BitDepth',
466             12 => {
467             Name => 'ColorMode',
468             PrintConvColumns => 2,
469             PrintConv => {
470             0 => 'Bitmap',
471             1 => 'Grayscale',
472             2 => 'Indexed',
473             3 => 'RGB',
474             4 => 'CMYK',
475             7 => 'Multichannel',
476             8 => 'Duotone',
477             9 => 'Lab',
478             },
479             },
480             );
481              
482             # Layer information
483             %Image::ExifTool::Photoshop::Layers = (
484             PROCESS_PROC => \&ProcessLayers,
485             GROUPS => { 2 => 'Image' },
486             NOTES => 'Tags extracted from Photoshop layer information.',
487             # tags extracted from layer information
488             # (tag ID's are for convenience only)
489             _xcnt => { Name => 'LayerCount', Format => 'int16u' },
490             _xrct => {
491             Name => 'LayerRectangles',
492             Format => 'int32u',
493             Count => 4,
494             List => 1,
495             Notes => 'top left bottom right',
496             },
497             _xnam => { Name => 'LayerNames',
498             Format => 'string',
499             List => 1,
500             ValueConv => q{
501             my $charset = $self->Options('CharsetPhotoshop') || 'Latin';
502             return $self->Decode($val, $charset);
503             },
504             },
505             _xbnd => {
506             Name => 'LayerBlendModes',
507             Format => 'undef',
508             List => 1,
509             RawConv => 'GetByteOrder() eq "II" ? pack "N*", unpack "V*", $val : $val',
510             PrintConv => {
511             pass => 'Pass Through',
512             norm => 'Normal',
513             diss => 'Dissolve',
514             dark => 'Darken',
515             'mul '=> 'Multiply',
516             idiv => 'Color Burn',
517             lbrn => 'Linear Burn',
518             dkCl => 'Darker Color',
519             lite => 'Lighten',
520             scrn => 'Screen',
521             'div '=> 'Color Dodge',
522             lddg => 'Linear Dodge',
523             lgCl => 'Lighter Color',
524             over => 'Overlay',
525             sLit => 'Soft Light',
526             hLit => 'Hard Light',
527             vLit => 'Vivid Light',
528             lLit => 'Linear Light',
529             pLit => 'Pin Light',
530             hMix => 'Hard Mix',
531             diff => 'Difference',
532             smud => 'Exclusion',
533             fsub => 'Subtract',
534             fdiv => 'Divide',
535             'hue '=> 'Hue',
536             'sat '=> 'Saturation',
537             colr => 'Color',
538             'lum '=> 'Luminosity',
539             },
540             },
541             _xopc => {
542             Name => 'LayerOpacities',
543             Format => 'int8u',
544             List => 1,
545             ValueConv => '100 * $val / 255',
546             PrintConv => 'sprintf("%d%%",$val)',
547             },
548             # tags extracted from additional layer information (tag ID's are real)
549             # - must be able to accommodate a blank entry to preserve the list ordering
550             luni => {
551             Name => 'LayerUnicodeNames',
552             List => 1,
553             RawConv => q{
554             return '' if length($val) < 4;
555             my $len = Get32u(\$val, 0);
556             return $self->Decode(substr($val, 4, $len * 2), 'UCS2');
557             },
558             },
559             lyid => {
560             Name => 'LayerIDs',
561             Description => 'Layer IDs',
562             Format => 'int32u',
563             List => 1,
564             Unknown => 1,
565             },
566             shmd => { # layer metadata (undocumented structure)
567             # (for now, only extract layerTime. May also contain "layerXMP" --
568             # it would be nice to decode this but I need a sample)
569             Name => 'LayerModifyDates',
570             Groups => { 2 => 'Time' },
571             List => 1,
572             RawConv => q{
573             return '' unless $val =~ /layerTime(doub|buod)(.{8})/s;
574             my $tmp = $2;
575             return GetDouble(\$tmp, 0);
576             },
577             ValueConv => 'length $val ? ConvertUnixTime($val,1) : ""',
578             PrintConv => 'length $val ? $self->ConvertDateTime($val) : ""',
579             },
580             );
581              
582             # tags extracted from ImageSourceData found in TIFF images (ref PH)
583             %Image::ExifTool::Photoshop::DocumentData = (
584             PROCESS_PROC => \&ProcessDocumentData,
585             GROUPS => { 2 => 'Image' },
586             Layr => {
587             Name => 'Layers',
588             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' },
589             },
590             Lr16 => { # (NC)
591             Name => 'Layers',
592             SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' },
593             },
594             );
595              
596             # image data
597             %Image::ExifTool::Photoshop::ImageData = (
598             PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
599             GROUPS => { 2 => 'Image' },
600             0 => {
601             Name => 'Compression',
602             Format => 'int16u',
603             PrintConv => {
604             0 => 'Uncompressed',
605             1 => 'RLE',
606             2 => 'ZIP without prediction',
607             3 => 'ZIP with prediction',
608             },
609             },
610             );
611              
612             # tags for unknown resource types
613             %Image::ExifTool::Photoshop::Unknown = (
614             GROUPS => { 2 => 'Unknown' },
615             );
616              
617             # define reference to IPTCDigest tagInfo hash for convenience
618             $iptcDigestInfo = $Image::ExifTool::Photoshop::Main{0x0425};
619              
620              
621             #------------------------------------------------------------------------------
622             # AutoLoad our writer routines when necessary
623             #
624             sub AUTOLOAD
625             {
626 15     15   86 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
627             }
628              
629             #------------------------------------------------------------------------------
630             # Convert pascal string(s) to something we can use
631             # Inputs: 1) Pascal string data
632             # Returns: Strings, concatenated with ', '
633             sub ConvertPascalString($$)
634             {
635 0     0 0 0 my ($et, $inStr) = @_;
636 0         0 my $outStr = '';
637 0         0 my $len = length($inStr);
638 0         0 my $i=0;
639 0         0 while ($i < $len) {
640 0         0 my $n = ord(substr($inStr, $i, 1));
641 0 0       0 last if $i + $n >= $len;
642 0 0       0 $i and $outStr .= ', ';
643 0         0 $outStr .= substr($inStr, $i+1, $n);
644 0         0 $i += $n + 1;
645             }
646 0   0     0 my $charset = $et->Options('CharsetPhotoshop') || 'Latin';
647 0         0 return $et->Decode($outStr, $charset);
648             }
649              
650             #------------------------------------------------------------------------------
651             # Process Photoshop layers and mask information section of PSD/PSB file
652             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
653             # Returns: 1 on success (and seeks to the end of this section)
654             sub ProcessLayersAndMask($$$)
655             {
656 4     4 0 7 local $_;
657 4         10 my ($et, $dirInfo, $tagTablePtr) = @_;
658 4         11 my $raf = $$dirInfo{RAF};
659 4         11 my $fileType = $$et{VALUE}{FileType};
660 4         7 my $data;
661              
662 4 50 33     15 return 0 unless $fileType eq 'PSD' or $fileType eq 'PSB'; # (no layer section in CS1 files)
663              
664             # (some words are 4 bytes in PSD files and 8 bytes in PSB)
665 4 50       16 my ($psb, $psiz) = $fileType eq 'PSB' ? (1, 8) : (undef, 4);
666              
667             # read the layer information header
668 4         10 my $n = $psiz * 2 + 2;
669 4 50       22 $raf->Read($data, $n) == $n or return 0;
670 4 50       19 my $tot = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer and mask info
671 4 50       16 return 1 if $tot == 0;
672 4         26 my $end = $raf->Tell() - $psiz - 2 + $tot;
673 4         14 $data = substr $data, $psiz;
674 4 50       15 my $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer info section
675 4         12 my $num = Get16s(\$data, $psiz);
676             # check for Lr16 block if layers length is 0 (ref https://forums.adobe.com/thread/1540914)
677 4 50 33     22 if ($len == 0 and $num == 0) {
678 4 50       12 $raf->Read($data,10) == 10 or return 0;
679 4 50       26 if ($data =~ /^..8BIMLr16/s) {
    50          
680 0 0       0 $raf->Read($data, $psiz+2) == $psiz+2 or return 0;
681 0 0       0 $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0);
682             } elsif ($data =~ /^..8BIMMt16/s) { # (have seen Mt16 before Lr16, ref PH)
683 0 0       0 $raf->Read($data, $psiz) == $psiz or return 0;
684 0 0       0 $raf->Read($data, 8) == 8 or return 0;
685 0 0       0 if ($data eq '8BIMLr16') {
686 0 0       0 $raf->Read($data, $psiz+2) == $psiz+2 or return 0;
687 0 0       0 $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0);
688             } else {
689 0 0       0 $raf->Seek(-18-$psiz, 1) or return 0;
690             }
691             } else {
692 4 50       17 $raf->Seek(-10, 1) or return 0;
693             }
694             }
695 4         12 $len += 2; # include layer count with layer info section
696 4 50       14 $raf->Seek(-2, 1) or return 0;
697 4         23 my %dinfo = (
698             RAF => $raf,
699             DirLen => $len,
700             );
701 4         13 $$et{IsPSB} = $psb; # set PSB flag
702 4         19 ProcessLayers($et, \%dinfo, $tagTablePtr);
703              
704             # seek to the end of this section and return success flag
705 4 50       18 return $raf->Seek($end, 0) ? 1 : 0;
706             }
707              
708             #------------------------------------------------------------------------------
709             # Process Photoshop layers (beginning with layer count)
710             # Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
711             # Returns: 1 on success
712             # Notes: Uses ExifTool IsPSB member to determine whether file is PSB format
713             sub ProcessLayers($$$)
714             {
715 4     4 0 7 local $_;
716 4         12 my ($et, $dirInfo, $tagTablePtr) = @_;
717 4         9 my ($i, $n, %count, $buff, $buf2);
718 4         8 my $raf = $$dirInfo{RAF};
719 4         10 my $dirLen = $$dirInfo{DirLen};
720 4         10 my $verbose = $$et{OPTIONS}{Verbose};
721 4         16 my %dinfo = ( DataPt => \$buff, Base => $raf->Tell() );
722 4         9 my $pos = 0;
723 4 50       12 return 0 if $dirLen < 2;
724 4 50       14 $raf->Read($buff, 2) == 2 or return 0;
725 4         14 my $num = Get16s(\$buff, 0);
726 4 50       17 $num = -$num if $num < 0; # (first channel is transparency data if negative)
727 4         22 $et->VerboseDir('Layers', $num, $dirLen);
728 4         22 $et->HandleTag($tagTablePtr, '_xcnt', $num, Start => $pos, Size => 2, %dinfo); # LayerCount
729 4         10 my $oldIndent = $$et{INDENT};
730 4         12 $$et{INDENT} .= '| ';
731              
732 4         8 $pos += 2;
733 4         7 my $psb = $$et{IsPSB}; # is PSB format?
734 4 50       9 my $psiz = $psb ? 8 : 4;
735 4         18 for ($i=0; $i<$num; ++$i) {
736 0         0 $et->VPrint(0, $oldIndent.'+ [Layer '.($i+1)." of $num]\n");
737 0 0       0 last if $pos + 18 > $dirLen;
738 0 0       0 $raf->Read($buff, 18) == 18 or last;
739 0         0 $dinfo{DataPos} = $pos;
740             # save the layer rectangle
741 0         0 $et->HandleTag($tagTablePtr, '_xrct', undef, Start => 0, Size => 16, %dinfo);
742 0         0 my $numChannels = Get16u(\$buff, 16);
743 0         0 $n = (2 + $psiz) * $numChannels; # size of channel information
744 0 0       0 $raf->Seek($n, 1) or last;
745 0         0 $pos += 18 + $n;
746 0 0       0 last if $pos + 20 > $dirLen;
747 0 0       0 $raf->Read($buff, 20) == 20 or last;
748 0         0 $dinfo{DataPos} = $pos;
749 0         0 my $sig = substr($buff, 0, 4);
750 0 0       0 $sig =~ /^(8BIM|MIB8)$/ or last; # verify signature
751 0         0 $et->HandleTag($tagTablePtr, '_xbnd', undef, Start => 4, Size => 4, %dinfo);
752 0         0 $et->HandleTag($tagTablePtr, '_xopc', undef, Start => 8, Size => 1, %dinfo);
753 0         0 my $nxt = $pos + 16 + Get32u(\$buff, 12);
754 0         0 $n = Get32u(\$buff, 16); # get size of layer mask data
755 0         0 $pos += 20 + $n; # skip layer mask data
756 0 0       0 last if $pos + 4 > $dirLen;
757 0 0 0     0 $raf->Seek($n, 1) and $raf->Read($buff, 4) == 4 or last;
758 0         0 $n = Get32u(\$buff, 0); # get size of layer blending ranges
759 0         0 $pos += 4 + $n; # skip layer blending ranges data
760 0 0       0 last if $pos + 1 > $dirLen;
761 0 0 0     0 $raf->Seek($n, 1) and $raf->Read($buff, 1) == 1 or last;
762 0         0 $n = Get8u(\$buff, 0); # get length of layer name
763 0 0       0 last if $pos + 1 + $n > $dirLen;
764 0 0       0 $raf->Read($buff, $n) == $n or last;
765 0         0 $dinfo{DataPos} = $pos + 1;
766 0         0 $et->HandleTag($tagTablePtr, '_xnam', undef, Start => 0, Size => $n, %dinfo);
767 0         0 my $frag = ($n + 1) & 0x3;
768 0 0 0     0 $raf->Seek(4 - $frag, 1) or last if $frag;
769 0         0 $n = ($n + 4) & 0xfffffffc; # +1 for length byte then pad to multiple of 4 bytes
770 0         0 $pos += $n;
771             # process additional layer info
772 0         0 while ($pos + 12 <= $nxt) {
773 0 0       0 $raf->Read($buff, 12) == 12 or last;
774 0         0 my $dat = substr($buff, 0, 8);
775 0 0       0 $dat = pack 'N*', unpack 'V*', $dat if GetByteOrder() eq 'II';
776 0         0 my $sig = substr($dat, 0, 4);
777 0 0 0     0 last unless $sig eq '8BIM' or $sig eq '8B64'; # verify signature
778 0         0 my $tag = substr($dat, 4, 4);
779             # (some structures have an 8-byte size word [augh!]
780             # --> it would be great if '8B64' indicated a 64-bit version, and this may well
781             # be the case, but it is not mentioned in the Photoshop file format specification)
782 0 0 0     0 if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) {
783 0 0       0 last if $pos + 16 > $nxt;
784 0 0       0 $raf->Read($buf2, 4) == 4 or last;
785 0         0 $buff .= $buf2;
786 0         0 $n = Get64u(\$buff, 8);
787 0         0 $pos += 4;
788             } else {
789 0         0 $n = Get32u(\$buff, 8);
790             }
791 0         0 $pos += 12;
792 0 0       0 last if $pos + $n > $nxt;
793 0         0 $frag = $n & 0x3;
794 0 0 0     0 if ($$tagTablePtr{$tag} or $verbose) {
795             # pad with empty entries if necessary to keep the same index for each item in the layer
796 0 0       0 $count{$tag} = 0 unless defined $count{$tag};
797 0 0       0 $raf->Read($buff, $n) == $n or last;
798 0         0 $dinfo{DataPos} = $pos;
799 0         0 while ($count{$tag} < $i) {
800 0         0 $et->HandleTag($tagTablePtr, $tag, '');
801 0         0 ++$count{$tag};
802             }
803 0         0 $et->HandleTag($tagTablePtr, $tag, undef, Start => 0, Size => $n, %dinfo);
804 0         0 ++$count{$tag};
805 0 0       0 if ($frag) {
806 0 0       0 $raf->Seek(4 - $frag, 1) or last;
807 0         0 $n += 4 - $frag; # pad to multiple of 4 bytes (PH NC)
808             }
809             } else {
810 0 0       0 $n += 4 - $frag if $frag;
811 0 0       0 $raf->Seek($n, 1) or last;
812             }
813 0         0 $pos += $n; # step to start of next structure
814             }
815 0         0 $pos = $nxt;
816             }
817 4         19 $$et{INDENT} = $oldIndent;
818 4         9 return 1;
819             }
820              
821             #------------------------------------------------------------------------------
822             # Process Photoshop ImageSourceData
823             # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
824             # Returns: 1 on success
825             sub ProcessDocumentData($$$)
826             {
827 0     0 0 0 my ($et, $dirInfo, $tagTablePtr) = @_;
828 0         0 my $verbose = $$et{OPTIONS}{Verbose};
829 0         0 my $raf = $$dirInfo{RAF};
830 0         0 my $dirLen = $$dirInfo{DirLen};
831 0         0 my $pos = 36; # length of header
832 0         0 my ($buff, $n, $err);
833              
834 0         0 $et->VerboseDir('Photoshop Document Data', undef, $dirLen);
835 0 0       0 unless ($raf) {
836 0         0 my $dataPt = $$dirInfo{DataPt};
837 0   0     0 my $start = $$dirInfo{DirStart} || 0;
838 0         0 $raf = new File::RandomAccess($dataPt);
839 0 0       0 $raf->Seek($start, 0) if $start;
840 0 0       0 $dirLen = length $$dataPt - $start unless defined $dirLen;
841 0         0 $et->VerboseDump($dataPt, Start => $start, Len => $dirLen, Base => $$dirInfo{Base});
842             }
843 0 0 0     0 unless ($raf->Read($buff, $pos) == $pos and
844             $buff =~ /^Adobe Photoshop Document Data (Block|V0002)\0/)
845             {
846 0         0 $et->Warn('Invalid Photoshop Document Data');
847 0         0 return 0;
848             }
849 0         0 my $psb = ($1 eq 'V0002');
850 0         0 my %dinfo = ( DataPt => \$buff );
851 0         0 $$et{IsPSB} = $psb; # set PSB flag (needed when handling Layers directory)
852 0         0 while ($pos + 12 <= $dirLen) {
853 0 0       0 $raf->Read($buff, 8) == 8 or $err = 'Error reading document data', last;
854             # set byte order according to byte order of first signature
855 0 0       0 SetByteOrder($buff =~ /^(8BIM|8B64)/ ? 'MM' : 'II') if $pos == 36;
    0          
856 0 0       0 $buff = pack 'N*', unpack 'V*', $buff if GetByteOrder() eq 'II';
857 0         0 my $sig = substr($buff, 0, 4);
858 0 0 0     0 $sig eq '8BIM' or $sig eq '8B64' or $err = 'Bad photoshop resource', last; # verify signature
859 0         0 my $tag = substr($buff, 4, 4);
860 0 0 0     0 if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) {
861 0 0       0 $pos + 16 > $dirLen and $err = 'Short PSB resource', last;
862 0 0       0 $raf->Read($buff, 8) == 8 or $err = 'Error reading PSB resource', last;
863 0         0 $n = Get64u(\$buff, 0);
864 0         0 $pos += 4;
865             } else {
866 0 0       0 $raf->Read($buff, 4) == 4 or $err = 'Error reading PSD resource', last;
867 0         0 $n = Get32u(\$buff, 0);
868             }
869 0         0 $pos += 12;
870 0 0       0 $pos + $n > $dirLen and $err = 'Truncated photoshop resource', last;
871 0         0 my $pad = (4 - ($n & 3)) & 3; # number of padding bytes
872 0         0 my $tagInfo = $$tagTablePtr{$tag};
873 0 0 0     0 if ($tagInfo or $verbose) {
874 0 0 0     0 if ($tagInfo and $$tagInfo{SubDirectory}) {
875 0         0 my $fpos = $raf->Tell() + $n + $pad;
876 0         0 my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable});
877 0         0 $et->ProcessDirectory({ RAF => $raf, DirLen => $n }, $subTable);
878 0 0       0 $raf->Seek($fpos, 0) or $err = 'Seek error', last;
879             } else {
880 0         0 $dinfo{DataPos} = $raf->Tell();
881 0         0 $dinfo{Start} = 0;
882 0         0 $dinfo{Size} = $n;
883 0 0       0 $raf->Read($buff, $n) == $n or $err = 'Error reading photoshop resource', last;
884 0         0 $et->HandleTag($tagTablePtr, $tag, undef, %dinfo);
885 0 0       0 $raf->Seek($pad, 1) or $err = 'Seek error', last;
886             }
887             } else {
888 0 0       0 $raf->Seek($n + $pad, 1) or $err = 'Seek error', last;
889             }
890 0         0 $pos += $n + $pad; # step to start of next structure
891             }
892 0 0       0 $err and $et->Warn($err);
893 0         0 return 1;
894             }
895              
896             #------------------------------------------------------------------------------
897             # Process Photoshop APP13 record
898             # Inputs: 0) ExifTool object reference, 1) Reference to directory information
899             # 2) Tag table reference
900             # Returns: 1 on success
901             sub ProcessPhotoshop($$$)
902             {
903 93     93 0 243 my ($et, $dirInfo, $tagTablePtr) = @_;
904 93         211 my $dataPt = $$dirInfo{DataPt};
905 93         206 my $pos = $$dirInfo{DirStart};
906 93         200 my $dirEnd = $pos + $$dirInfo{DirLen};
907 93         301 my $verbose = $et->Options('Verbose');
908 93         224 my $success = 0;
909              
910             # ignore non-standard XMP while in strict MWG compatibility mode
911 93 100 66     453 if (($Image::ExifTool::MWG::strict or $et->Options('Validate')) and
      66        
912             $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/)
913             {
914 5         21 my $path = $et->MetadataPath();
915 5 50       31 unless ($path =~ /^(JPEG-APP13-Photoshop|TIFF-IFD0-Photoshop|PSD)$/) {
916 0 0       0 if ($Image::ExifTool::MWG::strict) {
917 0         0 $et->Warn("Ignored non-standard Photoshop at $path");
918 0         0 return 1;
919             } else {
920 0         0 $et->Warn("Non-standard Photoshop at $path", 1);
921             }
922             }
923             }
924 93 50 66     498 if ($$et{FILE_TYPE} eq 'JPEG' and $$dirInfo{Parent} ne 'APP13') {
925 0         0 $$et{LOW_PRIORITY_DIR}{'*'} = 1; # lower priority of all these tags
926             }
927 93         352 SetByteOrder('MM'); # Photoshop is always big-endian
928 93 50       284 $verbose and $et->VerboseDir('Photoshop', 0, $$dirInfo{DirLen});
929              
930             # scan through resource blocks:
931             # Format: 0) Type, 4 bytes - '8BIM' (or the rare 'PHUT', 'DCSR', 'AgHg' or 'MeSa')
932             # 1) TagID,2 bytes
933             # 2) Name, pascal string padded to even no. bytes
934             # 3) Size, 4 bytes - N
935             # 4) Data, N bytes
936 93         321 while ($pos + 8 < $dirEnd) {
937 1182         2006 my $type = substr($$dataPt, $pos, 4);
938 1182         1633 my ($ttPtr, $extra, $val, $name);
939 1182 50       1940 if ($type eq '8BIM') {
    0          
940 1182         1455 $ttPtr = $tagTablePtr;
941             } elsif ($type =~ /^(PHUT|DCSR|AgHg|MeSa)$/) { # (PHUT~ImageReady, MeSa~PhotoDeluxe)
942 0         0 $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Unknown');
943             } else {
944 0         0 $type =~ s/([^\w])/sprintf("\\x%.2x",ord($1))/ge;
  0         0  
945 0         0 $et->Warn(qq{Bad Photoshop IRB resource "$type"});
946 0         0 last;
947             }
948 1182         2533 my $tag = Get16u($dataPt, $pos + 4);
949 1182         1763 $pos += 6; # point to start of name
950 1182         2155 my $nameLen = Get8u($dataPt, $pos);
951 1182         1735 my $namePos = ++$pos;
952             # skip resource block name (pascal string, padded to an even # of bytes)
953 1182         1397 $pos += $nameLen;
954 1182 50       2097 ++$pos unless $nameLen & 0x01;
955 1182 50       1958 if ($pos + 4 > $dirEnd) {
956 0         0 $et->Warn("Bad Photoshop resource block");
957 0         0 last;
958             }
959 1182         2154 my $size = Get32u($dataPt, $pos);
960 1182         1570 $pos += 4;
961 1182 50       2111 if ($size + $pos > $dirEnd) {
962 0         0 $et->Warn("Bad Photoshop resource data size $size");
963 0         0 last;
964             }
965 1182         1460 $success = 1;
966 1182 50       1842 if ($nameLen) {
967 0         0 $name = substr($$dataPt, $namePos, $nameLen);
968 0         0 $extra = qq{, Name="$name"};
969             } else {
970 1182         1573 $name = '';
971             }
972 1182         2399 my $tagInfo = $et->GetTagInfo($ttPtr, $tag);
973             # append resource name to value if requested (braced by "/#...#/")
974 1182 0 66     3596 if ($tagInfo and defined $$tagInfo{SetResourceName} and
      33        
      33        
975             $$tagInfo{SetResourceName} eq '1' and $name !~ m{/#})
976             {
977 0         0 $val = substr($$dataPt, $pos, $size) . '/#' . $name . '#/';
978             }
979             $et->HandleTag($ttPtr, $tag, $val,
980             TagInfo => $tagInfo,
981             Extra => $extra,
982             DataPt => $dataPt,
983             DataPos => $$dirInfo{DataPos},
984             Size => $size,
985             Start => $pos,
986             Base => $$dirInfo{Base},
987             Parent => $$dirInfo{DirName},
988 1182         4710 );
989 1182 100       2978 $size += 1 if $size & 0x01; # size is padded to an even # bytes
990 1182         2701 $pos += $size;
991             }
992             # warn about incorrect IPTCDigest
993 93 100 100     666 if ($$et{VALUE}{IPTCDigest} and $$et{VALUE}{CurrentIPTCDigest} and
      100        
994             $$et{VALUE}{IPTCDigest} ne $$et{VALUE}{CurrentIPTCDigest})
995             {
996 32         156 $et->WarnOnce('IPTCDigest is not current. XMP may be out of sync');
997             }
998 93         236 delete $$et{LOW_PRIORITY_DIR}{'*'};
999 93         252 return $success;
1000             }
1001              
1002             #------------------------------------------------------------------------------
1003             # extract information from Photoshop PSD file
1004             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
1005             # Returns: 1 if this was a valid PSD file, -1 on write error
1006             sub ProcessPSD($$)
1007             {
1008 5     5 0 16 my ($et, $dirInfo) = @_;
1009 5         13 my $raf = $$dirInfo{RAF};
1010 5         13 my $outfile = $$dirInfo{OutFile};
1011 5         8 my ($data, $err, $tagTablePtr);
1012              
1013 5 50       16 $raf->Read($data, 30) == 30 or return 0;
1014 5 50       33 $data =~ /^8BPS\0([\x01\x02])/ or return 0;
1015 5         22 SetByteOrder('MM');
1016 5 50       33 $et->SetFileType($1 eq "\x01" ? 'PSD' : 'PSB'); # set the FileType tag
1017 5         26 my %dirInfo = (
1018             DataPt => \$data,
1019             DirStart => 0,
1020             DirName => 'Photoshop',
1021             );
1022 5         18 my $len = Get32u(\$data, 26);
1023 5 100       16 if ($outfile) {
1024 1 50       5 Write($outfile, $data) or $err = 1;
1025 1 50       7 $raf->Read($data, $len) == $len or return -1;
1026 1 50       10 Write($outfile, $data) or $err = 1; # write color mode data
1027             # initialize map of where things are written
1028 1         6 $et->InitWriteDirs(\%psdMap);
1029             } else {
1030             # process the header
1031 4         11 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Header');
1032 4         14 $dirInfo{DirLen} = 30;
1033 4         17 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1034 4 50       16 $raf->Seek($len, 1) or $err = 1; # skip over color mode data
1035             }
1036             # read image resource section
1037 5 50       21 $raf->Read($data, 4) == 4 or $err = 1;
1038 5         21 $len = Get32u(\$data, 0);
1039 5 50       16 $raf->Read($data, $len) == $len or $err = 1;
1040 5         18 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main');
1041 5         14 $dirInfo{DirLen} = $len;
1042 5         11 my $rtnVal = 1;
1043 5 100       20 if ($outfile) {
    50          
1044             # rewrite IRB resources
1045 1         9 $data = WritePhotoshop($et, \%dirInfo, $tagTablePtr);
1046 1 50       6 if ($data) {
1047 1         4 $len = Set32u(length $data);
1048 1 50       5 Write($outfile, $len, $data) or $err = 1;
1049             # look for trailer and edit if necessary
1050 1         6 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
1051 1 50       5 if ($trailInfo) {
1052 1         2 my $tbuf = '';
1053 1         4 $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s)
1054             # rewrite all trailers to buffer
1055 1 50       6 if ($et->ProcessTrailers($trailInfo)) {
1056 1         5 my $copyBytes = $$trailInfo{DataPos} - $raf->Tell();
1057 1 50       4 if ($copyBytes >= 0) {
1058             # copy remaining PSD file up to start of trailer
1059 1         4 while ($copyBytes) {
1060 1 50       3 my $n = ($copyBytes > 65536) ? 65536 : $copyBytes;
1061 1 50       4 $raf->Read($data, $n) == $n or $err = 1;
1062 1 50       5 Write($outfile, $data) or $err = 1;
1063 1         4 $copyBytes -= $n;
1064             }
1065             # write the trailer (or not)
1066 1 50       5 $et->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1;
1067             } else {
1068 0         0 $et->Warn('Overlapping trailer');
1069 0         0 undef $trailInfo;
1070             }
1071             } else {
1072 0         0 undef $trailInfo;
1073             }
1074             }
1075 1 50       8 unless ($trailInfo) {
1076             # copy over the rest of the file
1077 0         0 while ($raf->Read($data, 65536)) {
1078 0 0       0 Write($outfile, $data) or $err = 1;
1079             }
1080             }
1081             } else {
1082 0         0 $err = 1;
1083             }
1084 1 50       4 $rtnVal = -1 if $err;
1085             } elsif ($err) {
1086 0         0 $et->Warn('File format error');
1087             } else {
1088             # read IRB resources
1089 4         19 ProcessPhotoshop($et, \%dirInfo, $tagTablePtr);
1090             # read layer and mask information section
1091 4         10 $dirInfo{RAF} = $raf;
1092 4         14 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Layers');
1093 4         14 my $oldIndent = $$et{INDENT};
1094 4         14 $$et{INDENT} .= '| ';
1095 4 50 33     20 if (ProcessLayersAndMask($et, \%dirInfo, $tagTablePtr) and
1096             # read compression mode from image data section
1097             $raf->Read($data,2) == 2)
1098             {
1099 4         17 my %dirInfo = (
1100             DataPt => \$data,
1101             DataPos => $raf->Tell() - 2,
1102             );
1103 4         16 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::ImageData');
1104 4         15 $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1105             }
1106 4         18 $$et{INDENT} = $oldIndent;
1107             # process trailers if they exist
1108 4         19 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
1109 4 50       27 $et->ProcessTrailers($trailInfo) if $trailInfo;
1110             }
1111 5         27 return $rtnVal;
1112             }
1113              
1114             1; # end
1115              
1116              
1117             __END__