Photoshop: How to check if document has background layer with jsx javascript script

  • 3
  • Question
  • Updated 6 months ago
  • Answered
  • (Edited)
I am going to answer my own question here. Sorry for the keyword soup in the title, but I wanted to make this very searchable for later, in case someone else has this question.

For context, when scripting in photoshop, the index of a layer it is offset by +1 if there is a background layer present. This can cause hard to track bugs if you don't realize it. Lot's of "It works on my file..." issues.

So far, I've discovered 4 methods to check for the presence of a background layer in order to account for that offset. I was curious about performance, so I wrote a quick test and results are... interesting.

Usually action manager code is lighting fast compared to the API, but in this case, when no background is present, the AM code is way slower!

#target photoshop
var timeStart = 0;

var activeDocBGLyr = function()
{
    try{ 
        activeDocument.backgroundLayer; 
        return true; 
    }catch(e){ 
        return false; 
    }
}

var activeDocHasPropBGLyr = function()
{
        return activeDocument.hasOwnProperty("backgroundLayer"); 
}

var artLyrIsBG = function()
{
    return activeDocument.artLayers[activeDocument.artLayers.length - 1].isBackgroundLayer ? 0 : 1;
}

 var hasBackgroundAM = function ()
    {
        var res = undefined;// jshint ignore:line
        try
        {
           var ref = new ActionReference();
           ref.putProperty( 1349677170, 1315774496 );
           ref.putIndex( 1283027488, 0 );
           executeActionGet( ref ).getString( 1315774496 );
           res = true;
        }
        catch ( e )
        {
           res = false
        }
        return res;
    }

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    activeDocBGLyr();
}
$.writeln ("activeDocBGLyr(): "+ $.hiresTimer/1000000);

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    activeDocHasPropBGLyr();
}
$.writeln ("activeDocHasPropBGLyr(): "+ $.hiresTimer/1000000);

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    artLyrIsBG();
}
$.writeln ("artLyrIsBG(): "+ $.hiresTimer/1000000)

timestart = $.hiresTimer;

for (i=0;i<1000;i++)
{
    hasBackgroundAM();
}
$.writeln ("hasBackgroundAM(): "+ $.hiresTimer/1000000)


In my case output with background:
activeDocBGLyr(): 0.139192
activeDocHasPropBGLyr(): 0.106644
artLyrIsBG(): 5.53341
hasBackgroundAM(): 0.067161

Without background:
activeDocBGLyr(): 0.301541
activeDocHasPropBGLyr(): 0.143729
artLyrIsBG(): 5.452488
hasBackgroundAM(): 5.955047
So for ease of use, legibility, and efficiency, the "hasOwnProperty" version seems to be the clear winner!

From now on, I will be using 
activeDocument.hasOwnProperty("backgroundLayer")
either wrapped as a function or just in-line.
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
  • useful

Posted 6 months ago

  • 3
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
Fixed broken syntax from copy-paste...
Photo of Paul Riggott

Paul Riggott

  • 359 Posts
  • 144 Reply Likes
Yet another..

function hasBackground() { 
   var ref = new ActionReference(); 
   ref.putProperty( charIDToTypeID("Prpr"), charIDToTypeID( "Bckg" )); 
   ref.putEnumerated(charIDToTypeID( "Lyr " ),charIDToTypeID( "Ordn" ),charIDToTypeID( "Back" ));
   var desc =  executeActionGet(ref); 
   var res = desc.getBoolean(charIDToTypeID( "Bckg" )); 
   return res;   
};
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
Out of curiosity... how did you even find those charID?
Photo of Paul Riggott

Paul Riggott

  • 359 Posts
  • 144 Reply Likes
It just a matter of looking through the descriptor.
This script was written by Javier Aroche and I have just put a front end on it.
You will be able to run the script from the Help Menu.

 www.ps-bridge-scripts.talktalk.net/download/AM_Details.zip
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
Thank you!
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
UPDATE:

Because I assumed things and was lazy, I didn't catch something...

activeDocument.hasOwnProperty("backgroundLayer") ALWAYS RETURNS TRUE. *facepalm*

