Copy link to clipboard
Copied
So,
I'm looking for the best way to count the number of selected layers in a PS document.
I've been looking around and stumbled on this piece of code : https://graphicdesign.stackexchange.com/questions/104786/is-it-possible-to-count-the-number-of-layer...
It does work, but it works by creating a new group before counting the layers, then undoing it all. This fires a "Mk " event. And since the script has an EventListenner looking out for "Mk " events, well... I get stuck in an endless loop...
Isn't there a simple and effective way to count the selected layers ? Actually... I just need to know if there's more than one selected...
Many thanks.
J.
s2t = stringIDToTypeID;
(r = new ActionReference).putProperty(s2t('property'), p = s2t('targetLayers'));
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
n = executeActionGet (r).hasKey(p) ? executeActionGet (r).getList(p).count : 0;
alert (n)
Copy link to clipboard
Copied
s2t = stringIDToTypeID;
(r = new ActionReference).putProperty(s2t('property'), p = s2t('targetLayers'));
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
n = executeActionGet (r).hasKey(p) ? executeActionGet (r).getList(p).count : 0;
alert (n)
Copy link to clipboard
Copied
Hi, This is a nice solution. Could you please explain me (just want to learn), how exactly it is working.
Highly appreciated!
Thanks
Copy link to clipboard
Copied
This is the code for the action manager, the Photoshop subsystem. Action manager objects are stored in ActionDescriptors. The names of properties and methods are encoded by numeric keys, which we can receive and convert using functions like stringIDToTypeID.
In that case, we get access to the ActionDescriptor of the current document using the ActionReference (this is something like a link to the desired object). Using the ActionReference, we describe the property that we need. We need to get a list of selected layers - it is stored as an ActionList named 'targetLayers'. To do that we create a new ActionReference, first put the name of the property we need into it (so as not to request the entire document object):
(r = new ActionReference) .putProperty (s2t ('property'), p = s2t ('targetLayers'));
then we indicate that this property should be taken from the active document:
r.putEnumerated (s2t ('document'), s2t ('ordinal'), s2t ('targetEnum'));
Since layers may or may not be selected, then using executeActionGet and the .hasKey () function, we check if we have the property we need, if so, we write the number of elements in the list of selected layers to a variable
executeActionGet (r) .getList (p) .count , if not, then write to the variable 0.
At the same time, it is not difficult to obtain the index of each layer from the resulting list and with its help obtain all the necessary properties of each selected layer.
Copy link to clipboard
Copied
I've been wanting to understand this markup a bit better, as the recorded markup is sometimes verbose and redundant. This is a start. Thanks!
Copy link to clipboard
Copied
@Deleted User
You mentioned it wouldn't be difficult to get the list of IDs from this, but I'm not sure I understand how?
I tried an alert/dump on all parts of your script, but at most it seems to have count associated with it.
I thought it was most likely here:
executeActionGet(r).getList(p)
But again it's just the count. Any tips?
Copy link to clipboard
Copied
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var docDesc = executeActionGet(ref);
var targetLayersIDs = docDesc.getList(stringIDToTypeID("targetLayersIDs"));
var theIDs = new Array;
for (var m = 0; m < targetLayersIDs.count; m++) {
theIDs.push(targetLayersIDs.getReference(m).getIdentifier())
};
alert ("the ids are\n"+theIDs.join("\n"));
Copy link to clipboard
Copied
Thanks! In testing it out, I've also realized that the id and itemIndex don't let you manipulate the layer directly: for example app.activeDocument.layers[858] will produce an error for id and app.activeDocument.layers[858] will produce an error for id and app.activeDocument.layers[57] will produce an error for itemIndex specifically. I did find addSelectedLayer() from Flatten All Layers.jsx, which seems to bridge the gap. The script I'm currently working on, sizes the active layer based on the layer below. Ideally I'd be able to reference the second layer without selecting it, but I'm not sure that's necessary?
///////////////////////////////////////////////////////////////////////////////
// From: Flatten All Layer Effects.jsx
// Function: getSelectedLayers
// Usage: creates and array of the currently selected layers
// Input: <none> Must have an open document
// Return: Array selectedLayers
///////////////////////////////////////////////////////////////////////////////
function addSelectedLayer( layerIndexOrName ) {
try {
var id243 = charIDToTypeID( "slct" );
var desc46 = new ActionDescriptor();
var id244 = charIDToTypeID( "null" );
var ref44 = new ActionReference();
var id245 = charIDToTypeID( "Lyr " );
if ( typeof layerIndexOrName == "number" ) {
ref44.putIndex( id245, layerIndexOrName );
} else {
ref44.putName( id245, layerIndexOrName );
}
desc46.putReference( id244, ref44 );
var id246 = stringIDToTypeID( "selectionModifier" );
var id247 = stringIDToTypeID( "selectionModifierType" );
var id248 = stringIDToTypeID( "addToSelection" );
desc46.putEnumerated( id246, id247, id248 );
var id249 = charIDToTypeID( "MkVs" );
desc46.putBoolean( id249, false );
executeAction( id243, desc46, DialogModes.NO );
}
Copy link to clipboard
Copied
DOM code is »problematic« and doesn’t use the identifier anway.
And its indexing may, if I remember correctly, be messed up by Groups. (Edit: »messed up« in relation to the AM indices, the indices retrieved via DOM-code work for DOM-code itself fine.)
Copy link to clipboard
Copied
Also what do you mean by »manipulate« exactly?
Certain operations can be performed with AM-code by addressing a Layer via index or identifier, for some operations one has to select it first even with AM-code.
Copy link to clipboard
Copied
Thanks for the reply! What I mean by manipulate, is to refer to, extract data from, or modify the layers directly based on the indices mentioned in AM or DOM references. I don't know how one would get information indirectly(without making a layer active) via AM, were via DOM, I had been attempting to get information via app.activeDocument.layers[id or indexItem]. Even selecting the active layer based on id or indexItem, seems to require AM, correct?
Copy link to clipboard
Copied
You beem to be pretty vague on this.
What data do you want to extract from the Layers?
Which modifications do you want to perform on the Layers?
Getting the seleted Layers’ bounds, blend mode, color, opacity, … can be done without selecting the Layers individually.
An example for bounds:
// 2023, use it at your own risk;
#target photoshop
if (app.documents.length > 0) {
alert (collectSelectedLayersBounds ())
};
////// collect bounds of selected layers //////
function collectSelectedLayersBounds () {
// set to pixels;
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
// get selected layers;
var selectedLayers = new Array;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var desc = executeActionGet(ref);
if (desc.getBoolean(stringIDToTypeID("hasBackgroundLayer")) == true) {var theAdd =0}
else {var theAdd = 1};
if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) ){
desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
var c = desc.count;
var selectedLayers = new Array();
// run through selected layers;
for(var i=0;i<c;i++){
var theIndex = desc.getReference( i ).getIndex()+theAdd;
// get id for solid color layers;
try {
var ref = new ActionReference();
ref.putIndex( charIDToTypeID("Lyr "), theIndex );
var layerDesc = executeActionGet(ref);
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
selectedLayers.push([theName, theIdentifier, theseBounds]);
} catch (e) {};
};
// if only one:
}else{
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
try {
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
selectedLayers = [[theName, theIdentifier, theseBounds]]
} catch (e) {};
};
// reset;
app.preferences.rulerUnits = originalRulerUnits;
return selectedLayers;
};
Copy link to clipboard
Copied
Sorry, not trying to be vague. I'm trying to understand if there's a simple way to reference layer 2 based on active layer 1. Originally I had hard coded 0 and 1, which limits placement within the document. The file I was working on, I wanted to get properties from itemIndex 57(selected) and 58, which seems as though you'd need AM to do that.
function psDims(theLayer) {
/* Left, Top, Right and Bottom
+ - 1 - +
| |
0 2
| |
+ - 3 - +
*/
var bounds = theLayer.bounds;
left = bounds[0];
top = bounds[1];
right = bounds[2];
bottom = bounds[3];
width = right - left;
height = bottom - top;
props = {
lyr: theLayer,
left: left,
top: top,
right: right,
bottom: bottom,
width: width,
height: height
};
return props;
}
aD = app.activeDocument;
layer1 = psDims(aD.artLayers[0]);
layer2 = psDims(aD.artLayers[1]);
Copy link to clipboard
Copied
If I understand correctly this would get the bounds-information; otherwise please post a description including meaningful screenshots including the pertinent Panels:
edited; sorry, I had mixed up index and identifier
var theID = getSelectedLayerIndex();
var bounds1 = getLayerBoundsByIndex(theID);
var bounds2 = getLayerBoundsByIndex(theID+1);
alert ("\nbounds selected layer\n"+bounds1.join("\n")+"\n\nbounds higher layer\n"+bounds2.join("\n"))
// by mike hale;
// http://ps-scripts.com/bb/viewtopic.php?f=9&t=3217&p=14468&hilit=selectLayerByIndex&sid=95f9e770da5eb2014c336c0c67802704#p14468
function getSelectedLayerIndex() {
try{
activeDocument.backgroundLayer;
var mod = 1;
}catch(e){
var mod = 0
}
var ref = new ActionReference();
ref.putProperty( charIDToTypeID("Prpr") , charIDToTypeID( "ItmI" ));
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
return desc = executeActionGet(ref).getInteger(charIDToTypeID( "ItmI" ))-mod;
};
function getLayerBoundsByIndex( idx ) {
var ref = new ActionReference();
ref.putProperty( charIDToTypeID("Prpr") , stringIDToTypeID( "bounds" ));
ref.putIndex( charIDToTypeID( "Lyr " ), idx );
var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID( "bounds" ));
var bounds = [];
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('left')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('top')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('right')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('bottom')));
return bounds;
};
Copy link to clipboard
Copied
Thanks for this. Is the below snippet to avoid errors because Photoshop doesn't index the background layers?
try{
activeDocument.backgroundLayer;
var mod = 1;
}catch(e){
var mod = 0
}
The most helpful piece for me is.
function getLayerBoundsByIndex(idx) {
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Prpr"), stringIDToTypeID("bounds"));
ref.putIndex(charIDToTypeID("Lyr "), idx);
var desc = executeActionGet(ref).getObjectValue(stringIDToTypeID("bounds"));
var bounds = [];
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('left')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('top')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('right')));
bounds.push(desc.getUnitDoubleValue(stringIDToTypeID('bottom')));
return bounds;
};
//Added some alerts for testing
aL = app.activeDocument.activeLayer;
aLID = aL.itemIndex;
alert(getLayerBoundsByIndex(aLID));
alert(getLayerBoundsByIndex(aLID - 1));
With bounds as an example, I think what I was trying to do/ask, is if there is a way to read a layer from AM into a layer object that can be accessed through DOM. The only way I could find was to select a layer, and then access its attributes/properties via activeLayer. I don't really have a great reason for going this route other than it's easier for me to understand.
Copy link to clipboard
Copied
As far as I know you are correct and a Layer identified with AM-code can only be realiably addressed for DOM-code by selecting it.
Copy link to clipboard
Copied
This works perfectly. Many thanks.
Copy link to clipboard
Copied
Hi,
This is another version of the script that you have shared above.
var doc = app.activeDocument;
var layers = doc.layers;
function getSelectedLayers() {
var resultLayers = new Array();
try {
var idGrp = stringIDToTypeID("groupLayersEvent");
var descGrp = new ActionDescriptor();
var refGrp = new ActionReference();
refGrp.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
descGrp.putReference(charIDToTypeID("null"), refGrp);
executeAction(idGrp, descGrp, DialogModes.NO);
for (var i = 0; i < app.activeDocument.activeLayer.layers.length; i++) {
resultLayers.push(app.activeDocument.activeLayer.layers[i])
}
var id8 = charIDToTypeID("slct");
var desc5 = new ActionDescriptor();
var id9 = charIDToTypeID("null");
var ref2 = new ActionReference();
var id10 = charIDToTypeID("HstS");
var id11 = charIDToTypeID("Ordn");
var id12 = charIDToTypeID("Prvs");
ref2.putEnumerated(id10, id11, id12);
desc5.putReference(id9, ref2);
executeAction(id8, desc5, DialogModes.NO);
return resultLayers;
} catch (e) {
alert("No Layer selected or locked layer selected");
return resultLayers;
}
}
var selectedLayers = getSelectedLayers();
$.writeln(selectedLayers.length);
This will give you an array of selected layers. I hope this helps
Thanks
Copy link to clipboard
Copied
This helped me a lot, thanks