@jonathanb67250519
You can try this first draft script. It's mostly there and should only require cosmetic tweaking, such as removing the manual save folder selection step if you want to save the merged files to the TIFF input folder or automatically create a new merged output folder alongside or within the TIFF input folder.
Notes:
* Saves a layered TIFF file, change the boolean from true to false to save a flattened TIFF file [line 71]
* Layer opacity at 40% [line 108]
* Optional feature to crop to transparency, remove the leading double-forward slash // comments to enable [line 111]
* Adds a '_Merged' filename suffix to the saved TIFF file, you could change this name or remove the content inside the single quotes '' [line 112]
I'm happy to make other modifications if required.
/*
Stack and Blend 2 Input Folder Files to TIFF.jsx
v1.0, 17th July 2024 - Stephen Marsh
https://community.adobe.com/t5/photoshop-ecosystem-discussions/bulk-action-solution-needed/td-p/14741490
*/
#target photoshop
(function () {
if (app.documents.length === 0) {
try {
// JPEG Input folder
var folder1 = Folder.selectDialog("Select the JPEG image folder:");
if (folder1 === null) {
alert('Script cancelled!');
return;
}
// TIFF Input folder
var folder2 = Folder.selectDialog("Select the TIFF image folder:");
if (folder2 === null) {
alert('Script cancelled!');
return;
}
// Validate input folder selection
var validateInputDir = (folder1.fsName === folder2.fsName);
if (validateInputDir === true) {
alert("Script cancelled as both the input folders are the same!");
return;
}
// Limit the file input to jpg/jpeg
var list1 = folder1.getFiles(/\.(jpg|jpeg)$/i);
var list2 = folder2.getFiles(/\.(tif|tiff)$/i);
// Alpha-numeric sort
list1.sort();
list2.sort();
// Validate that folder 1 & 2 lists are not empty
var validateEmptyList = (list1.length > 0 && list2.length > 0);
if (validateEmptyList === false) {
alert("Script cancelled as one of the input folders is empty!");
return;
}
// Validate that the item count in folder 1 & 2 matches
var validateListLength = (list1.length === list2.length);
if (validateListLength === false) {
alert("Script cancelled as the input folders don't have equal quantities of images!");
return;
}
// Output folder
var saveFolder = Folder.selectDialog("Please select the folder to save to...");
// Save and set the dialog display settings
var savedDisplayDialogs = app.displayDialogs;
app.displayDialogs = DialogModes.NO;
// TIFF save options
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.NONE; // TIFFLZW | TIFFZIP | JPEG
tiffSaveOptions.embedColorProfile = true;
tiffSaveOptions.byteOrder = ByteOrder.IBM;
tiffSaveOptions.transparency = true;
tiffSaveOptions.layers = true;
tiffSaveOptions.layerCompression = LayerCompression.ZIP;
tiffSaveOptions.interleaveChannels = true;
tiffSaveOptions.alphaChannels = true;
tiffSaveOptions.annotations = true;
tiffSaveOptions.spotColors = true;
tiffSaveOptions.saveImagePyramid = false;
// 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 counter = 0;
// Perform the stacking and saving
for (var i = 0; i < list1.length; i++) {
var doc = open(list1[i]);
var docName = doc.name.replace(/\.[^\.]+$/, '');
placeFile(list2[i], 100);
resetTransforms();
app.runMenuItem(stringIDToTypeID('rasterizePlaced'));
app.activeDocument.activeLayer.opacity = 40; // Layer opacity
selectAllLayers();
autoAlign();
cropTransparency(); // Optional crop
app.activeDocument.saveAs(new File(saveFolder + '/' + docName + '_Merged' + '.tif'), tiffSaveOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
counter++; // Increment the counter
}
// Ensure Photoshop has focus before closing the running script notification window
app.bringToFront();
working.close();
// End of script notification
app.displayDialogs = savedDisplayDialogs;
app.beep();
alert('Script completed!' + '\r' + counter + ' merged TIFF files saved to:' + '\r' + saveFolder.fsName);
// Restore the Photoshop panels
app.togglePalettes();
} catch (err) {
while (app.documents.length > 0) {
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
alert("Error!" + "\r" + err + ' ' + err.line);
}
} else {
alert('Please close all open documents before running this script!');
}
///// Functions /////
function placeFile(file, scale) {
try {
var idPlc = charIDToTypeID("Plc ");
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
desc2.putPath(idnull, new File(file));
var idFTcs = charIDToTypeID("FTcs");
var idQCSt = charIDToTypeID("QCSt");
var idQcsa = charIDToTypeID("Qcsa");
desc2.putEnumerated(idFTcs, idQCSt, idQcsa);
var idOfst = charIDToTypeID("Ofst");
var desc3 = new ActionDescriptor();
var idHrzn = charIDToTypeID("Hrzn");
var idPxl = charIDToTypeID("#Pxl");
desc3.putUnitDouble(idHrzn, idPxl, 0.000000);
var idVrtc = charIDToTypeID("Vrtc");
var idPxl = charIDToTypeID("#Pxl");
desc3.putUnitDouble(idVrtc, idPxl, 0.000000);
var idOfst = charIDToTypeID("Ofst");
desc2.putObject(idOfst, idOfst, desc3);
var idWdth = charIDToTypeID("Wdth");
var idPrc = charIDToTypeID("#Prc");
desc2.putUnitDouble(idWdth, idPrc, scale);
var idHght = charIDToTypeID("Hght");
var idPrc = charIDToTypeID("#Prc");
desc2.putUnitDouble(idHght, idPrc, scale);
var idAntA = charIDToTypeID("AntA");
desc2.putBoolean(idAntA, true);
executeAction(idPlc, desc2, DialogModes.NO);
} catch (e) { }
}
function resetTransforms() {
try {
var idplacedLayerResetTransforms = stringIDToTypeID("placedLayerResetTransforms");
executeAction(idplacedLayerResetTransforms, undefined, DialogModes.NO);
} catch (e) {
//alert("Error!" + "\r" + e + ' ' + e.line); // Errors in 2019 version
app.refresh();
}
}
function selectAllLayers() {
try {
var c2t = function (s) {
return app.charIDToTypeID(s);
};
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
var reference = new ActionReference();
var reference2 = new ActionReference();
reference2.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
descriptor.putReference(c2t("null"), reference2);
executeAction(s2t("selectAllLayers"), descriptor, DialogModes.NO);
// Add the Background layer if it exists
reference.putProperty(s2t("layer"), s2t("background"));
descriptor2.putReference(c2t("null"), reference);
descriptor2.putEnumerated(s2t("selectionModifier"), s2t("selectionModifierType"), s2t("addToSelection"));
descriptor2.putBoolean(s2t("makeVisible"), false);
executeAction(s2t("select"), descriptor2, DialogModes.NO);
} catch (e) {
alert("Error!" + "\r" + e + ' ' + e.line);
}
}
function autoAlign() {
try {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
desc.putReference(charIDToTypeID('null'), ref);
desc.putEnumerated(charIDToTypeID('Usng'), charIDToTypeID('ADSt'), stringIDToTypeID('ADSContent'));
desc.putEnumerated(charIDToTypeID('Aply'), stringIDToTypeID('projection'), charIDToTypeID('Auto'));
desc.putBoolean(stringIDToTypeID('vignette'), false);
desc.putBoolean(stringIDToTypeID('radialDistort'), false);
executeAction(charIDToTypeID('Algn'), desc, DialogModes.NO);
} catch (e) {
alert("Error!" + "\r" + e + ' ' + e.line);
}
}
function cropTransparency() {
try {
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
var descriptor3 = new ActionDescriptor();
var descriptor4 = new ActionDescriptor();
var descriptor5 = new ActionDescriptor();
var descriptor6 = new ActionDescriptor();
var reference = new ActionReference();
var reference2 = new ActionReference();
var reference3 = new ActionReference();
var reference4 = new ActionReference();
// Dupe layers to temp merged layer
descriptor.putBoolean(s2t("duplicate"), true);
executeAction(s2t("mergeLayersNew"), descriptor, DialogModes.NO);
// Load selection from layer transparency
reference.putProperty(s2t("channel"), s2t("selection"));
descriptor2.putReference(s2t("null"), reference);
reference2.putEnumerated(s2t("channel"), s2t("channel"), s2t("transparencyEnum"));
descriptor2.putReference(s2t("to"), reference2);
executeAction(s2t("set"), descriptor2, DialogModes.NO);
// Enter quick mask mode
reference3.putProperty(s2t("property"), s2t("quickMask"));
reference3.putEnumerated(s2t("document"), s2t("ordinal"), s2t("targetEnum"));
descriptor3.putReference(s2t("null"), reference3);
// Threshold to 255 levels
executeAction(s2t("set"), descriptor3, DialogModes.NO);
descriptor4.putInteger(s2t("level"), 255);
executeAction(s2t("thresholdClassEvent"), descriptor4, DialogModes.NO);
// Exit quick mask mode
reference4.putProperty(s2t("property"), s2t("quickMask"));
reference4.putEnumerated(s2t("document"), s2t("ordinal"), s2t("targetEnum"));
descriptor5.putReference(s2t("null"), reference4);
// Crop selection
executeAction(s2t("clearEvent"), descriptor5, DialogModes.NO);
descriptor6.putBoolean(s2t("delete"), true);
executeAction(s2t("crop"), descriptor6, DialogModes.NO);
// Clean up
app.activeDocument.activeLayer.remove();
app.activeDocument.selection.deselect();
} catch (e) {
alert("Error!" + "\r" + e + ' ' + e.line);
}
}
}());
Instructions for saving and use:
https://prepression.blogspot.com/2017/11/downloading-and-installing-adobe-scripts.html