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
25package Image::ExifTool::Photoshop;
26
27use strict;
28use vars qw($VERSION $AUTOLOAD $iptcDigestInfo);
29use Image::ExifTool qw(:DataAccess :Utils);
30
31$VERSION = '1.65';
32
33sub ProcessPhotoshop($$$);
34sub WritePhotoshop($$$);
35sub ProcessLayers($$$);
36
37# map of where information is stored in PSD image
38my %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
55my %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
71my %unicodeString = (
72    ValueConv => sub {
73        my ($val, $et) = @_;
74        return '<err>' if length($val) < 4;
75        my $len = unpack('N', $val) * 2;
76        return '<err>' 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 '<err>' 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.  When writing, special values of
252            "new" and "old" represent the digests of the IPTC from the edited and
253            original files respectively, and are undefined if the IPTC does not exist in
254            the respective file.  Set this to "new" as an indication that the XMP is
255            synchronized with the IPTC
256        },
257        # also note the 'new' feature requires that the IPTC comes before this tag is written
258        ValueConv => 'unpack("H*", $val)',
259        ValueConvInv => q{
260            if (lc($val) eq 'new' or lc($val) eq 'old') {
261                {
262                    local $SIG{'__WARN__'} = sub { };
263                    return lc($val) if eval { require Digest::MD5 };
264                }
265                warn "Digest::MD5 must be installed\n";
266                return undef;
267            }
268            return pack('H*', $val) if $val =~ /^[0-9a-f]{32}$/i;
269            warn "Value must be 'new', 'old' or 32 hexadecimal digits\n";
270            return undef;
271        }
272    },
273    0x0426 => {
274        Name => 'PrintScaleInfo',
275        SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PrintScaleInfo' },
276    },
277    0x0428 => {
278        Name => 'PixelInfo',
279        SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::PixelInfo' },
280    },
281    0x0429 => { Unknown => 1, Name => 'LayerComps' }, #5
282    0x042a => { Unknown => 1, Name => 'AlternateDuotoneColors' }, #5
283    0x042b => { Unknown => 1, Name => 'AlternateSpotColors' }, #5
284    0x042d => { #7
285        Name => 'LayerSelectionIDs',
286        Description => 'Layer Selection IDs',
287        Unknown => 1,
288        ValueConv => q{
289            my ($n, @a) = unpack("nN*",$val);
290            $#a = $n - 1 if $n > @a;
291            return join(' ', @a);
292        },
293    },
294    0x042e => { Unknown => 1, Name => 'HDRToningInfo' }, #7
295    0x042f => { Unknown => 1, Name => 'PrintInfo' }, #7
296    0x0430 => { Unknown => 1, Name => 'LayerGroupsEnabledID', Format => 'int8u' }, #7
297    0x0431 => { Unknown => 1, Name => 'ColorSamplersResource2' }, #7
298    0x0432 => { Unknown => 1, Name => 'MeasurementScale' }, #7
299    0x0433 => { Unknown => 1, Name => 'TimelineInfo' }, #7
300    0x0434 => { Unknown => 1, Name => 'SheetDisclosure' }, #7
301    0x0435 => { Unknown => 1, Name => 'DisplayInfo' }, #7
302    0x0436 => { Unknown => 1, Name => 'OnionSkins' }, #7
303    0x0438 => { Unknown => 1, Name => 'CountInfo' }, #7
304    0x043a => { Unknown => 1, Name => 'PrintInfo2' }, #7
305    0x043b => { Unknown => 1, Name => 'PrintStyle' }, #7
306    0x043c => { Unknown => 1, Name => 'MacintoshNSPrintInfo' }, #7
307    0x043d => { Unknown => 1, Name => 'WindowsDEVMODE' }, #7
308    0x043e => { Unknown => 1, Name => 'AutoSaveFilePath' }, #7
309    0x043f => { Unknown => 1, Name => 'AutoSaveFormat' }, #7
310    0x0440 => { Unknown => 1, Name => 'PathSelectionState' }, #7
311    # 0x07d0-0x0bb6 Path information
312    0x0bb7 => {
313        Name => 'ClippingPathName',
314        # convert from a Pascal string (ignoring 6 bytes of unknown data after string)
315        ValueConv => q{
316            my $len = ord($val);
317            $val = substr($val, 0, $len+1) if $len < length($val);
318            return Image::ExifTool::Photoshop::ConvertPascalString($self,$val);
319        },
320    },
321    0x0bb8 => { Unknown => 1, Name => 'OriginPathInfo' }, #7
322    # 0x0fa0-0x1387 - plug-in resources (ref 7)
323    0x1b58 => { Unknown => 1, Name => 'ImageReadyVariables' }, #7
324    0x1b59 => { Unknown => 1, Name => 'ImageReadyDataSets' }, #7
325    0x1f40 => { Unknown => 1, Name => 'LightroomWorkflow' }, #7
326    0x2710 => { Unknown => 1, Name => 'PrintFlagsInfo' },
327);
328
329# Photoshop JPEG quality record (ref 2)
330%Image::ExifTool::Photoshop::JPEG_Quality = (
331    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
332    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
333    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
334    FORMAT => 'int16s',
335    GROUPS => { 2 => 'Image' },
336    0 => {
337        Name => 'PhotoshopQuality',
338        Writable => 1,
339        PrintConv => '$val + 4',
340        PrintConvInv => '$val - 4',
341    },
342    1 => {
343        Name => 'PhotoshopFormat',
344        PrintConv => {
345            0x0000 => 'Standard',
346            0x0001 => 'Optimized',
347            0x0101 => 'Progressive',
348        },
349    },
350    2 => {
351        Name => 'ProgressiveScans',
352        PrintConv => {
353            1 => '3 Scans',
354            2 => '4 Scans',
355            3 => '5 Scans',
356        },
357    },
358);
359
360# Photoshop Slices
361%Image::ExifTool::Photoshop::SliceInfo = (
362    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
363    20 => { Name => 'SlicesGroupName', Format => 'var_ustr32' },
364    24 => { Name => 'NumSlices',       Format => 'int32u' },
365);
366
367# Photoshop resolution information #PH
368%Image::ExifTool::Photoshop::Resolution = (
369    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
370    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
371    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
372    FORMAT => 'int16u',
373    FIRST_ENTRY => 0,
374    WRITABLE => 1,
375    GROUPS => { 2 => 'Image' },
376    0 => {
377        Name => 'XResolution',
378        Format => 'int32u',
379        Priority => 0,
380        ValueConv => '$val / 0x10000',
381        ValueConvInv => 'int($val * 0x10000 + 0.5)',
382        PrintConv => 'int($val * 100 + 0.5) / 100',
383        PrintConvInv => '$val',
384    },
385    2 => {
386        Name => 'DisplayedUnitsX',
387        PrintConv => {
388            1 => 'inches',
389            2 => 'cm',
390        },
391    },
392    4 => {
393        Name => 'YResolution',
394        Format => 'int32u',
395        Priority => 0,
396        ValueConv => '$val / 0x10000',
397        ValueConvInv => 'int($val * 0x10000 + 0.5)',
398        PrintConv => 'int($val * 100 + 0.5) / 100',
399        PrintConvInv => '$val',
400    },
401    6 => {
402        Name => 'DisplayedUnitsY',
403        PrintConv => {
404            1 => 'inches',
405            2 => 'cm',
406        },
407    },
408);
409
410# Photoshop version information
411%Image::ExifTool::Photoshop::VersionInfo = (
412    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
413    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
414    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
415    FIRST_ENTRY => 0,
416    GROUPS => { 2 => 'Image' },
417    # (always 1) 0 => { Name => 'PhotoshopVersion', Format => 'int32u' },
418    4 => { Name => 'HasRealMergedData', Format => 'int8u', PrintConv => { 0 => 'No', 1 => 'Yes' } },
419    5 => { Name => 'WriterName', Format => 'var_ustr32' },
420    9 => { Name => 'ReaderName', Format => 'var_ustr32' },
421    # (always 1) 13 => { Name => 'FileVersion', Format => 'int32u' },
422);
423
424# Print Scale
425%Image::ExifTool::Photoshop::PrintScaleInfo = (
426    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
427    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
428    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
429    FIRST_ENTRY => 0,
430    GROUPS => { 2 => 'Image' },
431    0 => {
432        Name => 'PrintStyle',
433        Format => 'int16u',
434        PrintConv => {
435            0 => 'Centered',
436            1 => 'Size to Fit',
437            2 => 'User Defined',
438        },
439    },
440    2  => { Name => 'PrintPosition', Format => 'float[2]' },
441    10 => { Name => 'PrintScale',    Format => 'float' },
442);
443
444# Pixel Aspect Ratio
445%Image::ExifTool::Photoshop::PixelInfo = (
446    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
447    WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
448    CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
449    FIRST_ENTRY => 0,
450    GROUPS => { 2 => 'Image' },
451    # 0 - version
452    4 => { Name => 'PixelAspectRatio', Format => 'double' },
453);
454
455# Photoshop PSD file header
456%Image::ExifTool::Photoshop::Header = (
457    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
458    FORMAT => 'int16u',
459    GROUPS => { 2 => 'Image' },
460    NOTES => 'This information is found in the PSD file header.',
461    6 => 'NumChannels',
462    7 => { Name => 'ImageHeight', Format => 'int32u' },
463    9 => { Name => 'ImageWidth', Format => 'int32u' },
464    11 => 'BitDepth',
465    12 => {
466        Name => 'ColorMode',
467        PrintConvColumns => 2,
468        PrintConv => {
469            0 => 'Bitmap',
470            1 => 'Grayscale',
471            2 => 'Indexed',
472            3 => 'RGB',
473            4 => 'CMYK',
474            7 => 'Multichannel',
475            8 => 'Duotone',
476            9 => 'Lab',
477        },
478    },
479);
480
481# Layer information
482%Image::ExifTool::Photoshop::Layers = (
483    PROCESS_PROC => \&ProcessLayers,
484    GROUPS => { 2 => 'Image' },
485    NOTES => 'Tags extracted from Photoshop layer information.',
486    # tags extracted from layer information
487    # (tag ID's are for convenience only)
488    _xcnt => { Name => 'LayerCount', Format => 'int16u' },
489    _xrct => {
490        Name => 'LayerRectangles',
491        Format => 'int32u',
492        Count => 4,
493        List => 1,
494        Notes => 'top left bottom right',
495    },
496    _xnam => { Name => 'LayerNames',
497        Format => 'string',
498        List => 1,
499        ValueConv => q{
500            my $charset = $self->Options('CharsetPhotoshop') || 'Latin';
501            return $self->Decode($val, $charset);
502        },
503    },
504    _xbnd => {
505        Name => 'LayerBlendModes',
506        Format => 'undef',
507        List => 1,
508        RawConv => 'GetByteOrder() eq "II" ? pack "N*", unpack "V*", $val : $val',
509        PrintConv => {
510            pass => 'Pass Through',
511            norm => 'Normal',
512            diss => 'Dissolve',
513            dark => 'Darken',
514           'mul '=> 'Multiply',
515            idiv => 'Color Burn',
516            lbrn => 'Linear Burn',
517            dkCl => 'Darker Color',
518            lite => 'Lighten',
519            scrn => 'Screen',
520           'div '=> 'Color Dodge',
521            lddg => 'Linear Dodge',
522            lgCl => 'Lighter Color',
523            over => 'Overlay',
524            sLit => 'Soft Light',
525            hLit => 'Hard Light',
526            vLit => 'Vivid Light',
527            lLit => 'Linear Light',
528            pLit => 'Pin Light',
529            hMix => 'Hard Mix',
530            diff => 'Difference',
531            smud => 'Exclusion',
532            fsub => 'Subtract',
533            fdiv => 'Divide',
534           'hue '=> 'Hue',
535           'sat '=> 'Saturation',
536            colr => 'Color',
537           'lum '=> 'Luminosity',
538        },
539    },
540    _xopc  => {
541        Name => 'LayerOpacities',
542        Format => 'int8u',
543        List => 1,
544        ValueConv => '100 * $val / 255',
545        PrintConv => 'sprintf("%d%%",$val)',
546    },
547    # tags extracted from additional layer information (tag ID's are real)
548    # - must be able to accommodate a blank entry to preserve the list ordering
549    luni => {
550        Name => 'LayerUnicodeNames',
551        List => 1,
552        RawConv => q{
553            return '' if length($val) < 4;
554            my $len = Get32u(\$val, 0);
555            return $self->Decode(substr($val, 4, $len * 2), 'UCS2');
556        },
557    },
558    lyid => {
559        Name => 'LayerIDs',
560        Description => 'Layer IDs',
561        Format => 'int32u',
562        List => 1,
563        Unknown => 1,
564    },
565    shmd => { # layer metadata (undocumented structure)
566        # (for now, only extract layerTime.  May also contain "layerXMP" --
567        #  it would be nice to decode this but I need a sample)
568        Name => 'LayerModifyDates',
569        Groups => { 2 => 'Time' },
570        List => 1,
571        RawConv => q{
572            return '' unless $val =~ /layerTime(doub|buod)(.{8})/s;
573            my $tmp = $2;
574            return GetDouble(\$tmp, 0);
575        },
576        ValueConv => 'length $val ? ConvertUnixTime($val,1) : ""',
577        PrintConv => 'length $val ? $self->ConvertDateTime($val) : ""',
578    },
579);
580
581# tags extracted from ImageSourceData found in TIFF images (ref PH)
582%Image::ExifTool::Photoshop::DocumentData = (
583    PROCESS_PROC => \&ProcessDocumentData,
584    GROUPS => { 2 => 'Image' },
585    Layr => {
586        Name => 'Layers',
587        SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' },
588    },
589    Lr16 => { # (NC)
590        Name => 'Layers',
591        SubDirectory => { TagTable => 'Image::ExifTool::Photoshop::Layers' },
592    },
593);
594
595# image data
596%Image::ExifTool::Photoshop::ImageData = (
597    PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
598    GROUPS => { 2 => 'Image' },
599    0 => {
600        Name => 'Compression',
601        Format => 'int16u',
602        PrintConv => {
603            0 => 'Uncompressed',
604            1 => 'RLE',
605            2 => 'ZIP without prediction',
606            3 => 'ZIP with prediction',
607        },
608    },
609);
610
611# tags for unknown resource types
612%Image::ExifTool::Photoshop::Unknown = (
613    GROUPS => { 2 => 'Unknown' },
614);
615
616# define reference to IPTCDigest tagInfo hash for convenience
617$iptcDigestInfo = $Image::ExifTool::Photoshop::Main{0x0425};
618
619
620#------------------------------------------------------------------------------
621# AutoLoad our writer routines when necessary
622#
623sub AUTOLOAD
624{
625    return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
626}
627
628#------------------------------------------------------------------------------
629# Convert pascal string(s) to something we can use
630# Inputs: 1) Pascal string data
631# Returns: Strings, concatenated with ', '
632sub ConvertPascalString($$)
633{
634    my ($et, $inStr) = @_;
635    my $outStr = '';
636    my $len = length($inStr);
637    my $i=0;
638    while ($i < $len) {
639        my $n = ord(substr($inStr, $i, 1));
640        last if $i + $n >= $len;
641        $i and $outStr .= ', ';
642        $outStr .= substr($inStr, $i+1, $n);
643        $i += $n + 1;
644    }
645    my $charset = $et->Options('CharsetPhotoshop') || 'Latin';
646    return $et->Decode($outStr, $charset);
647}
648
649#------------------------------------------------------------------------------
650# Process Photoshop layers and mask information section of PSD/PSB file
651# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
652# Returns: 1 on success (and seeks to the end of this section)
653sub ProcessLayersAndMask($$$)
654{
655    local $_;
656    my ($et, $dirInfo, $tagTablePtr) = @_;
657    my $raf = $$dirInfo{RAF};
658    my $fileType = $$et{VALUE}{FileType};
659    my $data;
660
661    return 0 unless $fileType eq 'PSD' or $fileType eq 'PSB';   # (no layer section in CS1 files)
662
663    # (some words are 4 bytes in PSD files and 8 bytes in PSB)
664    my ($psb, $psiz) = $fileType eq 'PSB' ? (1, 8) : (undef, 4);
665
666    # read the layer information header
667    my $n = $psiz * 2 + 2;
668    $raf->Read($data, $n) == $n or return 0;
669    my $tot = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer and mask info
670    return 1 if $tot == 0;
671    my $end = $raf->Tell() - $psiz - 2 + $tot;
672    $data = substr $data, $psiz;
673    my $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0); # length of layer info section
674    my $num = Get16s(\$data, $psiz);
675    # check for Lr16 block if layers length is 0 (ref https://forums.adobe.com/thread/1540914)
676    if ($len == 0 and $num == 0) {
677        $raf->Read($data,10) == 10 or return 0;
678        if ($data =~ /^..8BIMLr16/s) {
679            $raf->Read($data, $psiz+2) == $psiz+2 or return 0;
680            $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0);
681        } elsif ($data =~ /^..8BIMMt16/s) { # (have seen Mt16 before Lr16, ref PH)
682            $raf->Read($data, $psiz) == $psiz or return 0;
683            $raf->Read($data, 8) == 8 or return 0;
684            if ($data eq '8BIMLr16') {
685                $raf->Read($data, $psiz+2) == $psiz+2 or return 0;
686                $len = $psb ? Get64u(\$data, 0) : Get32u(\$data, 0);
687            } else {
688                $raf->Seek(-18-$psiz, 1) or return 0;
689            }
690        } else {
691            $raf->Seek(-10, 1) or return 0;
692        }
693    }
694    $len += 2;  # include layer count with layer info section
695    $raf->Seek(-2, 1) or return 0;
696    my %dinfo = (
697        RAF => $raf,
698        DirLen => $len,
699    );
700    $$et{IsPSB} = $psb; # set PSB flag
701    ProcessLayers($et, \%dinfo, $tagTablePtr);
702
703    # seek to the end of this section and return success flag
704    return $raf->Seek($end, 0) ? 1 : 0;
705}
706
707#------------------------------------------------------------------------------
708# Process Photoshop layers (beginning with layer count)
709# Inputs: 0) ExifTool ref, 1) DirInfo ref, 2) tag table ref
710# Returns: 1 on success
711# Notes: Uses ExifTool IsPSB member to determine whether file is PSB format
712sub ProcessLayers($$$)
713{
714    local $_;
715    my ($et, $dirInfo, $tagTablePtr) = @_;
716    my ($i, $n, %count, $buff, $buf2);
717    my $raf = $$dirInfo{RAF};
718    my $dirLen = $$dirInfo{DirLen};
719    my $verbose = $$et{OPTIONS}{Verbose};
720    my %dinfo = ( DataPt => \$buff, Base => $raf->Tell() );
721    my $pos = 0;
722    return 0 if $dirLen < 2;
723    $raf->Read($buff, 2) == 2 or return 0;
724    my $num = Get16s(\$buff, 0);
725    $num = -$num if $num < 0;       # (first channel is transparency data if negative)
726    $et->VerboseDir('Layers', $num, $dirLen);
727    $et->HandleTag($tagTablePtr, '_xcnt', $num, Start => $pos, Size => 2, %dinfo); # LayerCount
728    my $oldIndent = $$et{INDENT};
729    $$et{INDENT} .= '| ';
730
731    $pos += 2;
732    my $psb = $$et{IsPSB};  # is PSB format?
733    my $psiz = $psb ? 8 : 4;
734    for ($i=0; $i<$num; ++$i) {
735        $et->VPrint(0, $oldIndent.'+ [Layer '.($i+1)." of $num]\n");
736        last if $pos + 18 > $dirLen;
737        $raf->Read($buff, 18) == 18 or last;
738        $dinfo{DataPos} = $pos;
739        # save the layer rectangle
740        $et->HandleTag($tagTablePtr, '_xrct', undef, Start => 0, Size => 16, %dinfo);
741        my $numChannels = Get16u(\$buff, 16);
742        $n = (2 + $psiz) * $numChannels;    # size of channel information
743        $raf->Seek($n, 1) or last;
744        $pos += 18 + $n;
745        last if $pos + 20 > $dirLen;
746        $raf->Read($buff, 20) == 20 or last;
747        $dinfo{DataPos} = $pos;
748        my $sig = substr($buff, 0, 4);
749        $sig =~ /^(8BIM|MIB8)$/ or last;    # verify signature
750        $et->HandleTag($tagTablePtr, '_xbnd', undef, Start => 4, Size => 4, %dinfo);
751        $et->HandleTag($tagTablePtr, '_xopc', undef, Start => 8, Size => 1, %dinfo);
752        my $nxt = $pos + 16 + Get32u(\$buff, 12);
753        $n = Get32u(\$buff, 16);        # get size of layer mask data
754        $pos += 20 + $n;                # skip layer mask data
755        last if $pos + 4 > $dirLen;
756        $raf->Seek($n, 1) and $raf->Read($buff, 4) == 4 or last;
757        $n = Get32u(\$buff, 0);         # get size of layer blending ranges
758        $pos += 4 + $n;                 # skip layer blending ranges data
759        last if $pos + 1 > $dirLen;
760        $raf->Seek($n, 1) and $raf->Read($buff, 1) == 1 or last;
761        $n = Get8u(\$buff, 0);          # get length of layer name
762        last if $pos + 1 + $n > $dirLen;
763        $raf->Read($buff, $n) == $n or last;
764        $dinfo{DataPos} = $pos + 1;
765        $et->HandleTag($tagTablePtr, '_xnam', undef, Start => 0, Size => $n, %dinfo);
766        my $frag = ($n + 1) & 0x3;
767        $raf->Seek(4 - $frag, 1) or last if $frag;
768        $n = ($n + 4) & 0xfffffffc;     # +1 for length byte then pad to multiple of 4 bytes
769        $pos += $n;
770        # process additional layer info
771        while ($pos + 12 <= $nxt) {
772            $raf->Read($buff, 12) == 12 or last;
773            my $dat = substr($buff, 0, 8);
774            $dat = pack 'N*', unpack 'V*', $dat if GetByteOrder() eq 'II';
775            my $sig = substr($dat, 0, 4);
776            last unless $sig eq '8BIM' or $sig eq '8B64';   # verify signature
777            my $tag = substr($dat, 4, 4);
778            # (some structures have an 8-byte size word [augh!]
779            # --> it would be great if '8B64' indicated a 64-bit version, and this may well
780            # be the case, but it is not mentioned in the Photoshop file format specification)
781            if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) {
782                last if $pos + 16 > $nxt;
783                $raf->Read($buf2, 4) == 4 or last;
784                $buff .= $buf2;
785                $n = Get64u(\$buff, 8);
786                $pos += 4;
787            } else {
788                $n = Get32u(\$buff, 8);
789            }
790            $pos += 12;
791            last if $pos + $n > $nxt;
792            $frag = $n & 0x3;
793            if ($$tagTablePtr{$tag} or $verbose) {
794                # pad with empty entries if necessary to keep the same index for each item in the layer
795                $count{$tag} = 0 unless defined $count{$tag};
796                $raf->Read($buff, $n) == $n or last;
797                $dinfo{DataPos} = $pos;
798                while ($count{$tag} < $i) {
799                    $et->HandleTag($tagTablePtr, $tag, '');
800                    ++$count{$tag};
801                }
802                $et->HandleTag($tagTablePtr, $tag, undef, Start => 0, Size => $n, %dinfo);
803                ++$count{$tag};
804                if ($frag) {
805                    $raf->Seek(4 - $frag, 1) or last;
806                    $n += 4 - $frag;    # pad to multiple of 4 bytes (PH NC)
807                }
808            } else {
809                $n += 4 - $frag if $frag;
810                $raf->Seek($n, 1) or last;
811            }
812            $pos += $n; # step to start of next structure
813        }
814        $pos = $nxt;
815    }
816    $$et{INDENT} = $oldIndent;
817    return 1;
818}
819
820#------------------------------------------------------------------------------
821# Process Photoshop ImageSourceData
822# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref
823# Returns: 1 on success
824sub ProcessDocumentData($$$)
825{
826    my ($et, $dirInfo, $tagTablePtr) = @_;
827    my $verbose = $$et{OPTIONS}{Verbose};
828    my $raf = $$dirInfo{RAF};
829    my $dirLen = $$dirInfo{DirLen};
830    my $pos = 36;   # length of header
831    my ($buff, $n, $err);
832
833    $et->VerboseDir('Photoshop Document Data', undef, $dirLen);
834    unless ($raf) {
835        my $dataPt = $$dirInfo{DataPt};
836        my $start = $$dirInfo{DirStart} || 0;
837        $raf = new File::RandomAccess($dataPt);
838        $raf->Seek($start, 0) if $start;
839        $dirLen = length $$dataPt - $start unless defined $dirLen;
840        $et->VerboseDump($dataPt, Start => $start, Len => $dirLen, Base => $$dirInfo{Base});
841    }
842    unless ($raf->Read($buff, $pos) == $pos and
843            $buff =~ /^Adobe Photoshop Document Data (Block|V0002)\0/)
844    {
845        $et->Warn('Invalid Photoshop Document Data');
846        return 0;
847    }
848    my $psb = ($1 eq 'V0002');
849    my %dinfo = ( DataPt => \$buff );
850    $$et{IsPSB} = $psb; # set PSB flag (needed when handling Layers directory)
851    while ($pos + 12 <= $dirLen) {
852        $raf->Read($buff, 8) == 8 or $err = 'Error reading document data', last;
853        # set byte order according to byte order of first signature
854        SetByteOrder($buff =~ /^(8BIM|8B64)/ ? 'MM' : 'II') if $pos == 36;
855        $buff = pack 'N*', unpack 'V*', $buff if GetByteOrder() eq 'II';
856        my $sig = substr($buff, 0, 4);
857        $sig eq '8BIM' or $sig eq '8B64' or $err = 'Bad photoshop resource', last; # verify signature
858        my $tag = substr($buff, 4, 4);
859        if ($psb and $tag =~ /^(LMsk|Lr16|Lr32|Layr|Mt16|Mt32|Mtrn|Alph|FMsk|lnk2|FEid|FXid|PxSD)$/) {
860            $pos + 16 > $dirLen and $err = 'Short PSB resource', last;
861            $raf->Read($buff, 8) == 8 or $err = 'Error reading PSB resource', last;
862            $n = Get64u(\$buff, 0);
863            $pos += 4;
864        } else {
865            $raf->Read($buff, 4) == 4 or $err = 'Error reading PSD resource', last;
866            $n = Get32u(\$buff, 0);
867        }
868        $pos += 12;
869        $pos + $n > $dirLen and $err = 'Truncated photoshop resource', last;
870        my $pad = (4 - ($n & 3)) & 3;   # number of padding bytes
871        my $tagInfo = $$tagTablePtr{$tag};
872        if ($tagInfo or $verbose) {
873            if ($tagInfo and $$tagInfo{SubDirectory}) {
874                my $fpos = $raf->Tell() + $n + $pad;
875                my $subTable = GetTagTable($$tagInfo{SubDirectory}{TagTable});
876                $et->ProcessDirectory({ RAF => $raf, DirLen => $n }, $subTable);
877                $raf->Seek($fpos, 0) or $err = 'Seek error', last;
878            } else {
879                $dinfo{DataPos} = $raf->Tell();
880                $dinfo{Start} = 0;
881                $dinfo{Size} = $n;
882                $raf->Read($buff, $n) == $n or $err = 'Error reading photoshop resource', last;
883                $et->HandleTag($tagTablePtr, $tag, undef, %dinfo);
884                $raf->Seek($pad, 1) or $err = 'Seek error', last;
885            }
886        } else {
887            $raf->Seek($n + $pad, 1) or $err = 'Seek error', last;
888        }
889        $pos += $n + $pad;              # step to start of next structure
890    }
891    $err and $et->Warn($err);
892    return 1;
893}
894
895#------------------------------------------------------------------------------
896# Process Photoshop APP13 record
897# Inputs: 0) ExifTool object reference, 1) Reference to directory information
898#         2) Tag table reference
899# Returns: 1 on success
900sub ProcessPhotoshop($$$)
901{
902    my ($et, $dirInfo, $tagTablePtr) = @_;
903    my $dataPt = $$dirInfo{DataPt};
904    my $pos = $$dirInfo{DirStart};
905    my $dirEnd = $pos + $$dirInfo{DirLen};
906    my $verbose = $et->Options('Verbose');
907    my $success = 0;
908
909    # ignore non-standard XMP while in strict MWG compatibility mode
910    if (($Image::ExifTool::MWG::strict or $et->Options('Validate')) and
911        $$et{FILE_TYPE} =~ /^(JPEG|TIFF|PSD)$/)
912    {
913        my $path = $et->MetadataPath();
914        unless ($path =~ /^(JPEG-APP13-Photoshop|TIFF-IFD0-Photoshop|PSD)$/) {
915            if ($Image::ExifTool::MWG::strict) {
916                $et->Warn("Ignored non-standard Photoshop at $path");
917                return 1;
918            } else {
919                $et->Warn("Non-standard Photoshop at $path", 1);
920            }
921        }
922    }
923    if ($$et{FILE_TYPE} eq 'JPEG' and $$dirInfo{Parent} ne 'APP13') {
924        $$et{LOW_PRIORITY_DIR}{'*'} = 1;    # lower priority of all these tags
925    }
926    SetByteOrder('MM');     # Photoshop is always big-endian
927    $verbose and $et->VerboseDir('Photoshop', 0, $$dirInfo{DirLen});
928
929    # scan through resource blocks:
930    # Format: 0) Type, 4 bytes - '8BIM' (or the rare 'PHUT', 'DCSR', 'AgHg' or 'MeSa')
931    #         1) TagID,2 bytes
932    #         2) Name, pascal string padded to even no. bytes
933    #         3) Size, 4 bytes - N
934    #         4) Data, N bytes
935    while ($pos + 8 < $dirEnd) {
936        my $type = substr($$dataPt, $pos, 4);
937        my ($ttPtr, $extra, $val, $name);
938        if ($type eq '8BIM') {
939            $ttPtr = $tagTablePtr;
940        } elsif ($type =~ /^(PHUT|DCSR|AgHg|MeSa)$/) { # (PHUT~ImageReady, MeSa~PhotoDeluxe)
941            $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Unknown');
942        } else {
943            $type =~ s/([^\w])/sprintf("\\x%.2x",ord($1))/ge;
944            $et->Warn(qq{Bad Photoshop IRB resource "$type"});
945            last;
946        }
947        my $tag = Get16u($dataPt, $pos + 4);
948        $pos += 6;  # point to start of name
949        my $nameLen = Get8u($dataPt, $pos);
950        my $namePos = ++$pos;
951        # skip resource block name (pascal string, padded to an even # of bytes)
952        $pos += $nameLen;
953        ++$pos unless $nameLen & 0x01;
954        if ($pos + 4 > $dirEnd) {
955            $et->Warn("Bad Photoshop resource block");
956            last;
957        }
958        my $size = Get32u($dataPt, $pos);
959        $pos += 4;
960        if ($size + $pos > $dirEnd) {
961            $et->Warn("Bad Photoshop resource data size $size");
962            last;
963        }
964        $success = 1;
965        if ($nameLen) {
966            $name = substr($$dataPt, $namePos, $nameLen);
967            $extra = qq{, Name="$name"};
968        } else {
969            $name = '';
970        }
971        my $tagInfo = $et->GetTagInfo($ttPtr, $tag);
972        # append resource name to value if requested (braced by "/#...#/")
973        if ($tagInfo and defined $$tagInfo{SetResourceName} and
974            $$tagInfo{SetResourceName} eq '1' and $name !~ m{/#})
975        {
976            $val = substr($$dataPt, $pos, $size) . '/#' . $name . '#/';
977        }
978        $et->HandleTag($ttPtr, $tag, $val,
979            TagInfo => $tagInfo,
980            Extra   => $extra,
981            DataPt  => $dataPt,
982            DataPos => $$dirInfo{DataPos},
983            Size    => $size,
984            Start   => $pos,
985            Base    => $$dirInfo{Base},
986            Parent  => $$dirInfo{DirName},
987        );
988        $size += 1 if $size & 0x01; # size is padded to an even # bytes
989        $pos += $size;
990    }
991    delete $$et{LOW_PRIORITY_DIR}{'*'};
992    return $success;
993}
994
995#------------------------------------------------------------------------------
996# extract information from Photoshop PSD file
997# Inputs: 0) ExifTool object reference, 1) dirInfo reference
998# Returns: 1 if this was a valid PSD file, -1 on write error
999sub ProcessPSD($$)
1000{
1001    my ($et, $dirInfo) = @_;
1002    my $raf = $$dirInfo{RAF};
1003    my $outfile = $$dirInfo{OutFile};
1004    my ($data, $err, $tagTablePtr);
1005
1006    $raf->Read($data, 30) == 30 or return 0;
1007    $data =~ /^8BPS\0([\x01\x02])/ or return 0;
1008    SetByteOrder('MM');
1009    $et->SetFileType($1 eq "\x01" ? 'PSD' : 'PSB'); # set the FileType tag
1010    my %dirInfo = (
1011        DataPt => \$data,
1012        DirStart => 0,
1013        DirName => 'Photoshop',
1014    );
1015    my $len = Get32u(\$data, 26);
1016    if ($outfile) {
1017        Write($outfile, $data) or $err = 1;
1018        $raf->Read($data, $len) == $len or return -1;
1019        Write($outfile, $data) or $err = 1; # write color mode data
1020        # initialize map of where things are written
1021        $et->InitWriteDirs(\%psdMap);
1022    } else {
1023        # process the header
1024        $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Header');
1025        $dirInfo{DirLen} = 30;
1026        $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1027        $raf->Seek($len, 1) or $err = 1;    # skip over color mode data
1028    }
1029    # read image resource section
1030    $raf->Read($data, 4) == 4 or $err = 1;
1031    $len = Get32u(\$data, 0);
1032    $raf->Read($data, $len) == $len or $err = 1;
1033    $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main');
1034    $dirInfo{DirLen} = $len;
1035    my $rtnVal = 1;
1036    if ($outfile) {
1037        # rewrite IRB resources
1038        $data = WritePhotoshop($et, \%dirInfo, $tagTablePtr);
1039        if ($data) {
1040            $len = Set32u(length $data);
1041            Write($outfile, $len, $data) or $err = 1;
1042            # look for trailer and edit if necessary
1043            my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
1044            if ($trailInfo) {
1045                my $tbuf = '';
1046                $$trailInfo{OutFile} = \$tbuf;  # rewrite trailer(s)
1047                # rewrite all trailers to buffer
1048                if ($et->ProcessTrailers($trailInfo)) {
1049                    my $copyBytes = $$trailInfo{DataPos} - $raf->Tell();
1050                    if ($copyBytes >= 0) {
1051                        # copy remaining PSD file up to start of trailer
1052                        while ($copyBytes) {
1053                            my $n = ($copyBytes > 65536) ? 65536 : $copyBytes;
1054                            $raf->Read($data, $n) == $n or $err = 1;
1055                            Write($outfile, $data) or $err = 1;
1056                            $copyBytes -= $n;
1057                        }
1058                        # write the trailer (or not)
1059                        $et->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1;
1060                    } else {
1061                        $et->Warn('Overlapping trailer');
1062                        undef $trailInfo;
1063                    }
1064                } else {
1065                    undef $trailInfo;
1066                }
1067            }
1068            unless ($trailInfo) {
1069                # copy over the rest of the file
1070                while ($raf->Read($data, 65536)) {
1071                    Write($outfile, $data) or $err = 1;
1072                }
1073            }
1074        } else {
1075            $err = 1;
1076        }
1077        $rtnVal = -1 if $err;
1078    } elsif ($err) {
1079        $et->Warn('File format error');
1080    } else {
1081        # read IRB resources
1082        ProcessPhotoshop($et, \%dirInfo, $tagTablePtr);
1083        # read layer and mask information section
1084        $dirInfo{RAF} = $raf;
1085        $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Layers');
1086        my $oldIndent = $$et{INDENT};
1087        $$et{INDENT} .= '| ';
1088        if (ProcessLayersAndMask($et, \%dirInfo, $tagTablePtr) and
1089            # read compression mode from image data section
1090            $raf->Read($data,2) == 2)
1091        {
1092            my %dirInfo = (
1093                DataPt  => \$data,
1094                DataPos => $raf->Tell() - 2,
1095            );
1096            $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::ImageData');
1097            $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
1098        }
1099        $$et{INDENT} = $oldIndent;
1100        # process trailers if they exist
1101        my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
1102        $et->ProcessTrailers($trailInfo) if $trailInfo;
1103    }
1104    return $rtnVal;
1105}
1106
11071; # end
1108
1109
1110__END__
1111
1112=head1 NAME
1113
1114Image::ExifTool::Photoshop - Read/write Photoshop IRB meta information
1115
1116=head1 SYNOPSIS
1117
1118This module is loaded automatically by Image::ExifTool when required.
1119
1120=head1 DESCRIPTION
1121
1122Photoshop writes its own format of meta information called a Photoshop IRB
1123resource which is located in the APP13 record of JPEG files.  This module
1124contains the definitions to read this information.
1125
1126=head1 NOTES
1127
1128Photoshop IRB blocks may have an associated resource name.  These names are
1129usually just an empty string, but if not empty they are displayed in the
1130verbose level 2 (or greater) output.  A special C<SetResourceName> flag may
1131be set to '1' in the tag information hash to cause the resource name to be
1132appended to the value when extracted.  If this is done, the returned value
1133has the form "VALUE/#NAME#/".  When writing, the writer routine looks for
1134this syntax (if C<SetResourceName> is defined), and and uses the embedded
1135name to set the name of the new resource.  This allows the resource names to
1136be preserved when copying Photoshop information via user-defined tags.
1137
1138=head1 AUTHOR
1139
1140Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com)
1141
1142This library is free software; you can redistribute it and/or modify it
1143under the same terms as Perl itself.
1144
1145=head1 REFERENCES
1146
1147=over 4
1148
1149=item L<http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf>
1150
1151=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html>
1152
1153=item L<http://www.fileformat.info/format/psd/egff.htm>
1154
1155=item L<http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf>
1156
1157=item L<http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/>
1158
1159=back
1160
1161=head1 SEE ALSO
1162
1163L<Image::ExifTool::TagNames/Photoshop Tags>,
1164L<Image::ExifTool(3pm)|Image::ExifTool>,
1165L<Image::MetaData::JPEG(3pm)|Image::MetaData::JPEG>
1166
1167=cut
1168