Skip to main content
Participant
March 17, 2010
Answered

Iteration through activeDocument.layers array is very slow

  • March 17, 2010
  • 1 reply
  • 4431 views

Hi,

I'm trying to build an array of all the currently visible layers in a document, and the only way I know how to do it is by iterating (using for or do loop method) app.activeDocument.layers however, the problem is it is really slow for some reason. For 37 layers, it will sit there for about 10 to 15 seconds and the mouse cursor will fluctuate between normal and busy pointer.

A couple questions:

a) Is there a better way to build a list of visible layers?

b) Why is it taking so long to complete this (note: there is only one action within the loop which is assigning the current layer in the layers array to l):

var layers = app.activeDocument.layers;

for (var i=0; i<layers.length; i++)

{

     var l = layers;

}


?

Any help on this matter will be much appreciated!

Thanks

Nate

This topic has been closed for replies.
Correct answer Michael_L_Hale

If you have CS4 you can use the function below to get the AM index of the selected layers.

function getSelectedLayersIdx(){
     var selectedLayers = new Array;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
     var desc = executeActionGet(ref);
     if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) ){
          desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
          var c = desc.count
          var selectedLayers = new Array();
          for(var i=0;i<c;i++){
               selectedLayers.push(  desc.getReference( i ).getIndex());
          }
     }else{
          var ref = new ActionReference();
          ref.putProperty( charIDToTypeID('Prpr') , charIDToTypeID( 'ItmI' ));
          ref.putEnumerated( charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
          selectedLayers.push( executeActionGet(ref).getInteger(charIDToTypeID( 'ItmI' )));
     }
     return selectedLayers;
}

There isn't really any documentation on using Action Manager. The javascript guide that ships with Photoshop does list the properties and methods for Action Descriptor, Action Reference, and Action List. And the app.object has methods for converting IDs.

1 reply

Inspiring
March 17, 2010

It is a known problem. I think the reason has to do with creating the layer objects. All of the properties for each layer is updated including histogram which take time.

You can get a little faster using a mix of DOM and Action Manager. The more layer the bigger the time savings.


var lyrs = collectLayersByMixedAPI();// returns an array of layer objects
alert( lyrs );

// does require making the layer active
function collectLayersByMixedAPI(){
     var doc = app.activeDocument;
     var allLayers = new Array();
   doc.activeLayer = doc.layers[ (doc.layers.length-1) ];// start at bottom
   var startLoop = Number( !hasBackground() );
   var endLoop = getNumberOfLayer();
   for( var l = startLoop;l < endLoop; l++){
        while( !isValidActiveLayer( l ) ) {
            l++;
        }
        makeActiveByIndex( l+1, false );
        allLayers.push( doc.activeLayer );
    }
     return allLayers;
}
/*//////////////////////////////////////////////////////////////////////////////
// Function: isValidActiveLayer( )
// Description: Checks LayerSection for 'real' layers
// Usage: if( isValidActiveLayer() )
// Input: None
// Return: Boolean - True if not the end of a Set
// Notes:  Needed only if the layer was made active
//               using Action Manager API
//////////////////////////////////////////////////////////////////////////////*/
function isValidActiveLayer( idx ) {
     var propName = stringIDToTypeID( 'layerSection' );// can't replace
     var ref = new ActionReference();
     ref.putProperty( 1349677170 , propName);// TypeID for "Prpr"
     // 'Lyr ", idx
     ref.putIndex( 1283027488, idx );
     var desc =  executeActionGet( ref );
     var type = desc.getEnumerationValue( propName );
     var res = typeIDToStringID( type );
     return res == 'layerSectionEnd' ? false:true;
}
/*//////////////////////////////////////////////////////////////////////////////
// Function: hasBackground
// Description: Test for background layer using AM API
// Usage: if( hasBackground() );
// Input: None
// Return: Boolean - true if doc has background layer
// Notes:  Requires the document to be active
//  DOM:  App.Document.backgroundLayer
//////////////////////////////////////////////////////////////////////////////*/
function hasBackground(){
    var res = undefined;
    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;
}
function makeActiveByIndex( idx, forceVisible ){
     try{
          var desc = new ActionDescriptor();
          var ref = new ActionReference();
          ref.putIndex(charIDToTypeID( "Lyr " ), idx)
          desc.putReference( charIDToTypeID( "null" ), ref );
          desc.putBoolean( charIDToTypeID( "MkVs" ), forceVisible );
          executeAction( charIDToTypeID( "slct" ), desc, DialogModes.NO );
     }catch(e){ return -1;}
}
function getNumberOfLayer(){
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var desc = executeActionGet(ref);
var numberOfLayer = desc.getInteger(charIDToTypeID("NmbL"));
return numberOfLayer;
}

You can get even faster results using Action Manager alone but there is no direct access from AM to the DOM layer object so the code needed depend on what task needs to be done. Do you just need an array of visible layer objects?

NateNorrAuthor
Participant
March 17, 2010

Thanks for the reply, your answer is as I expected regarding iterating through the layers array. I'm going to study the code you provided to understand it better. I am not very knowledgeable about the Action Manager yet!

My objective is actually to get a list of the currently selected layers, if you know an effecient way of achieving this then that would be even better!

Do you have any documentation sources for the Action Manager? I can't seem to find much on it anywhere.

Many thanks!

Nate

Michael_L_HaleCorrect answer
Inspiring
March 17, 2010

If you have CS4 you can use the function below to get the AM index of the selected layers.

function getSelectedLayersIdx(){
     var selectedLayers = new Array;
     var ref = new ActionReference();
     ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
     var desc = executeActionGet(ref);
     if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) ){
          desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
          var c = desc.count
          var selectedLayers = new Array();
          for(var i=0;i<c;i++){
               selectedLayers.push(  desc.getReference( i ).getIndex());
          }
     }else{
          var ref = new ActionReference();
          ref.putProperty( charIDToTypeID('Prpr') , charIDToTypeID( 'ItmI' ));
          ref.putEnumerated( charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
          selectedLayers.push( executeActionGet(ref).getInteger(charIDToTypeID( 'ItmI' )));
     }
     return selectedLayers;
}

There isn't really any documentation on using Action Manager. The javascript guide that ships with Photoshop does list the properties and methods for Action Descriptor, Action Reference, and Action List. And the app.object has methods for converting IDs.