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