Adobe Photoshop: programming for designers – Scripting Listener

with No Comments

March 2019

Introduction

To automate our tasks, we can use Actions and javascript. We can also use javascript to create actions and run them. The commands to create actions are somewhat cryptic. Adobe has developed a tool to help us with the creation of the javascript. The ‘Scripting Listener’ is a tool that records all the actions that are performed and writes its javascript equivalent to a log file.

I haven’t touched upon the ‘Scripting Listener’ before, because it has some quirks and the generated code is still somewhat difficult to read for an unexperienced programmer. For the completeness I will describe it in this article. I am sure that a lot of people will actually benefit from it, despite it quirks.

Installation

The first quirk is the uninstallation of the ‘Scripting Listener’. Older documentation mentions that the ‘Scripting Listener’ sometimes can’t be uninstalled. This is extremely annoying, as everything you do in ‘Adobe Photoshop’ is stored in the log file. If the plugin cannot be uninstalled you have to remove the log files every now and then.

I didn’t want to take the risk and first installed the plugin on a laptop that I hardly use.

The downloadable for the ‘Scripting Listener’ can be found at:
Scripting Listener Plugin

Follow the instruction on the page to install the ‘Scripting Listener’. Althought the documentation doesn’t say it; be sure you copy the ‘Utilities’ folder the ‘Plug-Ins’ folder.

You can easily check if the plugin is installed. As soon as you start up ‘Adobe Photoshop’ it will create two log files on the desktop.
ScriptingListenerJS.log containing javascript code
ScriptingListenerVB.log containing VB script code

Uninstallation

To uninstall the ‘Scripting Listener’ you have to remove the ‘Utilities’ folder from the ‘Plugins’ folder.

Planning the creation of the code

Before we start recording action and creating script, we first have to think what functions we want to create. I prefer to make multiple short functions with only one responsibility. This keeps the functionality clear and easy to maintain. By calling the more of the shorter function in succession I can complete a bigger task.

It is also easier to reuse smaller functions than big monolith functions.

Example:
I want to do the following actions on an image, in this particular order:

  1. Adjust brightness and contrast
  2. Apply Glass Effect
  3. Resizes the image
  4. Saves the image
Method 1

Record an action that does all the above in the desired order and make one big function out of it.

function executeAll(brightness, contrast, distortion, smoothness, scaling, width, filename){
	// All the functionality here
}

If a want to have another function that does less, like:

  1. Apply Glass Effect
  2. Saves the image

I will have to record a new set of actions and create a new function out of it like

function executeLess(distortion, smoothness, scaling, filename) {
	// All its own functionality here
}
Method 2

Record an action for each separate function. It is one of the principals of good software engineering to give a function only one responsibility and keep them short. This improves the maintainability.

Example:

var brightness = 50;
var contrast = 50;

var distortion = 10;
var smoothness = 5;
var scaling = 150;

var width = 120;

var filename = "c:\\temp\\ed.jpg";

adjustBrightnessContrast(brightness, contrast);
applyGlassEffect(distortion, smoothness, scaling);
resizeImage(width);
saveImage(filename);


function adjustBrightnessContrast(brightness, contrast) {
	// Adjust brightness and contrast
}
function applyGlassEffect(distortion, smoothness, scaling) {
	// Apply Glass Effect
}
function resizeImage(width) {
	// Resize image
}
function saveImage(filename) {
	// Save image
}

If you want you can create new functions using the existing functions.


function execute(brightness, contrast, distortion, smoothness, scaling, width, filename)
	adjustBrightness(brightness, contrast)
	applyGlassEffect(distortion, smoothness, scaling)
	resizeImage(width)
	saveImage(filename)
}

function executeLess(distortion, smoothness, scaling, filename)
	applyGlassEffect()
	saveImage(filename)
}

As you can imagine, it will also be a lot easier to change the order of the actions.

Creating the code

Bow we have got our framework, we only have to fill in the functions.
The ‘Scripting Listener’ logs all the performed actions and it ends each recording with a separator: /=====
I kept the log file open during the execution of the actions. After each action I added some extra comment to mark the portion that I wanted to copy. Don’t forget to save.

I copied all the necessary code in the block below.

