Copy link to clipboard
Copied
Can someone help me how to perform this process?
Automatically combine two images into one layered PSD or TIFF image, then save.
Let's say I have a bunch of original images and retouched images in a folder.
Like this,
image_01.tiff
image_01_retouched.tiff
image_02.tiff
image_02_retouched.tiff
I would like some kind of script to automatically add retouched image into the second layer in the original image and then save the file. (It would be amazing if it could also change the file names as well)
Is this possible?
I'm doing this manually, and as you can guess, it's painful.
By opening both images and dragging the retouched image into the original image. Then save the file.
I have cleaned up the code a little bit from the generic script previously posted and changed over to layered TIFF output:
/*
Stack x2 Image Sets to Layered TIFF.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-automatically-add-two-images-into-layer-in-a-single-psd-or-tiff-then-save/m-p/13897545
v1.0 - 28th June 2023, Stephen Marsh
* This script requires input files from a single folder to be alpha/numeric sorting in order to stack in the correc
...
Let's make a minor change to the previous code to retain the Background layer, as that may be the only thing you need!
Find the following chunk of code:
// Set the base doc layer name
app.activeDocument = documents[0];
docNameToLayerName();
Delete or comment out // the 3rd line #78 for docNameToLayerName();
// Set the base doc layer name
app.activeDocument = documents[0];
//docNameToLayerName();
If required, further refinements can be made as previously discussed, a
...Hi @Stephen Marsh
If I run the script as they are, the layer order will flip in the stacked file.
By @hosongn52153496
Find the following section in the code:
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() for the last filename in the merged file
fileList.sort().reverse();
Then change the last line #36 to:
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() fo
...
Copy link to clipboard
Copied
Hi @hosongn52153496 ,
Yep, I can see that would be painful. Luckily there's an easy solution:
Just go to File>Scripts>Load Files into Stack...
You can run on either files that are open in Photoshop, or files you select on your hard drive.
Give it a try and see which way you think might be the quickest for your workflow. Just click the browse button to navigate to images on your hard drive.
You might also record an action that selects File>Open so you can select 2 files, then runs the Load Files into Stack script and assign a shortcut key to it. That way you could just hit an F-Key, choose 2 files, OK, Load Open Files, and Save. Viola.
Let us know what works for you!
Thanks,
Sef
Copy link to clipboard
Copied
Thank you @Sef McCullough for the suggestion!
However, I was looking for a solution that works with several hundreds of images in a folder.
Is this method works for that?
Copy link to clipboard
Copied
No worries @hosongn52153496 - as you already know, I think @Stephen Marsh 's script solution below is the thing!
Copy link to clipboard
Copied
Another neat feature of using Sef's idea about "Loading files into Stacks" is that it also loads the file name as th layer name so you can see exactly which layers go toether!
Michelle
Copy link to clipboard
Copied
This topic comes up regularly, I'll post the code later.
Copy link to clipboard
Copied
Re: the code - @Stephen Marsh did you write that script?
Copy link to clipboard
Copied
Re: the code - @Stephen Marsh did you write that script?
By @Sef McCullough
Yes.
Copy link to clipboard
Copied
Is there a bowing emoji?
Copy link to clipboard
Copied
Is there a bowing emoji?
By @Sef McCullough
I'd need one for "standing on the shoulders of giants" as I certainly didn't create the script in a vacuum.
Copy link to clipboard
Copied
Totally. I was just talking to the team today @Stephen Marsh at the developer days breakout about Image Processor in the context of UXP migration. Something seemingly so simplistic at surface, under the hood has a huge footprint. 20+ years old, still a fundamental part of my workflows. Curious if Russell wrote both that and IP Pro? Or maybe added to it to create the extra features in Pro.
Is the code you pasted in part of the actual code running in the public Load Files script?
Sry this is going off topic, we can leave it at that 😉
@hosongn52153496 if you stop back by plz let us know if you have any questions/feedback.
Sef
Copy link to clipboard
Copied
@Sef McCullough – no, the script I wrote is much simpler than any of the Adobe scripts that ship with Photoshop.
In many ways, the script I posted reflects what I have learnt over time at the forum. I kept adding bits as I learned new things. The original and earlier versions were not as refined as the current version. The script is adaptable, sometimes people have two or more input folders, with the files named the same in different folders. Or perhaps they bulk stack 3 or more sets into a single image. Or rather than a single top-level input folder, there are also sub-folders under the main input folder. Or perhaps the output folder isn't separate and is also the input folder etc. Everyone has different workflows, but the common theme is the need to batch folders of images into sets of 2 or more stacked images per combined image (not all images into one single image).
IMHO, the default scripts that ship with Photoshop do need to be updated, at least to support PNG or WebP formats in their current ExtendScript form, before they are updated to UXP.
Copy link to clipboard
Copied
Thank you @Stephen Marsh - your sharing of knowledge is invaluable.
Copy link to clipboard
Copied
@hosongn52153496 – Try the following generic script. It saves to PSD, however, it could also save to layered TIFF or flattened JPEG etc. Let me know if any changes are required for your specific use case.
/*
Stack N Number of Document Sets to Layers - Top Level Folder.jsx
Stephen Marsh
8th June 2024 Version
https://community.adobe.com/t5/photoshop-ecosystem-discussions/script-to-automate-load-files-into-stack/m-p/13499068
A generic, skeleton "framework" script to help fast-track development of similar scripts for combining multiple "sequence" single-layer files to layers.
This script requires input files from a single folder to be alpha/numeric sorting in order to stack in the correct set quantity.
Example: File-01.jpg File-02.jpg etc, FileA1.tif FileA2.tif etc, File1a.tif File1b.tif etc.
A minimum of 2 or more files per stack is required. The quantity of input files must be evenly divisible by the stack quantity.
A named action set and action can be set on line 159 to "do something" with the stacked layers.
*/
#target photoshop
// app.documents.length === 0
if (!app.documents.length) {
try {
// Save and disable dialogs
var restoreDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.NO;
// Main script function
(function () {
// Select the input folder
var inputFolder = Folder.selectDialog('Please select the folder with files to process');
if (inputFolder === null) return;
// Limit the file format input, add or remove as required
var fileList = inputFolder.getFiles(/\.(png|jpg|jpeg|tif|tiff|psd|psb)$/i);
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() for the last filename in the merged file
fileList.sort().reverse();
//////////////////////////// Static Set Quantity - No GUI ////////////////////////////
// var setQty = 2;
//////////////////////////////////////////////////////////////////////////////////////
// or...
//////////////////////////// Variable Set Quantity - GUI /////////////////////////////
// Loop the input prompt until a number is entered
var origInput;
while (isNaN(origInput = prompt('No. of files per set (minimum 2):', '2')));
// Test if cancel returns null, then terminate the script
if (origInput === null) {
alert('Script cancelled!');
return
}
// Test if an empty string is returned, then terminate the script
if (origInput === '') {
alert('A value was not entered, script cancelled!');
return
}
// Test if a value less than 2 is returned, then terminate the script
if (origInput < 2) {
alert('A value less than 2 was entered, script cancelled!');
return
}
// Convert decimal input to integer
var setQty = parseInt(origInput);
//////////////////////////////////////////////////////////////////////////////////////
// Validate that the file list is not empty
var inputCount = fileList.length;
var cancelScript1 = (inputCount === 0);
if (cancelScript1 === true) {
alert('Zero input files found, script cancelled!');
return;
}
// Validate the input count vs. output count - Thanks to Kukurykus for the advice to test using % modulus
var cancelScript2 = !(inputCount % setQty);
alert(inputCount + ' input files stacked into sets of ' + setQty + ' will produce ' + inputCount / setQty + ' output files.');
// Test if false, then terminate the script
if (cancelScript2 === false) {
alert('Script cancelled as the quantity of input files are not evenly divisible by the set quantity.');
return;
}
// Select the output folder
var outputFolder = Folder.selectDialog("Please select the folder to save to");
if (outputFolder === null) {
alert('Script cancelled!');
return;
}
// or
/*
// Create the output sub-directory
var outputFolder = Folder(decodeURI(inputFolder + '/Output Sets Folder'));
if (!outputFolder.exists) outputFolder.create();
*/
// Hide the Photoshop panels
app.togglePalettes();
// Script running notification window - courtesy of William Campbell
// https://www.marspremedia.com/download?asset=adobe-script-tutorial-11.zip
// https://youtu.be/JXPeLi6uPv4?si=Qx0OVNLAOzDrYPB4
var working;
working = new Window("palette");
working.preferredSize = [300, 80];
working.add("statictext");
working.t = working.add("statictext");
working.add("statictext");
working.display = function (message) {
this.t.text = message || "Script running, please wait...";
this.show();
app.refresh();
};
working.display();
// Set the file processing counter
var fileCounter = 0;
// Loop through and open the file sets
while (fileList.length) {
// Sets of N quantity files
for (var a = 0; a < setQty; a++) {
try {
app.open(fileList.pop());
} catch (e) { }
}
// Set the base doc layer name
app.activeDocument = documents[0];
docNameToLayerName();
// Stack all open docs to the base doc
while (app.documents.length > 1) {
app.activeDocument = documents[1];
docNameToLayerName();
app.activeDocument.activeLayer.duplicate(documents[0]);
app.activeDocument = documents[0];
// Do something to the stacked active layer (blend mode, opacity etc)
// app.activeDocument.activeLayer.blendMode = BlendMode.MULTIPLY;
app.documents[1].close(SaveOptions.DONOTSAVECHANGES);
}
////////////////////////////////// Start doing stuff //////////////////////////////////
/*
// Run an action on the stacked layers, change the case-sensitive names as required...
try {
app.doAction("My Action", "My Action Set Folder");
} catch (e) { }
*/
/*
// Average stacked layers
app.runMenuItem(stringIDToTypeID("selectAllLayers"));
var idnewPlacedLayer = stringIDToTypeID("newPlacedLayer");
executeAction(idnewPlacedLayer, undefined, DialogModes.NO);
var idapplyImageStackPluginRenderer = stringIDToTypeID("applyImageStackPluginRenderer");
var desc1217 = new ActionDescriptor();
var idimageStackPlugin = stringIDToTypeID("imageStackPlugin");
var idavrg = charIDToTypeID("avrg");
desc1217.putClass(idimageStackPlugin, idavrg);
var idname = stringIDToTypeID("name");
desc1217.putString(idname, """Mean""");
executeAction(idapplyImageStackPluginRenderer, desc1217, DialogModes.NO);
*/
////////////////////////////////// Finish doing stuff //////////////////////////////////
// Delete XMP metadata to reduce final file size of output files
removeXMP();
// Save name + suffix & save path
var Name = app.activeDocument.name.replace(/\.[^\.]+$/, '');
var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.psd');
// var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.jpg');
// Call the save function
savePSD(saveFile);
//saveTIFF(saveFile);
//saveJPEG(saveFile);
//savePNG(saveFile);
// Close all open files without saving
while (app.documents.length) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
// Increment the file saving counter
fileCounter++;
///// Functions /////
function savePSD(saveFile) {
psdSaveOptions = new PhotoshopSaveOptions();
psdSaveOptions.embedColorProfile = true;
psdSaveOptions.alphaChannels = true;
psdSaveOptions.layers = true;
psdSaveOptions.annotations = true;
psdSaveOptions.spotColors = true;
// Save as
app.activeDocument.saveAs(saveFile, psdSaveOptions, true, Extension.LOWERCASE);
}
/* Not currently used, a placeholder to swap in/out as needed
function saveTIFF(saveFile) {
tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.embedColorProfile = true;
tiffSaveOptions.byteOrder = ByteOrder.IBM;
tiffSaveOptions.transparency = true;
// Change layers to false to save without layers
tiffSaveOptions.layers = true;
tiffSaveOptions.layerCompression = LayerCompression.ZIP;
tiffSaveOptions.interleaveChannels = true;
tiffSaveOptions.alphaChannels = true;
tiffSaveOptions.annotations = true;
tiffSaveOptions.spotColors = true;
tiffSaveOptions.saveImagePyramid = false;
// Image compression = NONE | JPEG | TIFFLZW | TIFFZIP
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW;
// Save as
app.activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
}
*/
/* Not currently used, a placeholder to swap in/out as needed
function saveJPEG(saveFile) {
jpgSaveOptions = new JPEGSaveOptions();
jpgSaveOptions.embedColorProfile = true;
jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
jpgSaveOptions.matte = MatteType.NONE;
jpgSaveOptions.quality = 10;
// Save as
activeDocument.saveAs(saveFile, jpgSaveOptions, true, Extension.LOWERCASE);
}
*/
/* Not currently used, a placeholder to swap in/out as needed
function savePNG(saveFile) {
var pngOptions = new PNGSaveOptions();
pngOptions.compression = 0; // 0-9
pngOptions.interlaced = false;
// Save as
app.activeDocument.saveAs(saveFile, pngOptions, true, Extension.LOWERCASE);
}
*/
function docNameToLayerName() {
var layerName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
app.activeDocument.activeLayer.name = layerName;
}
function removeXMP() {
if (!documents.length) return;
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);
XMPUtils.removeProperties(xmp, "", "", XMPConst.REMOVE_ALL_PROPERTIES);
app.activeDocument.xmpMetadata.rawData = xmp.serialize();
}
}
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
// Restore the Photoshop panels
app.togglePalettes();
// Ensure Photoshop has focus before closing the running script notification window
app.bringToFront();
working.close();
// End of script notification
app.beep();
alert('Script completed!' + '\n' + fileCounter + ' combined files saved to:' + '\n' + outputFolder.fsName);
// Open the output folder in the Finder or Explorer
// outputFolder.execute();
}());
} catch (e) {
// Restore the Photoshop panels
app.togglePalettes();
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
alert("If you see this message, something went wrong!" + "\r" + e + ' ' + e.line);
}
}
else {
alert('Stack "N" Number of Sets:' + '\n' + 'Please close all open documents before running this script!');
}
https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html
Copy link to clipboard
Copied
@Stephen Marsh
O M G !!
It totally worked!!!
Thank you so so much!
Now, is it possible to change the final format to TIFF?
Copy link to clipboard
Copied
@Stephen Marsh
O M G !!
It totally worked!!!
Thank you so so much!
Now, is it possible to change the final format to TIFF?
By @hosongn52153496
Certainly, however, is there anything else that needs to be changed? Layer names? Output file name? Output location? Do the stacked layers require further processing? Something else?
Copy link to clipboard
Copied
No, nothing else for now. It's exactly what I was looking for.
I just liked to have another version for TIFF.
However, I have another script request, but I will post that separately.
Copy link to clipboard
Copied
I have cleaned up the code a little bit from the generic script previously posted and changed over to layered TIFF output:
/*
Stack x2 Image Sets to Layered TIFF.jsx
https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-automatically-add-two-images-into-layer-in-a-single-psd-or-tiff-then-save/m-p/13897545
v1.0 - 28th June 2023, Stephen Marsh
* This script requires input files from a single folder to be alpha/numeric sorting in order to stack in the correct set quantity.
* A minimum of 2 or more files per stack is required. The quantity of input files must be evenly divisible by the stack quantity.
*/
#target photoshop
if (!app.documents.length) {
try {
// Save and disable dialogs
var restoreDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.NO;
// Main script function
(function () {
// Select the input folder
var inputFolder = Folder.selectDialog('Please select the folder with files to process');
if (inputFolder === null) return;
// Limit the file format input, add or remove as required
var fileList = inputFolder.getFiles(/\.(png|jpg|jpeg|tif|tiff|psd|psb)$/i);
// Force alpha-numeric list sort
// Use .reverse() for the first filename in the merged file
// Remove .reverse() for the last filename in the merged file
fileList.sort().reverse();
// Set Quantity
var setQty = 2;
// Validate that the file list is not empty
var inputCount = fileList.length;
var cancelScript1 = (inputCount === 0);
if (cancelScript1 === true) {
alert('Zero input files found, script cancelled!');
return;
}
// Validate the input count vs. output count - Thanks to Kukurykus for the advice to test using % modulus
var cancelScript2 = !(inputCount % setQty);
alert(inputCount + ' input files stacked into sets of ' + setQty + ' will produce ' + inputCount / setQty + ' output files.');
// Test if false, then terminate the script
if (cancelScript2 === false) {
alert('Script cancelled as the quantity of input files are not evenly divisible by the set quantity.');
return;
}
// Select the output folder
var outputFolder = Folder.selectDialog("Please select the folder to save to");
if (outputFolder === null) {
alert('Script cancelled!');
return;
}
// Set the file processing counter
var fileCounter = 0;
// Loop through and open the file sets
while (fileList.length) {
// Sets of 2 files
for (var a = 0; a < setQty; a++) {
try {
app.open(fileList.pop());
} catch (e) { }
}
// Set the base doc layer name
app.activeDocument = documents[0];
docNameToLayerName();
// Stack all open docs to the base doc
while (app.documents.length > 1) {
app.activeDocument = documents[1];
docNameToLayerName();
app.activeDocument.activeLayer.duplicate(documents[0]);
app.activeDocument = documents[0];
app.documents[1].close(SaveOptions.DONOTSAVECHANGES);
}
// Delete XMP metadata to reduce final file size of output files
removeXMP();
// Save name + suffix & save path
var Name = app.activeDocument.name.replace(/\.[^\.]+$/, '');
var saveFile = File(outputFolder + '/' + Name + '_x' + setQty + '-Sets' + '.tif');
// Call the save function
saveTIFF(saveFile);
// Close all open files without saving
while (app.documents.length) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
// Increment the file saving counter
fileCounter++;
///// Functions /////
function saveTIFF(saveFile) {
tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.embedColorProfile = true;
tiffSaveOptions.byteOrder = ByteOrder.IBM;
tiffSaveOptions.transparency = true;
// Change layers to false to save without layers
tiffSaveOptions.layers = true;
tiffSaveOptions.layerCompression = LayerCompression.ZIP;
tiffSaveOptions.interleaveChannels = true;
tiffSaveOptions.alphaChannels = true;
tiffSaveOptions.annotations = true;
tiffSaveOptions.spotColors = true;
tiffSaveOptions.saveImagePyramid = false;
// Image compression = NONE | JPEG | TIFFLZW | TIFFZIP
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW;
// Save as
app.activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
}
function docNameToLayerName() {
var layerName = app.activeDocument.name.replace(/\.[^\.]+$/, '');
app.activeDocument.activeLayer.name = layerName;
}
function removeXMP() {
if (!documents.length) return;
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject("lib:AdobeXMPScript");
var xmp = new XMPMeta(activeDocument.xmpMetadata.rawData);
XMPUtils.removeProperties(xmp, "", "", XMPConst.REMOVE_ALL_PROPERTIES);
app.activeDocument.xmpMetadata.rawData = xmp.serialize();
}
}
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
// End of script notification
app.beep();
alert('Script completed!' + '\n' + fileCounter + ' combined files saved to:' + '\n' + outputFolder.fsName);
// Open the output folder in the Finder or Explorer
// outputFolder.execute();
}());
} catch (e) {
// Restore saved dialogs
app.displayDialogs = restoreDialogMode;
alert("If you see this message, something went wrong!" + "\r" + e + ' ' + e.line);
}
}
else {
alert('Stack x2 Image Sets Sets to Layers:' + '\n' + 'Please close all open documents before running this script!');
}
Copy link to clipboard
Copied
Thank you so much @Stephen Marsh !!
You are Magic!
Copy link to clipboard
Copied
@hosongn52153496 - You're welcome!
Copy link to clipboard
Copied
Hello @Stephen Marsh !!
Your script has been great help but I wish I could modify some if possible. Can you please help me with this if you don't mind?
Just like before, I wish to automatically combine 100s files.
But the previous script automatically 'unlocks' the bottom layer, which can be problematic for my other actions.
I want the bottommost layer to be unchanged from an original file, which has the default name 'Background' and Locked.
Also, I wish to combine so that new stack images can be stacked onto the layered images. It could be two or multiple layers. When combined, I just want the new stack image to be on the topmost layer.
Is this possible?
I'm attaching example screenshots.
Please note the locked 'background' layer on the original file.
Copy link to clipboard
Copied
I understand the request for a special Background layer rather than a floating layer. This is easy enough to add to my previous script if needed. Or do you need this in a new script as below?
To clarify your second request. If you have an existing image using 1, 2 or more layers, you wish to stack the new documents onto this existing layered file. Is that correct?
Would this still be adding 2 images to the layered file, or would you need to change the quantity of images being stacked to another value? Or do you simply wish to select the files to stack onto the existing image without automating sets 2 or more files?
I ask because the previous script was for a set quantity of 2 (unless you changed the variable value). Additionally, the previous script created a new file, it didn't add to an open file.
So it sounds like your new requirements are very different.
Copy link to clipboard
Copied
Holy crap @Stephen Marsh, thank you so much for taking the time to help me with this!!!
The only situations where I need this script and what it needs to accomplish is this.
Let's say I have First_image_with_layers.tif -->. It could be two or more layers, but if it makes it easier, just two will do.
And the Second_image_flattened.jpeg(or tif)
In a folder, there will be multiple images with the First file and a second file that will stack. They have the same file name but are differentiated by the suffix on the second image(that goes on the top of the layer)
It does not matter if the stacked file overwrites the First file(with layers) or creates an entirely new file(with two images stacked)
This script is meant to be stacking two files that share the same file name (the only difference is the suffix)
Without breaking the original layer structure of the First file and keeping the very first layer 'Background'
I hope this makes sense.
Let me know if there's a better way to communicate this.
Copy link to clipboard
Copied
OK, now this makes more sense. Your first layered file already has a special Background layer, so there is no need to create one, is that correct?
Then the script will get the flattened file with the same name + suffix, and stack that single flattened image as a floating layer in the first layered file. Can I presume that both files have the same canvas size in pixels and PPI resolution values?
What will this layer be named? The stacked document name + suffix, or something else?
For safety, it would probably be better to save the combined files into a subfolder, then you can perform housekeeping on the original files as needed.
Copy link to clipboard
Copied
OK, now this makes more sense. Your first layered file already has a special Background layer, so there is no need to create one, is that correct?
Yes!
Then the script will get the flattened file with the same name + suffix, and stack that single flattened image as a floating layer in the first layered file. Can I presume that both files have the same canvas size in pixels and PPI resolution values?
Yes, same canvas size, ppi, and everything will be same.
What will this layer be named? The stacked document name + suffix, or something else?
document name + suffix will work!
For safety, it would probably be better to save the combined files into a subfolder, then you can perform housekeeping on the original files as needed.
That will be the safest option.