Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Tree Scatter Tool - (Plan Renderings)

Community Beginner ,
May 07, 2025 May 07, 2025

Why?
For plan renderings, I currently use tree/forest scatter brushes with layer styles applied, but this method doesn't look as great due to not having the shadow in the tree canopy. I thought about having a pattern overlay that incorporates trees canopy shadows, but then you have to get precise with tree brush placements on the edge of forests to match up with the texture and if needing to change the shadow direction, the pattern overlay would need to be rotated too and the forest border would need to be refined.

General Solution
Create a tool that can scatter trees on individual layers so bevel and emboss can be applied to each canopy vs an entire forest canopy mass. Downside is it's processing intensive and if there are significant design updates, the quickest thing would be to delete all trees and run the script again so the script needs to be efficient to run on your average laptop. There would be a "forest area" layer that is just a solid fill for where trees should be scattered/spawned so that border can be easily selected to run the script. 

What I've done so far
Used AI to create scripts and experimented with different methods to try and get desired results with optimization. I first tried to randomly place trees as smart objects, but couldn't get the script to respect a pre-selected forest area very well and it was very slow. Then tried a grid sample approach if a point was in the selected area to spawn trees, this was slow and I let my pc process overnight, but never saw results. Lastly, switched to a stamp method (see attached image and script) which works alright but it didn't achieve the desired density and was relatively slow since it has to open and close a tree.png for every tree placement which could be hundreds of times, which is not ideal and some trees are too overlapped. I tried improving it to increase density, reduce overlap, and even open all the tree .pngs first and/or add them to the same .psd and reference them for better performance but couldn't get those options to really work. 

Another solution would be to have an array of trees with realistic shadows vs dropshadows that are connected to a dynamic border, but I haven't seen anything like that in Photoshop to know if that's feasible.  

Disclaimer: the trees and the effects below are just a quick draft to see if it's possible to work with a script (tree .png's are attached). If someone here can improve this script or Adobe creates a better workflow for this, then I'll create much better looking trees and effects. 

TreeScatter-Progress.PNGTreeScatter_DialogueBox.png

#target photoshop

// Define the resolution parameter (pixels/inch)
var resolution = 200; // Default to 200 DPI, user can modify

// Define the server path
var serverPath = "C:\\Vegetation\\Trees\\Plan";

// Define the tree file names
var treeFiles = ["Tree01.png", "Tree02.png", "Tree03.png", "Tree04.png", "Tree05.png", "Tree06.png"];

// Load .png files from server
function loadPNGsFromServer(serverPath, treeFiles) {
    var pngPaths = [];
    for (var i = 0; i < treeFiles.length; i++) {
        var fullPath = new File(serverPath + "\\" + treeFiles[i]);
        if (fullPath.exists) {
            pngPaths.push(fullPath);
        } else {
            alert("File not found: " + fullPath.fsName);
        }
    }
    return pngPaths;
}

// Scatter .png files within a preselected area
function scatterPNGsInArea(pngPaths, scale, treeDensity, resolution, minSizeFeet, maxSizeFeet) {
    var docRef = app.activeDocument;
    try {
        if (docRef.selection && docRef.selection.bounds && docRef.selection.bounds.length > 0) {
            var selectionBounds = docRef.selection.bounds;
            var xMin = selectionBounds[0].value;
            var yMin = selectionBounds[1].value;
            var xMax = selectionBounds[2].value;
            var yMax = selectionBounds[3].value;
            var width = xMax - xMin;
            var height = yMax - yMin;
            var area = width * height;

            // Convert min/max sizes from feet to pixels
            var minSizePx = (minSizeFeet / scale) * resolution; // Convert to pixels
            var maxSizePx = (maxSizeFeet / scale) * resolution; // Convert to pixels

            // Calculate tree count for specified density
            var avgSizePx = (minSizePx + maxSizePx) / 2;
            var effectiveArea = area * (treeDensity / 100); // Density as percentage
            var treesPerRow = Math.max(1, Math.floor(Math.sqrt(effectiveArea) / avgSizePx));
            var treeCount = Math.min(1000, Math.max(100, Math.floor(treesPerRow * treesPerRow))); // Increased density by 10x

            var placedTrees = 0;
            for (var i = 0; i < treeCount; i++) {
                try {
                    var randomIndex = Math.floor(Math.random() * pngPaths.length);
                    var fileRef = pngPaths[randomIndex];
                    var treeDoc = app.open(fileRef);
                    
                    // Ensure the tree document is at the correct resolution
                    treeDoc.resizeImage(undefined, undefined, resolution, ResampleMethod.NONE);
                    
                    treeDoc.selection.selectAll();
                    treeDoc.selection.copy();
                    treeDoc.close(SaveOptions.DONOTSAVECHANGES);
                    
                    docRef.paste();
                    var layerRef = docRef.activeLayer;
                    
                    // Random size within min/max range
                    var sizePx = minSizePx + (Math.random() * (maxSizePx - minSizePx));
                    
                    // Resize the layer using percentage scaling (base size is 100px)
                    var scaleFactor = (sizePx / 100) * 100; // Percentage relative to 100px
                    layerRef.resize(scaleFactor, scaleFactor, AnchorPosition.MIDDLECENTER);
                    
                    // Get dimensions for positioning
                    var newWidth = layerRef.bounds[2].value - layerRef.bounds[0].value;
                    var newHeight = layerRef.bounds[3].value - layerRef.bounds[1].value;
                    
                    // Random position ensuring the entire tree stays within bounds
                    var xPos = xMin + (newWidth / 2) + Math.random() * (width - newWidth);
                    var yPos = yMin + (newHeight / 2) + Math.random() * (height - newHeight);
                    layerRef.translate(xPos - layerRef.bounds[0].value, yPos - layerRef.bounds[1].value);
                    
                    // Random rotation
                    var rotation = Math.random() * 360;
                    var idTrnf = charIDToTypeID("Trnf");
                    var desc3 = new ActionDescriptor();
                    desc3.putEnumerated(charIDToTypeID("FTcs"), charIDToTypeID("QCSt"), charIDToTypeID("Qcsa"));
                    var idOfst = charIDToTypeID("Ofst");
                    var desc4 = new ActionDescriptor();
                    desc4.putUnitDouble(charIDToTypeID("Hrzn"), charIDToTypeID("#Pxl"), 0);
                    desc4.putUnitDouble(charIDToTypeID("Vrtc"), charIDToTypeID("#Pxl"), 0);
                    desc3.putObject(idOfst, charIDToTypeID("Ofst"), desc4);
                    desc3.putUnitDouble(charIDToTypeID("Angl"), charIDToTypeID("#Ang"), rotation);
                    executeAction(idTrnf, desc3, DialogModes.NO);
                    
                    placedTrees++;
                } catch (e) {
                    alert("Failed to place tree " + (i + 1) + ". Error: " + e + "\nContinuing with next tree.");
                    continue;
                }
            }
            
            docRef.selection.deselect();
            alert("Successfully placed " + placedTrees + " trees in the selected area.");
        } else {
            alert("No valid selection found. Please draw a selection with the Marquee Tool (M) before running the script.");
        }
    } catch (e) {
        alert("Error processing selection.\nError: " + e + "\nPlease ensure a selection exists and try again.");
    }
}

