#------------------------------------------------------------------------------
# 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 : '<none>';
    }
    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
