Action recordable scripts in Photoshop

Davide Barranca —  — 1 Comment

Scripts, by default, leave traces in the History palette; a common practice is to pack and hide into a labeled, single history step some code (either few commands or entire scripts) this way:

You can record such a script into an action (using the File – Scripts – Browse… menu item), and play it later.

Things get more complicate when your script requires some kind of user interaction, say a GUI that asks for an input value. Take the following as an example (this will be the starting point that I’ll be elaborating throughout this post):

It doesn’t do anything exciting – it just pops up a ScriptUI modal window asking for a value that will be used as a radius in pixels for a Gaussian Blur filter.

ExampleScriptBut… when you try to embed this into an action (say you’ve input 32 as the value while recording it), the action won’t remember the used value. Instead, it will keep popping up the GUI asking for a number, each time you play it. Not exactly what you wanted.

In order to build an “action-aware”, user-input required, recordable script you need to implement a specific logic, that also requires a set of specific instructions: I’ll be using this very code for the GUI, adding and commenting the bits of code required to make it fully working into Actions.

Javascript Resource

Section 3 of the Photoshop CS6 Javascript Reference deals with the Javascript Resource, a set of instruction that includes, among the rest:

  • the ability to specify a menu the script appears in as a command [that is, a new item in the Filter / Automate / Help menu]
  • a terminology resource so the script can function with the Action Manager, which allows your script to record and be automated by scripting parameters [that is: Actions];

Third feature should be the possibility to group commands within menus, but it seems to be buggy. I won’t duplicate the Reference here, let me just report a comment from the Adobe’s Fit Image.jsx script:

That said, the needed code for my example is as follows:

Things to notice (besides the fact that everything is enclosed in comment marks):

[Line 7] The Label as it will appear in the menu ($$$ should refer to localized strings – even though if no correspondence is found, it keeps using what’s provided; RBG is a custom string, add your own initial for instance)
[Line 8] The menu the script will appear listed into (Filter in the example)
[Line 9] A conditional that is evaluated in order to determine when to list the script in the menu (for instance only if the document is CMYK, etc) – in the example set to true, i.e. always.
[Line 10] A unique string that is associated to your script in the Photoshop registry. You can throw in whatever you like, but in order to be sure it won’t be duplicated, a UUID is recommended – find a UUID generator here.
[Line 13] The event as it will be listed in the recorded Action, with its associated string/UUID.
[Line 15] The parameter(s) that will be listed within, and used in, the recorded Action. In the example the only parameter is the radius – which happens to be included in the localized strings set (otherwise I would have used a customized string as in line 7 that will not be translated).

Basically, the code says: please, add this script as a new item in the Filter’s menu and get ready to use the provided labels as the script and parameter’s name within an Action. But it’s not enough, we’re just at the beginning.

 Script’s logic

In meta-language the main routine has to:

That is, the GUI doesn’t pop-up when the script is called by an action – it’s the action itself that has to pass the parameters (actually, you can make an action display the dialogs, toggling the second column of icons in the Actions panel). This translates into actual code as follows:

The highlighted lines are new, compared to the starting script.

  • In the Globals object I define a new isCancelled key – which is a toggle that’s switched to false when the routine is finished; otherwise I assume that the script isn’t done yet.
  • I instantiate a paramHolder object, which contains a radius property that I’ll be using to store the value from the GUI. Of course in this example it is just a single parameter, but they can be as many as you need.
  • I’m checking the app.playbackDisplayDialog property: it controls whether to display the dialog (and what kind of dialogs are allowed) when the script is called by an action. If it’s equal to DialogModes.ALL it means that either the Action is set to play with all the dialogs on, or the call comes from the menu item; as a result, the GUI must pop up.
  • The .initParameters() function retrieves the parameter from the Action and assign it to the paramHolder object – which is what the .actualRoutine() is then fed with.
  • When the .actualRoutine() is done, it switches to false the isCancelled key, so that the script saves the paramHolder object (the radius’ container, in the example). If the Action is recording, the parameter is saved inside the Action.

Write the Parameter from the Action

The script communicates with Actions by means of Action Descriptors, special objects that store key-value pairs, used to drive Photoshop at a lower level: for instance when DOM commands aren’t specified (the Action terms doesn’t refer to recordable/playable actions, afaik). The so-called Action Manager code is kind of the Photoshop’s voodoo equivalent – yet when you start to scratch the surface of such an unfriendly mechanism, it’s less scaring than it appears.

Anyway: the key point is to pass a descriptor to app.playbackParameters (which is what is going to be stored within the Action itself in order to be played later). This is done in Adobe’s own scripts by means of the objectToDescriptor() function:

As you see from the commented code, you feed the objectToDescriptor() function with an object to be converted (in the example: paramHolder) and a String (in the example, coming from an updated version of the Globals object, “Recordable Gaussian Blur Action Settings”), which looks to me as a key that you’ll be using to retrieve the object back from the descriptor in the next step.

Read the Parameter from the Action

I’ll make use of another Adobe made function, descriptorToObject(), to extract paramHolder from the stored descriptor (which is represented by the app.playbackParameters):

descriptorToObject() works in a peculiar way, since it takes as a parameter an instance of the object (paramHolder) that he’s going to update – with the information extracted from the other parameter (the actual descriptor).

You see that, in the initParameters() function I define an isFromAction boolean, casting the app.playbackParameters.count property to Boolean. If it’s zero (i.e. false – the descriptor has no keys) the script isn’t run from an Action. In this very case, the count is equal to two, and if you want to know what the keys look like you can:

Mind you, the javascriptresource is used in the process to determine the label that will appear in the Actions palette – so they must match.

Final script

The completed example’s code is as follows (please find a note just below it regarding line 99):

Regarding line 99, the always helpful Mike Hale from Ps-scripts pointed out the following:

it’s important that the function return the un-localized string ‘cancel’ if there is an error so an action that is recording the script will know something went wrong and not store the step in the action. It should return undefined if everything went ok and the step should be recorded.

Code Protection

Be aware that if you’re willing to protect your work via binary encoding, you must leave alone everything from the beginning to the #target photoshop (included). Basically you have to:

  1. Put the code (without the <javascriptresource>) on a temporary file, and export it (File – Export to Binary…)
  2. Open the resulting .jsxbin and add a backslash to each line ending:

  1.  Copy the binary file’s content and put everything inside an eval("...") function, then add before it the <javascriptresource>:

[I’d like to thank Mike Hale and Xbytor from ps-scripts.com who helped me understanding how this stuff works!]


Print Friendly

Trackbacks and Pingbacks:

  1. Adobe Scripting: collaborative code sharing | Photoshop, etc. - December 8, 2013

    […] 2014 (that is: tomorrow morning, so to speak). Extensions cannot replace scripted windows (that are action recordable for instance), but are interfaces to, and evaluates, “traditional” […]

Leave a Reply

Text formatting is available via select .

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">