// Main function
function main() {
    var pngPaths = loadPNGsFromServer(serverPath, treeFiles);
    if (pngPaths.length === 0) {
        alert("No valid tree files found. Check the server path and file names.");
        return;
    }

    // Create a dialog for setting parameters
    var dlg = new Window("dialog", "Tree Scatter");
    dlg.orientation = "column";
    dlg.alignChildren = "left";

    dlg.add("statictext", undefined, "Resolution (DPI):");
    var resolutionInput = dlg.add("edittext", undefined, "200");
    resolutionInput.characters = 5;

    dlg.add("statictext", undefined, "Scale:");
    var scaleInput = dlg.add("edittext", undefined, "100");
    scaleInput.characters = 5;

    dlg.add("statictext", undefined, "Minimum Size (feet diameter):");
    var minSizeInput = dlg.add("edittext", undefined, "15");
    minSizeInput.characters = 5;

    dlg.add("statictext", undefined, "Maximum Size (feet diameter):");
    var maxSizeInput = dlg.add("edittext", undefined, "50");
    maxSizeInput.characters = 5;

    dlg.add("statictext", undefined, "Density Percent:");
    var densityInput = dlg.add("edittext", undefined, "95");
    densityInput.characters = 5;

    var scatterButton = dlg.add("button", undefined, "Scatter Trees");
    scatterButton.onClick = function() {
        var resolution = parseFloat(resolutionInput.text);
        var scale = parseFloat(scaleInput.text);
        var minSizeFeet = parseFloat(minSizeInput.text);
        var maxSizeFeet = parseFloat(maxSizeInput.text);
        var treeDensity = parseFloat(densityInput.text);

        if (isNaN(resolution) || isNaN(scale) || isNaN(minSizeFeet) || isNaN(maxSizeFeet) || isNaN(treeDensity)) {
            alert("Please enter valid numerical values for all fields.");
            return;
        }
        if (resolution <= 0 || scale <= 0 || minSizeFeet <= 0 || maxSizeFeet <= 0 || treeDensity <= 0 || treeDensity > 100) {
            alert("Resolution, scale, sizes, and density must be positive numbers; density must be 0-100.");
            return;
        }
        if (minSizeFeet > maxSizeFeet) {
            alert("Minimum size cannot be greater than maximum size.");
            return;
        }

        scatterPNGsInArea(pngPaths, scale, treeDensity, resolution, minSizeFeet, maxSizeFeet);
        dlg.close();
    };

    dlg.show();
}

main();

 

Idea No status
TOPICS
Actions and scripting , Windows
538
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
2 Comments
Community Expert ,
May 08, 2025 May 08, 2025

• Full color brush tips would be a useful improvement and I think Feature Requests on this issue exist. (Though they may have been back on the now defunct feedback.photoshop.com.) 

 

• One can »high-jack« Tools one uses little (in my case the Count Tool or the Color Sampler Tool) to have a Script insert duplicate Smart Object instances to manually set positions by using Script Events Manager. 

 

• A while back I created a Script (and while I think I posted it on the Forum I cannot locate the thread at current) for distribution of Smart Objects in a Group according to a Channel. 

