source: gs2-extensions/parallel-building/trunk/src/perllib/cpan/Image/ExifTool/Photoshop.pm@ 24626

Last change on this file since 24626 was 24626, checked in by jmt12, 13 years ago

An (almost) complete copy of the perllib directory from a (circa SEP2011) head checkout from Greenstone 2 trunk - in order to try and make merging in this extension a little easier later on (as there have been some major changes to buildcol.pl commited in the main trunk but not in the x64 branch)

File size: 20.8 KB
Line 
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#------------------------------------------------------------------------------
23
24package Image::ExifTool::Photoshop;
25
26use strict;
27use vars qw($VERSION $AUTOLOAD $iptcDigestInfo);
28use Image::ExifTool qw(:DataAccess :Utils);
29
30$VERSION = '1.41';
31
32sub ProcessPhotoshop($$$);
33sub WritePhotoshop($$$);
34
35# map of where information is stored in PSD image
36my %psdMap = (
37 IPTC => 'Photoshop',
38 XMP => 'Photoshop',
39 EXIFInfo => 'Photoshop',
40 IFD0 => 'EXIFInfo',
41 IFD1 => 'IFD0',
42 ICC_Profile => 'Photoshop',
43 ExifIFD => 'IFD0',
44 GPS => 'IFD0',
45 SubIFD => 'IFD0',
46 GlobParamIFD => 'IFD0',
47 PrintIM => 'IFD0',
48 InteropIFD => 'ExifIFD',
49 MakerNotes => 'ExifIFD',
50);
51
52# Photoshop APP13 tag table
53# (set Unknown flag for information we don't want to display normally)
54%Image::ExifTool::Photoshop::Main = (
55 GROUPS => { 2 => 'Image' },
56 PROCESS_PROC => \&ProcessPhotoshop,
57 WRITE_PROC => \&WritePhotoshop,
58 0x03e8 => { Unknown => 1, Name => 'Photoshop2Info' },
59 0x03e9 => { Unknown => 1, Name => 'MacintoshPrintInfo' },
60 0x03ea => { Unknown => 1, Name => 'XMLData', Binary => 1 }, #PH
61 0x03eb => { Unknown => 1, Name => 'Photoshop2ColorTable' },
62 0x03ed => {
63 Name => 'ResolutionInfo',
64 SubDirectory => {
65 TagTable => 'Image::ExifTool::Photoshop::Resolution',
66 },
67 },
68 0x03ee => {
69 Name => 'AlphaChannelsNames',
70 ValueConv => 'Image::ExifTool::Photoshop::ConvertPascalString($self,$val)',
71 },
72 0x03ef => { Unknown => 1, Name => 'DisplayInfo' },
73 0x03f0 => { Unknown => 1, Name => 'PStringCaption' },
74 0x03f1 => { Unknown => 1, Name => 'BorderInformation' },
75 0x03f2 => { Unknown => 1, Name => 'BackgroundColor' },
76 0x03f3 => { Unknown => 1, Name => 'PrintFlags' },
77 0x03f4 => { Unknown => 1, Name => 'BW_HalftoningInfo' },
78 0x03f5 => { Unknown => 1, Name => 'ColorHalftoningInfo' },
79 0x03f6 => { Unknown => 1, Name => 'DuotoneHalftoningInfo' },
80 0x03f7 => { Unknown => 1, Name => 'BW_TransferFunc' },
81 0x03f8 => { Unknown => 1, Name => 'ColorTransferFuncs' },
82 0x03f9 => { Unknown => 1, Name => 'DuotoneTransferFuncs' },
83 0x03fa => { Unknown => 1, Name => 'DuotoneImageInfo' },
84 0x03fb => { Unknown => 1, Name => 'EffectiveBW' },
85 0x03fc => { Unknown => 1, Name => 'ObsoletePhotoshopTag1' },
86 0x03fd => { Unknown => 1, Name => 'EPSOptions' },
87 0x03fe => { Unknown => 1, Name => 'QuickMaskInfo' },
88 0x03ff => { Unknown => 1, Name => 'ObsoletePhotoshopTag2' },
89 0x0400 => { Unknown => 1, Name => 'LayerStateInfo' },
90 0x0401 => { Unknown => 1, Name => 'WorkingPath' },
91 0x0402 => { Unknown => 1, Name => 'LayersGroupInfo' },
92 0x0403 => { Unknown => 1, Name => 'ObsoletePhotoshopTag3' },
93 0x0404 => {
94 Name => 'IPTCData',
95 SubDirectory => {
96 DirName => 'IPTC',
97 TagTable => 'Image::ExifTool::IPTC::Main',
98 },
99 },
100 0x0405 => { Unknown => 1, Name => 'RawImageMode' },
101 0x0406 => { #2
102 Name => 'JPEG_Quality',
103 SubDirectory => {
104 TagTable => 'Image::ExifTool::Photoshop::JPEG_Quality',
105 },
106 },
107 0x0408 => { Unknown => 1, Name => 'GridGuidesInfo' },
108 0x0409 => {
109 Name => 'PhotoshopBGRThumbnail',
110 Notes => 'this is a JPEG image, but in BGR format instead of RGB',
111 RawConv => 'my $img=substr($val,0x1c);$self->ValidateImage(\$img,$tag)',
112 },
113 0x040a => {
114 Name => 'CopyrightFlag',
115 Writable => 'int8u',
116 Groups => { 2 => 'Author' },
117 ValueConv => 'join(" ",unpack("C*", $val))',
118 ValueConvInv => 'pack("C*",split(" ",$val))',
119 PrintConv => { #3
120 0 => 'False',
121 1 => 'True',
122 },
123 },
124 0x040b => {
125 Name => 'URL',
126 Writable => 'string',
127 Groups => { 2 => 'Author' },
128 },
129 0x040c => {
130 Name => 'PhotoshopThumbnail',
131 RawConv => 'my $img=substr($val,0x1c);$self->ValidateImage(\$img,$tag)',
132 },
133 0x040d => {
134 Name => 'GlobalAngle',
135 Writable => 'int32u',
136 ValueConv => 'unpack("N",$val)',
137 ValueConvInv => 'pack("N",$val)',
138 },
139 0x040e => { Unknown => 1, Name => 'ColorSamplersResource' },
140 0x040f => {
141 Name => 'ICC_Profile',
142 SubDirectory => {
143 TagTable => 'Image::ExifTool::ICC_Profile::Main',
144 },
145 },
146 0x0410 => { Unknown => 1, Name => 'Watermark' },
147 0x0411 => { Unknown => 1, Name => 'ICC_Untagged' },
148 0x0412 => { Unknown => 1, Name => 'EffectsVisible' },
149 0x0413 => { Unknown => 1, Name => 'SpotHalftone' },
150 0x0414 => { Unknown => 1, Name => 'IDsBaseValue', Description => 'IDs Base Value' },
151 0x0415 => { Unknown => 1, Name => 'UnicodeAlphaNames' },
152 0x0416 => { Unknown => 1, Name => 'IndexedColourTableCount' },
153 0x0417 => { Unknown => 1, Name => 'TransparentIndex' },
154 0x0419 => {
155 Name => 'GlobalAltitude',
156 Writable => 'int32u',
157 ValueConv => 'unpack("N",$val)',
158 ValueConvInv => 'pack("N",$val)',
159 },
160 0x041a => { Unknown => 1, Name => 'Slices' },
161 0x041b => { Unknown => 1, Name => 'WorkflowURL' },
162 0x041c => { Unknown => 1, Name => 'JumpToXPEP' },
163 0x041d => { Unknown => 1, Name => 'AlphaIdentifiers' },
164 0x041e => { Unknown => 1, Name => 'URL_List' },
165 0x0421 => { Unknown => 1, Name => 'VersionInfo' },
166 0x0422 => {
167 Name => 'EXIFInfo', #PH (Found in EPS and PSD files)
168 SubDirectory => {
169 TagTable=> 'Image::ExifTool::Exif::Main',
170 ProcessProc => \&Image::ExifTool::ProcessTIFF,
171 WriteProc => \&Image::ExifTool::WriteTIFF,
172 },
173 },
174 0x0423 => { Unknown => 1, Name => 'ExifInfo2', Binary => 1 }, #5
175 0x0424 => {
176 Name => 'XMP',
177 SubDirectory => {
178 TagTable => 'Image::ExifTool::XMP::Main',
179 },
180 },
181 0x0425 => {
182 Name => 'IPTCDigest',
183 Writable => 'string',
184 Protected => 1,
185 Notes => q{
186 when writing, special values of "new" and "old" represent the digests of the
187 IPTC from the edited and original files respectively, and are undefined if
188 the IPTC does not exist in the respective file
189 },
190 # also note the 'new' feature requires that the IPTC comes before this tag is written
191 ValueConv => 'unpack("H*", $val)',
192 ValueConvInv => q{
193 if (lc($val) eq 'new' or lc($val) eq 'old') {
194 {
195 local $SIG{'__WARN__'} = sub { };
196 return lc($val) if eval 'require Digest::MD5';
197 }
198 warn "Digest::MD5 must be installed\n";
199 return undef;
200 }
201 return pack('H*', $val) if $val =~ /^[0-9a-f]{32}$/i;
202 warn "Value must be 'new', 'old' or 32 hexadecimal digits\n";
203 return undef;
204 }
205 },
206 0x0426 => { Unknown => 1, Name => 'PrintScale' }, #5
207 0x0428 => { Unknown => 1, Name => 'PixelAspectRatio' }, #5
208 0x0429 => { Unknown => 1, Name => 'LayerComps' }, #5
209 0x042a => { Unknown => 1, Name => 'AlternateDuotoneColors' }, #5
210 0x042b => { Unknown => 1, Name => 'AlternateSpotColors' }, #5
211 # 0x07d0-0x0bb6 Path information
212 0x0bb7 => {
213 Name => 'ClippingPathName',
214 # convert from a Pascal string (ignoring 6 bytes of unknown data after string)
215 ValueConv => q{
216 my $len = ord($val);
217 $val = substr($val, 0, $len+1) if $len < length($val);
218 return Image::ExifTool::Photoshop::ConvertPascalString($self,$val);
219 },
220 },
221 0x2710 => { Unknown => 1, Name => 'PrintFlagsInfo' },
222);
223
224# Photoshop JPEG quality record (ref 2)
225%Image::ExifTool::Photoshop::JPEG_Quality = (
226 PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
227 WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
228 CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
229 FORMAT => 'int16s',
230 GROUPS => { 2 => 'Image' },
231 0 => {
232 Name => 'PhotoshopQuality',
233 Writable => 1,
234 PrintConv => '$val + 4',
235 PrintConvInv => '$val - 4',
236 },
237 1 => {
238 Name => 'PhotoshopFormat',
239 PrintConv => {
240 0x0000 => 'Standard',
241 0x0001 => 'Optimised',
242 0x0101 => 'Progressive',
243 },
244 },
245 2 => {
246 Name => 'ProgressiveScans',
247 PrintConv => {
248 1 => '3 Scans',
249 2 => '4 Scans',
250 3 => '5 Scans',
251 },
252 },
253);
254
255# Photoshop resolution information #PH
256%Image::ExifTool::Photoshop::Resolution = (
257 PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
258 WRITE_PROC => \&Image::ExifTool::WriteBinaryData,
259 CHECK_PROC => \&Image::ExifTool::CheckBinaryData,
260 FORMAT => 'int16u',
261 FIRST_ENTRY => 0,
262 WRITABLE => 1,
263 GROUPS => { 2 => 'Image' },
264 0 => {
265 Name => 'XResolution',
266 Format => 'int32u',
267 Priority => 0,
268 ValueConv => '$val / 0x10000',
269 ValueConvInv => 'int($val * 0x10000 + 0.5)',
270 PrintConv => 'int($val * 100 + 0.5) / 100',
271 PrintConvInv => '$val',
272 },
273 2 => {
274 Name => 'DisplayedUnitsX',
275 PrintConv => {
276 1 => 'inches',
277 2 => 'cm',
278 },
279 },
280 4 => {
281 Name => 'YResolution',
282 Format => 'int32u',
283 Priority => 0,
284 ValueConv => '$val / 0x10000',
285 ValueConvInv => 'int($val * 0x10000 + 0.5)',
286 PrintConv => 'int($val * 100 + 0.5) / 100',
287 PrintConvInv => '$val',
288 },
289 6 => {
290 Name => 'DisplayedUnitsY',
291 PrintConv => {
292 1 => 'inches',
293 2 => 'cm',
294 },
295 },
296);
297
298# Photoshop PSD file header
299%Image::ExifTool::Photoshop::Header = (
300 PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
301 FORMAT => 'int16u',
302 GROUPS => { 2 => 'Image' },
303 NOTES => 'This information is found in the PSD file header.',
304 6 => 'NumChannels',
305 7 => { Name => 'ImageHeight', Format => 'int32u' },
306 9 => { Name => 'ImageWidth', Format => 'int32u' },
307 11 => 'BitDepth',
308 12 => {
309 Name => 'ColorMode',
310 PrintConvColumns => 2,
311 PrintConv => {
312 0 => 'Bitmap',
313 1 => 'Grayscale',
314 2 => 'Indexed',
315 3 => 'RGB',
316 4 => 'CMYK',
317 7 => 'Multichannel',
318 8 => 'Duotone',
319 9 => 'Lab',
320 },
321 },
322);
323
324# tags for unknown resource types
325%Image::ExifTool::Photoshop::Unknown = (
326 GROUPS => { 2 => 'Unknown' },
327);
328
329# define reference to IPTCDigest tagInfo hash for convenience
330$iptcDigestInfo = $Image::ExifTool::Photoshop::Main{0x0425};
331
332
333#------------------------------------------------------------------------------
334# AutoLoad our writer routines when necessary
335#
336sub AUTOLOAD
337{
338 return Image::ExifTool::DoAutoLoad($AUTOLOAD, @_);
339}
340
341#------------------------------------------------------------------------------
342# Convert pascal string(s) to something we can use
343# Inputs: 1) Pascal string data
344# Returns: Strings, concatenated with ', '
345sub ConvertPascalString($$)
346{
347 my ($exifTool, $inStr) = @_;
348 my $outStr = '';
349 my $len = length($inStr);
350 my $i=0;
351 while ($i < $len) {
352 my $n = ord(substr($inStr, $i, 1));
353 last if $i + $n >= $len;
354 $i and $outStr .= ', ';
355 $outStr .= substr($inStr, $i+1, $n);
356 $i += $n + 1;
357 }
358 my $charset = $exifTool->Options('CharsetPhotoshop') || 'Latin';
359 return $exifTool->Decode($outStr, $charset);
360}
361
362#------------------------------------------------------------------------------
363# Process Photoshop APP13 record
364# Inputs: 0) ExifTool object reference, 1) Reference to directory information
365# 2) Tag table reference
366# Returns: 1 on success
367sub ProcessPhotoshop($$$)
368{
369 my ($exifTool, $dirInfo, $tagTablePtr) = @_;
370 my $dataPt = $$dirInfo{DataPt};
371 my $pos = $$dirInfo{DirStart};
372 my $dirEnd = $pos + $$dirInfo{DirLen};
373 my $verbose = $exifTool->Options('Verbose');
374 my $success = 0;
375
376 SetByteOrder('MM'); # Photoshop is always big-endian
377 $verbose and $exifTool->VerboseDir('Photoshop', 0, $$dirInfo{DirLen});
378
379 # scan through resource blocks:
380 # Format: 0) Type, 4 bytes - '8BIM' (or the rare 'PHUT', 'DCSR' or 'AgHg')
381 # 1) TagID,2 bytes
382 # 2) Name, pascal string padded to even no. bytes
383 # 3) Size, 4 bytes - N
384 # 4) Data, N bytes
385 while ($pos + 8 < $dirEnd) {
386 my $type = substr($$dataPt, $pos, 4);
387 my ($ttPtr, $extra, $val, $name);
388 if ($type eq '8BIM') {
389 $ttPtr = $tagTablePtr;
390 } elsif ($type =~ /^(PHUT|DCSR|AgHg)$/) {
391 $ttPtr = GetTagTable('Image::ExifTool::Photoshop::Unknown');
392 } else {
393 $type =~ s/([^\w])/sprintf("\\x%.2x",ord($1))/ge;
394 $exifTool->Warn(qq{Bad Photoshop IRB resource "$type"});
395 last;
396 }
397 my $tag = Get16u($dataPt, $pos + 4);
398 $pos += 6; # point to start of name
399 my $nameLen = Get8u($dataPt, $pos);
400 my $namePos = ++$pos;
401 # skip resource block name (pascal string, padded to an even # of bytes)
402 $pos += $nameLen;
403 ++$pos unless $nameLen & 0x01;
404 if ($pos + 4 > $dirEnd) {
405 $exifTool->Warn("Bad Photoshop resource block");
406 last;
407 }
408 my $size = Get32u($dataPt, $pos);
409 $pos += 4;
410 if ($size + $pos > $dirEnd) {
411 $exifTool->Warn("Bad Photoshop resource data size $size");
412 last;
413 }
414 $success = 1;
415 if ($nameLen) {
416 $name = substr($$dataPt, $namePos, $nameLen);
417 $extra = qq{, Name="$name"};
418 } else {
419 $name = '';
420 }
421 my $tagInfo = $exifTool->GetTagInfo($ttPtr, $tag);
422 # append resource name to value if requested (braced by "/#...#/")
423 if ($tagInfo and defined $$tagInfo{SetResourceName} and
424 $$tagInfo{SetResourceName} eq '1' and $name !~ m{/#})
425 {
426 $val = substr($$dataPt, $pos, $size) . '/#' . $name . '#/';
427 }
428 $exifTool->HandleTag($ttPtr, $tag, $val,
429 TagInfo => $tagInfo,
430 Extra => $extra,
431 DataPt => $dataPt,
432 DataPos => $$dirInfo{DataPos},
433 Size => $size,
434 Start => $pos,
435 Parent => $$dirInfo{DirName},
436 );
437 $size += 1 if $size & 0x01; # size is padded to an even # bytes
438 $pos += $size;
439 }
440 return $success;
441}
442
443#------------------------------------------------------------------------------
444# extract information from Photoshop PSD file
445# Inputs: 0) ExifTool object reference, 1) dirInfo reference
446# Returns: 1 if this was a valid PSD file, -1 on write error
447sub ProcessPSD($$)
448{
449 my ($exifTool, $dirInfo) = @_;
450 my $raf = $$dirInfo{RAF};
451 my $outfile = $$dirInfo{OutFile};
452 my ($data, $err, $tagTablePtr);
453
454 $raf->Read($data, 30) == 30 or return 0;
455 $data =~ /^8BPS\0([\x01\x02])/ or return 0;
456 SetByteOrder('MM');
457 $exifTool->SetFileType($1 eq "\x01" ? 'PSD' : 'PSB'); # set the FileType tag
458 my %dirInfo = (
459 DataPt => \$data,
460 DirStart => 0,
461 DirName => 'Photoshop',
462 );
463 my $len = Get32u(\$data, 26);
464 if ($outfile) {
465 Write($outfile, $data) or $err = 1;
466 $raf->Read($data, $len) == $len or return -1;
467 Write($outfile, $data) or $err = 1; # write color mode data
468 # initialize map of where things are written
469 $exifTool->InitWriteDirs(\%psdMap);
470 } else {
471 # process the header
472 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Header');
473 $dirInfo{DirLen} = 30;
474 $exifTool->ProcessDirectory(\%dirInfo, $tagTablePtr);
475 $raf->Seek($len, 1) or $err = 1; # skip over color mode data
476 }
477 $raf->Read($data, 4) == 4 or $err = 1;
478 $len = Get32u(\$data, 0);
479 $raf->Read($data, $len) == $len or $err = 1;
480 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main');
481 $dirInfo{DirLen} = $len;
482 my $rtnVal = 1;
483 if ($outfile) {
484 # rewrite IRB resources
485 $data = WritePhotoshop($exifTool, \%dirInfo, $tagTablePtr);
486 if ($data) {
487 $len = Set32u(length $data);
488 Write($outfile, $len, $data) or $err = 1;
489 # look for trailer and edit if necessary
490 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
491 if ($trailInfo) {
492 my $tbuf = '';
493 $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s)
494 # rewrite all trailers to buffer
495 if ($exifTool->ProcessTrailers($trailInfo)) {
496 my $copyBytes = $$trailInfo{DataPos} - $raf->Tell();
497 if ($copyBytes >= 0) {
498 # copy remaining PSD file up to start of trailer
499 while ($copyBytes) {
500 my $n = ($copyBytes > 65536) ? 65536 : $copyBytes;
501 $raf->Read($data, $n) == $n or $err = 1;
502 Write($outfile, $data) or $err = 1;
503 $copyBytes -= $n;
504 }
505 # write the trailer (or not)
506 $exifTool->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1;
507 } else {
508 $exifTool->Warn('Overlapping trailer');
509 undef $trailInfo;
510 }
511 } else {
512 undef $trailInfo;
513 }
514 }
515 unless ($trailInfo) {
516 # copy over the rest of the file
517 while ($raf->Read($data, 65536)) {
518 Write($outfile, $data) or $err = 1;
519 }
520 }
521 } else {
522 $err = 1;
523 }
524 $rtnVal = -1 if $err;
525 } elsif ($err) {
526 $exifTool->Warn('File format error');
527 } else {
528 ProcessPhotoshop($exifTool, \%dirInfo, $tagTablePtr);
529 # process trailers if they exist
530 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf);
531 $exifTool->ProcessTrailers($trailInfo) if $trailInfo;
532 }
533 return $rtnVal;
534}
535
5361; # end
537
538
539__END__
540
541=head1 NAME
542
543Image::ExifTool::Photoshop - Read/write Photoshop IRB meta information
544
545=head1 SYNOPSIS
546
547This module is loaded automatically by Image::ExifTool when required.
548
549=head1 DESCRIPTION
550
551Photoshop writes its own format of meta information called a Photoshop IRB
552resource which is located in the APP13 record of JPEG files. This module
553contains the definitions to read this information.
554
555=head1 NOTES
556
557Photoshop IRB blocks may have an associated resource name. These names are
558usually just an empty string, but if not empty they are displayed in the
559verbose level 2 (or greater) output. A special C<SetResourceName> flag may
560be set to '1' in the tag information hash to cause the resource name to be
561appended to the value when extracted. If this is done, the returned value
562has the form "VALUE/#NAME#/". When writing, the writer routine looks for
563this syntax (if C<SetResourceName> is defined), and and uses the embedded
564name to set the name of the new resource. This allows the resource names to
565be preserved when copying Photoshop information via user-defined tags.
566
567=head1 AUTHOR
568
569Copyright 2003-2011, Phil Harvey (phil at owl.phy.queensu.ca)
570
571This library is free software; you can redistribute it and/or modify it
572under the same terms as Perl itself.
573
574=head1 REFERENCES
575
576=over 4
577
578=item L<http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf>
579
580=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html>
581
582=item L<http://www.fileformat.info/format/psd/egff.htm>
583
584=item L<http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf>
585
586=back
587
588=head1 SEE ALSO
589
590L<Image::ExifTool::TagNames/Photoshop Tags>,
591L<Image::ExifTool(3pm)|Image::ExifTool>,
592L<Image::MetaData::JPEG(3pm)|Image::MetaData::JPEG>
593
594=cut
Note: See TracBrowser for help on using the repository browser.