#------------------------------------------------------------------------------ # File: photoshop_paths.config # # Description: This config file generates user-defined tags for Photoshop # paths, and may be used to extract path names and/or Bezier knot # anchor points, or copy path information from one file to # another. # # The anchor points may be extracted along with the path names by # setting the "Anchor" user parameter (ie. "-userparam anchor"), # or by themselves with "AnchorOnly". # # An "AllPaths" shortcut tag is also provided represent all # Photoshop path tags. This shortcut must be used when copying # because these tags are marked as "Protected" so they won't be # copied by default (also see the notes below). # # Path anchor points are converted to pixel coordinates by the # Composite PathPixXXX tags, and an "AllPathPix" shortcut is # provided to represent these tags. # # Finally, a Composite PathCount tag is provided to return the # number of paths in an image, and a TotalPathPoints tag counts # the total number of path anchor points. # # Notes: 1) Print conversion must be disabled to be able to copy the paths # (via either the -n option, or by adding a "#" to the tag name, # eg. "-tagsfromfile SRC -allpaths#"). # # 2) When copying the paths, OriginPathInfo must also be copied # (otherwise Photoshop may give a "program error" and refuse to # load the image). # # Usage: # # 1) Extract Photoshop path names: # # exiftool -config photoshop_paths.config -allpaths FILE # # 2) Extract Photoshop path names and anchor points: # # exiftool -config photoshop_paths.config -userparam anchor -allpaths FILE # # 3) Extract Photoshop path anchor points only: # # exiftool -config photoshop_paths.config -userparam anchoronly -allpaths FILE # # 4) Copy all Photoshop paths from one file (SRC) to another (DST): # (note that OriginPathInfo must also be copied when copying all paths) # # exiftool -config photoshop_paths.config -tagsfromfile SRC -allpaths# -originpathinfo DST # # 5) Extract path names and anchor points in pixel coordinates: # # exiftool -config photoshop_paths.config -allpathpix FILE # # Requires: ExifTool version 9.95 or later # # Notes: A "-" before a set of Bezier path points indicates a closed subpath, # and a "+ indicates the start of an open subpath. # # Revisions: 2015/05/07 - P. Harvey Created # 2016/09/14 - PH Added feature to allow extracting anchor points # 2017/01/24 - PH Added PathCount and PathPix Composite tags # 2017/02/02 - PH Added support for copying OriginPathInfo # 2017/02/22 - PH Fixed problem printing some paths # 2017/03/19 - PH Added "-" or "+" at the start of closed or open # subpath respectively # 2017/06/03 - PH Added TotalPathPoints # 2017/07/17 - PH Added UniquePathPoints # 2022/02/03 - PH Added WorkingPath and WorkingPathPix # # References: https://exiftool.org/forum/index.php/topic,1621.0.html # https://exiftool.org/forum/index.php/topic,3910.0.html # https://exiftool.org/forum/index.php/topic,6647.0.html #------------------------------------------------------------------------------ # Print Photoshop path name and/or anchor points # Inputs: 0) reference to Photoshop path data, 1) ExifTool object reference # 2-3) optional image width/height to convert anchor points to pixels # 4) optional path name # Returns: String with name and/or Bezier knot anchor points sub PrintPath($$;$$$) { my ($val, $et, $w, $h, $nm) = @_; my ($pos, $name, @rtn); my $len = length($$val) - 26; # recover exiftool-added path name if it exists if ($$val =~ m{.*/#(.{0,255})#/$}s) { $name = $1; $len -= length($1) + 4; $name = $nm if defined $nm and not length $name; } else { $name = defined $nm ? $nm : ''; } my $anchorOnly = $et->Options(UserParam => 'AnchorOnly'); push @rtn, $name unless $anchorOnly; # loop through path points and extract anchor points if specified if ($anchorOnly or $et->Options(UserParam => 'Anchor') or defined $w) { SetByteOrder('MM'); for ($pos=0; $pos<=$len; $pos+=26) { my $type = Get16u($val, $pos); $type == 0 and push(@rtn, '-'), next; $type == 3 and push(@rtn, '+'), next; # Bezier knot records are types 1, 2, 4 and 5 next unless {1=>1,2=>1,4=>1,5=>1}->{$type}; # the anchor point is at offset 10 in the Bezier knot record # (fixed-point values with 24-bits after the decimal point) my $y = Get32s($val, $pos+10) / 0x1000000; # (vertical component first) my $x = Get32s($val, $pos+14) / 0x1000000; if (defined $w and defined $h) { push @rtn, sprintf('(%g,%g)', $x * $w, $y * $h); } else { push @rtn, sprintf('(%g,%g)', $x, $y); } } } return join ' ', @rtn; } %Image::ExifTool::Shortcuts::UserDefined = ( # create "AllPaths" shortcut for all Photoshop path tags (except WorkingPath) AllPaths => [ map { sprintf "Path%x", $_ } (0x7d0 .. 0xbb5), ], AllPathPix => [ map { sprintf "PathPix%x", $_ } (0x7d0 .. 0xbb5), ], ); %Image::ExifTool::UserDefined = ( 'Image::ExifTool::Photoshop::Main' => { 0xbb8 => { Name => 'OriginPathInfo', Flags => [ qw(Writable Protected Binary SetResourceName) ], }, 0x401 => { Name => 'WorkingPath', Flags => [ qw(Writable Protected Binary ConvertBinary SetResourceName) ], PrintConv => sub { my ($val, $et) = @_; PrintPath($val, $et, undef, undef, 'Work Path'); }, }, # generate tags for each of the 998 possible Photoshop paths map { $_ => { Name => sprintf('Path%x', $_), Description => sprintf('Path %x', $_), Flags => [ qw(Writable Protected Binary ConvertBinary SetResourceName) ], PrintConv => \&PrintPath, } } (0x7d0 .. 0xbb5), }, 'Image::ExifTool::Composite' => { PathCount => { # (PathCount statistics do not include WorkingPath) Desire => { map { $_-0x7d0 => sprintf('Path%x', $_) } (0x7d0 .. 0xbb5), }, ValueConv => sub { my ($val, $self) = @_; my $count = 0; my $pts = 0; my $uniq = 0; my %uniq; foreach (@$val) { next unless defined $_; ++$count; # determine the total number of path anchor points my $len = length($$_) - 26; for ($pos=0; $pos<=$len; $pos+=26) { SetByteOrder('MM'); my $type = Get16u($_, $pos); last if $type == 0x2f23; # (start of path name added by ExifTool) next unless {1=>1,2=>1,4=>1,5=>1}->{$type}; my $pt = substr($$_, $pos+10, 8); $uniq{$pt} or ++$uniq, $uniq{$pt} = 1; ++$pts; } } $$self{TotalPathPoints} = $pts; $$self{UniquePathPoints} = $uniq; return $count; }, }, UniquePathPoints => { Require => 'PathCount', ValueConv => '$$self{UniquePathPoints}', }, TotalPathPoints => { Require => 'PathCount', ValueConv => '$$self{TotalPathPoints}', }, WorkingPathPix => { Require => { 0 => 'ImageWidth', 1 => 'ImageHeight', 2 => 'WorkingPath', }, ValueConv => sub { my ($val, $et) = @_; PrintPath($$val[2], $et, $$val[0], $$val[1], 'Work Path'); }, }, map { sprintf('PathPix%x', $_) => { Require => { 0 => 'ImageWidth', 1 => 'ImageHeight', 2 => sprintf('Path%x', $_), }, Description => sprintf('Path Pix %x', $_), ValueConv => sub { my ($val, $et) = @_; PrintPath($$val[2], $et, $$val[0], $$val[1]); }, } } (0x7d0 .. 0xbb5), }, ); 1; #end