Below is updated to remove that as an option and to add @Paul's snippet in the test.

#target photoshop
var timeStart = 0;

var activeDocBGLyr = function()
{
    try{ 
        activeDocument.backgroundLayer; 
        return true; 
    }catch(e){ 
        return false; 
    }
}

var artLyrIsBG = function()
{
    return activeDocument.artLayers[activeDocument.artLayers.length - 1].isBackgroundLayer;
}

 var hasBackgroundAM = function ()
    {
        var res = undefined;// jshint ignore:line
        try
        {
           var ref = new ActionReference();
           ref.putProperty( 1349677170, 1315774496 );
           ref.putIndex( 1283027488, 0 );
           executeActionGet( ref ).getString( 1315774496 );
           res = true;
        }
        catch ( e )
        {
           res = false
        }
        return res;
    }

var  hasBackground = function () { 
    var ref = new ActionReference(); 
    ref.putProperty( charIDToTypeID("Prpr"), charIDToTypeID( "Bckg" )); 
    ref.putEnumerated(charIDToTypeID( "Lyr " ),charIDToTypeID( "Ordn" ),charIDToTypeID( "Back" ));
    var desc =  executeActionGet(ref); 
    var res = desc.getBoolean(charIDToTypeID( "Bckg" )); 
    return res;   
};

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    activeDocBGLyr();
}
$.writeln ("activeDocBGLyr(): "+ $.hiresTimer/1000000+ "= "+activeDocBGLyr());

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    artLyrIsBG();
}
$.writeln ("artLyrIsBG(): "+ $.hiresTimer/1000000+ "= "+artLyrIsBG())

timestart = $.hiresTimer;

for (i=0;i<1000;i++)
{
    hasBackgroundAM();
}
$.writeln ("hasBackgroundAM(): "+ $.hiresTimer/1000000+ "= "+hasBackgroundAM())

timestart = $.hiresTimer;

for (i=0;i<1000;i++)
{
    hasBackground();
}
$.writeln ("hasBackground(): "+ $.hiresTimer/1000000+ "= "+hasBackground())
With bg:
activeDocBGLyr(): 0.150941= true
artLyrIsBG(): 6.665813= true
hasBackgroundAM(): 0.080708= true
hasBackground(): 0.086598= true

No BG:
activeDocBGLyr(): 0.321263= false
artLyrIsBG(): 6.0933= false
hasBackgroundAM(): 5.796047= false
hasBackground(): 0.083114= false

The new code from @Paul is the clear and uncontested winner!
Photo of Kukurykus

Kukurykus

  • 664 Posts
  • 170 Reply Likes
That last method is fastest indeed, hovever I don't use it as it checks active layer, while mostly a layer that I may have selected is not last / background. So far I used method 3, but like we see it is slow when there is no background in document. So the best one from all 4 proposed I would recomend 1st one.

Still if I wanted to use Action Manager only what I do in most cases as it is supposed to be fastest way to get the result I would chose method 3.

Now the question, how is that possible that method one, that is Domestic Oriented Model method, so based on Action Manager is fastest that Action Manager method (at least in case when there is no background, however not only).

Who can answer this question, since I assume there must be some uknown AM method we don't know yet that has been originally used by programmers they converted it to DOM method. If so that secret AM method to check there is bacground would be even faster than your method one.
(Edited)
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
My suspicion is that the data collection for the error code delivered to the "catch" is the part that's slowing things down. Just my guess, though.
Photo of Kukurykus

Kukurykus

  • 664 Posts
  • 170 Reply Likes
Yes of course. That's my guess too, but you can't do it other way like using try...catch statement, so if it is somehow done then most possibly like it or there is some magic Action Manager code that doesn't use "checking for error" part. Otherwise how that is possible DOM works faster than AM.

I hope someone smart will come with AM code there could be used originally to be later converted to Domestic Oriented Model. I do believe there must be some, as I didn't see yet anything in DOM that wouldn't have needed to be based on AM. Probably Paul Riggott may elight us a little more :)
(Edited)
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
Level Up!

With a little fiddling, the code Paul Riggott, and digging into document properties, I have a thing! Note, this works on the active document, not the active layer.

