getting layer by index performance issue (CS2)

Discussion of actual or possible Photoshop Scripting Bugs, Anomalies and Documentation Errors

Moderators: Tom, Kukurykus

xbytor

getting layer by index performance issue (CS2)

Post by xbytor »

Making the reference and active is done before calling the function so I would expect the performance hit there and not the function.

I misspoke. I meant to say the making obtaining a DOM reference to the active layer is the problem, which is apparently no longer the case. It may be that PS-JS is deferring some of the actual layer's DOM object construction until a property is actually referenced.

If that's the case, then the

Code: Select all    this.layerWidth = layer.bounds[2].value - layer.bounds[0].value;

will be where the majority of the cycles are getting burned. I recommend running the test again using line-level profiling in ESTK to determine precisely where the cpu is spending the bulk of its time.
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

xbytor wrote:It may be that PS-JS is deferring some of the actual layer's DOM object construction until a property is actually referenced.I didn't think of that so you may be right. I agree with you that the main thing when working with a lot of layers is it is better not to make DOM layer object references.

maybe it is time for me to do some more timing test.
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

Here is a quick test with a document that has 150 top level layers. Like EPA gas figures, the numbers themselves are not as important as the differences in the numbers.

Getting the bounds for each layer using only action manager took 0.156 sec. Making the layer active took 11.406 sec. Making a layer reference didn't add much time or accessing a property of that reference. Nor did adding getLayerInformation(). Note I ran the test in ESTK and if the ESTK window was full screen it run a good bit faster. The times above are with Photoshop showing in the background. This is on Windows. I don't know how that would effect the results on Mac. Here is the code I used. To run the different test I just comment out lines. It seems at least in this test that making the layer active is the real speed hit.
Code: Select all    function getAllLayersByIndex(){
       function getNumberLayers(){
       var ref = new ActionReference();
       ref.putProperty( charIDToTypeID("Prpr") , charIDToTypeID("NmbL") )
       ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
       return executeActionGet(ref).getInteger(charIDToTypeID("NmbL"));
       }
       function hasBackground() {
           var ref = new ActionReference();
           ref.putProperty( charIDToTypeID("Prpr"), charIDToTypeID( "Bckg" ));
           ref.putEnumerated(charIDToTypeID( "Lyr " ),charIDToTypeID( "Ordn" ),charIDToTypeID( "Back" ))//bottom Layer/background
           var desc =  executeActionGet(ref);
           var res = desc.getBoolean(charIDToTypeID( "Bckg" ));
           return res   
        };
       function getLayerType(idx,prop) {       
           var ref = new ActionReference();
           ref.putIndex(charIDToTypeID( "Lyr " ), idx);
           var desc =  executeActionGet(ref);
           var type = desc.getEnumerationValue(prop);
           var res = typeIDToStringID(type);
           return res   
        };
       function getLayerVisibilityByIndex( idx ) {
           var ref = new ActionReference();
           ref.putProperty( charIDToTypeID("Prpr") , charIDToTypeID( "Vsbl" ));
           ref.putIndex( charIDToTypeID( "Lyr " ), idx );
           return executeActionGet(ref).getBoolean(charIDToTypeID( "Vsbl" ));;
       };
       var cnt = getNumberLayers()+1;
       var res = new Array();
       if(hasBackground()){
    var i = 0;
          }else{
    var i = 1;
          };
       var prop =  stringIDToTypeID("layerSection")
       for(i;i<cnt;i++){
          var temp = getLayerType(i,prop);
          if(temp != "layerSectionEnds") res.push(i);
       };
       return res;
    };

    function makeActiveByIndex( idx, visible ){
        var desc = new ActionDescriptor();
          var ref = new ActionReference();
          ref.putIndex(charIDToTypeID( "Lyr " ), idx)
          desc.putReference( charIDToTypeID( "null" ), ref );
          desc.putBoolean( charIDToTypeID( "MkVs" ), visible );
       executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO );
    };
