I'm trying to convert PSD path records to SVG Path data.
Since I don't want to cross post I'll link to the original question. Anyone who wants to copy in the relevant data can copy it back here.
Basically, I get the PSD, parse it, and get the shape information from vector mask object.
It contains an array called paths that what look to be points shown below:
_ = require 'lodash'
# A path record describes a single point in a vector path. This is used
# in a couple of different places, but most notably in vector shapes.
module.exports = class PathRecord
constructor: (@file) ->
@recordType = null
parse: ->
@recordType = @file.readShort()
switch @recordType
when 0, 3 then @_readPathRecord()
when 1, 2, 4, 5 then @_readBezierPoint()
when 7 then @_readClipboardRecord()
when 8 then @_readInitialFill()
else @file.seek(24, true)
export: ->
_.merge { recordType: @recordType }, switch @recordType
when 0, 3 then { numPoints: @numPoints }
when 1, 2, 4, 5
linked: @linked
closed: (@recordType in [1, 2])
preceding:
vert: @precedingVert
horiz: @precedingHoriz
anchor:
vert: @anchorVert
horiz: @anchorHoriz
leaving:
vert: @leavingVert
horiz: @leavingHoriz
when 7
clipboard:
top: @clipboardTop
left: @clipboardLeft
bottom: @clipboardBottom
right: @clipboardRight
resolution: @clipboardResolution
when 8 then { initialFill: @initialFill }
else {}
isBezierPoint: -> @recordType in [1, 2, 4, 5]
_readPathRecord: ->
@numPoints = @file.readShort()
@file.seek 22, true
_readBezierPoint: ->
@linked = @recordType in [1, 4]
@precedingVert = @file.readPathNumber()
@precedingHoriz = @file.readPathNumber()
@anchorVert = @file.readPathNumber()
@anchorHoriz = @file.readPathNumber()
@leavingVert = @file.readPathNumber()
@leavingHoriz = @file.readPathNumber()
_readClipboardRecord: ->
@clipboardTop = @file.readPathNumber()
@clipboardLeft = @file.readPathNumber()
@clipboardBottom = @file.readPathNumber()
@clipboardRight = @file.readPathNumber()
@clipboardResolution = @file.readPathNumber()
@file.seek 4, true
_readInitialFill: ->
@initialFill = @file.readShort()
@file.seek 22, true
I'm trying to convert that info into SVG path data but I am stuck at two points. What record relates to what path command and the data seems to be values less than 1.
Here is example path data for the Tiger shape you can create in Photoshop:
I've trimmed the data
[
{
"recordType": 6
},
{
"recordType": 8,
"initialFill": 0
},
{
"recordType": 0,
"numPoints": 257
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.14081686735153198,
"horiz": 0.07748442888259888
},
"anchor": {
"vert": 0.14081686735153198,
"horiz": 0.0777387022972107
},
"leaving": {
"vert": 0.13936221599578857,
"horiz": 0.0777667760848999
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.13929903507232666,
"horiz": 0.07793217897415161
},
"anchor": {
"vert": 0.1385088562965393,
"horiz": 0.07837295532226562
},
"leaving": {
"vert": 0.13777965307235718,
"horiz": 0.07837295532226562
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.13706856966018677,
"horiz": 0.07837295532226562
},
"anchor": {
"vert": 0.13632577657699585,
"horiz": 0.07837295532226562
},
"leaving": {
"vert": 0.1364198923110962,
"horiz": 0.07855236530303955
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.13649815320968628,
"horiz": 0.07873183488845825
},
"anchor": {
"vert": 0.13657790422439575,
"horiz": 0.07890427112579346
},
"leaving": {
"vert": 0.1359773874282837,
"horiz": 0.07879406213760376
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.13536030054092407,
"horiz": 0.07869088649749756
},
"anchor": {
"vert": 0.1347590684890747,
"horiz": 0.07858771085739136
},
"leaving": {
"vert": 0.13486969470977783,
"horiz": 0.07879406213760376
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.13499760627746582,
"horiz": 0.07900881767272949
},
"anchor": {
"vert": 0.13512402772903442,
"horiz": 0.07922220230102539
},
"leaving": {
"vert": 0.1344437599182129,
"horiz": 0.07920092344284058
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.1268816590309143,
"horiz": 0.08006417751312256
},
"anchor": {
"vert": 0.12613815069198608,
"horiz": 0.08038073778152466
},
"leaving": {
"vert": 0.12613815069198608,
"horiz": 0.08055287599563599
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.12613815069198608,
"horiz": 0.08073228597640991
},
"anchor": {
"vert": 0.12613815069198608,
"horiz": 0.08091175556182861
},
"leaving": {
"vert": 0.1256791353225708,
"horiz": 0.0807945728302002
}
},
{
"recordType": 2,
"linked": false,
"closed": true,
"preceding": {
"vert": 0.12177199125289917,
"horiz": 0.08080857992172241
},
"anchor": {
"vert": 0.12177199125289917,
"horiz": 0.08080857992172241
},
"leaving": {
"vert": 0.12169301509857178,
"horiz": 0.08107715845108032
}
}
]
The post on github has the function that is parsing the data.
Different path object models
Photoshop path model: based on drawing points
The photoshop path model defines a path based on drawing points – each of these points can contain 2 "tangent handles".
This model actually reflects the UI concept you would experience in a graphic application: you can pull tangent handles from any drawing point to control the curvature of a path segment.
point.anchor– visually set drawing pointpoint.preceding– left tangent handlepoint.leaving– right tangent handleSVG path model: segment based
In comparison to the psd/psd.js model a SVG
Ccommand would start with the previous point'sleavingcoordinates like so:previousPoint.leaving– previous point's tangent handlepoint.preceding– right tangent handlepoint.anchor– final point/positionThe right
point.leavingwould already belong to the next command in svg.So you need to reorder the retrieved point/coordinate data.
recordTypeto svg cammand typesYou can rather ignore them – these properties are only relevant for a photoshop UI behavior.
Command types
Fortunately, you can easily translate all psd points to
Ccubic curvetos. The only exception is the mandatoryM(moveto) command that is decribed by:firstPoint.anchorL(linetos) are also described by 3 object properties: ifpreceding,anchorandleavingare equal – we can convert these points toLcommands (aCcommand with equal values for the 2 control points and the final point would also work).Closing the clip path
As mentioned before we need to reorder/shift the photoshop point data:
The last closing command will need the first point's
precedingcoordinates:point.leavingpointStart.preceding– first point left tangent handlepointStart.anchor– first point/positionCoordinate scaling
Actually a bit odd – coordinates are stored relative to the psd document's width and height.
scaleX = psdDocumentWidth
scaleY = psdDocumentHeight
Since we don't have to bother about shorthand commands like
VorHorA(arcs) we can multiply x (even - as starting from 0) and y (odd) values within a loop.Compound paths
A clip path might include sub paths e.g the hole in an "O" shaped path.
Sub paths are introduced by empty object items (not containing any preceding, anchor or leaving property values) like
So we can use this for splitting the data into array chunks.
Example: translate psd clip path to svg