var  hasBackgroundLayerAM = function () { 
    var ref = new ActionReference(); 
    ref.putProperty( charIDToTypeID("Prpr"), stringIDToTypeID( "hasBackgroundLayer" )); 
    ref.putEnumerated(charIDToTypeID( "Dcmn" ),charIDToTypeID( "Ordn" ),charIDToTypeID( "Trgt" ));
    var desc =  executeActionGet(ref); 
    var res = desc.getBoolean(stringIDToTypeID( "hasBackgroundLayer" )); 
    return res;   
};

timestart = $.hiresTimer;
for (i=0;i<1000;i++)
{
    hasBackgroundLayerAM();
}
$.writeln ("hasBackgroundLayerAM(): "+ $.hiresTimer/1000000+ "= "+hasBackgroundLayerAM());

Gives us
BG:
hasBackgroundLayerAM(): 0.06694= true
No BG:
hasBackgroundLayerAM(): 0.066474= false

And has the added advantage of being almost human readable... 
(Edited)
Photo of Kukurykus

Kukurykus

  • 664 Posts
  • 170 Reply Likes
I knew this solution as well - nothing new for me, so unfortunately it's not answer for my question.

"hasBackgroundLayer" was introduced to some CC Photoshop series. Before that (incl. CS6) there was only "background" available.

Personally I switched from CS6 to CC 2018, and found in no time I can use "hasBackgroundLayer", but because CC 2018 got 3 bugs I couldn't deal with I had to go back to CS6. Only after few months (and over 50 posts about) of me begging Adobe to fix those bugs they fixed just one. Remaining two make my work is still too slow, so I stayed in CS6 EXTENDED.

As you see there was not "hasBackgroundLayer", but even then you could use DOM to check presence of background in document that was faster than some of AM methods to do the same.

Again I'm asking anyone concerned about what is the right code to do it, so check by AM existence of background in document that would take so short time like we can do by DOM (irrespectivelly bG is available or not). Note: not using modern way by recently implemented hasBackgroundLayer
(Edited)
Photo of Max Johnson

Max Johnson, Champion

  • 493 Posts
  • 239 Reply Likes
interesting... thanks for the info! I wonder if it would be good to make two functions, one for legacy and one modern and the modern has a try/catch that falls back to the legacy function (that also needs a try/catch, so has to be it's own discreet function). Then you've got your bases covered.

I really wish I could edit my OP with the updated information I got here.
Photo of Kukurykus

Kukurykus

  • 664 Posts
  • 170 Reply Likes
Yes - taking into consideration how many users choose CS6 Extended, or use both Photoshops it would be comprehended. Regarding missing Action Manager code that DOM comes from probably we will never get to know how that was constructed, but who knows. Photohsop is only one app. I never can not use newer versions though next ones don't differ much from predecessors. The only big differnece are bugs nothing what good was introduced with time can't be so valuable when you meet annoying obstacles. Only yestarday I found another bug about Action Manager that makes in some cases iteration over documents slower than that was in CS6 EXTENDED, just look at my post:

https://forums.adobe.com/message/10450249#10450249
(Edited)
Photo of Ronald Chambers

Ronald Chambers

  • 54 Posts
  • 6 Reply Likes
Max,
Excellent article. 

I have been searching about why some scripts run with a BG layer and others don't.  I think you answered it with :
For context, when scripting in photoshop, the index of a layer it is offset by +1 if there is a background layer present. This can cause hard to track bugs if you don't realize it. Lot's of "It works on my file..." issues."
So with a BG layer active the first regular active layer will be at index 2 and others after follow as if two layers for BG.  This same problem is present when one builds a script and testing with BG layer but not having in other data.  Usually get an error about some option in JSX is not available on this machine.   The BG layer has to be in slot 0 also????

This is a bigger problem than the runtime to find whether BG is there.  I'd use any option to not have a failed run.

Champion a bunch more of these.  The documentation for JSX is pretty slim and going nowhere.  TOO BAD!!!!  If only we could use C/C++ code to directly modify pixels within JSX we could basically forget plugins and just run JSX.

Thanks,
RONC