function getLayerBoundsByIndex( idx ) {
    var ref = new ActionReference();
    ref.putProperty( charIDToTypeID("Prpr") , stringIDToTypeID( "bounds" ));
    ref.putIndex( charIDToTypeID( "Lyr " ), idx );
    var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID( "bounds" ));
    var bounds = [];// array of Numbers as pixels regardless of ruler
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('top')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('left')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('bottom')));
    bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('right')));
    return bounds;
}
function getLayerInformation(layer){
   this.layerWidth = layer.bounds[2].value - layer.bounds[0].value;
    this.layerHeight = layer.bounds[3].value - layer.bounds[1].value;
   return this;
}
function main(){
    var layers = getAllLayersByIndex();
    for (var i=0 ; i < layers.length ; i++) {
       
        //var b = getLayerBoundsByIndex(layers);
        //  var width = b[2]-b[0];
       //   var height = b[3]-b[1];
         makeActiveByIndex( layers, false );
         var layer = activeDocument.activeLayer;
         var layerBounds = getLayerInformation(layer);
          //var layerBounds = layer.bounds;
         // var height = layerBounds.layerHeight;
         // var width = layerBounds.layerWidth;
    }
}
var t = new Timer();
main();
t.getElapsed();
///////////////////////////////////////////////////////////////////////////////
// Object: Timer
// Usage: Time how long things take or delay script execution
// Input: <none>
// Return: Timer object
// Example:
//
//   var a = new Timer();
//   for (var i = 0; i < 2; i++)
//      a.pause(3.33333);
//   a.getElapsed();
//  jeff tranberry
///////////////////////////////////////////////////////////////////////////////
function Timer() {
   // member properties
   this.startTime = new Date();
   this.endTime = new Date();
   
   // member methods
   
   // reset the start time to now
   this.start = function () { this.startTime = new Date(); }
   
   // reset the end time to now
   this.stop = function () { this.endTime = new Date(); }
   
   // get the difference in milliseconds between start and stop
   this.getTime = function () { return (this.endTime.getTime() - this.startTime.getTime()) / 1000; }
   
   // get the current elapsed time from start to now, this sets the endTime
   this.getElapsed = function () { this.endTime = new Date(); return this.getTime(); }
   
   // pause for this many seconds
   this.pause = function ( inSeconds ) {
      var t = 0;
      var s = new Date();
      while( t < inSeconds ) {
         t = (new Date().getTime() - s.getTime()) / 1000;
      }
   }
}
robpat

getting layer by index performance issue (CS2)

Post by robpat »

I need to access parent layer sets and children layers in my work.
Access them using the DOM is very slow.

Mike's code returns an array of Action Manager layer indices.
I wonder if there is a way to get the index of a parent layer set using an index of a layer.
It should be something like getParentIndex(7) = 11, where 11 is the parent layer set of the layer 7.
I would like to build a tree of Action Manager layer indices.
The tree can then be used instead of the PS DOM when accessing layers.

It seems that Mike's code does not handle the case when there is only one backgroud layer.
An exception "General Photoshop error occurred.\n- The object " <unknown> of back layer" is not currently available."
is thrown when I run it on a psd containing one backgroud layer only.

Also I observed that the active layer would influence scripts' performance.
If the active layer is a layer set containing lots of layers, the performance drops.
I tested Mike's code with two documents containing 466 and 501 layers respectively.
They both have 2 layer sets and a layer on the top level.
Code: Select allDocument     Active layer                        Time spent (ms)
1            LayerSet 0 (contains 429 layers)    9969
             LayerSet 1 (contains 36 layers)     8297
             Layer 2                             8234
2            LayerSet 0 (contains 430 layers)    10641
             LayerSet 1 (contains 70 layers)     7282
             Layer 2                             7235
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

The script was an example of working with a large number of layers that is faster than the DOM. I'll admit I never tested it on a document with just one layer.

There is no direct way I know of to get a layer's parent using Action Manager. The layer descriptor does not have a 'parent' key. However each layerset has two AM indexes. One for the start of the set and one for the end of the set. So it would be possible to add your own parent property as you looped the indexes by keeping track of the starts and ends.
robpat

getting layer by index performance issue (CS2)

Post by robpat »

Thanks for hinting. I have built the tree.
Using the tree is much faster than the DOM as expected.

I believe I find out why masterfab's getLayerInformation() is slow.
LayerSet.bounds is the cumulative bounds of all children layers.
It seems that it is calculated at runtime.
It is very slow if the layer set has lots of layers.

getLayerBoundsByIndex() is different from LayerSet.bounds.
It returns the document's dimensions as the bounds if the layer is a layer set.