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 # warn about incorrect IPTCDigest 992 if ($$et{VALUE}{IPTCDigest} and $$et{VALUE}{CurrentIPTCDigest} and 993 $$et{VALUE}{IPTCDigest} ne $$et{VALUE}{CurrentIPTCDigest}) 994 { 995 $et->WarnOnce('IPTCDigest is not current. XMP may be out of sync'); 996 } 997 delete $$et{LOW_PRIORITY_DIR}{'*'}; 998 return $success; 999} 1000 1001#------------------------------------------------------------------------------ 1002# extract information from Photoshop PSD file 1003# Inputs: 0) ExifTool object reference, 1) dirInfo reference 1004# Returns: 1 if this was a valid PSD file, -1 on write error 1005sub ProcessPSD($$) 1006{ 1007 my ($et, $dirInfo) = @_; 1008 my $raf = $$dirInfo{RAF}; 1009 my $outfile = $$dirInfo{OutFile}; 1010 my ($data, $err, $tagTablePtr); 1011 1012 $raf->Read($data, 30) == 30 or return 0; 1013 $data =~ /^8BPS\0([\x01\x02])/ or return 0; 1014 SetByteOrder('MM'); 1015 $et->SetFileType($1 eq "\x01" ? 'PSD' : 'PSB'); # set the FileType tag 1016 my %dirInfo = ( 1017 DataPt => \$data, 1018 DirStart => 0, 1019 DirName => 'Photoshop', 1020 ); 1021 my $len = Get32u(\$data, 26); 1022 if ($outfile) { 1023 Write($outfile, $data) or $err = 1; 1024 $raf->Read($data, $len) == $len or return -1; 1025 Write($outfile, $data) or $err = 1; # write color mode data 1026 # initialize map of where things are written 1027 $et->InitWriteDirs(\%psdMap); 1028 } else { 1029 # process the header 1030 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Header'); 1031 $dirInfo{DirLen} = 30; 1032 $et->ProcessDirectory(\%dirInfo, $tagTablePtr); 1033 $raf->Seek($len, 1) or $err = 1; # skip over color mode data 1034 } 1035 # read image resource section 1036 $raf->Read($data, 4) == 4 or $err = 1; 1037 $len = Get32u(\$data, 0); 1038 $raf->Read($data, $len) == $len or $err = 1; 1039 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Main'); 1040 $dirInfo{DirLen} = $len; 1041 my $rtnVal = 1; 1042 if ($outfile) { 1043 # rewrite IRB resources 1044 $data = WritePhotoshop($et, \%dirInfo, $tagTablePtr); 1045 if ($data) { 1046 $len = Set32u(length $data); 1047 Write($outfile, $len, $data) or $err = 1; 1048 # look for trailer and edit if necessary 1049 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf); 1050 if ($trailInfo) { 1051 my $tbuf = ''; 1052 $$trailInfo{OutFile} = \$tbuf; # rewrite trailer(s) 1053 # rewrite all trailers to buffer 1054 if ($et->ProcessTrailers($trailInfo)) { 1055 my $copyBytes = $$trailInfo{DataPos} - $raf->Tell(); 1056 if ($copyBytes >= 0) { 1057 # copy remaining PSD file up to start of trailer 1058 while ($copyBytes) { 1059 my $n = ($copyBytes > 65536) ? 65536 : $copyBytes; 1060 $raf->Read($data, $n) == $n or $err = 1; 1061 Write($outfile, $data) or $err = 1; 1062 $copyBytes -= $n; 1063 } 1064 # write the trailer (or not) 1065 $et->WriteTrailerBuffer($trailInfo, $outfile) or $err = 1; 1066 } else { 1067 $et->Warn('Overlapping trailer'); 1068 undef $trailInfo; 1069 } 1070 } else { 1071 undef $trailInfo; 1072 } 1073 } 1074 unless ($trailInfo) { 1075 # copy over the rest of the file 1076 while ($raf->Read($data, 65536)) { 1077 Write($outfile, $data) or $err = 1; 1078 } 1079 } 1080 } else { 1081 $err = 1; 1082 } 1083 $rtnVal = -1 if $err; 1084 } elsif ($err) { 1085 $et->Warn('File format error'); 1086 } else { 1087 # read IRB resources 1088 ProcessPhotoshop($et, \%dirInfo, $tagTablePtr); 1089 # read layer and mask information section 1090 $dirInfo{RAF} = $raf; 1091 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::Layers'); 1092 my $oldIndent = $$et{INDENT}; 1093 $$et{INDENT} .= '| '; 1094 if (ProcessLayersAndMask($et, \%dirInfo, $tagTablePtr) and 1095 # read compression mode from image data section 1096 $raf->Read($data,2) == 2) 1097 { 1098 my %dirInfo = ( 1099 DataPt => \$data, 1100 DataPos => $raf->Tell() - 2, 1101 ); 1102 $tagTablePtr = GetTagTable('Image::ExifTool::Photoshop::ImageData'); 1103 $et->ProcessDirectory(\%dirInfo, $tagTablePtr); 1104 } 1105 $$et{INDENT} = $oldIndent; 1106 # process trailers if they exist 1107 my $trailInfo = Image::ExifTool::IdentifyTrailer($raf); 1108 $et->ProcessTrailers($trailInfo) if $trailInfo; 1109 } 1110 return $rtnVal; 1111} 1112 11131; # end 1114 1115 1116__END__ 1117 1118=head1 NAME 1119 1120Image::ExifTool::Photoshop - Read/write Photoshop IRB meta information 1121 1122=head1 SYNOPSIS 1123 1124This module is loaded automatically by Image::ExifTool when required. 1125 1126=head1 DESCRIPTION 1127 1128Photoshop writes its own format of meta information called a Photoshop IRB 1129resource which is located in the APP13 record of JPEG files. This module 1130contains the definitions to read this information. 1131 1132=head1 NOTES 1133 1134Photoshop IRB blocks may have an associated resource name. These names are 1135usually just an empty string, but if not empty they are displayed in the 1136verbose level 2 (or greater) output. A special C<SetResourceName> flag may 1137be set to '1' in the tag information hash to cause the resource name to be 1138appended to the value when extracted. If this is done, the returned value 1139has the form "VALUE/#NAME#/". When writing, the writer routine looks for 1140this syntax (if C<SetResourceName> is defined), and and uses the embedded 1141name to set the name of the new resource. This allows the resource names to 1142be preserved when copying Photoshop information via user-defined tags. 1143 1144=head1 AUTHOR 1145 1146Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com) 1147 1148This library is free software; you can redistribute it and/or modify it 1149under the same terms as Perl itself. 1150 1151=head1 REFERENCES 1152 1153=over 4 1154 1155=item L<http://www.fine-view.com/jp/lab/doc/ps6ffspecsv2.pdf> 1156 1157=item L<http://www.ozhiker.com/electronics/pjmt/jpeg_info/irb_jpeg_qual.html> 1158 1159=item L<http://www.fileformat.info/format/psd/egff.htm> 1160 1161=item L<http://libpsd.graphest.com/files/Photoshop%20File%20Formats.pdf> 1162 1163=item L<http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/> 1164 1165=back 1166 1167=head1 SEE ALSO 1168 1169L<Image::ExifTool::TagNames/Photoshop Tags>, 1170L<Image::ExifTool(3pm)|Image::ExifTool>, 1171L<Image::MetaData::JPEG(3pm)|Image::MetaData::JPEG> 1172 1173=cut 1174