I have updated the code to (hopefully) work as expected with multiple layers.
/*
Random Selection from Transparent Layer v1-1.jsx
Stephen Marsh
v1.0 - 31st August 2025: Initial release
v1.1 - 3rd September 2025: Modified to gracefully handle multiple layers
https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-select-transparency-with-a-script/td-p/15481286
Based in part on:
https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-do-i-seperate-subjects-into-different-seperate-files-automatically/td-p/14449644
*/
#target photoshop
(function () {
try {
if (!app.documents.length) {
alert("No documents are open.");
return;
}
// Check for pathItems and abort script if any are found
if (app.activeDocument.pathItems.length > 0) {
alert("Script aborted: Path items detected in the document.");
return;
}
app.activeDocument.suspendHistory("Random Selection from transparent layer", "main()");
} catch (e) {
alert("Error: " + e.message + " (Line: " + e.line + ")");
}
})();
// Main processing function
function main() {
var doc = app.activeDocument;
// Step 1: Duplicate the layer/s and prepare
mergeVisibleToDupe();
doc.activeLayer.name = "tempLayer";
// Step 2: Load layer transparency and expand selection
try {
app.activeDocument.selection.deselect();
} catch (e) { }
loadLayerTransparency();
doc.selection.invert();
doc.selection.expand(10);
// Step 3: Convert selection to work path and process
convertSelectionToWorkPath();
separateWorkPathIntoPaths();
// Remove the original work path
if (doc.pathItems.length > 0) {
doc.pathItems[0].remove();
}
// Step 4: Select random path and create selection
selectRandomNormalPath(doc);
convertPathToSelection();
deselectPaths();
// Step 5: Fill and cleanup
doc.selection.invert();
var fgColor = app.foregroundColor;
var bgColor = app.backgroundColor;
resetForegroundBackgroundColors();
fillWithForeground();
loadLayerTransparency();
doc.selection.invert();
doc.activeLayer.remove();
removePaths();
app.foregroundColor = fgColor;
app.backgroundColor = bgColor;
}
///// Helper functions /////
function loadLayerTransparency() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
desc.putReference(stringIDToTypeID("null"), ref);
var targetRef = new ActionReference();
targetRef.putEnumerated(
stringIDToTypeID("channel"),
stringIDToTypeID("channel"),
stringIDToTypeID("transparencyEnum")
);
desc.putReference(stringIDToTypeID("to"), targetRef);
executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
}
function convertSelectionToWorkPath() {
var desc = new ActionDescriptor();
var pathRef = new ActionReference();
pathRef.putClass(charIDToTypeID("Path"));
desc.putReference(charIDToTypeID("null"), pathRef);
var selectionRef = new ActionReference();
selectionRef.putProperty(charIDToTypeID("csel"), charIDToTypeID("fsel"));
desc.putReference(charIDToTypeID("From"), selectionRef);
desc.putUnitDouble(charIDToTypeID("Tlrn"), charIDToTypeID("#Pxl"), 2.0); // Set tolerance to 2 pixels
executeAction(charIDToTypeID("Mk "), desc, DialogModes.NO);
}
function separateWorkPathIntoPaths() {
if (!app.documents.length || !app.activeDocument.pathItems.length) {
return;
}
var doc = app.activeDocument;
var selectedPath = getSelectedPath();
if (!selectedPath) {
alert("No path selected");
return;
}
var pathData = extractPathData(doc, selectedPath);
// Create separate paths from each subpath
for (var i = 0; i < pathData.length; i++) {
var subPathArray = [pathData[i]]; // Wrap single subpath in array
var pathName = selectedPath.name + "_part_" + i;
createPathFromData(subPathArray, pathName);
}
}
function extractPathData(doc, path) {
var originalUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
try {
var ref = new ActionReference();
// Handle different path types
if (path.kind === PathKind.WORKPATH) {
ref.putProperty(charIDToTypeID("Path"), charIDToTypeID("WrPt"));
} else if (path.kind === PathKind.VECTORMASK) {
ref.putEnumerated(
charIDToTypeID("Path"),
charIDToTypeID("Path"),
stringIDToTypeID("vectorMask")
);
} else {
// Normal path - find its index
for (var i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i] === path) {
ref.putIndex(charIDToTypeID("Path"), i + 1);
break;
}
}
}
var desc = executeActionGet(ref);
var pathComponents = desc.getObjectValue(charIDToTypeID("PthC")).getList(stringIDToTypeID('pathComponents'));
var pathArray = [];
// Process each subpath
for (var i = 0; i < pathComponents.count; i++) {
var component = pathComponents.getObjectValue(i);
var subpathList = component.getList(stringIDToTypeID("subpathListKey"));
var operation = component.getEnumerationValue(stringIDToTypeID("shapeOperation"));
// Process each subpath in the component
for (var j = 0; j < subpathList.count; j++) {
var subPath = [];
var subpathObj = subpathList.getObjectValue(j);
var points = subpathObj.getList(stringIDToTypeID('points'));
var isClosed = false;
try {
isClosed = subpathObj.getBoolean(stringIDToTypeID("closedSubpath"));
} catch (e) { }
// Extract point data
for (var k = 0; k < points.count; k++) {
var pointObj = points.getObjectValue(k);
var anchorObj = pointObj.getObjectValue(stringIDToTypeID("anchor"));
var anchor = [
anchorObj.getUnitDoubleValue(stringIDToTypeID('horizontal')),
anchorObj.getUnitDoubleValue(stringIDToTypeID('vertical'))
];
var leftHandle = anchor;
var rightHandle = anchor;
var isSmooth = false;
try {
var leftObj = pointObj.getObjectValue(charIDToTypeID("Fwd "));
leftHandle = [
leftObj.getUnitDoubleValue(stringIDToTypeID('horizontal')),
leftObj.getUnitDoubleValue(stringIDToTypeID('vertical'))
];
} catch (e) { }
try {
var rightObj = pointObj.getObjectValue(charIDToTypeID("Bwd "));
rightHandle = [
rightObj.getUnitDoubleValue(stringIDToTypeID('horizontal')),
rightObj.getUnitDoubleValue(stringIDToTypeID('vertical'))
];
} catch (e) { }
try {
isSmooth = pointObj.getBoolean(charIDToTypeID("Smoo"));
} catch (e) { }
subPath.push([anchor, leftHandle, rightHandle, isSmooth]);
}
subPath.push(isClosed);
subPath.push(operation);
pathArray.push(subPath);
}
}
return pathArray;
} finally {
app.preferences.rulerUnits = originalUnits;
}
}
function createPathFromData(pathArray, pathName) {
var originalUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
try {
var desc = new ActionDescriptor();
var pathRef = new ActionReference();
pathRef.putProperty(charIDToTypeID('Path'), charIDToTypeID('WrPt'));
desc.putReference(stringIDToTypeID('null'), pathRef);
var componentList = new ActionList();
for (var i = 0; i < pathArray.length; i++) {
var subPath = pathArray[i];
var componentDesc = new ActionDescriptor();
var operation = subPath[subPath.length - 1];
var isClosed = subPath[subPath.length - 2];
componentDesc.putEnumerated(
stringIDToTypeID('shapeOperation'),
stringIDToTypeID('shapeOperation'),
operation
);
var subpathList = new ActionList();
var subpathDesc = new ActionDescriptor();
subpathDesc.putBoolean(charIDToTypeID('Clsp'), isClosed);
var pointsList = new ActionList();
// Add points (excluding the last two elements which are flags)
for (var j = 0; j < subPath.length - 2; j++) {
var point = subPath[j];
var pointDesc = new ActionDescriptor();
// Anchor point
var anchorDesc = new ActionDescriptor();
anchorDesc.putUnitDouble(charIDToTypeID('Hrzn'), charIDToTypeID('#Rlt'), point[0][0]);
anchorDesc.putUnitDouble(charIDToTypeID('Vrtc'), charIDToTypeID('#Rlt'), point[0][1]);
pointDesc.putObject(charIDToTypeID('Anch'), charIDToTypeID('Pnt '), anchorDesc);
// Forward handle
var forwardDesc = new ActionDescriptor();
forwardDesc.putUnitDouble(charIDToTypeID('Hrzn'), charIDToTypeID('#Rlt'), point[1][0]);
forwardDesc.putUnitDouble(charIDToTypeID('Vrtc'), charIDToTypeID('#Rlt'), point[1][1]);
pointDesc.putObject(charIDToTypeID('Fwd '), charIDToTypeID('Pnt '), forwardDesc);
// Backward handle
var backwardDesc = new ActionDescriptor();
backwardDesc.putUnitDouble(charIDToTypeID('Hrzn'), charIDToTypeID('#Rlt'), point[2][0]);
backwardDesc.putUnitDouble(charIDToTypeID('Vrtc'), charIDToTypeID('#Rlt'), point[2][1]);
pointDesc.putObject(charIDToTypeID('Bwd '), charIDToTypeID('Pnt '), backwardDesc);
// Smooth flag
pointDesc.putBoolean(charIDToTypeID('Smoo'), point[3]);
pointsList.putObject(charIDToTypeID('Pthp'), pointDesc);
}
subpathDesc.putList(charIDToTypeID('Pts '), pointsList);
subpathList.putObject(charIDToTypeID('Sbpl'), subpathDesc);
componentDesc.putList(charIDToTypeID('SbpL'), subpathList);
componentList.putObject(charIDToTypeID('PaCm'), componentDesc);
}
desc.putList(charIDToTypeID('T '), componentList);
executeAction(charIDToTypeID('setd'), desc, DialogModes.NO);
// Rename the work path
var doc = app.activeDocument;
for (var i = 0; i < doc.pathItems.length; i++) {
if (doc.pathItems[i].kind === PathKind.WORKPATH) {
doc.pathItems[i].name = pathName;
return doc.pathItems[i];
}
}
} finally {
app.preferences.rulerUnits = originalUnits;
}
return null;
}
function getSelectedPath() {
try {
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Path"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var desc = executeActionGet(ref);
var pathName = desc.getString(charIDToTypeID("PthN"));
var doc = app.activeDocument;
// Handle work path case
if (pathName === "Work Path") {
// Save work path first if it exists
if (doc.pathItems.length > 0 &&
doc.pathItems[doc.pathItems.length - 1].kind === PathKind.WORKPATH) {
saveWorkPath();
}
return doc.pathItems[doc.pathItems.length - 1];
} else {
return doc.pathItems.getByName(pathName);
}
} catch (e) {
return null;
}
}
function saveWorkPath() {
// Select work path
var selectDesc = new ActionDescriptor();
var selectRef = new ActionReference();
selectRef.putProperty(charIDToTypeID("Path"), charIDToTypeID("WrPt"));
selectDesc.putReference(charIDToTypeID("null"), selectRef);
executeAction(charIDToTypeID("slct"), selectDesc, DialogModes.NO);
// Create new path from work path
var createDesc = new ActionDescriptor();
var createRef = new ActionReference();
createRef.putClass(charIDToTypeID("Path"));
createDesc.putReference(charIDToTypeID("null"), createRef);
var fromRef = new ActionReference();
fromRef.putProperty(charIDToTypeID("Path"), charIDToTypeID("WrPt"));
createDesc.putReference(charIDToTypeID("From"), fromRef);
createDesc.putString(charIDToTypeID("Nm "), generateTimestamp());
executeAction(charIDToTypeID("Mk "), createDesc, DialogModes.NO);
}
function selectRandomNormalPath(doc) {
if (!doc || !doc.pathItems || doc.pathItems.length === 0) {
alert("No path items found.");
return null;
}
var normalPaths = [];
// Filter for normal paths only
for (var i = 0; i < doc.pathItems.length; i++) {
var path = doc.pathItems[i];
if (path.kind === PathKind.NORMALPATH) {
normalPaths.push(path);
}
}
if (normalPaths.length === 0) {
alert("No saved normal paths found.");
return null;
}
// Select random path
var randomIndex = Math.floor(Math.random() * normalPaths.length);
var selectedPath = normalPaths[randomIndex];
// Select the path using Action Manager
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putName(charIDToTypeID("Path"), selectedPath.name);
desc.putReference(charIDToTypeID("null"), ref);
executeAction(charIDToTypeID("slct"), desc, DialogModes.NO);
return selectedPath;
}
function convertPathToSelection() {
var desc = new ActionDescriptor();
var selectionRef = new ActionReference();
selectionRef.putProperty(charIDToTypeID("Chnl"), charIDToTypeID("fsel"));
desc.putReference(charIDToTypeID("null"), selectionRef);
var pathRef = new ActionReference();
pathRef.putEnumerated(charIDToTypeID("Path"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc.putReference(charIDToTypeID("T "), pathRef);
desc.putInteger(charIDToTypeID("Vrsn"), 1);
desc.putBoolean(stringIDToTypeID("vectorMaskParams"), true);
executeAction(charIDToTypeID("setd"), desc, DialogModes.NO);
}
function deselectPaths() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Path"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc.putReference(charIDToTypeID("null"), ref);
executeAction(charIDToTypeID("Dslc"), desc, DialogModes.NO);
}
function resetForegroundBackgroundColors() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putProperty(charIDToTypeID("Clr "), charIDToTypeID("Clrs"));
desc.putReference(charIDToTypeID("null"), ref);
executeAction(charIDToTypeID("Rset"), desc, DialogModes.NO);
}
function fillWithForeground() {
var desc = new ActionDescriptor();
desc.putEnumerated(charIDToTypeID("Usng"), charIDToTypeID("FlCn"), charIDToTypeID("FrgC"));
desc.putUnitDouble(charIDToTypeID("Opct"), charIDToTypeID("#Prc"), 100.0);
desc.putEnumerated(charIDToTypeID("Md "), charIDToTypeID("BlnM"), charIDToTypeID("Nrml"));
executeAction(charIDToTypeID("Fl "), desc, DialogModes.NO);
}
function generateTimestamp() {
var now = new Date();
var day = now.getDate();
var month = now.getMonth() + 1; // Month is 0-based
var year = now.getFullYear();
var hour = now.getHours();
var minutes = now.getMinutes();
var seconds = now.getSeconds();
return day + "-" + month + "-" + year + "_" + hour + "-" + minutes + "-" + seconds;
}
function removePaths() {
var doc = activeDocument;
var pLen = doc.pathItems.length;
var pArray = [];
for (var i = 0; i < pLen; i++) {
pArray.push(doc.pathItems[i].name);
}
for (var i = 0; i < pLen; i++) {
var curPath = doc.pathItems.getByName(pArray[i]);
curPath.remove();
}
}
function mergeVisibleToDupe() {
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
descriptor.putBoolean( s2t( "duplicate" ), true );
executeAction( s2t( "mergeVisible" ), descriptor, DialogModes.NO );
}
... View more