- Messages
- 115
- Country

Hello All,
Just thought I'd share this for anyone in the same boat.
I am processing a large (210,000 x 180,000 pixel) terrain imagery in photoshop, and exporting to TIFF or other GIS compatable format was proving error prone and tedious, so I wrote this script to take my large image and export all the sources to TIFF files in <2GB tiles for (in my case) georeferencing.
It has made a process I had to do manually go from a tedious, prone to error 5 hour process into a 1 hour automated process I can sleep through...
Basically, I create a grid of saved selections with a consistent naming scheme referencing the rows and columns (with no gaps in the rows or columns), then loop through:
Several functions gratuitously lifted from other programmers more gifted than I. I have noted this where I could remember where I lifted the code from.
cheers
Braedon
[EDIT] Reversed the order of myLayersBM and myLayersWM in the definition of the array myLayers. This caused the export of the Water Mask as the Blend Mask, and vise versa. Updated the code and the attached file.
Just thought I'd share this for anyone in the same boat.
I am processing a large (210,000 x 180,000 pixel) terrain imagery in photoshop, and exporting to TIFF or other GIS compatable format was proving error prone and tedious, so I wrote this script to take my large image and export all the sources to TIFF files in <2GB tiles for (in my case) georeferencing.
It has made a process I had to do manually go from a tedious, prone to error 5 hour process into a 1 hour automated process I can sleep through...
Basically, I create a grid of saved selections with a consistent naming scheme referencing the rows and columns (with no gaps in the rows or columns), then loop through:
- load selection
- crop to selection (it's an order of magnitude faster than copy, new, paste for huge images).
- enable all the layers in turn for each of the sources I want to create, saving to TIFF between each.
- undo the crop,
- rinse, lather, repeat...
Several functions gratuitously lifted from other programmers more gifted than I. I have noted this where I could remember where I lifted the code from.
cheers
Braedon
Code:
#target photoshop
/******* NOTES *******************************************************************
Before you get to here, you should have a Photoshop document that has a uniform (or at least uniformly numbered)
grid of saved selectons in your channels, and the grid references should be complete (no missing elements).
This script essentially loops through:
Load a saved channel referenced by row and column name.
Crops the image to the loaded selection.
Hides all the layers.
Steps thriough a loop of Show, Save, Hide for the layers in each of the sources you specify.
Undoes the crop action.
moves onto the next saved selection.
Rinse, Lather, Repeat until all the saved selections are saved for all the sources you specify.
Things you will need to tell the script in variables below:
List of row and column identifiers, and a base name (if any), for the saved selectons. Row then Column reference is assumed.
A base file name for all the sources (grid and source reference details will be added to this).
A base path to save all the files to.
Layer names for each source (including all adjustment layers).
The full path to each layer should specify Group/Sub-Group/...<etc>.../Layer Name.
Layers at the root level have just the layer name.
All the groups at the root of the layer structure (subgroups not necessary).
*/
var debugMode = false;
var reqSourceName = ["WI","HW","SP","SU","FA","BM","WM","BI"]; // DON'T CHANGE. This is the full list of sources. The first element is index 0, for use in the reqSources[] array.
/*************BEGIN Configure the Script ****************************************************
============================================================================================
Use '/' as a directory symbol, even on Windows which normally uses the "\" character.
- If a drive or OSX volume exists with a name matching the first part of the path,
that part is always interpreted as that drive letter or OSX volume.
- Always append a trailing "/" character.
============================================================================================*/
Folder.current = "/D/Data/GIS/Tas/TAS-SE/EXPORT-TEST/"; // Where you want to save the files to. Must already exist and be writeable.
var baseFileName = "TAS-SE"; // The first part of all the file names generated
/*===========================================================================================
Full list of Sources:
Index Source Description
0 WI Winter
1 HW Hard Winter
2 SP Spring
3 SU Summer
4 FA Fall/Autumn
5 BM Blend Mask
6 WM Water Mask
7 BI Base Image (no seasonal correction. I normally only use this for troubleshooting or comparison)
** Use the index numbers above to populate the array below. These are the sources you will generate **
============================================================================================*/
var reqSources = [0,5,6]; // Your zero-base index number for the list of sources. Needs to be a (sub)set of the full list above.
var rowID = ["A","B","C","D"]; // Row Identifiers of the grid of saved selections.
var colID = ["1","2","3","4","5","6","7"]; // Column Identifiers of the grid of saved selections.
var baseChanName = "TILE-"; // This is the base of the channel name you are using for the saved selections.
/* When adding layers within layer groups, specify these like a path with "/" as the delimiter.
ie "WI/ADJ-CURV-WI" is the layer ADJ-CURV-WI in the group WI. */
var myLayersWI = ["ADJ-HS-TAS-PEN-N","TAS-PEN-N","SNOW","ADJ-CB-BROWN","WI/ADJ-CURV-WI","WI/ADJ-HS-WI","WI/ADJ-BR-WI","WI/ADJ-CB-WI","WI/BASE-LAYER","WI/HUON-W-BASE","BACKGROUND"];
var myLayersHW = [];
var myLayersSP = [];
var myLayersSU = [];
var myLayersFA = [];
var myLayersWM = ["WATER-MASKS/ADJ-TH-WATER-MASK","WATER-MASKS/WATER MASK","WATER-MASKS/MASK-WHITE-WM"];
var myLayersBM = ["BLEND-MASKS/BLEND-BLACK","BLEND-MASKS/BLEND-GREY","BLEND-MASKS/MASK-WHITE-BM"];
var myLayersBI = [];
var myLayerGroups = ["WI","WATER-MASKS","BLEND-MASKS"]; // The list of Layer Groups at the root of the layer set. Subgroups are not required to be specified.
/*************END Configure the Script ****************************************************/
function loadSel(chanID) {
/* Generated and slightly modified from PS Script Listener.
Using "getByName" methods caused the histogram to be rebuilt each time,
dramatically increasing runtime. */
try {
var idsetd = charIDToTypeID( "setd" );
var aDesc = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var aRef0 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idfsel = charIDToTypeID( "fsel" );
aRef0.putProperty( idChnl, idfsel );
aDesc.putReference( idnull, aRef0 );
var idT = charIDToTypeID( "T " );
var aRef1 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
aRef1.putName( idChnl, chanID );
aDesc.putReference( idT, aRef1 );
executeAction( idsetd, aDesc, DialogModes.NO );
} catch(e) {};
};
function crop2sel() { //Generated and slightly modified from PS Script Listener
var idCrop = charIDToTypeID( "Crop" );
var aDesc = new ActionDescriptor();
var idDlt = charIDToTypeID( "Dlt " );
aDesc.putBoolean( idDlt, true );
executeAction( idCrop, aDesc, DialogModes.NO );
};
function hideLayer(layerID) { //Generated and slightly modified from PS Script Listener
var idHd = charIDToTypeID( "Hd " );
var aDesc = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var aList = new ActionList();
var aRef = new ActionReference();
var idLyr = charIDToTypeID( "Lyr " );
aRef.putName( idLyr, layerID );
aList.putReference( aRef );
aDesc.putList( idnull, aList );
executeAction( idHd, aDesc, DialogModes.NO );
};
function hideAllLayers() {
var numLayers = app.activeDocument.layers.length;
for ( i = 0 ; i < numLayers ; i++ ) {
app.activeDocument.layers[i].visible = false;
};
};
function saveAsTIFF(baseName,cellName,maskType) {
/* Make sure Folder.current is defined elsewhere */
var saveFile = new File(baseName + "-" + cellName + "-" + maskType);
tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.embedColorProfile = false;
tiffSaveOptions.alphaChannels = false;
tiffSaveOptions.layers = false;
tiffSaveOptions.imageCompression = TIFFEncoding.NONE;
activeDocument.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
};
function showLayers(layerSet) {
for (i = 0; i < layerSet.length ; i++ ) {
if(debugMode) { $.writeln(layerSet[i]); };
SetStatus(layerSet[i],true);
};
};
//undo ( the same as ctrl-z )
function undoLast(){
executeAction( charIDToTypeID('undo'), undefined, DialogModes.NO );
};
// For the menu item 'Step Backward' ( the same as alt-ctrl-z ) use this
function stepHistoryBack(){
var aDesc = new ActionDescriptor();
var aRef = new ActionReference();
aRef.putEnumerated( charIDToTypeID( "HstS" ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Prvs" ));
aDesc.putReference(charIDToTypeID( "null" ), aRef);
executeAction( charIDToTypeID( "slct" ), aDesc, DialogModes.NO );
};
/******************************************/
// GET BY PATH
// Author: Max Kielland
//
// Gets the LayerSet or Layer at the path's end.
// Example path "Icons/left" will return the LayerSet object "Left"
// while "Icons/left/Star" will return the Layer object "Star"
// If fSetPath is true, all the parents will be visible as well.
function GetByPath(fPath,fSetPath) {
var lGroup = null;
var lPathArray = new Array();
lPathArray = fPath.split('/');
try {
lGroup = app.activeDocument.layers.getByName(lPathArray[0]);
} catch (err) {
lGroup = app.activeDocument.layerSets.getByName(lPathArray[0]);
};
if (fSetPath) lGroup.visible = true;
for (n=1; n<lPathArray.length; n++) {
try {
lGroup = lGroup.layerSets.getByName(lPathArray[n]);
} catch(err) {
lGroup = lGroup.layers.getByName(lPathArray[n]);
};
if (fSetPath == true)
lGroup.visible = true;
};
return lGroup;
};
/******************************************/
// SET STATUS
// Author: Max Kielland
//
// Sets the Group or Layer's visible property
// at the end of the path to fStatus.
function SetStatus(fPath, fStatus) {
Obj = GetByPath(fPath,true);
Obj.visible = fStatus;
};
/******************************************/
// CLEAR GROUP
// Author: Max Kielland
//
// Clears the visible property in a single
// group/layer with the option to clear all
// its children as well (fRecurs = true).
// fLayerSet can be a layerSet object or a
// String path.
function ClearGroup(fLayerSet,fRecurs) {
var n;
var TargetGroup;
// Get LayerSet Object if reference is a string.
if ( typeof fLayerSet == "string" )
TargetGroup = GetByPath(fLayerSet);
else
TargetGroup = fLayerSet;
// Iterate through all LayerSets
for ( n=0; n<TargetGroup.layerSets.length; n++ ) {
if (fRecurs == true)
ClearGroup(TargetGroup.layerSets[n],true);
else
TargetGroup.layerSets[n].visible = false;
};
// Iterate through all layers
for ( n=0; n<TargetGroup.layers.length; n++ ) {
TargetGroup.layers[n].visible = false;
};
// Clear self
TargetGroup.visible = false;
};
/************* Some more variables *******************************************************/
var myLayers = [myLayersWI,myLayersHW,myLayersSP,myLayersSU,myLayersFA,myLayersBM,myLayersWM,myLayersBI]
var channelRef = "";
var thisdoc = app.activeDocument;
/* Set Adobe Photoshop CC to use pixels and display no dialogs */
var startTypeUnits = app.preferences.typeUnits;
var startDisplayDialogs = app.displayDialogs;
app.preferences.rulerUnits = Units.PIXELS;
app.preferences.typeUnits = TypeUnits.PIXELS;
app.displayDialogs = DialogModes.NO;
/************* Main process of the Script ************************************************/
if(debugMode) {
var startDate = Date.now(); // Get the current timestamp to display script execution time at the end.
$.writeln("Begin..."); // Write to the javascript console in the ESTK for debugging.
};
for ( var f = 0 ; f < rowID.length ; f++ ) {
if(debugMode) { $.writeln( "f=" + f + ", colID.length = " + colID.length + ", rowID.length = " + rowID.length ) };
for ( var g= 0 ; g < colID.length ; g++ ) {
tileCoord = rowID[f] + colID[g];
if(debugMode) { $.writeln( tileCoord + ", " + f + ", " + g ); };
chanID = baseChanName + tileCoord;
loadSel( chanID );
crop2sel(); // On larger images, cropping is quicker than copying the selection merged, making new and pasting.
for ( k = 0 ; k < reqSources.length ; k++ ) {
if(debugMode) { $.writeln("ShowLayers: " + k + ", " + reqSources.length); };
for ( m = 0 ; m < myLayerGroups.length ; m++ ) {
ClearGroup( myLayerGroups[m], true);
};
hideAllLayers();
showLayers(myLayers[reqSources[k]]);
if(debugMode) { $.writeln("Save As: " + baseFileName + "-" + reqSourceName[reqSources[k]] + "-" + tileCoord); };
saveAsTIFF(baseFileName,reqSourceName[reqSources[k]],tileCoord);
};
/* Using the step backwards function as the Undo list takes longer to populate than
the script excecution allows, The undo entry does not exist when the script goes to undo the crop. */
//undoLast();
stepHistoryBack();
};
};
if(debugMode) {
var endDate = Date.now();
var dateDiff = endDate - startDate;
alert( "Completed in " + (dateDiff / 3600000) + "Hours" );
};
[EDIT] Reversed the order of myLayersBM and myLayersWM in the definition of the array myLayers. This caused the export of the Water Mask as the Blend Mask, and vise versa. Updated the code and the attached file.
Attachments
Last edited: