Getter.jsx Tutorial

Documentation, Reference material and Tutorials for Photoshop Scripting

Moderators: Tom, Kukurykus

xbytor

Getter.jsx Tutorial

Post by xbytor »

Introduction
In a recent post, Mike Hale asked how you could determine whether or not a layer mask was linked or not. I pointed him to the Getter code in XToolkit. This got him part of the way there, but it is less than obvious how to utilize the all of that information and code.

This document describes the steps I took in order to write a function that determines whether or not a layer mask is linked or not.

The MaskLink.zip file contains these files:
MaskLink.psd: a test document that has two layers. The "MaskLinkOn" layer has the link turned on while the "MaskLinkOff" layer has the link turned off.
MaskLink.xml: output from GetterDemo.jsx containing only layer information
IsMaskLinked.jsx: the final script.

Step 1
Check the docs to verify that this property really isn't available anywhere. It isn't.

Step 2
Define what the final API should look like. I settled on this:
Code: Select allfunction isMaskLinked(doc, layer)
The document must be specified. The layer is optional and defaults to doc.activeLayer.


Step 3
Create a test document. The result of this is in the attached zip file.

Step 4
Run 'GetterDemo.jsx' on the document. After a quick scan of the XML I realized that the layer info is not provided by default. Modify GetterDemo.jsx to turn off document info and turn on layer info. The result of this is the attached MaskLink.xml file.

Step 5
Take a look through the XML for the attributes. Good news. There are a couple of Mask properties available.
Code: Select all<DescValueType.BOOLEANTYPE key="1433629261" id="1433629261" symname="UserMaskEnabled" sym="UsrM" boolean="true">
</DescValueType.BOOLEANTYPE>
<DescValueType.BOOLEANTYPE key="1433629299" id="1433629299" symname="UserMaskLinked" sym="Usrs" boolean="false">

The UserMaskLinked property ('Usrs') looks like the best candidate. It's state is 'true' for the On layer and 'false' for the Off layer.

Step 6
On a slight sidetrack, turn one of the links on and off and check the ScriptingListener log. (I always have the Listener turned on. Although it's not recommended, it is very appropriate for the kind of work that I do.) More good news. SLCode is generated, and there is a 'Usrs' key in there as well. We're on the right track. I then realize that I've seen this code before. A quick look in stdlib.js leads me to this block of code:
Code: Select all//
// link/unlink the image and mask
//
Stdlib._linkMask = function(doc, layer, linkOn) {
  function _ftn() {
    var desc = new ActionDescriptor();

    var lref = new ActionReference();
    lref.putEnumerated(cTID("Lyr "), cTID("Ordn"), cTID("Trgt"));
    desc.putReference(cTID("null"), lref);

    var ldesc = new ActionDescriptor();
    ldesc.putBoolean(cTID("Usrs"), linkOn);

    desc.putObject(cTID("T   "), cTID("Lyr "), ldesc);
    executeAction(cTID("setd"), desc, DialogModes.NO);
  }

  Stdlib.wrapLCLayer(doc, layer, _ftn);
};
Stdlib.unlinkMask = function(doc, layer) {
  Stdlib._linkMask(doc, layer, false);
};
Stdlib.linkMask = function(doc, layer) {
  Stdlib._linkMask(doc, layer, true);
};


This validates the conclusion to use the 'Usrs' property.

Step 7
Set up a template for the script. The needed 'include' files can be determined by looking at the top of Getter.jsx. We also include Getter.jsx.
Code: Select all//
// IsMaskLinked.jsx
//
//@show include
//
//@includepath "/c/Program Files/Adobe/Adobe Photoshop CS2/xtools"
//
//@include "xlib/PSConstants.js"
//@include "xlib/stdlib.js"
//@include "xlib/Action.js"
//@include "xlib/xml/atn2xml.jsx"
//@include "xlib/Getter.jsx"
//

function isMaskLinked(doc, layer) {
  return true;
};

function main() {
  var isLinked = isMaskLinked(app.activeDocument);

  var neg = (isLinked ? '' : "not ");
  alert("Mask is " + neg + "linked.");
};

main();

"IsMaskLinked.jsx";

// EOF


This gives us a test wrapper for the script. When we are finished with the implementation, the 'main' function definition and call at the bottom can be commented out.

All that is left is to fill in the definition of 'isMaskLinked'.

Step 8
Many of the calls to executeAction or executeActionGet expect that the document and layer that you are targetting be the active ones. This is not always the case, but I account for it anyway. This means we need to add code to 'isMaskLinked' to take care of these as well as handle the case where the layer is not specified and the activeLayer should be used:
Code: Select allfunction isMaskLinked(doc, layer) {
  var ad = app.activeDocument;
  app.activeDocument = doc;
  var al = doc.activeLayer;

  if (!layer) {
    layer = doc.activeLayer;
  }
 
  var res = false;
  try {
    var lindex = Getter.getLayerIndex(doc, layer);
    if (lindex == -1) {
      throw "Unable to find layer \"" + layer.name + "\" in document.";
    }
    var desc = Getter.getInfoByIndexIndex(lindex, docIndex, PSClass.Layer,
                                          PSClass.Document);
    res = desc.getBoolean(cTID("Usrs"));
    break;
  }

  } finally {
    doc.activeLayer = al;
    app.activeDocument = ad;
  }

  return res;
};


Step 9
We know that Getter.jsx has all of the code we need for the rest of the function definition. The function Getter.getLayerInfo seems to be the best place to start. And, upon further inspection, all of the code we need is at the beginning of the function:
Code: Select allGetter.getLayerInfo = function(doc, docIndex) {
  if (docIndex == undefined) { docIndex = Getter.getDocumentIndex(doc); }

  var str = '';
  var layers = doc.layers;
  var al = doc.activeLayer;
  try {
    for (var i = 0; i < layers.length; i++) {
      var layer = layers;
      doc.activeLayer = layer;
      var lindex = layers.length - i;
      try {
        var desc = Getter.getInfoByIndexIndex(lindex, docIndex, PSClass.Layer,
                                              PSClass.Document);
        str += desc.serialize("Layer-" + layer.name);
      } catch (e) {
      }
      //etc...


Step 10
In particular, the call we are looking for is this one:
Code: Select allvar desc = Getter.getInfoByIndexIndex(lindex, docIndex, PSClass.Layer,
                                      PSClass.Document);

Once we have the ActionDescriptor for the desired layer, getting the desired property ('Usrs') out of it shouldn't be a problem. Looking at the XML shows us that it is a top level property so the call to get the current value will be something like:
Code: Select alldesc.getBoolean(cTID('Usrs'));

Step 11
From the previous step, we know we need the document index and the layer index. The document index is easy to get and we can copy over this code from Getter.jsx:
Code: Select alldocIndex = Getter.getDocumentIndex(doc);

The layer index is part of the loop in Getter.getLayerInfo. To clean up the code, we can create our own getLayerIndex function:
Code: Select allGetter.getLayerIndex = function(doc, layer) {
  var layers = doc.layers;
  var lindex = -1;
  for (var i = 0; i < layers.length; i++) {
    if (layer == layers) {
      lindex = layers.length - i;
      break;
    }
  }
  return lindex;
};



Step 12
With all of these pieces, we can now finish the implementation of 'isMaskLinked':
Code: Select allfunction isMaskLinked(doc, layer) {
  var ad = app.activeDocument;
  app.activeDocument = doc;
  var al = doc.activeLayer;

  if (!layer) {
    layer = doc.activeLayer;
  }

  var docIndex = Getter.getDocumentIndex(doc);

  var res = false;
  try {
    var lindex = Getter.getLayerIndex(doc, layer);
    if (lindex == -1) {
      throw "Unable to find layer \"" + layer.name + "\" in document.";
    }
    var desc = Getter.getInfoByIndexIndex(lindex, docIndex, PSClass.Layer,
                                          PSClass.Document);
    res = desc.getBoolean(cTID("Usrs"));

  } finally {
    doc.activeLayer = al;
    app.activeDocument = ad;
  }

  return res;
};


Step 13
While writing this document and testing with a couple of different test documents, I ran across a problem. If there is no Background layer in the document, the layer index is off by one. After bit of working on the problem, I realized that the code in Getter.jsx was also wrong. Internally, layer index 0 appears to be reserved for the background layer whether there is one or not. I missed it before because my test documents had always had background layers. The final version of the script handles the layer index problem. Getter.jsx will be changed in an upcoming release. Email me if you need the fixed version.

Two other cases that I initially missed was testing the background (which can't have a mask) and testing for a regular layer without a mask. The final version of the script returns false for these two cases.

Step 14
One 'problem' with this is the vast amount of unused and unneeded code that gets include'd. Looking at isMaskLinked shows us that we use Getter.getDocumentIndex and Getter.getInfoByIndexByIndex from Getter.jsx and cTID from stdlib.js. With a little more time, we could extract these functions (and the ones that they also depend on) and remove all of the include directives. This would add about 70 lines of code to the file and remove about 14,000 lines of included code. This is left as an exercise for the reader...

Summary
Hopefully, some of you will find this write-up helpful. Post comments and questions here.

ciao,
-X
Mike Hale

Getter.jsx Tutorial

Post by Mike Hale »

Thanks X,

I have been playing with the getterdemo, now mabe I will be able to use it

I havn't had time to study this, but I'm sure I will have questions when I do.

But I have one question about the getterdemo now. Does it list everything? I don't remember what I was testing for, it had to do with either layers or channels, but I remember that scriptlistner recorded something that was not in the log?

I glad you took the time to write this, thanks again

Mike
xbytor

Getter.jsx Tutorial

Post by xbytor »

By default, GetterDemo only lists document information. To get the rest of the stuff (layers, channels, history, application, etc...) change the code in 'main' so that it looks like:
Code: Select all  var opts = new GetterOptions();
//  opts.disableAll();
//  opts.docInfo= true;

  Getter.getInfo(opts);


Barring bugs, this will list all properties accessible from executeActionGet. Be reminded that there are siginificant portions of PS that are not accessible using the mechanism. You may see things being 'set' in SLCode that you can't 'get' via executeActionGet or the api.

I probably need to put a UI on top of GetterDemo, but I figured modifying the code would probably work initially.
Mike Hale

Getter.jsx Tutorial

Post by Mike Hale »

xbytor wrote: You may see things being 'set' in SLCode that you can't 'get' via executeActionGet or the api.

I probably need to put a UI on top of GetterDemo, but I figured modifying the code would probably work initially.

You told me about changing the options so I have been doing that. I's so easy to change, I have not missed a UI.

Too bad that you can't get everything that SL sets. I remember now what I was looking for. As a user you can highlight more than one layer at a time and work with that group. I made a SL function that let me do that in a script. I was looking for a way to tell which layer are in that group.

I was looking in the layer output, but I just remembered that SL teats iclinking on a layer like adding to a selection. I can't find where getter lists selections. I guess that's one of the thing you can't get. Maybe that is why selection.bounds doesn't work?


Anyway, thanks again. This sould keep me busy for a few weeks

Mike
ShadowM8

Getter.jsx Tutorial

Post by ShadowM8 »

Hey. Thanks for the document, very informative.
However if you don't mind I would like some further explanation.
My question arises from exactly the same problem, how to check if layer mask is linked or not. I've only been going through the Photoshop documentation this weekend and so far managed to grasp how its object model works as the basics of script listener and ActionDescriptor.
I also understood the logics of this document and the final script resulting form it. But I really want to try and get to the same place without the use of function libraries in the xtoolkit and this is where i got totally lots

So I'm wondering if you can help me understand how to get to the ActionDescriptor for a desired layer and how would one approach the task without using any external libraries. And what is the basic logic behind this.

I know this is probably a lot to ask, I would really appreciate the help!

Cheers.