• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

Photoshop script for tiling large terrain into <2GB TIFF export files

Messages
115
Country
australia
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...:cool:

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...
Not extensively tested other than on my particular circumstance, and a few criteria that have to be satisfied first, as stated in the comments, so if you wish to expand, revise, or make it more rebust, feel free.

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:
Back
Top