getting layer by index performance issue (CS2)

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

Moderators: Tom, Kukurykus

masterfab

getting layer by index performance issue (CS2)

Post by masterfab »

Thank u Mike now i have my array of all layers.

I am trying to create an Xml file from my array using a recursive function :

Code: Select allvar layers = getAllLayersIndex(); // this is the function to get all layers( artLayers and LayerSets) 
function exportLayerInformation(doc, layers, ftn, reverse, depth) {
    var ok = true;
   for (var i = 0; i < layers.length && ok != false; i++) {
       makeActiveByIndex( layers, false );
     var layer = activeDocument.activeLayer;
        if (exportInfo.visibleOnly) { // visible layer only
            if (!layer.visible) {
            continue;
         }
        }
      layer.visible = true;
   
       if (layer.typename == "LayerSet") { //if the layer is a layerSet i use a recursive call to get all layers of this LayerSet
          createMainContainer();   // function i use to create XML element   
          depth =  depth + 1;
          ok = exportLayerInformation(doc, layer.layers, ftn, reverse,depth); //recursive call
          closeMainContainer();
       }else {           
          ok = createSimpleContainer();
      }
    }
    return ok;
  };

But i have an error on the recursive call : illegal argument - argument 2 numeric value expected
I tried to debug my script with ExtendScript debugger and it seems that the problem is : layer.layers
on the recursive call i have this :
exportLayerInformation([document], [Layers], function createSimpleContainer, false, 2)// the 2 argument should be an array
makeActiveByIndex([ArtLayer], false) // the 1 argument of makeActiveByIndex() should be a numeric

Is there is any way that i can modify my function getAllLayerIndex() so that it can return all layers of a layerSet ?

Thank u
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

The script that we have been working with does not return an array of layer objects. It returns an array of Action Manager layer indexes. Action Manager is a lot faster than the DOM but it is hard to mix the two. You could do something like this to get an array of all layer and layersets but I think that it still would not work with a recursive function that expects layer or layerSet objects because the nested layers are already in the array.
Code: Select all    function getLayerSetsIndex(){
       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   
        };
       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 == "layerSectionStart" || temp == "layerSectionContent") 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 );
    };
    var groups = getLayerSetsIndex();
var allLayersAndSets = [];
    for(var i = 0; i < groups.length; ++i) {
       makeActiveByIndex( groups, true );
       allLayersAndSets.push(app.activeDocument.activeLayer);
    }
alert(allLayersAndSets)
masterfab

getting layer by index performance issue (CS2)

Post by masterfab »

Hello Mike,

I decided to do not use a recursive function and it works very good as i wanted.
Now I want to modify again the function getAllLayersAndSets so that it can return only visible layers.

I think i should change the if statement and add an other condition to get only visible layers:
if(temp == "layerSectionStart" ||temp == "layerSectionContent") res.push(i);

Is there any other stringID i can use to do this ?

Thank u !
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

You could do something like this.
Code: Select allfunction getLayerSetsIndex(){
   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 == "layerSectionStart" || temp == "layerSectionContent") && getLayerVisibilityByIndex( i )) 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 );
};
var groups = getLayerSetsIndex();
    var allLayersAndSets = [];
for(var i = 0; i < groups.length; ++i) {
   makeActiveByIndex( groups, true );
   allLayersAndSets.push(app.activeDocument.activeLayer);
}
alert(allLayersAndSets);
masterfab

getting layer by index performance issue (CS2)

Post by masterfab »

Hello,
My script is taking a lot of time and it seems that the problem is this function :

Code: Select allfunction getLayerInformation(doc, layer){
     this.layerWidth = layer.bounds[2].value - layer.bounds[0].value;
    this.layerHeight = layer.bounds[3].value - layer.bounds[1].value;
    return this;
}

Is there any other way i can get layer width and height faster ?
Thank u.
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

The way that is written you are setting two properties to the global object. I don't think that is a good thing. But if you are going to do that there is no need for the return. I think it would be better to make your own object/function with getLayerInformation as a method. That way when you assign a property to this inside getLayerInformation it gets assigned to getLayerInformation's parent and not the global object.

Normally what slows down the DOM is making a reference to a doc or layer. Those references have already been make so I am not sure why your function would be slow unless it's because of the global object issue. It is possible to get a layer's bounds with Action Manager but you can use a DOM layer reference. You need the layer's AM index, ID, or make the layer active. It is not clear to me how to convert that function without much info. Are you sure that is where your code is slow? Did you profile in ESTK?
masterfab

getting layer by index performance issue (CS2)

Post by masterfab »

Hello,
This is how i call my function :

Code: Select all.....................
var layers = getAllLayersByIndex();
for (var i=0 ; i < layers.length ; i++) {
      makeActiveByIndex( layers, false );
      var layer = activeDocument.activeLayer;
      var layerBounds = getLayerInformation(layer);
      var height = layerBounds.layerHeight;
      var width = layerBounds.layerWidth;
      ..............
}
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;
}


I used Date and Time functions to see which function was taking a lot of time and it seems it is getLayerInformation():
Code: Select allThe average execution of getLayerInfomation()  in a document with 151 layers is : 2111.56291390728 Milliseconds
xbytor

getting layer by index performance issue (CS2)

Post by xbytor »

Don't make the layer the active layer. That will force PS/JS to create a Layer DOM object. This will kill you performance. You will need to use more index functions to access the bounds from the layer descriptor. The bounds object is an ActionDescriptor (aka Object) with a key of "bounds" and the actual limits are UnitDoubles stored at "Top ", "Left", "Btom", "Rght" in that descriptor.
Mike Hale

getting layer by index performance issue (CS2)

Post by Mike Hale »

I agree that making a layer active( or even making a DOM reference to a layer ) slows a script down, I'm still not sure that is why your function is slow. Making the reference and active is done before calling the function so I would expect the performance hit there and not the function.

But here is how to get the layer bounds by AM index. Note the values are pixels regardless of the ruler settings.Code: Select allfunction 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;
}
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.