Skip to main content
Inspiring
March 28, 2023
Answered

Javascript or ScriptListener - which one is better?

  • March 28, 2023
  • 2 replies
  • 3059 views

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.

This topic has been closed for replies.
Correct answer jazz-y

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:

 
 

 

 

2 replies

Inspiring
March 28, 2023

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é 😄

 

jazz-yCorrect answer
Legend
March 28, 2023

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:

 
 

 

 

Inspiring
March 28, 2023

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.

 

 

Legend
March 28, 2023

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.