Example of ScriptingListenerJS.log


	// Adjust brightness
	var idBrgC = charIDToTypeID( "BrgC" );
	var desc7 = new ActionDescriptor();
	var idBrgh = charIDToTypeID( "Brgh" );
	desc7.putInteger( idBrgh, 50 );
	var idCntr = charIDToTypeID( "Cntr" );
	desc7.putInteger( idCntr, 50 );
	var iduseLegacy = stringIDToTypeID( "useLegacy" );
	desc7.putBoolean( iduseLegacy, false );
	executeAction( idBrgC, desc7, DialogModes.NO );

	// Apply Glass Effect
	var idGEfc = charIDToTypeID( "GEfc" );
	var desc9 = new ActionDescriptor();
	var idGEfk = charIDToTypeID( "GEfk" );
	var idGEft = charIDToTypeID( "GEft" );
	var idGls = charIDToTypeID( "Gls " );
	desc9.putEnumerated( idGEfk, idGEft, idGls );
	var idDstr = charIDToTypeID( "Dstr" );
	desc9.putInteger( idDstr, 10 );
	var idSmth = charIDToTypeID( "Smth" );
	desc9.putInteger( idSmth, 5 );
	var idTxtT = charIDToTypeID( "TxtT" );
	var idTxtT = charIDToTypeID( "TxtT" );
	var idTxFr = charIDToTypeID( "TxFr" );
	desc9.putEnumerated( idTxtT, idTxtT, idTxFr );
	var idScln = charIDToTypeID( "Scln" );
	desc9.putInteger( idScln, 150 );
	var idInvT = charIDToTypeID( "InvT" );
	desc9.putBoolean( idInvT, false );
	executeAction( idGEfc, desc9, DialogModes.NO );

	// Resize image
	var idImgS = charIDToTypeID( "ImgS" );
	var desc13 = new ActionDescriptor();
	var idWdth = charIDToTypeID( "Wdth" );
	var idPxl = charIDToTypeID( "#Pxl" );
	desc13.putUnitDouble( idWdth, idPxl, 500.000000 );
	var idscaleStyles = stringIDToTypeID( "scaleStyles" );
	desc13.putBoolean( idscaleStyles, true );
	var idCnsP = charIDToTypeID( "CnsP" );
	desc13.putBoolean( idCnsP, true );
	var idIntr = charIDToTypeID( "Intr" );
	var idIntp = charIDToTypeID( "Intp" );
	var idautomaticInterpolation = stringIDToTypeID( "automaticInterpolation" );
	desc13.putEnumerated( idIntr, idIntp, idautomaticInterpolation );
	executeAction( idImgS, desc13, DialogModes.NO );

	// Save image
	var idsave = charIDToTypeID( "save" );
	var desc19 = new ActionDescriptor();
	var idAs = charIDToTypeID( "As  " );
	var desc20 = new ActionDescriptor();
	var idEQlt = charIDToTypeID( "EQlt" );
	desc20.putInteger( idEQlt, 12 );
	var idMttC = charIDToTypeID( "MttC" );
	var idMttC = charIDToTypeID( "MttC" );
	var idNone = charIDToTypeID( "None" );
	desc20.putEnumerated( idMttC, idMttC, idNone );
	var idJPEG = charIDToTypeID( "JPEG" );
	desc19.putObject( idAs, idJPEG, desc20 );
	var idIn = charIDToTypeID( "In  " );
	desc19.putPath( idIn, new File( "C:\\Users\\eschimmel\\Pictures\\ed2.jpg" ) );
	var idDocI = charIDToTypeID( "DocI" );
	desc19.putInteger( idDocI, 197 );
	var idsaveStage = stringIDToTypeID( "saveStage" );
	var idsaveStageType = stringIDToTypeID( "saveStageType" );
	var idsaveBegin = stringIDToTypeID( "saveBegin" );
	desc19.putEnumerated( idsaveStage, idsaveStageType, idsaveBegin );
	executeAction( idsave, desc19, DialogModes.NO );

I replaced some cryptic variables with ones that are better readable and added function headers.
To complete the script, I would recommend to add the variable at the top. This makes it easier to find them.


var brightness = 50;
var contrast = 50;

var distortion = 10;
var smoothness = 5;
var scaling = 150;

var width = 120;

var filename = "c:\\temp\\ed.jpg";

adjustBrightnessContrast(brightness, contrast);
applyGlassEffect(distortion, smoothness, scaling);
resizeImage(width);
saveImage(filename);


function adjustBrightnessContrast(brightness, contrast) {
	// Adjust brightness
	var idBrgC = charIDToTypeID( "BrgC" );
	var desc7 = new ActionDescriptor();
	var idBrgh = charIDToTypeID( "Brgh" );
	desc7.putInteger( idBrgh, brightness );
	var idCntr = charIDToTypeID( "Cntr" );
	desc7.putInteger( idCntr, contrast );
	var iduseLegacy = stringIDToTypeID( "useLegacy" );
	desc7.putBoolean( iduseLegacy, false );
	executeAction( idBrgC, desc7, DialogModes.NO );
}

