Copy link to clipboard
Copied
Hi experts
I am wring a photoshop script and wondering whether javascript will be better or scriptlistener ?
I read that javascript will be faster, but all the APIs are not compatible with all the versions of photoshop.
On the otherhand, scriptlistener may be slower, but it's compatible with all the photoshop versions.
Is it true? Can you please confirm?
I know that many commands can't be executed by javascript; scriptlistener is the only option. But when both are working which one to choose?
For example I am duplicating two layerSets to a different document and aligning it to right side.
In Javascript -
var openDocs = app.documents;
app.displayDialogs = DialogModes.NO;
var curDoc = openDocs[1];
var topLayerSet1 = curDoc.layerSets.getByName("TOP");
var bottomLayerSet1 = curDoc.layerSets.getByName("BOTTOM");
topLayerSet1.duplicate(openDocs[0], ElementPlacement.PLACEATBEGINNING);
bottomLayerSet1.duplicate(openDocs[0], ElementPlacement.PLACEATEND);
curDoc.close(SaveOptions.DONOTSAVECHANGES);
var doc = app.activeDocument;
var topLayerSet2 = doc.layerSets.getByName("TOP");
var bottomLayerSet2 = doc.layerSets.getByName("BOTTOM");
topLayerSet2.translate(5400, 0);
bottomLayerSet2.translate(5400, 0);
In scriptlistener -
//Right Align
var idAlgn = charIDToTypeID( "Algn" );
var desc317 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref27 = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref27.putEnumerated( idLyr, idOrdn, idTrgt );
desc317.putReference( idnull, ref27 );
var idUsng = charIDToTypeID( "Usng" );
var idADSt = charIDToTypeID( "ADSt" );
var idAdRg = charIDToTypeID( "AdRg" );
desc317.putEnumerated( idUsng, idADSt, idAdRg );
var idalignToCanvas = stringIDToTypeID( "alignToCanvas" );
desc317.putBoolean( idalignToCanvas, false );
executeAction( idAlgn, desc317, DialogModes.NO );
Which one shoud I choose?
Will eagerly wait for your valuable comments.
I would say this - if you study the ActionManager (or its upgraded version of batchPlay (UXP)) sufficiently, then there will be no desire to return to DOM objects. I usually only use the DOM for basic functionality related to interacting with the application.
A simple example: a 1x1px file with 601 layers (including background) is attached to a post. Let's say we need to count how many invisible layers are in this document:
DOM code: 
$.hiresTimer
var doc = activeDocument,
invisible = 0
...
Copy link to clipboard
Copied
In both cases, you are dealing with javascript (more precisely, with its variant extendScript).
The first example is the Photoshop Object Model (DOM), the second example is ActionManger code.
The DOM is well documented, however it doesn't fully cover all of Photoshop's automation capabilities. ActionManger has no documentation, it's essentially an internal action language. But with it, you can perform a significantly larger number of operations than with the help of DOM.
ActionManger code typically runs faster or at the same speed as DOM.
Both options have compatibility issues with past versions of Photoshop.
ActionManger is of interest when you need to do something that can't be done with regular DOM functions.
Copy link to clipboard
Copied
To the question "which should I choose?" the best answer is: yes.
What you should do, if you are unsure and have not found what is important to you, is to work with "javascript" as much as possible. When you need to use scriptlistener, think about why you need it now, and why you could need it in the future.
With that in mind, make a function using the scriptlistener code and put it in a separate file.
This file will grow and grow and grow, and will contain all your "i cannot fix this with javascript" code.
Example:
You go from this scriptlistener code:
var idAddT = charIDToTypeID( "AddT" );
var desc1667 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref82 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idfsel = charIDToTypeID( "fsel" );
ref82.putProperty( idChnl, idfsel );
desc1667.putReference( idnull, ref82 );
var idT = charIDToTypeID( "T " );
var desc1668 = new ActionDescriptor();
var idHrzn = charIDToTypeID( "Hrzn" );
var idPxl = charIDToTypeID( "#Pxl" );
desc1668.putUnitDouble( idHrzn, idPxl, 471.000000 );
var idVrtc = charIDToTypeID( "Vrtc" );
var idPxl = charIDToTypeID( "#Pxl" );
desc1668.putUnitDouble( idVrtc, idPxl, 200.000000 );
var idPnt = charIDToTypeID( "Pnt " );
desc1667.putObject( idT, idPnt, desc1668 );
var idTlrn = charIDToTypeID( "Tlrn" );
desc1667.putInteger( idTlrn, 20 );
var idMrgd = charIDToTypeID( "Mrgd" );
desc1667.putBoolean( idMrgd, true );
var idAntA = charIDToTypeID( "AntA" );
desc1667.putBoolean( idAntA, true );
executeAction( idAddT, desc1667, DialogModes.NO );
To this:
function magicWand(x, y, tolerance, selectMergedLayers, AntiAlias) {
var idAddT = charIDToTypeID("AddT");
var idnull = charIDToTypeID("null");
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
var idT = charIDToTypeID("T ");
var idPxl = charIDToTypeID("#Pxl");
var idHrzn = charIDToTypeID("Hrzn");
var idVrtc = charIDToTypeID("Vrtc");
var idPnt = charIDToTypeID("Pnt ");
var idTlrn = charIDToTypeID("Tlrn");
var idMrgd = charIDToTypeID("Mrgd");
var idAntA = charIDToTypeID("AntA");
var adMagicWand = new ActionDescriptor();
var arSelect = new ActionReference();
arSelect.putProperty(idChnl, idfsel);
adMagicWand.putReference(idnull, arSelect);
var adMWPoint = new ActionDescriptor();
adMWPoint.putUnitDouble(idHrzn, idPxl, x);
adMWPoint.putUnitDouble(idVrtc, idPxl, y);
adMagicWand.putObject(idT, idPnt, adMWPoint);
adMagicWand.putInteger(idTlrn, tolerance);
adMagicWand.putBoolean(idMrgd, selectMergedLayers);
adMagicWand.putBoolean(idAntA, AntiAlias);
executeAction(idAddT, adMagicWand, DialogModes.NO);
};
You put this in a separate file called "mystuff.jsx" or something similar and keep it around.
When you have a script you want to make, at the top of the file you put:
//@include "./mystuff.jsx";
This will load the magicWand function so your script "knows it exists".
I will use your code to show how to use it:
//@include "./mystuff.jsx"; // <-------- I ADDED THIS LINE
var openDocs = app.documents;
app.displayDialogs = DialogModes.NO;
var curDoc = openDocs[1];
var topLayerSet1 = curDoc.layerSets.getByName("TOP");
var bottomLayerSet1 = curDoc.layerSets.getByName("BOTTOM");
topLayerSet1.duplicate(openDocs[0], ElementPlacement.PLACEATBEGINNING);
bottomLayerSet1.duplicate(openDocs[0], ElementPlacement.PLACEATEND);
curDoc.close(SaveOptions.DONOTSAVECHANGES);
var doc = app.activeDocument;
var topLayerSet2 = doc.layerSets.getByName("TOP");
var bottomLayerSet2 = doc.layerSets.getByName("BOTTOM");
topLayerSet2.translate(5400, 0);
bottomLayerSet2.translate(5400, 0);
magicWand(100,200,15,true,false); // <-------- AND THIS LINE TOO
The one line on top loads the file with your scriptlistener functions in
The one line on the bottom is using a part of that file, namely the magicWand function I made.
- - - don't read beyond this point, it is extra information in case you want to think ahead - - -
Once you have this, and after you know what is important to you, and you are motivated, you can for example time how long it takes for a piece of code to run...
Try the same code in javascript and in scriptlistener, see which is fastest, and then choose which one you need/prefer.
If it's scriptlistener, add it to the file.
After a while, you will have more and more functions, it will be messy. You can learn about prototyping.
This will let you do all the work again (refactoring it) and you can put the functions not in one huge list, but in a nice structure.
Before (as in the example) you would call magicWand by writing "magicWand(1,2,3,true,true);"
After you have learned prototyping, and you have structured your functions you would call magicWand by for example writing "nirmalya.scriptlistenerFunctions.selections.magicWand(1,2,3,true,true);"
If you have read beyond the point that said don't read beyond this point, start simple.
Just do it the easy way first, achieve what you want to achieve without extras or "best practices".
You will learn from it more than you will from trying to be perfect the first time.
After you have something that works, stop. This is good. Now, you can do it again to make it better.
Or move to the next task. Both are how you grow.
Namasté 😄
Copy link to clipboard
Copied
I would say this - if you study the ActionManager (or its upgraded version of batchPlay (UXP)) sufficiently, then there will be no desire to return to DOM objects. I usually only use the DOM for basic functionality related to interacting with the application.
A simple example: a 1x1px file with 601 layers (including background) is attached to a post. Let's say we need to count how many invisible layers are in this document:
DOM code: 
$.hiresTimer
var doc = activeDocument,
invisible = 0;
for (var i=0; i<doc.layers.length;i++) if (!doc.layers[i].visible) invisible++
alert ('Total layers: ' + i +'\nInvisible layers: ' + invisible +'\nTimer: ' + Number($.hiresTimer)/1000000+ 's')
Result:
ActionManager code:
$.hiresTimer
var s2t = stringIDToTypeID;
invisible = 0;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('hasBackgroundLayer'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var from = executeActionGet(r).getBoolean(p) ? 0 : 1;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var to= executeActionGet(r).getInteger(p);
for (var i = from; i <= to; i++) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('visible'));
r.putIndex(s2t('layer'), i);
if (!executeActionGet(r).getBoolean(p)) invisible++
}
alert ('Total layers: ' + (to + !from) + '\nInvisible layers: ' + invisible +'\nTimer: ' + Number($.hiresTimer)/1000000+ 's')
Result:
Copy link to clipboard
Copied
I agree with Jazz-y for the most part.
The scriptlistener code is significantly faster, and easy to generate.
It is worth mentioning that you need to keep in mind having to change code in the future.
Reusability and Readability of your code will be helpful in this case.
Put it in a different file to reuse what you can when you need it.
You can document it as much as you want and you know exactly how it works.
If it stops working, you only need to fix it in one spot and all your scripts using it are fixed.
Using Jazz-y's code as a base, but adding more readiblity & usability to it, I would have 2 files:
One with all my functions in it, called myFunctions.jsx
function documentHasBackgroundLayer() {
var adSelectBGLayer = new ActionDescriptor();
var arLyr = new ActionReference();
arLyr.putIdentifier(charIDToTypeID('Lyr '), 1);
adSelectBGLayer.putReference(charIDToTypeID('null'), arLyr);
executeAction(charIDToTypeID('slct'), adSelectBGLayer, DialogModes.NO);
};
function countInvisibleLayers() {
var from = (documentHasBackgroundLayer() ? 0 : 1),
length = app.activeDocument.layers.length,
s2t = stringIDToTypeID,
invisibleLayers = 0;
for (var i = from; i < length; i++) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('visible'));
r.putIndex(s2t('layer'), i);
if (!executeActionGet(r).getBoolean(p)) {
invisibleLayers++;
}
}
return invisibleLayers;
};
And another file, theScriptImMakingToday.jsx
//@include "./myFunctions.jsx";
$.hiresTimer
alert('Total layers: ' + app.activeDocument.layers.length + '\nInvisible layers: ' + countInvisibleLayers() + '\nTimer: ' + Number($.hiresTimer) / 1000000 + 's')
It has a price to pay, it is .05s slower than Jazz-y's original code.
And after paying that price your script is now 3 lines long, easy to read, and the functionality is reusable in another script.
Copy link to clipboard
Copied
@jefbr, remove background layer from the document and run your code again 😉
Copy link to clipboard
Copied
It will go kaboom. 🙂 Glad you spotted this @jazz-y
Let me update the well structured code real quick... (puts down coffee, opens the myFunctions.jsx file, goes to
function documentHasBackgroundLayer() {
try{
var adSelectBGLayer = new ActionDescriptor();
var arLyr = new ActionReference();
arLyr.putIdentifier(charIDToTypeID('Lyr '), 1);
adSelectBGLayer.putReference(charIDToTypeID('null'), arLyr);
return executeAction(charIDToTypeID('slct'), adSelectBGLayer, DialogModes.NO);
} catch(e){
return false;
}
};
All scripts using this documentHasBackgroundLayer are now fixed, nobody has to have the confusing AM code in view, and we get most of the performance. Time spent: idk 30 seconds?
This is why I would suggest to wrap all code in reusable functions.
To keep it readable when you need it later.
Copy link to clipboard
Copied
Good! Now test your code on these files:
Test1.psd has 7 invisible layers.
Test2.psd has 5 invisible layers (note - the visibility of the background layer is turned off)
Would it be that easy to change your code to get the right answer? 🤔
* initially my ActionManager code didn't take groups into account, because I just wanted to demonstrate the difference in execution time, now I've fixed it
** i just want to push you to certain conclusions
$.hiresTimer
var s2t = stringIDToTypeID;
invisible = layerCount = 0;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('hasBackgroundLayer'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var from = executeActionGet(r).getBoolean(p) ? 0 : 1;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('numberOfLayers'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var to= executeActionGet(r).getInteger(p);
for (var i = from; i <= to; i++) {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerKind'));
r.putIndex(s2t('layer'), i);
if (executeActionGet(r).getInteger(p) != 13){
layerCount++
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('visible'));
r.putIndex(s2t('layer'), i);
if (!executeActionGet(r).getBoolean(p)) invisible++
}
}
alert ('Total layers: ' + layerCount +'\nInvisible layers: ' + invisible +'\nTimer: ' + Number($.hiresTimer)/1000000+ 's')
Copy link to clipboard
Copied
We're drifting away from OP's question.
Yes it would be easy to change my code. It's not my intent here to produce working code, but to illustrate what I meant for OP. Centralizing your functions and making them readable makes them easier to use+understand.
Using straight-up AM code does not allow me to do that. I cannot infer meaning from your code easily. Wdyt?
If @nirmalya123 is interested in AM... Jazz-y posted a really cool explanation about working with AM code here
Copy link to clipboard
Copied
I wanted to lead you to the idea that mixing DOM and AM code is not a good idea. DOM objects often have attributes that are similar to AM, but carry a completely different meaning. In your code, you use indexes from the activeDocument.layers collection, hoping that it will coincide with the indexing order in AM, however, in AM, indexing of layers is end-to-end within the document (counting from bottom to top) plus it contains indexes of hidden elements (closing sections of groups), in the DOM, layer indexing goes at the level of the current parent element, starts from top to bottom and does not contain indexes of hidden elements.
Basically I write the code by hand. It took me a couple of years to learn and memorize frequently used objects and functions. When it comes to something unfamiliar, I use either scriptListener or app.notifiers (in the latter case, I can immediately get a ready-made descriptor and study it in a way that suits me)
I often reuse code. I have an object template with basic functions for getting and setting parameters, in which I usually put the functions necessary for the operation of a particular script, something like in this topic: .jsx script extremely slow If necessary, I combine some functions and make them dependent on the properties of the parent object (for example, you can activate, delete a layer, document, channel, path with the same function, simply by changing the name of the object class and the name of the action).
I don't like compiling code into one big library because I try to make every script clean of dependencies. In general, I am an ordinary photographer and writing scripts is nothing more than a hobby. You can learn a lot from @xbytor2 xtools, he did a great job putting together a lot of features. Many of the things he did are still beyond my comprehension.
Copy link to clipboard
Copied
Your approach is indeed structurally better and your explanation is indicative of the effort needed to achieve it.
That AM & DOM interpret several properties/attributes in different ways is new to me, thank you.
I personally find it very hard to slow down enough to be able to take the time to investigate properly the documentation, which is why I wrap so much of what I reuse.
Avoiding dependencies makes sense for scripts that do not have anything to do with each other.
I agree on that completely. How would you work in the case of 10 scripts that all use the same function?
Do you copy+paste/write it 10 times? How do you maintain it?
xbytor & xtools... You're so right about that. He's such a legend. But he has retired a few years ago no?
In any case, his tools have saved me a couple of times already. Understanding them is beyond my comprehension as well.
Copy link to clipboard
Copied