Copy link to clipboard
Copied
Photoshop 2021 Line Tool is different than previous versions. In previous versions the width of the line/arrow could be set. Now it's set with a stroke. In both the old and new versions, it was difficult to change the arrowhead shapes. You basically had to redraw them. This script will allow you to change an existing arrow and save the preferences so that you can change multiple arrows.
Use at your own risk.
//copyright 2020 Chuck Uebele, all rights reserved
//Use at your own risk.
var xmlFile = new File(Folder.desktop+'/ArrowPrefs2.xml');
var xmlPref
if(xmlFile.exists){
xmlPref = readXMLFile (xmlFile)
}
else{xmlPref = new XML('<root><prefs><arrows/></prefs>')};
var doc = activeDocument
var oldPrefs = app.preferences.rulerUnits
var oldRes = doc.resolution
changeRes (72)
app.preferences.rulerUnits = Units.POINTS;
var dlg
var lineAngTan1;//from first point to oppsite side of line
var lineAngTan2;//From oppsite side of line to first point
var lineLengthAng;
var lineLength;
var arrowWidth;
var arrowHeadMid;
var arrowHeadBase;
var arrowLength;
var concaveLength;
var arrowConcave;
var arrowConcaveFactor;
var newPointArray = [];
var dropLst = new Array()
var prefsDrop
var run = true;
try{
var sLay = doc.activeLayer
var sLayName = sLay.name + ' Shape Path'
var cPath = doc.pathItems.getByName(sLayName)
var ptArray = new Array();
var ptNewArray = new Array();
var idPath;
recordAnchors (cPath);
var numAnchors = ptArray.length;
}
catch(e){
run = false;
var numAnchors=0
}
if(run){main ()}
function main(){
switch (numAnchors){
case 0:
alert('An arrow Layer is not selected');
break;
case 6:
if(Math.round (ptArray[0][0])==Math.round (ptArray[4][0])&&Math.round (ptArray[0][1])==Math.round (ptArray[4][1])){
lineLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[5][0], ptArray[5][1]);
arrowWidth = lineL (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineLengthAng = getAng (ptArray[2][0], ptArray[2][1], ptArray[5][0], ptArray[5][1]);
lineAngTan1 = getAng (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineAngTan2 = getAng (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowHeadMid = bisect (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
arrowHeadBase = (ptArray[0][0], ptArray[0][1]);
arrowLength = lineL (ptArray[2][0], ptArray[2][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[0][0], ptArray[0][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[2] = [ptArray[2][0],ptArray[2][1]]; //tip
newPointArray[4] = newPointArray[0] = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base
var tempPt = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength), lineLengthAng);//temp midpoint for arrow ends
newPointArray[1] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan2); //first arrow end
newPointArray[3] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan1); //second arrow end
newPointArray[5] = newPt (ptArray[2][0], ptArray[2][1], -(lineLength), lineLengthAng); //arrow end
}
else{
lineLength = lineL (ptArray[0][0], ptArray[0][1], ptArray[3][0], ptArray[3][1]);
arrowWidth = lineL (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
lineLengthAng = getAng (ptArray[0][0], ptArray[0][1], ptArray[3][0], ptArray[3][1]);
lineAngTan1 = getAng (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
lineAngTan2 = getAng (ptArray[4][0], ptArray[4][1], ptArray[2][0], ptArray[2][1]);
arrowHeadMid = bisect (ptArray[2][0], ptArray[2][1], ptArray[4][0], ptArray[4][1]);
arrowHeadBase = (ptArray[3][0], ptArray[3][1]);
arrowLength = lineL (ptArray[3][0], ptArray[3][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[3] = [ptArray[3][0],ptArray[3][1]]; //tip
newPointArray[5] = newPointArray[1] = newPt (ptArray[3][0], ptArray[3][1], (arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base
var tempPt = newPt (ptArray[3][0], ptArray[3][1], (arrowLength), lineLengthAng);//temp midpoint for arrow ends
newPointArray[2] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan2); //first arrow end
newPointArray[4] = newPt (tempPt[0],tempPt[1], -(arrowWidth)/2, lineAngTan1); //second arrow end
newPointArray[0] = newPt (ptArray[3][0], ptArray[3][1], (lineLength), lineLengthAng); //arrow end
}
break;
case 10:
lineLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[7][0], ptArray[7][1]);
arrowWidth = lineL (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineLengthAng = getAng (ptArray[2][0], ptArray[2][1], ptArray[7][0], ptArray[7][1]);
lineAngTan1 = getAng (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
lineAngTan2 = getAng (ptArray[3][0], ptArray[3][1], ptArray[1][0], ptArray[1][1]);
arrowHeadMid = bisect (ptArray[1][0], ptArray[1][1], ptArray[3][0], ptArray[3][1]);
arrowHeadBase = (ptArray[0][0], ptArray[0][1]);
arrowLength = lineL (ptArray[2][0], ptArray[2][1], arrowHeadMid[0], arrowHeadMid[1]);
concaveLength = lineL (ptArray[2][0], ptArray[2][1], ptArray[0][0], ptArray[0][1]);
arrowConcaveFactor = concaveLength/arrowLength;
ui ();
//Get new points
newPointArray[2] = [ptArray[2][0],ptArray[2][1]]; //tip1
newPointArray[7] = [ptArray[7][0],ptArray[7][1]]; //tip2
newPointArray[4] = newPointArray[0] = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base 1
newPointArray[5] = newPointArray[9] = newPt (ptArray[7][0], ptArray[7][1], (arrowLength*arrowConcaveFactor), lineLengthAng); // arrow base 2
var tempPt1 = newPt (ptArray[2][0], ptArray[2][1], -(arrowLength), lineLengthAng);//temp midpoint for arrow ends 1
var tempPt2 = newPt (ptArray[7][0], ptArray[7][1], (arrowLength), lineLengthAng);//temp midpoint for arrow ends 2
newPointArray[1] = newPt (tempPt1[0],tempPt1[1], -(arrowWidth)/2, lineAngTan2); //first arrow end 1
newPointArray[6] = newPt (tempPt2[0],tempPt2[1], -(arrowWidth)/2, lineAngTan2); //first arrow end 2
newPointArray[3] = newPt (tempPt1[0],tempPt1[1], -(arrowWidth)/2, lineAngTan1); //second arrow end 1
newPointArray[8] = newPt (tempPt2[0],tempPt2[1], -(arrowWidth)/2, lineAngTan1); //second arrow end 2
//newPointArray[5] = newPt (ptArray[2][0], ptArray[2][1], -(lineLength), lineLengthAng); //arrow end
break;
};
var myPathInfo = mkNewPathInfo ();
deltPath ();
var tempPath = app.activeDocument.pathItems.add( 'temp', myPathInfo);
selPath ();
doc.activeLayer = sLay;
mkPath ()
tempPath.remove();
app.preferences.rulerUnits = oldPrefs;
changeRes (oldRes)
}//end function run
function ui(){
dlg = new Window('dialog','Edit Arrow');
dlg.alignChildren = ['left','top'];
dlg.aLgp = dlg.add('group');
dlg.aLgp.alignChildren = ['left','top'];
dlg.aLgp.sTxts = dlg.aLgp.add('statictext',undefined,'Arrow Length');
dlg.aLgp.ALen = dlg.aLgp.add('edittext',undefined,arrowLength.toFixed (2));
dlg.aLgp.ALen.size = [50,12];
dlg.aLgp.sTxte = dlg.aLgp.add('statictext',undefined,'px');
for(var i=0;i<xmlPref.prefs.arrows.children().length();i++){
dropLst[i]= xmlPref.prefs.arrows.children()[i].@preset
}
dlg.aWgp = dlg.add('group');
dlg.aWgp.alignChildren = ['left','top'];
dlg.aWgp.sTxts = dlg.aWgp.add('statictext',undefined,'Arrow Width');
dlg.aWgp.AWid = dlg.aWgp.add('edittext',undefined,arrowWidth.toFixed (2));
dlg.aWgp.AWid.size = [50,12];
dlg.aWgp.sTxte = dlg.aWgp.add('statictext',undefined,'px');
dlg.aConGp = dlg.add('group');
dlg.aConGp.alignChildren = ['left','top'];
dlg.aConGp.sTxts = dlg.aConGp.add('statictext',undefined,'Arrow Concave');
dlg.aConGp.ACon = dlg.aConGp.add('edittext',undefined,((1-arrowConcaveFactor)*100).toFixed (2));
dlg.aConGp.ACon.size = [50,12];
dlg.aConGp.sTxte = dlg.aConGp.add('statictext',undefined,'%');
dlg.btnGp = dlg.add('group');
dlg.btnGp.okay = dlg.btnGp.add('button',undefined,'Okay');
dlg.btnGp.cancel = dlg.btnGp.add('button',undefined,'Cancel');
dlg.prefsPn = dlg.add('panel',undefined,'Preferences');
dlg.prefsPn.alignChildren = ['left','top'];
dlg.prefsPn.save = dlg.prefsPn.add('button',undefined,'Save Preference');
dlg.prefsPn.del = dlg.prefsPn.add('button',undefined,'Delete Preference');
prefsDrop = dlg.prefsPn.add('dropdownlist',undefined,dropLst);
prefsDrop.title = 'Load Preferences';
prefsDrop.size = [300,25];
prefsDrop.onChange = function(){
dlg.aLgp.ALen.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].leng
dlg.aWgp.AWid.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].aWidth
dlg.aConGp.ACon.text = xmlPref.prefs.arrows.children()[parseInt(prefsDrop.selection)].concave
};
dlg.aLgp.ALen.onChange = function(){
textToNum (1, 10000, dlg.aLgp.ALen, arrowLength);
}
dlg.aWgp.AWid.onChange = function(){
textToNum (1, 50000, dlg.aWgp.AWid, arrowWidth);
}
dlg.aConGp.ACon.onChange = function(){
textToNum (-50, 50, dlg.aConGp.ACon, arrowConcaveFactor);
}
dlg.prefsPn.save.onClick = function(){
savePrefs ();
}
dlg.prefsPn.del.onClick = function(){
if(prefsDrop.selection != null){
var selc = parseInt(prefsDrop.selection);
prefsDrop.remove(selc);
delete xmlPref.prefs.arrows.children()[selc];
writeXMLFile (xmlFile, xmlPref)
}
}
dlg.btnGp.okay.onClick = function(){
dlg.close();
arrowLength = parseInt (dlg.aLgp.ALen.text);
arrowWidth = parseInt (dlg.aWgp.AWid.text);
arrowConcaveFactor = (100 - parseInt (dlg.aConGp.ACon.text))*.01;
}
dlg.btnGp.cancel.onClick = function(){
run = false;
dlg.close()
}
dlg.show();
}
function textToNum(sMin,sMax,e,def){
def=Math.round (def)
var sHold = def;
if(isNaN(parseInt(e.text))){
alert('"' + e.text + '" is not a number\nEnter a value between ' + sMin + '-' + sMax );
e.text = def};
else{
sHold = parseInt(e.text)
if(sHold < sMin){
rangeAlert();
sHold = sMin;
};
if(sHold > sMax){
rangeAlert();
sHold = sMax;
};
e.text = sHold;
}
function rangeAlert(){alert('Number range must be between ' + sMin + '-' + sMax)};
};//end function textToNum
function recordAnchors(pathObj){
ptArray = new Array();
for(var i=0;i<pathObj.subPathItems[0].pathPoints.length;i++){
ptNewArray [i] = ptArray[i] = pathObj.subPathItems[0].pathPoints[i].anchor;
}//end loop
}//end function
function lineL (ax,ay,bx,by){
var x = Math.abs (ax-bx);
var y = Math.abs (ay-by);
var len = Math.sqrt (Math.pow (x, 2) + Math.pow (y, 2));
return len
}
function getAng(ax,ay,bx,by){
var dx = ax-bx;
var dy = ay-by;
var theta = Math.atan2 (dy, dx);
//theta *= 180/Math.PI
return theta;
}
function newPt (x1,y1,rad,ang){
var newEnd = new Array();
newEnd[0] = x1 + rad * Math.cos (ang);
newEnd[1] = y1 + rad * Math.sin (ang);
return newEnd;
}
function bisect(x1,y1,x2,y2){
var midPoint = new Array();
midPoint[0] = (x1+x2)/2;
midPoint[1] = (y1+y2)/2;
return midPoint;
}
/////////////////////
function extractSubPathInfo(pathObj,oldPoint,newPoint){
var pathArray = new Array();
var pl = pathObj.subPathItems.length;
for(var s=0;s<pl;s++){
var pArray = new Array();
for(var i=0;i<pathObj.subPathItems[s].pathPoints.length;i++){
pArray[i] = new PathPointInfo;
pArray[i].kind = pathObj.subPathItems[s].pathPoints[i].kind;
if(pathObj.subPathItems[s].pathPoints[i].anchor[0]==oldPoint[0] &&
pathObj.subPathItems[s].pathPoints[i].anchor[1]==oldPoint[1]){
pArray[i].anchor = newPoint;
pArray[i].leftDirection = newPoint;
pArray[i].rightDirection = newPoint;
}else{
pArray[i].anchor = pathObj.subPathItems[s].pathPoints[i].anchor;
pArray[i].leftDirection = pathObj.subPathItems[s].pathPoints[i].leftDirection;
pArray[i].rightDirection = pathObj.subPathItems[s].pathPoints[i].rightDirection;
}
};
pathArray[pathArray.length] = new Array();
pathArray[pathArray.length - 1] = new SubPathInfo();
pathArray[pathArray.length - 1].operation = pathObj.subPathItems[s].operation;
pathArray[pathArray.length - 1].closed = pathObj.subPathItems[s].closed;
pathArray[pathArray.length - 1].entireSubPath = pArray;
};
return pathArray;
};
function deltPath(){
var idDlt = charIDToTypeID( "Dlt " );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
idPath = charIDToTypeID( "Path" );
var idvectorMask = stringIDToTypeID( "vectorMask" );
ref1.putEnumerated( idPath, idPath, idvectorMask );
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref1.putEnumerated( idLyr, idOrdn, idTrgt );
desc2.putReference( idnull, ref1 );
executeAction( idDlt, desc2, DialogModes.NO );
}
function selPath(){
var idslct = charIDToTypeID( "slct" );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
ref1.putName( idPath, "temp" );
desc2.putReference( idnull, ref1 );
executeAction( idslct, desc2, DialogModes.NO );
}
function mkPath(){
var idMk = charIDToTypeID( "Mk " );
var desc10 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref3 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
ref3.putClass( idPath );
desc10.putReference( idnull, ref3 );
var idAt = charIDToTypeID( "At " );
var ref4 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idPath = charIDToTypeID( "Path" );
var idvectorMask = stringIDToTypeID( "vectorMask" );
ref4.putEnumerated( idPath, idPath, idvectorMask );
desc10.putReference( idAt, ref4 );
var idUsng = charIDToTypeID( "Usng" );
var ref5 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref5.putEnumerated( idPath, idOrdn, idTrgt );
desc10.putReference( idUsng, ref5 );
executeAction( idMk, desc10, DialogModes.NO );
}
function mkNewPathInfo(){
var pathArray = new Array();
var piArray = new Array();
for(var i=0;i<newPointArray.length;i++){
piArray[i] = new PathPointInfo;
piArray[i].kind = PointKind.CORNERPOINT;
piArray[i].anchor = newPointArray[i];
piArray[i].leftDirection = newPointArray[i];
piArray[i].rightDirection = newPointArray[i];
}//end loop
pathArray[0] = new SubPathInfo();
pathArray[0].operation = ShapeOperation.SHAPEXOR;
pathArray[0].closed = true;
pathArray[0].entireSubPath = piArray;
return pathArray;
};//end function
function changeRes(res){
var idImgS = charIDToTypeID( "ImgS" );
var desc15 = new ActionDescriptor();
var idRslt = charIDToTypeID( "Rslt" );
var idRsl = charIDToTypeID( "#Rsl" );
desc15.putUnitDouble( idRslt, idRsl, res );
executeAction( idImgS, desc15, DialogModes.NO );
}
function savePrefs(){
var newName = prompt ('Enter New Profile Name', '', 'Save Preferences');
var prefGood = true;
for(var i=0;i<prefsDrop.items.length;i++){
if(prefsDrop.items[i].text == newName){
newName = prompt ('That preset exists\nPlease choose a unique name','', 'Save Preferences' );
i=0
}
}
prefsDrop.add('item',newName)
xmlPref.prefs.arrows.appendChild (XML('<arrow preset="'+ newName+ '"><leng>'+dlg.aLgp.ALen.text+'</leng><aWidth>'+dlg.aWgp.AWid.text+'</aWidth><concave>'+dlg.aConGp.ACon.text+'</concave></arrow>'));
writeXMLFile (xmlFile, xmlPref)
}
//===============READ/WRITE functions========================================
//=========================================================================
function readXMLFile(file) {
if (!file.exists) {
alert( "Cannot find file: " + deodeURI(file.absoluteURI));
}
else{
file.encoding = "UTF8";
file.lineFeed = "unix";
file.open("r", "TEXT", "????");
var str = file.read();
file.close();
return new XML(str);
};
};
function writeXMLFile(file, xml) {
if (!(xml instanceof XML)) {
alert( "Bad XML parameter");
};
else{
file.encoding = "UTF8";
file.open("w", "TEXT", "????");
//unicode signature, this is UTF16 but will convert to UTF8 "EF BB BF"
file.write("\uFEFF");
file.lineFeed = "unix";
file.write(xml.toXMLString());
file.close();
};
};
//=========================================================================
Copy link to clipboard
Copied
IMO the change to the line tool in 2021 is a disaster. Chuck can you create a visible line shape layer. Adobe might as well disable disable shape option as well as pixel. I get no stroke with the line shape tool do you get one?.
Copy link to clipboard
Copied
What happens when you try these tips from @_Mark_Dahm_ and @Pete.Green ?
Copy link to clipboard
Copied
JJ, yes, I get a stroke.
Copy link to clipboard
Copied
It seems like I have a problem. I delete all preferences and now I do get a stroke. It still I bummer I can set not arrows a percentage of line weight,.
Copy link to clipboard
Copied
Did you try and change the position of the stroke? On the inside doesn't work.
Copy link to clipboard
Copied
The Preference the file was "Adobe Photoshop 2021 Prefs.psp" that cause the problem and just restored it and the does not work again. I change the stoke location and it fix the problem. When 2021 migrated my preferences my preference was indeed inside so I was bitten by the new line tool inside bug. Some day Adobe may learn how to develop and test code. I do not think that day will be within my life time though.
Copy link to clipboard
Copied
Ouch.
It's possible that the alignment setting for stroke is shared by other tools, and by changing in one place, it works against you in another. We'll look into that. The default is center aligned.
At the least we should differentiate it so that it could be set independently for the line tool.
Copy link to clipboard
Copied
Adobe broke the line shape tool when they removed line weight. The Line Shape tool can only create arrowhead Shape layers. You have to use the rectangle shape tool to create line weight shape layers. Use both tool and merge the two shape layers to create shape layer with arrowheads you want. For if you Stroke the Arrow Shape Layers the arrow line stroke will be a 0x wide stroke line, or a 1x wide stroke line or a 2x wide stroke line. None of these will be the arrow shape layer you want. For only 0x wide line arrow layer arrowheads point to the correct location you do not an arrow shaft you have been shafted.
Copy link to clipboard
Copied
The line tool draws a line and an arrowhead. The line is identical to the pen tool, and one perhaps negative side effect is that strokes that are inside aligned don't appear on two-point segments. Make sure your stroke alignment is center.
Copy link to clipboard
Copied
The line tool is not like the pen. With the pen tool you can drag out curved line segments and straight line segments connected together. With line tool can drag out a single straight line segment with optional closed arrowhead shape end point path where the point of the arrow is the pixels clicked on to create the line. Stroking the shape along the shape's path will extent the arrowheads past the line end point by some amount of pixels it depends on stroke width and stroke style whether the arrow tip will be blunt, rounded, or pointed. The line tool need to have a weight option so line can have closed paths so we can create line shape layer. Without line weight we can only create arrowhead shape layers for a line path can have on fill. Shape are fill layer with vector layer mask. A straight line path does not enclose any area so fill is not possible
Copy link to clipboard
Copied
Under Windows 10, Photoshop CC allows the user to set the width and length of a line's arrowhead by percentage (%), but under macOS one has to constantly calculate appropriate widths and lengths as one changes the line's width. At least under macOS one can now specify a line width with pixels, something that was missing for no practical reason for a long time, too.
Without percentages, this is incredibly tedious - and it worked for two decades in Photoshop CS and older. Anyone who marks up images with lines with arrowheads needs that feature back.
Please update!