Screenshot 2025-05-08 at 14.38.48.png

Screenshot 2025-05-08 at 11.39.04.pngScreenshot 2025-05-08 at 11.39.27.png

// copies, randomly moves and transforms artlayers in a laserset around based on an alpha-channel;
// size can be linked to the darkness of the alpha-channel;
// to even distribution out the target area can be split into segments to be treated searately;
// 2015, pfaffenbichler;
// use it at your own risk;
#target "photoshop-70.032"
var checksOut = photoshopCheck();
var thePrefPath = File($.fileName).path+"/confettiVerticalHalftone2015Pref.txt";
if (File(thePrefPath).exists == true) {var thePref = readPref(thePrefPath).split(";")}
else {var thePref = [Math.round(200 / activeDocument.activeLayer.layers.length),10,7,true,10,60,true,-10,10,true,false,false]};
// do the operation if the check pass;
if (checksOut == true) {
	app.activeDocument.suspendHistory("confetti", "main()");
	};
////// the operation //////
function main() {
//filter for checking if entry is numeric, whole and positive, thanks to xbytor;
wholePosNumberKeystrokeFilter = function() {
	this.text = this.text.replace(",", "");
	this.text = this.text.replace(".", "");
//	this.text = this.text.replace("-", "");
	if (this.text.match(/[^\-\.\d]/)) {
		this.text = this.text.replace(/[^\-\.\d]/g, '');
		};
	if (Number(this.text) <= 0) {this.text = 1}
	};
//filter for checking if entry is numeric and between -180 and 180, thanks to xbytor;
degreesKeystrokeFilter = function() {
	this.text = this.text.replace(",", ".");
	if (this.text.match(/[^\-\.\d]/)) {
		this.text = this.text.replace(/[^\-\.\d]/g, '');
		};
	if (this.text < -180) {this.text = -180};
	if (this.text > 180) {this.text = 180};
	};
var myDocument = app.activeDocument;
var myResolution = myDocument.resolution;
var myLayer = myDocument.activeLayer;
var theGroupIndex = getLayerIndex(myLayer)-1;
var theLayers = collectLayers(myLayer);
var theNumberOfLayers = theLayers.length;
var originalUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
// determine the composite channels;
theChannelNumber = numberOfCompChannels (myDocument);
// get list of channels that are not fully black;
var eligibleChannels = new Array;
for (var b = theChannelNumber; b < myDocument.channels.length; b++) {
	myDocument.channels[b].visible = true;
	if (myDocument.channels[b].histogram[0] != myDocument.width*myDocument.height) {eligibleChannels.push(myDocument.channels[b])};
	myDocument.channels[b].visible = false;
	};
if (eligibleChannels.length == 0) {return};
// set up dialog for channel selection;
////////////////////////////////////
var dlg = new Window('dialog', "select channel as distribution-target", [500,300,840,745]);
dlg.channelList = dlg.add('listbox', [15,15,325,137], 'field', {multiselect: false});
// populate the list with the open files’ names;
for (var m = 0; m < eligibleChannels.length; m++) {
	dlg.channelList.add ("item", eligibleChannels[m].name);
	};
dlg.channelList.items[0].selected = true;
// field for copies;
dlg.copies = dlg.add('panel', [15,147,325,197], 'number of copies');
dlg.copies.number = dlg.copies.add('edittext', [12,12,60,32], thePref[0]/*Math.round(200 / theNumberOfLayers)*/, {multiline:false});
dlg.copies.numberText = dlg.copies.add('statictext', [65,14,320,32], "instances, amounts to "+String(Number (dlg.copies.number.text) * theNumberOfLayers)+" elements", {multiline:false});
dlg.copies.number.onChange = wholePosNumberKeystrokeFilter;
// update test to correct number;
dlg.copies.number.onChange = function () {dlg.copies.numberText.text = "instances, amounts to "+(theNumberOfLayers*Number(this.text))+" elements"};
// entry-field for fragmentation;
dlg.fragmentNumber = dlg.add('panel', [15,200,165,250], "derandomize");
dlg.fragmentNumber.number = dlg.fragmentNumber.add('edittext', [12,12,60,32], 0, {multiline:false});
dlg.fragmentNumber.number.text = thePref[1];
dlg.fragmentNumber.expl = dlg.fragmentNumber.add('statictext', [70,12,160,32], "parts\/side", {multiline:false});
dlg.fragmentNumber.number.onChange = wholePosNumberKeystrokeFilter;
/*dlg.fragmentNumber.number.onChange = function() {
	this.text = this.text.replace(",", "");
	this.text = this.text.replace(".", "");
	this.text = this.text.replace("-", "");
	if (this.text.match(/[^\-\.\d]/)) {
		this.text = this.text.replace(/[^\-\.\d]/g, '');
		};
	if (Number(this.text) <= 0) {this.text = 1}
	};*/
// field for rotate;
dlg.posterize = dlg.add('panel', [175,200,325,250], 'posterize');
dlg.posterize.number = dlg.posterize.add('edittext', [12,12,50,32], thePref[2], {multiline:false});
dlg.posterize.check = dlg.posterize.add('checkbox', [55,12,145,32], "posterize");
dlg.posterize.check.value = thePref[3];
dlg.posterize.number.onChange = wholePosNumberKeystrokeFilter;
// field for rescale;
dlg.scale = dlg.add('panel', [15,260,165,310], 'scale between %');
dlg.scale.number1 = dlg.scale.add('edittext', [12,12,50,32], thePref[4], {multiline:false});
dlg.scale.number2 = dlg.scale.add('edittext', [55,12,93,32], thePref[5], {multiline:false});
dlg.scale.flip = dlg.scale.add('checkbox', [100,12,150,32], "flip");
dlg.scale.flip.value = thePref[6];
dlg.scale.number1.onChange = wholePosNumberKeystrokeFilter;
dlg.scale.number2.onChange = wholePosNumberKeystrokeFilter;
// field for rotate;
dlg.rotate = dlg.add('panel', [175,260,325,310], 'rotate between ˚');
dlg.rotate.number1 = dlg.rotate.add('edittext', [12,12,60,32], thePref[7], {multiline:false});
dlg.rotate.number2 = dlg.rotate.add('edittext', [75,12,123,32], thePref[8], {multiline:false});
dlg.rotate.number1.onChange = degreesKeystrokeFilter;
dlg.rotate.number2.onChange = degreesKeystrokeFilter;
// buttons for direction;
dlg.direction = dlg.add('panel', [15,320,325,370], 'direction for overlaps');
dlg.direction.top2Bottom = dlg.direction.add('radiobutton', [13,13,145,30], 'top2Bottom');
dlg.direction.bottom2Top = dlg.direction.add('radiobutton', [115,13,290,30], 'bottom2Top');
dlg.direction.random = dlg.direction.add('radiobutton', [220,13,310,30], 'random');
dlg.direction.top2Bottom.value = eval(thePref[9]);
dlg.direction.bottom2Top.value = eval(thePref[10]);
dlg.direction.random.value = eval(thePref[11]);
// buttons for ok, and cancel;
dlg.buttons = dlg.add('panel', [15,380,325,430], '');
dlg.buttons.buildBtn = dlg.buttons.add('button', [13,13,145,33], 'OK', {name:'ok'});
dlg.buttons.cancelBtn = dlg.buttons.add('button', [155,13,290,33], 'Cancel', {name:'cancel'});
// show the dialog;
dlg.center();
var myReturn = dlg.show ();
////////////////////////////////////
if (myReturn != 1) {return}
else {
	app.togglePalettes();
	var originalResolution = myDocument.resolution;
	myDocument.resizeImage(undefined, undefined, 72, ResampleMethod.NONE);
var time1 = Number(timeString());
// get the number instead of the name;	
	var theColl = dlg.channelList.items;
	for (var p = 0; p < dlg.channelList.items.length; p++) {
		if (dlg.channelList.items[p].selected == true) {
			theChannelSelection = p + theChannelNumber;
			}
		};
// collect the rest of the entries;
var theCopies = dlg.copies.number.text;
var theFragments = dlg.fragmentNumber.number .text;
var theScale1 = Math.min(dlg.scale.number1.text, dlg.scale.number2.text);
var theScale2 = Math.max(dlg.scale.number1.text, dlg.scale.number2.text);
//var theScaleProp = dlg.scale.proportion.value;
var theFlip = dlg.scale.flip.value;
var theRotate1 = Math.min(dlg.rotate.number1.text, dlg.rotate.number2.text);
var theRotate2 = Math.max(dlg.rotate.number1.text, dlg.rotate.number2.text);
if (dlg.direction.top2Bottom.value == true) {
	var theDirection = 2
	};
if (dlg.direction.bottom2Top.value == true) {
	var theDirection = 1
	};
if (dlg.direction.random.value == true) {
	var theDirection = 0
	};
// convert to smart objects if not such already;		
for (var d = 0; d < theNumberOfLayers; d++) {
	smartify (theLayers[d])
	};
// collect layers again because after converting them to smart objects they are no longer identifiable;
var theLayers = collectLayerAndIDs(myLayer);	
////////////////////////////////////
// check for the existence of a selcted channel;
if (theChannelSelection) {
// create an array of points within the non-black area of an alphachannel,;
myDocument.selection.load(myDocument.channels[theChannelSelection]);
var theChBounds = myDocument.selection.bounds;
// make the temp file;
// =======================================================
var idMk = charIDToTypeID( "Mk  " );
    var desc3 = new ActionDescriptor();
    var idNw = charIDToTypeID( "Nw  " );
    var idDcmn = charIDToTypeID( "Dcmn" );
    desc3.putClass( idNw, idDcmn );
    var idUsng = charIDToTypeID( "Usng" );
        var ref3 = new ActionReference();
        var idChnl = charIDToTypeID( "Chnl" );
        var idfsel = charIDToTypeID( "fsel" );
        ref3.putProperty( idChnl, idfsel );
    desc3.putReference( idUsng, ref3 );
    var idChnN = charIDToTypeID( "ChnN" );
    desc3.putString( idChnN, "temp" );
executeAction( idMk, desc3, DialogModes.NO );
// make the copy rgb;
var myTemp = app.activeDocument;
if (app.activeDocument.mode == DocumentMode.MULTICHANNEL) {
	// =======================================================
	var idCnvM = charIDToTypeID( "CnvM" );
		var desc2 = new ActionDescriptor();
		var idT = charIDToTypeID( "T   " );
		var idGrys = charIDToTypeID( "Grys" );
		desc2.putClass( idT, idGrys );
	executeAction( idCnvM, desc2, DialogModes.NO );
	};
myTemp.convertProfile ("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC, true, false);
// posterize if so selected;
if (dlg.posterize.check.value == true) {
	myTemp.layers[0].posterize(dlg.posterize.number.text)
	};
myTemp.layers[0].applyMedianNoise(3);
////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
app.preferences.rulerUnits = Units.PIXELS;
// thenumber of layers to transform;
theNumberOfLayers = theNumberOfLayers + (theNumberOfLayers * (theCopies - 1));
// do the fragmentation;
var theBoundsWidth = theChBounds[2] - theChBounds[0];
var theBoundsHeight = theChBounds[3] - theChBounds[1];
var theFragmentWidth = theBoundsWidth / theFragments;
var theFragmentHeight = theBoundsHeight / theFragments;
var fragmentsBounds = new Array;
// get the non-black histograms for the fragments;
var theNumbersPerFragment = new Array;
for (var k = 0; k < theFragments * theFragments; k++) {
	var theNewBounds = new Array;
	theNewBounds[0] = Math.round(theChBounds[0] + (theFragmentWidth * (k - (Math.floor(k / theFragments)) * theFragments)));
	theNewBounds[1] = Math.round(theChBounds[1] + (theFragmentHeight * Math.floor(k / theFragments)));
	theNewBounds[2] = Math.round(theNewBounds[0] + theFragmentWidth);
	theNewBounds[3] = Math.round(theNewBounds[1] + theFragmentHeight);
	var theFourPoints = [[theNewBounds[0], theNewBounds[1]], [theNewBounds[2], theNewBounds[1]], [theNewBounds[2], theNewBounds[3]], [theNewBounds[0], theNewBounds[3]]];
	myTemp.selection.select( theFourPoints, SelectionType.REPLACE);
// get the relative amount of non-black pixels in relation to the whole bounds;
	theNumbersPerFragment[k] = (Math.round(theFragmentWidth) * Math.round(theFragmentHeight) - myTemp.histogram[0]);
	fragmentsBounds[k] = theNewBounds;
	};
// get the relation to the total non-black pixel numbers;
var theTotalNBPoints = addArray(theNumbersPerFragment);
var theRelativeNumbers = new Array;
for (var e = 0; e < theNumbersPerFragment.length; e++) {
	theRelativeNumbers[e] = theNumbersPerFragment[e] / theTotalNBPoints
	};
var theNumber4Fragments = roundDistribute(theRelativeNumbers, theNumberOfLayers);
// get the points;
app.preferences.rulerUnits = Units.POINTS;
var count = 0;
var theTargetCenters = new Array;
for (var l = 0; l < theFragments * theFragments; l++) {
	if (theNumber4Fragments[l] != 0) {
		for (var o = 0; o < theNumber4Fragments[l]; o++) {			
			var aPoint = getATarget (fragmentsBounds[l][0], fragmentsBounds[l][1], fragmentsBounds[l][2] - fragmentsBounds[l][0], fragmentsBounds[l][3] - fragmentsBounds[l][1]);
			theTargetCenters[count] = aPoint;			
			count = count + 1;
			}
		}
	};
// close the copy;
myTemp.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument = myDocument;
myDocument.selection.deselect();
// sort vertically;
if (theDirection == 1 || theDirection == 2) {
	theTargetCenters = sortArrayBy (theTargetCenters, theDirection, 1)
	};
////////////////////////////////////
// do the transformations;
var copyThis = 0;
// create rotation angles;
var theAngles = new Array;
	for (var p = 0; p < theTargetCenters.length; p++) {
// calculate angle between the maller and the bigger one;
		theAngles.push(theRotate1 + (Math.random() * (theRotate2 - theRotate1)))
		};
	for (var n = 0; n < theTargetCenters.length; n++) {
$.writeln(n);
// create the copies;
		if (theLayers.length > 1) {
			copyThis++
			if (copyThis == theLayers.length) {copyThis = 0}
			};
// determine the target position;
		var theHorOffset = Number(theTargetCenters[n][0]) - Number(theLayers[copyThis][2]);
		var theVerOffset = Number(theTargetCenters[n][1]) - Number(theLayers[copyThis][3]);		
// calculate scale factors between the smaller and the bigger one;
		var theScale = theScale1 + ((theScale2 - theScale1) * (theTargetCenters[n][2] / 255) );
// apply;
		try {
// attempt random flips;
			if (theFlip == true) {
				switch (Math.floor(Math.random()*2)) {
					case 0:
					var theHorScale = theScale;
					break;
					case 1:
					var theHorScale = theScale * (-1);
					break;
					}
				};
			duplicateMoveAndScale (myDocument, theLayers[copyThis][1], theGroupIndex++, theAngles[n], theHorOffset, theVerOffset, theHorScale, theScale)
			}
		catch (e) {}
		};
// hide original layers;
	for (var o = 0; o < theLayers.length; o++) {theLayers[o][0].visible = false};
	myDocument.activeLayer = myLayer;
	};
// reset;
	app.preferences.rulerUnits = originalUnits;
	myDocument.resizeImage(undefined, undefined, originalResolution, ResampleMethod.NONE);
	app.togglePalettes();
	writePref (theCopies+";"+theFragments+";"+Number(dlg.posterize.number.text)+";"+dlg.posterize.check.value+";"+theScale1+";"+theScale2+";"+theFlip+";"+theRotate1+";"+theRotate2+";"+dlg.direction.top2Bottom.value+";"+dlg.direction.bottom2Top.value+";"+dlg.direction.random.value, thePrefPath)
	};
var time2 = Number(timeString());
alert(((time2-time1)/1000)+" seconds\nstart "+time1+"\nend "+time2);
};
////////////////////////////////////
////////////////////////////////////
////////////////////////////////////
////// function collect all artlayers //////
function collectLayers (theParent) {
	if (!allLayers) {
		var allLayers = new Array} 
	else {};
	var theNumber = theParent.layers.length - 1;
	for (var m = theNumber; m >= 0;m--) {
		var theLayer = theParent.layers[m];
// apply the function to layersets;
		if (theLayer.typename == "ArtLayer") {
			allLayers = allLayers.concat(theLayer)
			}
		else {
// this line includes the layer groups;
//			allLayers = allLayers.concat(theLayer);
			allLayers = allLayers.concat(collectLayers(theLayer))
			}
		}
	return allLayers
};
////// function collect all artlayers //////
function collectLayerAndIDs (theParent, allLayers) {
	if (!allLayers) {
		var allLayers = new Array} 
	else {};
	var theNumber = theParent.layers.length - 1;
	for (var m = theNumber; m >= 0;m--) {
		var theLayer = theParent.layers[m];
		if (theLayer.typename == "ArtLayer") {
// calculate the center;
			var theBounds = theLayer.bounds;
			var theHorCenter = theBounds[0] + ((theBounds[2] - theBounds[0])/2);
			var theVerCenter = theBounds[1] + ((theBounds[3] - theBounds[1])/2);
			allLayers.push([theLayer, getLayerId(theLayer), theHorCenter, theVerCenter])
			}
		else {
			allLayers = allLayers.concat(collectLayerAndIDs(theLayer))
			}
		};
	return allLayers
};
////// function to get point that’s not black //////
function getATarget (horStart, verStart, theWidth, theHeight) {
	app.preferences.rulerUnits = Units.POINTS;
	if (app.activeDocument.colorSamplers.length == 0) {
		var theSampler = app.activeDocument.colorSamplers.add([0,0]);
		}
	else {
		var theSampler = app.activeDocument.colorSamplers[app.activeDocument.colorSamplers.length-1];
		};
	var theSamplerNumber = app.activeDocument.colorSamplers.length;
	var theCoord = [new UnitValue(horStart + (Math.random() * theWidth), "pt"), new UnitValue(verStart + (Math.random() * theHeight), "pt")];
// move color sampler =======================================================
var idmove = charIDToTypeID( "move" );
    var desc5 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref4 = new ActionReference();
        var idClSm = charIDToTypeID( "ClSm" );
        ref4.putIndex( idClSm, theSamplerNumber );
    desc5.putReference( idnull, ref4 );
    var idT = charIDToTypeID( "T   " );
        var desc6 = new ActionDescriptor();
        var idHrzn = charIDToTypeID( "Hrzn" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc6.putUnitDouble( idHrzn, idPxl, theCoord[0]);
        var idVrtc = charIDToTypeID( "Vrtc" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc6.putUnitDouble( idVrtc, idPxl, theCoord[1]);
    var idPnt = charIDToTypeID( "Pnt " );
    desc5.putObject( idT, idPnt, desc6 );
executeAction( idmove, desc5, DialogModes.NO );
	theNumber = Number(theSampler.color.rgb.red);
/*
var ref = new ActionReference();
ref.putProperty(stringIDToTypeID ("property"), stringIDToTypeID("colorSamplerList"));
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var docDesc = executeActionGet(ref);
var colorSamplers = docDesc.getList(stringIDToTypeID("colorSamplerList"));
theNumber = colorSamplers.getObjectValue(theSamplerNumber-1).getObjectValue(stringIDToTypeID("color")).getDouble(stringIDToTypeID("red"));
*/
	var check = isNaN(theNumber);
	if (theNumber >= 1 && check == false) {
		theCoord[2] = Number(theSampler.color.rgb.red);
		}
	else {
		theCoord = getATarget (horStart, verStart, theWidth, theHeight)
		};	
	return theCoord
	};
////// function to smartify if not //////
function smartify (theLayer) {
// make layers smart objects if they are not already;
	if (theLayer.kind != LayerKind.SMARTOBJECT) {
		app.activeDocument.activeLayer = theLayer;
		var id557 = charIDToTypeID( "slct" );
		var desc108 = new ActionDescriptor();
		var id558 = charIDToTypeID( "null" );
		var ref77 = new ActionReference();
		var id559 = charIDToTypeID( "Mn  " );
		var id560 = charIDToTypeID( "MnIt" );
		var id561 = stringIDToTypeID( "newPlacedLayer" );
		ref77.putEnumerated( id559, id560, id561 );
		desc108.putReference( id558, ref77 );
		executeAction( id557, desc108, DialogModes.NO )
		};
	return app.activeDocument.activeLayer
	};
////// function to sort targetcenters vertically //////
function sortArrayBy (theTargetCenters, theDirection, theElement) {
////// to sort a double array by sam, http://www.rhinocerus.net/forum/lang-javascript/ //////
function sortByDate(a,b) {
if (a[1]<b[1]) return -1;
if (a[1]>b[1]) return 1;
return 0;
};
if (theDirection != 0) {
var theTargetCentersSorted = theTargetCenters.sort(sortByDate);
switch (theDirection) {
	case 1:
	return theTargetCentersSorted.reverse();
	break;
/*	case 2:
	return theTargetCentersSorted;
	break;*/
	default:
	return theTargetCentersSorted;
	break;
	}
}
else {return theTargetCenters};
};
////// function distributes number amongst an array //////
function roundDistribute (theArray, theNumber) {
	var newArray = new Array;
	for (var x = 0; x < theArray.length; x++) {
		newArray[x] = theArray[x]
		};
	var theBreak =  (1 / theNumber);
// create an arraay of zeros to hold the added numbers;
	var theNumbers = new Array;
	for (var g = 0; g < theArray.length; g++) {
		theNumbers[g] = 0
		};
	var theCount = Number(theNumber);
	var theTotal = 0;
// add up ones until the count is exhausted;
	while (theCount > 0) {
		for (var f = 0; f < theArray.length; f++) {
			if (newArray[f] > 0 && theCount > 0) {
// make a second array to sort and identify the highest number;
				var secArray = new Array;
				for (var h = 0; h < theArray.length; h++) {
					secArray[h] = newArray[h]
					};
				secArray.sort(function(a,b){return b - a});
				if (newArray[f] == secArray[0]) {
				
// add one in the new series;
					theNumbers[f] = theNumbers[f] + 1;
// reduce by one unit;				
					newArray[f] = newArray[f] - (theBreak);
// add up;
					theTotal = theTotal + 1;
// decrease the count;
					theCount = theCount - 1
					}
				
				}
			}
		};
	return theNumbers
	};
////// function add all numbers in an array //////
function addArray (theArray) {
	theAdded = 0;
	for (var n = 0; n < theArray.length; n++) {
		theAdded = theAdded + theArray[n]
		};
	return theAdded
	};
////// rotate and move //////
function duplicateMoveAndScale (myDocument, theIndex, theGroupIndex, thisAngle, horizontalOffset, verticalOffset, horScale, verScale) {
// duplicate by index;
// =======================================================
var idmove = charIDToTypeID( "move" );
    var desc8 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref8 = new ActionReference();
        var idLyr = charIDToTypeID( "Lyr " );
        ref8.putIdentifier( charIDToTypeID( "Lyr " ), theIndex );
    desc8.putReference( idnull, ref8 );
    var idT = charIDToTypeID( "T   " );
        var ref9 = new ActionReference();
        var idLyr = charIDToTypeID( "Lyr " );
        ref9.putIndex( idLyr, theGroupIndex );
//        ref9.putIdentifier ( idLyr, theGroupIndex );
    desc8.putReference( idT, ref9 );
    var idDplc = charIDToTypeID( "Dplc" );
    desc8.putBoolean( idDplc, true );
    var idAdjs = charIDToTypeID( "Adjs" );
    desc8.putBoolean( idAdjs, false );
    var idVrsn = charIDToTypeID( "Vrsn" );
    desc8.putInteger( idVrsn, 5 );
executeAction( idmove, desc8, DialogModes.NO );
/*// =======================================================
var idDplc = charIDToTypeID( "Dplc" );
    var desc3 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref1 = new ActionReference();
        ref1.putIdentifier( charIDToTypeID( "Lyr " ), theIndex ); 
    desc3.putReference( idnull, ref1 );
    var idVrsn = charIDToTypeID( "Vrsn" );
    desc3.putInteger( idVrsn, 5 );
executeAction( idDplc, desc3, DialogModes.NO );*/
// do the transformations;
// =======================================================
var idTrnf = charIDToTypeID( "Trnf" );
    var desc3 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref2 = new ActionReference();
        var idLyr = charIDToTypeID( "Lyr " );
        var idOrdn = charIDToTypeID( "Ordn" );
        var idTrgt = charIDToTypeID( "Trgt" );
        ref2.putEnumerated( idLyr, idOrdn, idTrgt );
    desc3.putReference( idnull, ref2 );
    var idFTcs = charIDToTypeID( "FTcs" );
    var idQCSt = charIDToTypeID( "QCSt" );
    var idQcsa = charIDToTypeID( "Qcsa" );
    desc3.putEnumerated( idFTcs, idQCSt, idQcsa );
    var idOfst = charIDToTypeID( "Ofst" );
        var desc4 = new ActionDescriptor();
        var idHrzn = charIDToTypeID( "Hrzn" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc4.putUnitDouble( idHrzn, idPxl, horizontalOffset );
        var idVrtc = charIDToTypeID( "Vrtc" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc4.putUnitDouble( idVrtc, idPxl, verticalOffset );
    var idOfst = charIDToTypeID( "Ofst" );
    desc3.putObject( idOfst, idOfst, desc4 );
    var idWdth = charIDToTypeID( "Wdth" );
    var idPrc = charIDToTypeID( "#Prc" );
    desc3.putUnitDouble( idWdth, idPrc, horScale );
    var idHght = charIDToTypeID( "Hght" );
    var idPrc = charIDToTypeID( "#Prc" );
    desc3.putUnitDouble( idHght, idPrc, verScale );
if (thisAngle != 0) {
    var idAngl = charIDToTypeID( "Angl" );
    var idAng = charIDToTypeID( "#Ang" );
    desc3.putUnitDouble( idAngl, idAng, thisAngle );
	};
executeAction( idTrnf, desc3, DialogModes.NO );
};
// by mike hale, via paul riggott;
function getLayerId(theLayer){
// http://forums.adobe.com/message/1944754#1944754
app.activeDocument.activeLayer = theLayer;
//Assumes activeDocument and activeLayer	
    var ref = new ActionReference(); 
    ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); 
    d = executeActionGet(ref); 
return d.getInteger(charIDToTypeID('LyrI')); 
};
// by mike hale, via paul riggott;
function getLayerIndex(theLayer){
// http://forums.adobe.com/message/1944754#1944754
app.activeDocument.activeLayer = theLayer;
//Assumes activeDocument and activeLayer	
    var ref = new ActionReference(); 
    ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); 
    d = executeActionGet(ref); 
return d.getInteger(stringIDToTypeID('itemIndex')); 
};
////// function to determine if open document is eligible for operations //////
function photoshopCheck () {
var checksOut = true;
if (app.documents.length == 0) {
	alert ("no document open");
	checksOut = false
	}
else {
	try {var theMode = app.activeDocument.mode}
	catch (e) {var theChannelNumber = 0};
	theChannelNumber = numberOfCompChannels (app.activeDocument);
	if (app.activeDocument.channels.length == theChannelNumber) {
		alert ("document has no additional channel");
		checksOut = false
		}
	else {
	if (app.activeDocument.activeLayer.typename != "LayerSet") {
		alert ("please select a layerset");
		checksOut = false
		}
	else{
	if (app.activeDocument.activeLayer.typename == "LayerSet" && app.activeDocument.activeLayer.layers.length == 0) {
		alert ("layerset contains no layers");
		checksOut = false
		}
////////////////////////////////////
	else {		
		var theKinds = ["LayerKind.BLACKANDWHITE", "LayerKind.BRIGHTNESSCONTRAST", "LayerKind.CHANNELMIXER", "LayerKind.COLORBALANCE", "LayerKind.CURVES", "LayerKind.EXPOSURE",
		"LayerKind.GRADIENTMAP", "LayerKind.HUESATURATION", "LayerKind.INVERSION", "LayerKind.LAYER3D", "LayerKind.LEVELS", "LayerKind.PHOTOFILTER", "LayerKind.POSTERIZE",
		"LayerKind.SELECTIVECOLOR", "LayerKind.THRESHOLD", "LayerKind.VIBRANCE"];
		for (var m = 0; m < app.activeDocument.activeLayer.layers.length; m++) {
			for (var n = 0; n < theKinds.length; n++) {
				if (app.activeDocument.activeLayer.layers[m].kind == theKinds[n]) {
					checksOut = false;
					var eligibility = false
					}
				}
			};
		if (eligibility == false) {alert ("layerset contains ineligible layers")}
		};
////////////////////////////////////
	}
	}
	};
return checksOut
};
////// get the number of composite channels //////
function numberOfCompChannels (myDocument) {
	switch (myDocument.mode) {
		case DocumentMode.RGB: 
		var theChannelNumber = 3;
		break;
		case DocumentMode.LAB: 
		var theChannelNumber = 3;
		break;
		case DocumentMode.CMYK: 
		var theChannelNumber = 4;
		break;
		case DocumentMode.GRAYSCALE: 
		var theChannelNumber = 1;
		break;
		case DocumentMode.DUOTONE: 
		var theChannelNumber = 1;
		break;
		case DocumentMode.INDEXEDCOLOR: 
		var theChannelNumber = 1;
		break;
		default: 
		var theChannelNumber = 0;
		break;
		};
	return theChannelNumber
	};
////// function to get the date //////
function timeString () {
	var now = new Date();
	return now.getTime()
	};
////// read prefs file //////
function readPref (thePath) {
  if (File(thePath).exists == true) {
    var file = File(thePath);
    file.open("r");
    file.encoding= 'BINARY';
    var theText = new String;
    for (var m = 0; m < file.length; m ++) {
      theText = theText.concat(file.readch());
      };
    file.close();
    return String(theText)
    }
  };
////// function to write a preference-file storing a text //////
function writePref (theText, thePath) {
  try {
    var thePrefFile = new File(thePath);
    thePrefFile.open("w");
    for (var m = 0; m < theText.length; m ++) {
      thePrefFile.write(theText[m])
      };
    thePrefFile.close()
    }
  catch (e) {};
  };

 

Translate
Report
Community Expert ,
May 08, 2025 May 08, 2025
LATEST
Translate
Report