traverseLayersAM -- using the Action Manager to traverse

Discussion of Photoshop Scripting, Photoshop Actions and Photoshop Automation in General

Moderators: xbytor, Kukurykus, Tom


traverseLayersAM -- using the Action Manager to traverse

Post by jcr6 »

For many years now, I've been using Xbytor's traverseLayers function to access all of the layers in a document. Under CS4 and prior it was dog slow, and for some reason CC is about 25-30% SLOWER than CS5. Here is his reference recursive traversal, a very nice piece of work, BTW:

Code: Select allfunction traverseLayers(doc, ftn, reverse)

   function _traverse(doc, layers, ftn, reverse)
      var ok = true;
      for (var i = 1; i <= layers.length && ok != false; i++)
         var index = (reverse == true) ? layers.length - i : i - 1;
         var layer = layers[index];

         if (layer.typename == "LayerSet")
            ok = ftn(doc, layer); //process the parent, first
            ok = _traverse(doc, layer.layers, ftn, reverse); //then the children
            ok = ftn(doc, layer);
      return ok;

   var layers = app.activeDocument.layers; //make a copy so we don't hit the DOM any more
   var result = _traverse(doc, layers, ftn, reverse);
   return result;

It has a modest change in it to try to access the DOM less, but depending upon the version of Photoshop, that doesn't matter much.

It was time to dive into the Action Manager to see if there was a faster way. Many people have alluded to that, and it was time to make an example that would permit me to access all of the layers, but significantly faster. I thought that by replacing X's traverseFunction I could bury the Action Manager code into a more standard use case:

Code: Select all//non-recursive action manager traversal function
function traverseLayersAMFlat(doc, ftn)
   function _selectLayerById(ID)   //select just this layer
      var ref = new ActionReference();
      ref.putIdentifier(charIDToTypeID('Lyr '), ID);
      var desc = new ActionDescriptor();
      desc.putReference(charIDToTypeID('null'), ref);
      desc.putBoolean(charIDToTypeID('MkVs'), false);
      executeAction(charIDToTypeID('slct'), desc, DialogModes.NO);

   //how many layers are there in this document?
   var ref = new ActionReference();
   ref.putEnumerated(charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
   var count = executeActionGet(ref).getInteger(charIDToTypeID('NmbL'));

   //traverse the list backwards (does parents first)
   for (var i = count; i >= 1; i--)
      ref = new ActionReference();
      ref.putIndex(charIDToTypeID('Lyr '), i);
      var desc = executeActionGet(ref);   //access layer index #i
      var layerID = desc.getInteger(stringIDToTypeID('layerID'));   //ID for selecting by ID #
      var layerSection = typeIDToStringID(desc.getEnumerationValue(stringIDToTypeID('layerSection')));
      if (layerSection != 'layerSectionEnd')
      {   //do this layer
         ftn(doc, app.activeDocument.activeLayer);    //apply function to this layer
   }//for i-- countdown

   {   //if there is a magic background layer, process it, too
      app.activeDocument.activeLayer = app.activeDocument.backgroundLayer;
      ftn(doc, app.activeDocument.backgroundLayer);
   } catch (e) {;}


By using a simple stub function, I can traverse anywhere from 2x (CS4) to 8x (CS5) faster using traverseLayersAMFlat().

Code: Select allfunction stub(docRef, layer)

Using a more interesting layer metadata harvest function, I get 2x (CS4) to 3x (CS5) speedups.

Code: Select allfunction fastMetadata(docRef, layer)
   var isVisible = layer.visible;
   var layerdesc = getLayerDescription(layer);
   var layercomm = getLayerComment(layer);
   layer.visible = isVisible;
   output = output + layerdesc + " -- " + layercomm + "\n";
} //fastMetadata

Just thought I'd share this with anyone who needed a faster traversal.

Thanks, everyone!