function applyGlassEffect(distortion, smoothness, scaling) {
	// Apply Glass Effect
	var idGEfc = charIDToTypeID( "GEfc" );
	var desc9 = new ActionDescriptor();
	var idGEfk = charIDToTypeID( "GEfk" );
	var idGEft = charIDToTypeID( "GEft" );
	var idGls = charIDToTypeID( "Gls " );
	desc9.putEnumerated( idGEfk, idGEft, idGls );
	var idDstr = charIDToTypeID( "Dstr" );
	desc9.putInteger( idDstr, distortion );
	var idSmth = charIDToTypeID( "Smth" );
	desc9.putInteger( idSmth, smoothness );
	var idTxtT = charIDToTypeID( "TxtT" );
	var idTxtT = charIDToTypeID( "TxtT" );
	var idTxFr = charIDToTypeID( "TxFr" );
	desc9.putEnumerated( idTxtT, idTxtT, idTxFr );
	var idScln = charIDToTypeID( "Scln" );
	desc9.putInteger( idScln, scaling );
	var idInvT = charIDToTypeID( "InvT" );
	desc9.putBoolean( idInvT, false );
	executeAction( idGEfc, desc9, DialogModes.NO );
}

function resizeImage(width) {

	var idImgS = charIDToTypeID( "ImgS" );
	var desc13 = new ActionDescriptor();
	var idWdth = charIDToTypeID( "Wdth" );
	var idPxl = charIDToTypeID( "#Pxl" );
	desc13.putUnitDouble( idWdth, idPxl, width );
	var idscaleStyles = stringIDToTypeID( "scaleStyles" );
	desc13.putBoolean( idscaleStyles, true );
	var idCnsP = charIDToTypeID( "CnsP" );
	desc13.putBoolean( idCnsP, true );
	var idIntr = charIDToTypeID( "Intr" );
	var idIntp = charIDToTypeID( "Intp" );
	var idautomaticInterpolation = stringIDToTypeID( "automaticInterpolation" );
	desc13.putEnumerated( idIntr, idIntp, idautomaticInterpolation );
	executeAction( idImgS, desc13, DialogModes.NO );
}

function saveImage(filename) {
	// Save image

	var idsave = charIDToTypeID( "save" );
	var desc19 = new ActionDescriptor();
	var idAs = charIDToTypeID( "As  " );
	var desc20 = new ActionDescriptor();
	var idEQlt = charIDToTypeID( "EQlt" );
	desc20.putInteger( idEQlt, 12 );
	var idMttC = charIDToTypeID( "MttC" );
	var idMttC = charIDToTypeID( "MttC" );
	var idNone = charIDToTypeID( "None" );
	desc20.putEnumerated( idMttC, idMttC, idNone );
	var idJPEG = charIDToTypeID( "JPEG" );
	desc19.putObject( idAs, idJPEG, desc20 );
	var idIn = charIDToTypeID( "In  " );
	desc19.putPath( idIn, new File( filename ) );
	var idDocI = charIDToTypeID( "DocI" );
	desc19.putInteger( idDocI, 197 );
	var idsaveStage = stringIDToTypeID( "saveStage" );
	var idsaveStageType = stringIDToTypeID( "saveStageType" );
	var idsaveBegin = stringIDToTypeID( "saveBegin" );
	desc19.putEnumerated( idsaveStage, idsaveStageType, idsaveBegin );
	executeAction( idsave, desc19, DialogModes.NO );
}

Remark

I am not completely convinced about the use for the ‘Scripting Listener’.
By using ‘Adobe Photoshop’ javascript API the same functionality can be achieved with the following code.

var brightness = 50;
var contrast = 50;

var distortion = 10;
var smoothness = 5;
var scaling = 150;

var width = 120;

var filename = "c:\\temp\\ed.jpg";

adjustBrightnessContrast(brightness, contrast);
applyGlassEffect(distortion, smoothness, scaling);
resizeImage(width);
saveImage(file);

function adjustBrightnessContrast(brightness, contrast) {
	app.activeDocument.activeLayer.adjustBrightnessContrast(brightness, contrast);
}

function applyGlassEffect(distortion, smoothness, scaling) {
	app.activeDocument.activeLayer.applyGlassEffect(distortion, smoothness, scaling,false,TextureType.FROSTED);
}

function resizeImage(width) {
	app.activeDocument.resizeImage(width);
}

function saveImage(filename) {
	app.activeDocument.saveAs(new File(filename));
}