Copy link to clipboard
Copied
I'm sure this is very easy but as a new user I'm struggling to find how to do it.
I want to draw a curved line using the curvature pen tool and add an arrowhead to one end. What is the best way?
A Script can create an »arrowhead« for a stroked Shape Layer.
// create arrowhead for shape layer with stroke and one subpathitem;
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var myDocument = app.activeDocument;
// check stroke;
try {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = ex
...
A piece of advice regarding Photoshop Scripting:
This Script utilizes ExtendScript and this is essentially on legacy-status (without a definitive timeline, but it might at some time be removed from Photoshop altogether).
So if you want to dig into Scripting you may want to focus on the newer UXP Scripting right away.
Copy link to clipboard
Copied
I am afraid for this you should turn to Illustrator (or other vector oriented applications).
Copy link to clipboard
Copied
You would need to draw your curve first then use the line tool to add a short arrow on the end
Dave
Copy link to clipboard
Copied
@davescmyou still think it's normal we use this workaround in 2024? How do we push this topic to the top? It's driving me crazy for YEARS
Copy link to clipboard
Copied
Since you ask me in particular - if I want vector drawing tools I use Illustrator. I keep Photoshop vectors for paths to make selections/masks.
Dave
Copy link to clipboard
Copied
Copy link to clipboard
Copied
The easiest way in Photoshop would be to use a legacy custom arrow shape or the line tool set for adding an arrow head. Then use the curvature pen tool on the path Photoshop tool laid out.
Copy link to clipboard
Copied
Thank you, all. I'm rather surprised that this fairly basic function needs what are in effect workarounds.
Copy link to clipboard
Copied
You might be confusing Photoshop with a vector-oriented application like Illustrator.
Copy link to clipboard
Copied
You and me both brother.. it's 2024 and this is still not done. Sadness.
The workaround is still to:
- draw a curved shape with the pen tool
- switch to a line and draw the arrowhead
Annoying part is that the pen tool needs a stroke and the line needs a fill and there is no button to quickly switch fill and stroke color sadness again 😞 so you end up fiddling a lot in the upper left corner to achieve something as basic as a curved wine with an arrowhead. Insane right?
Photoshop are you listening to your users? Or is AI features all you can think of now? So sad 😞
Copy link to clipboard
Copied
You and me both brother.. it's 2024 and this is still not done. Sadness.
The workaround is still to:
- draw a curved shape with the pen tool
- switch to a line and draw the arrowhead
Annoying part is that the pen tool needs a stroke and the line needs a fill and there is no button to quickly switch fill and stroke color sadness again 😞 so you end up fiddling a lot in the upper left corner to achieve something as basic as a curved wine with an arrowhead. Insane right?
Photoshop are you listening to your users? Or is AI features all you can think of now? So sad 😞
By @RV999
1) Photoshop cannot be listening, on the other hand if you make a proper Feature Request that will register at Adobe. No promises on an implementation, though.
2) Please keep a civil tone and don’t call things »Insane« just because you want them to be different (which may make perfect sense in your workflow but you have not chosen to elaborate on that).
3) Why do you want to create a vector line with an arrowhead in Photoshop instead of a proper vector application?
Please read this and act accordingly:
https://community.adobe.com/t5/using-the-community-discussions/adobe-support-community-guidelines/td...
Copy link to clipboard
Copied
Thank you dear @c.pfaffenbichler excuses for the tone, frustration is hard to control sometimes.
I attach typical scanarios of image-collages with annotations/callouts. I do this kind of visual collages very often.
I would love to be able to do the arrowheads on curved lines in Photoshop without having to create these separately in Illustrator, it just doesn't suit my workflow and what about users that only have the money for a Photoshop subscription? Not everyone has all apps! As you see I normally just draw the arrowheads (or even the whole arrow) by hand becaise that's faster but sometimes when the work needs to be more quality I take the steps mentioned above, it takes a long time...
Copy link to clipboard
Copied
Well, I doubt there are many Photoshop users who haven’t experienced frustration with the application at times …
For the lines: Do you just draw the lines, use a Path and »Stroke Path«, create Shape Layers with a Stroke and no Fill, …?
For Shape Layers it would be possible to add the arrowhead via Script, but because any Shape Layer with two or more subPathItems reverts to »filled« it would need to be a second Shape Layer.
And that would not follow automatically if you should edit the original line later on; so clearly more of a work-around than a solution.
Copy link to clipboard
Copied
As mentioned previously a Script can be used to create »arrowheads« for Shape Layers with a Stroke.
Curiously the Shape layers created via the Script appear closed even when they are open, so I used a work-around to avoid the connecting line but that causes doubling with dashed lines.
Copy link to clipboard
Copied
A Script can create an »arrowhead« for a stroked Shape Layer.
// create arrowhead for shape layer with stroke and one subpathitem;
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var myDocument = app.activeDocument;
// check stroke;
try {
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
var theStroke = layerDesc.getObjectValue(stringIDToTypeID("AGMStrokeStyleInfo"));
var theStrokeWidth = theStroke.getUnitDoubleValue(stringIDToTypeID("strokeStyleLineWidth"));
// if stroked shape layer is selected;
var thePath = collectPathInfoFromDesc2023 (myDocument, myDocument.pathItems[myDocument.pathItems.length-1]);
if (thePath.length == 1) {
var thePoint = thePath[0][thePath[0].length - 3];
if (thePoint[0][0] != thePoint[2][0] && thePoint[0][1] != thePoint[2][1]) {var thePoint2 = thePoint[2]}
else {var thePoint2 = thePath[0][thePath[0].length - 4][1]};
var thePoint1 = thePoint[0];
var theAngle = getAngle (thePoint1, thePoint2);
var thePointBefore = createPointAtAngleAndDistance(thePoint1, theAngle-45, theStrokeWidth);
var thePointAfter = createPointAtAngleAndDistance(thePoint1, theAngle+45, theStrokeWidth);
// create arrowhead;
var theArray = [[[thePointBefore, thePointBefore, thePointBefore, false], [thePoint1, thePoint1, thePoint1, false], [thePointAfter, thePointAfter, thePointAfter, false],false,1097098272]];
var theX = rectangleShapeLayerToPolygon(theArray, theStroke);
} else {alert ("please select a stroked shape layer with only one subPathItem")};
} catch (e) {alert ("please select a stroked shape layer with only one subPathItem")};
app.preferences.rulerUnits = originalRulerUnits;
};
////////////////////////////////////
////// create shape layer and then change the path to maintain an open path //////
function rectangleShapeLayerToPolygon (theArray, theStroke) {
var idpixelsUnit = stringIDToTypeID( "pixelsUnit" );
var idcontentLayer = stringIDToTypeID( "contentLayer" );
// =======================================================
var desc254 = new ActionDescriptor();
var idnull = stringIDToTypeID( "null" );
var ref5 = new ActionReference();
ref5.putClass( idcontentLayer );
desc254.putReference( idnull, ref5 );
var idusing = stringIDToTypeID( "using" );
var desc255 = new ActionDescriptor();
var idtype = stringIDToTypeID( "type" );
var desc256 = new ActionDescriptor();
var idcolor = stringIDToTypeID( "color" );
var desc257 = new ActionDescriptor();
var idred = stringIDToTypeID( "red" );
desc257.putDouble( idred, 0.000000 );
var idgrain = stringIDToTypeID( "grain" );
desc257.putDouble( idgrain, 0.000000 );
var idblue = stringIDToTypeID( "blue" );
desc257.putDouble( idblue, 0.000000 );
desc256.putObject( idcolor, stringIDToTypeID( "RGBColor" ), desc257 );
desc255.putObject( idtype, stringIDToTypeID( "solidColorLayer" ), desc256 );
var idshape = stringIDToTypeID( "shape" );
var desc258 = new ActionDescriptor();
var idunitValueQuadVersion = stringIDToTypeID( "unitValueQuadVersion" );
desc258.putInteger( idunitValueQuadVersion, 1 );
var idtop = stringIDToTypeID( "top" );
desc258.putUnitDouble( idtop, idpixelsUnit, 1111.492188 );
var idleft = stringIDToTypeID( "left" );
desc258.putUnitDouble( idleft, idpixelsUnit, 413.148438 );
var idbottom = stringIDToTypeID( "bottom" );
desc258.putUnitDouble( idbottom, idpixelsUnit, 1827.453125 );
var idright = stringIDToTypeID( "right" );
desc258.putUnitDouble( idright, idpixelsUnit, 1107.109375 );
var idtopRight = stringIDToTypeID( "topRight" );
desc258.putUnitDouble( idtopRight, idpixelsUnit, 0.000000 );
var idtopLeft = stringIDToTypeID( "topLeft" );
desc258.putUnitDouble( idtopLeft, idpixelsUnit, 0.000000 );
var idbottomLeft = stringIDToTypeID( "bottomLeft" );
desc258.putUnitDouble( idbottomLeft, idpixelsUnit, 0.000000 );
var idbottomRight = stringIDToTypeID( "bottomRight" );
desc258.putUnitDouble( idbottomRight, idpixelsUnit, 0.000000 );
var idrectangle = stringIDToTypeID( "rectangle" );
desc255.putObject( idshape, idrectangle, desc258 );
desc255.putObject( stringIDToTypeID( "strokeStyle" ), stringIDToTypeID( "strokeStyle" ), theStroke );
desc254.putObject( idusing, idcontentLayer, desc255 );
executeAction( stringIDToTypeID( "make" ), desc254, DialogModes.NO );
////////////////////////////////////
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
// thanks to xbytor;
cTID = function(s) { return app.charIDToTypeID(s); };
sTID = function(s) { return app.stringIDToTypeID(s); };
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putEnumerated( cTID('Path'), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
desc1.putReference(sTID('null'), ref1);
var list1 = new ActionList();
for (var m = 0; m < theArray.length; m++) {
var thisSubPath = theArray[m];
var desc2 = new ActionDescriptor();
desc2.putEnumerated(sTID('shapeOperation'), sTID('shapeOperation'), thisSubPath[thisSubPath.length - 1]);
var list2 = new ActionList();
var desc3 = new ActionDescriptor();
desc3.putBoolean(cTID('Clsp'), thisSubPath[thisSubPath.length - 2]);
var list3 = new ActionList();
for (var n = 0; n < thisSubPath.length - 2; n++) {
var thisPoint = thisSubPath[n];
var desc4 = new ActionDescriptor();
var desc5 = new ActionDescriptor();
desc5.putUnitDouble(cTID('Hrzn'), cTID('#Rlt'), thisPoint[0][0]);
desc5.putUnitDouble(cTID('Vrtc'), cTID('#Rlt'), thisPoint[0][1]);
desc4.putObject(cTID('Anch'), cTID('Pnt '), desc5);
var desc6 = new ActionDescriptor();
desc6.putUnitDouble(cTID('Hrzn'), cTID('#Rlt'), thisPoint[1][0]);
desc6.putUnitDouble(cTID('Vrtc'), cTID('#Rlt'), thisPoint[1][1]);
desc4.putObject(cTID('Fwd '), cTID('Pnt '), desc6);
var desc7 = new ActionDescriptor();
desc7.putUnitDouble(cTID('Hrzn'), cTID('#Rlt'), thisPoint[2][0]);
desc7.putUnitDouble(cTID('Vrtc'), cTID('#Rlt'), thisPoint[2][1]);
desc4.putObject(cTID('Bwd '), cTID('Pnt '), desc7);
desc4.putBoolean(cTID('Smoo'), thisPoint[3]);
list3.putObject(cTID('Pthp'), desc4);
};
desc3.putList(cTID('Pts '), list3);
list2.putObject(cTID('Sbpl'), desc3);
desc2.putList(cTID('SbpL'), list2);
list1.putObject(cTID('PaCm'), desc2);
};
desc1.putList(cTID('T '), list1);
executeAction(cTID('setd'), desc1, DialogModes.NO);
app.preferences.rulerUnits = originalRulerUnits;
};
////// collect path info from actiondescriptor, indices start at 1, not 0 //////
function collectPathInfoFromDesc2023 (myDocument, thePath) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
// based of functions from xbytor’s stdlib;
var idPath = charIDToTypeID( "Path" );
var ref = new ActionReference();
// check if thePath is an index or a dom-path;
if (thePath.constructor == Number) {
ref.putIndex(idPath, thePath)
}
else {
thePath.select();
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID( "Path" ), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
};
// get stuff;
var desc = app.executeActionGet(ref);
var pname = desc.getString(cTID('PthN'));
// create new array;
var theArray = new Array;
var pathComponents = desc.getObjectValue(cTID("PthC")).getList(sTID('pathComponents'));
// for subpathitems;
for (var m = 0; m < pathComponents.count; m++) {
var listKey = pathComponents.getObjectValue(m).getList(sTID("subpathListKey"));
var operation1 = pathComponents.getObjectValue(m).getEnumerationValue(sTID("shapeOperation"));
switch (operation1) {
case 1097098272:
var operation = 1097098272 //cTID('Add ');
break;
case 1398961266:
var operation = 1398961266 //cTID('Sbtr');
break;
case 1231975538:
var operation = 1231975538 //cTID('Intr');
break;
default:
// case 1102:
var operation = sTID('xor') //ShapeOperation.SHAPEXOR;
break;
};
// for subpathitem’s count;
for (var n = 0; n < listKey.count; n++) {
theArray.push(new Array);
var points = listKey.getObjectValue(n).getList(sTID('points'));
try {var closed = listKey.getObjectValue(n).getBoolean(sTID("closedSubpath"))}
catch (e) {var closed = false};
// for subpathitem’s segment’s number of points;
for (var o = 0; o < points.count; o++) {
var anchorObj = points.getObjectValue(o).getObjectValue(sTID("anchor"));
var anchor = [anchorObj.getUnitDoubleValue(sTID('horizontal')), anchorObj.getUnitDoubleValue(sTID('vertical'))];
var thisPoint = [anchor];
try {
var left = points.getObjectValue(o).getObjectValue(cTID("Fwd "));
var leftDirection = [left.getUnitDoubleValue(sTID('horizontal')), left.getUnitDoubleValue(sTID('vertical'))];
thisPoint.push(leftDirection)
}
catch (e) {
thisPoint.push(anchor)
};
try {
var right = points.getObjectValue(o).getObjectValue(cTID("Bwd "));
var rightDirection = [right.getUnitDoubleValue(sTID('horizontal')), right.getUnitDoubleValue(sTID('vertical'))];
thisPoint.push(rightDirection)
}
catch (e) {
thisPoint.push(anchor)
};
try {
var smoothOr = points.getObjectValue(o).getBoolean(cTID("Smoo"));
thisPoint.push(smoothOr)
}
catch (e) {thisPoint.push(false)};
theArray[theArray.length - 1].push(thisPoint);
};
theArray[theArray.length - 1].push(closed);
theArray[theArray.length - 1].push(operation);
};
};
// by xbytor, thanks to him;
function cTID (s) { return cTID[s] || cTID[s] = app.charIDToTypeID(s); };
function sTID (s) { return sTID[s] || sTID[s] = app.stringIDToTypeID(s); };
// reset;
app.preferences.rulerUnits = originalRulerUnits;
return theArray;
};
////// get an angle, 3:00 being 0˚, 6:00 90˚, etc. //////
function getAngle (pointOne, pointTwo) {
// calculate the triangle sides;
var width = pointTwo[0] - pointOne[0];
var height = pointTwo[1] - pointOne[1];
var sideC = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
// calculate the angles;
if (width+width > width) {theAngle = Math.asin(height / sideC) * 360 / 2 / Math.PI}
else {theAngle = 180 - (Math.asin(height / sideC) * 360 / 2 / Math.PI)};
if (theAngle < 0) {theAngle = (360 + theAngle)};
// if (theAngle > 180) {theAngle = (360 - theAngle) * (-1)};
return theAngle
};
////// radians //////
function radiansOf (theAngle) {
return theAngle * Math.PI / 180
};
////// rotate and scale point //////
function createPointAtAngleAndDistance (thePoint, theAngle, theDist) {
var newX = thePoint[0] + Math.cos(radiansOf (theAngle))*theDist;
var newY = thePoint[1] + Math.sin(radiansOf (theAngle))*theDist;
return [newX, newY]
};
Copy link to clipboard
Copied
Hi @c.pfaffenbichler whoaaaa this is incredible!! I want to use this script, may I? And how do I go about saving this code as a script/action? I there a guide somewhere? I looked a bit online but didn't find a clear walk-thru for this kind of scenario, can you maybe point me in the right direction? Thank you for looking into this!! So wish Adobe would look at this an just implement it for everybody
Copy link to clipboard
Copied
Save the code as a txt-file (Simpletxt), change the extension to .jsx and copy it into Photoshop’s Presets/Scripts-Folder; after restarting Photoshop it should be available under File > Scripts and can be assigned a Shortcut or used in an Action.
Copy link to clipboard
Copied
This is pure GOLD, that you so much @c.pfaffenbichler !! I was sick for a few weeks but now I'm back to try this and I made a shortcut to it and it simply works, you are genius! Please Adobe give this person credit! brilliant! I will look deeper into this script stuff, never tried this but this is powerful stuff! Code! whoa!
Copy link to clipboard
Copied
A piece of advice regarding Photoshop Scripting:
This Script utilizes ExtendScript and this is essentially on legacy-status (without a definitive timeline, but it might at some time be removed from Photoshop altogether).
So if you want to dig into Scripting you may want to focus on the newer UXP Scripting right away.
Copy link to clipboard
Copied
That's very very cool! you're my superman!!!!!!
Copy link to clipboard
Copied
paint.NET is great for drawing arrows and it's free and extremely lightweight. it even head dynamic gradients way before photoshop. Adobe can learn a thing or two from that app.
Find more inspiration, events, and resources on the new Adobe Community
Explore Now