• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

Javascript or ScriptListener - which one is better?

Explorer ,
Mar 28, 2023 Mar 28, 2023

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.

TOPICS
Actions and scripting

Views

1.3K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Guide , Mar 28, 2023 Mar 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
...

Votes

Translate

Translate
Adobe
Guide ,
Mar 28, 2023 Mar 28, 2023

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Mar 28, 2023 Mar 28, 2023

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

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Mar 28, 2023 Mar 28, 2023

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:

2023-03-28_16-46-37.png

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:

2023-03-28_16-46-47.png

 
 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Mar 28, 2023 Mar 28, 2023

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.

Schermafbeelding 2023-03-28 164706.jpg

 

And after paying that price your script is now 3 lines long, easy to read, and the functionality is reusable in another script.

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Mar 28, 2023 Mar 28, 2023

Copy link to clipboard

Copied

@jefbr, remove background layer from the document and run your code again 😉

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Mar 28, 2023 Mar 28, 2023

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

the documentHasBackgroundLayer function)

 

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.

 

jefbr_0-1680019749091.png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Mar 28, 2023 Mar 28, 2023

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')

 

 

 

 
 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Mar 29, 2023 Mar 29, 2023

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

 

@jazz-y

  • For fun: Do you actually write this code 'by hand' or do you generate it using a plugin and then tweak it?
  • Do you ever reuse code? If so, how?
  • My usual approach, wrapping SL code as functions in a (messy) library I can just plug in, works.  How can I do better? I'm very interested in learning from you.

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Guide ,
Mar 29, 2023 Mar 29, 2023

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.

 
 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Contributor ,
Mar 29, 2023 Mar 29, 2023

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.

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Mar 29, 2023 Mar 29, 2023

Copy link to clipboard

Copied

LATEST
quote

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.

 

Does anybody happen to know what xbytor is up to nowadays? 

I think I haven’t noticed them on the Forum in a long time. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines