Save Layers

Script for Adobe Photoshop

Save each layer of images to a separate file.

  • Process active image or folder of images
  • Choose layer types to include
  • Save with layer groups or without
  • Keep layers transparent or flatten
  • Output JPG, PNG, TIF, or PSD
Download
Save Layers
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell

How-to Video

How to use the script

The interface has three sections, Process, Include and Output. Set options as desired and click the OK button to begin. A progress bar is displayed as layers are saved.

Section 1: Process

Active image — processes the image that is currently open and the top-most window if multiple images are open. Each layer of the image is saved to a separate file.

Folder — processes a folder of images. Click the Folder button to select the desired input folder. If the option Include subfolders is checked, all folders in the selected folder are also processed. If Photoshop encounters non-image files that it cannot open, the file is ignored. For each image file found, the script saves each of its layers to a separate file.

Section 2: Include

Each checkbox, when enabled, saves specific layer types. When disabled, the layer type is ignored.

Pixel — normal image and Smart Object layers.

Adjustment — any adjustment layer, for example, levels, curves, hue/saturation, etc.

Text — text layers not rasterized.

Fill — solid, gradient, and pattern fill layers.

Groups — when enabled, each layer retains the group structure in which it is found. When disabled, groups are ignored and results saved are the each layer only.

Other — 3D and video layers.

Section 3: Output

Output file names are the input file name, an underscore, and the layer name.

Folder — select the location to which layers are output.

Format — select the file format to output. Choices are JPG, PNG, PSD, or TIF.

Flatten — result is a single “Background” layer. JPG always performs this step. When disabled, layers remain as-is.

Quality — applies to JPEG images. Valid range is from 0 to 12. 0 is extreme compression resulting in low quality. 12 is light compression that is virtually indistinguishable from the original, the highest possible quality, which of course, results in the largest file size. 10 to 12 is recommended for print or other high-quality reproduction. For web images, 5 to 8 is an acceptable range.

Embed color profile — applies to JPEG images. Embeds into the JPG the current color profile, either for the original color space if not converted, or the profile selected for the Convert to profile option. The option exists for JPG so that files intended for print can include profiles, important to preserve in that case, but JPG for web may omit profiles, as the profile is excess and only increases file size.

PSD and TIF always embed profiles, and PNG never embeds profiles.

Source code

(download button below)

/*

Save Layers
Copyright 2023 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact

Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

*/

(function () {

    var title = "Save Layers";

    if (!/photoshop/i.test(app.name)) {
        alert("Script for Photoshop", title, false);
        return;
    }

    app.displayDialogs = DialogModes.ERROR;

    // Script variables.
    var count;
    var doneMessage;
    var error;
    var extension;
    var extensions;
    var folderInput;
    var folderOutput;
    var format;
    var formats;
    var progress;
    var qualities;

    // Reusable UI variables.
    var g; // group
    var p; // panel
    var w; // window

    // Permanent UI variables.
    var btnCancel;
    var btnFolderInput;
    var btnFolderOutput;
    var btnOk;
    var cbAdjust;
    var cbEmbedProfile;
    var cbFill;
    var cbFlatten;
    var cbGroups;
    var cbOther;
    var cbPixel;
    var cbSubfolders;
    var cbText;
    var grpFolderInput;
    var grpJpg;
    var listFormat;
    var listQuality;
    var rbProcessActive;
    var rbProcessFolder;
    var txtFolderInput;
    var txtFolderOutput;

    // LANGUAGE EXTENSIONS

    if (!String.prototype.trim) {
        String.prototype.trim = function () {
            return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
        };
    }

    // SETUP

    extensions = [
        "jpg",
        "png",
        "psd",
        "tif"
    ];
    formats = [
        "JPG",
        "PNG",
        "PSD",
        "TIF"
    ];
    qualities = [
        "0",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9",
        "10",
        "11",
        "12"
    ];

    // CREATE PROGRESS WINDOW

    progress = new Window("palette", "Progress");
    progress.t = progress.add("statictext");
    progress.t.preferredSize.width = 450;
    progress.b = progress.add("progressbar");
    progress.b.preferredSize.width = 450;
    progress.add("statictext", undefined, "Press ESC to cancel");
    progress.display = function (message) {
        message && (this.t.text = message);
        this.show();
        app.refresh();
    };
    progress.increment = function () {
        this.b.value++;
    };
    progress.set = function (steps) {
        this.b.value = 0;
        this.b.minvalue = 0;
        this.b.maxvalue = steps;
    };

    // CREATE USER INTERFACE

    w = new Window("dialog", title);
    w.alignChildren = "fill";

    // Panel 'Process'
    p = w.add("panel", undefined, "Process");
    p.alignChildren = "left";
    p.margins = [18, 18, 18, 18];
    g = p.add("group");
    rbProcessActive = g.add("radiobutton", undefined, "Active image");
    rbProcessFolder = g.add("radiobutton", undefined, "Folder");
    cbSubfolders = g.add("checkbox", undefined, "Include subfolders");
    grpFolderInput = p.add("group");
    btnFolderInput = grpFolderInput.add("button", undefined, "Folder...");
    txtFolderInput = grpFolderInput.add("statictext", undefined, undefined, {
        truncate: "middle"
    });
    txtFolderInput.preferredSize.width = 300;

    // Panel 'Include'
    p = w.add("panel", undefined, "Include");
    p.alignChildren = "left";
    p.orientation = "row";
    p.margins = [18, 18, 18, 18];
    p.spacing = 48;
    g = p.add("group");
    g.alignChildren = "left";
    g.orientation = "column";
    cbPixel = g.add("checkbox", undefined, "Pixel");
    cbAdjust = g.add("checkbox", undefined, "Adjustment");
    g = p.add("group");
    g.alignChildren = "left";
    g.orientation = "column";
    cbText = g.add("checkbox", undefined, "Text");
    cbFill = g.add("checkbox", undefined, "Fill");
    g = p.add("group");
    g.alignChildren = "left";
    g.orientation = "column";
    cbGroups = g.add("checkbox", undefined, "Groups");
    cbOther = g.add("checkbox", undefined, "Other");

    // Panel 'Output'
    p = w.add("panel", undefined, "Output");
    p.alignChildren = "left";
    p.margins = [18, 18, 18, 18];
    g = p.add("group");
    btnFolderOutput = g.add("button", undefined, "Folder...");
    txtFolderOutput = g.add("statictext", undefined, "", {
        truncate: "middle"
    });
    txtFolderOutput.preferredSize.width = 300;
    g = p.add("group");
    g.add("statictext", undefined, "Format:");
    listFormat = g.add("dropdownlist", undefined, formats);
    g = p.add("group");
    cbFlatten = g.add("checkbox", undefined, "Flatten");
    grpJpg = g.add("group");
    grpJpg.add("statictext", undefined, "Quality:");
    listQuality = grpJpg.add("dropdownlist", undefined, qualities);
    cbEmbedProfile = grpJpg.add("checkbox", undefined, "Embed profile");

    // Action Buttons
    g = w.add("group");
    g.alignment = "center";
    btnOk = g.add("button", undefined, "OK");
    btnCancel = g.add("button", undefined, "Cancel");

    // Panel Copyright
    p = w.add("panel");
    p.add("statictext", undefined, "Copyright 2023 William Campbell");

    // SET UI VALUES

    if (app.documents.length) {
        rbProcessActive.value = true;
    } else {
        rbProcessActive.value = false;
        rbProcessActive.enabled = false;
        rbProcessFolder.value = true;
    }
    cbPixel.value = true;
    cbText.value = true;
    cbGroups.value = true;
    cbAdjust.value = true;
    cbFill.value = true;
    cbOther.value = true;
    txtFolderOutput.text = "";
    listFormat.selection = 2; // 0=JPG, 1=PNG, 2=PSD, 3=TIF
    cbFlatten.value = false;
    listQuality.selection = 10;
    cbEmbedProfile.value = false;
    configureUi();

    // UI ELEMENT EVENT HANDLERS

    // Panel 'Process'
    rbProcessActive.onClick = configureUi;
    rbProcessFolder.onClick = configureUi;
    btnFolderInput.onClick = function () {
        var f = Folder.selectDialog("Select input folder", txtFolderInput.text);
        if (f) {
            txtFolderInput.text = Folder.decode(f.fullName);
        }
    };

    // Panel 'Output'
    btnFolderOutput.onClick = function () {
        var f = Folder.selectDialog("Select output folder", txtFolderOutput.text);
        if (f) {
            txtFolderOutput.text = Folder.decode(f.fullName);
        }
    };
    listFormat.onChange = configureUi;

    // Action Buttons
    btnOk.onClick = function () {
        if (rbProcessFolder.value) {
            folderInput = new Folder(txtFolderInput.text);
            if (!(folderInput && folderInput.exists)) {
                txtFolderInput.text = "";
                alert("Select input folder", " ", false);
                return;
            }
        }
        folderOutput = new Folder(txtFolderOutput.text);
        if (!(folderOutput && folderOutput.exists)) {
            txtFolderOutput.text = "";
            alert("Select output folder", " ", false);
            return;
        }
        w.close(1);
    };
    btnCancel.onClick = function () {
        w.close(0);
    };

    // DISPLAY THE DIALOG

    if (w.show() == 1) {
        doneMessage = "";
        try {
            process();
            if (rbProcessActive.value) {
                doneMessage = "Done";
            } else {
                doneMessage = doneMessage || count + " files processed";
            }
        } catch (e) {
            if (/User cancel/.test(e.message)) {
                doneMessage = "Canceled";
            } else {
                error = error || e;
                doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
            }
        }
        app.bringToFront();
        progress.close();
        doneMessage && alert(doneMessage, title, error);
    }

    //====================================================================
    //               END PROGRAM EXECUTION, BEGIN FUNCTIONS
    //====================================================================

    function addDoc(d) {
        // Creates a new document.
        // Parameter 'd' is document object.
        // If defined, new doc is based on doc object passed.
        // Otherwise call app.documents.add() no parameters.
        var newDoc;
        var newDocumentMode;
        if (d) {
            switch (d.mode) {
                case DocumentMode.RGB:
                    newDocumentMode = NewDocumentMode.RGB;
                    break;
                case DocumentMode.CMYK:
                    newDocumentMode = NewDocumentMode.CMYK;
                    break;
                case DocumentMode.GRAYSCALE:
                    newDocumentMode = NewDocumentMode.GRAYSCALE;
                    break;
                case DocumentMode.LAB:
                    newDocumentMode = NewDocumentMode.LAB;
                    break;
                case DocumentMode.BITMAP:
                    newDocumentMode = NewDocumentMode.BITMAP;
                    break;
            }
            newDoc = app.documents.add(
                d.width,
                d.height,
                d.resolution,
                d.name.replace(/\.[^\.]*$/, ""),
                newDocumentMode,
                DocumentFill.WHITE,
                d.pixelAspectRatio,
                d.bitsPerChannel
            );
            if (d.colorProfileType == ColorProfile.NONE) {
                newDoc.colorProfileType = ColorProfile.NONE;
            } else {
                newDoc.colorProfileName = d.colorProfileName;
            }
        } else {
            newDoc = app.documents.add();
        }
        return newDoc;
    }

    function configureUi() {
        if (!(rbProcessActive.value || rbProcessFolder.value)) {
            rbProcessFolder.value = true;
        }
        cbSubfolders.enabled = rbProcessFolder.value;
        grpFolderInput.enabled = rbProcessFolder.value;
        if (listFormat.selection && listFormat.selection.index == 0) {
            // JPG
            grpJpg.visible = true;
            cbFlatten.value = true;
            cbFlatten.enabled = false;
        } else {
            grpJpg.visible = false;
            cbFlatten.value = false;
            cbFlatten.enabled = true;
        }
    }

    function getFiles(folder, subfolders) {
        // folder = folder object, not folder name.
        // subfolders = true to include subfolders.
        // Ignores hidden files and folders.
        var f;
        var files;
        var i;
        var pattern;
        var results = [];
        pattern = new RegExp(
            "\.8PBS|AFX|AI|ARW|BLZ|BMP|CAL|CALS|CIN|CR2|CRW|CT|DCM|DCR|DCS|DCX|DDS|" +
            "DIB|DIC|DNG|DPX|EPS|EPSF|EXR|FFF|FIF|GIF|HDP|HDR|HEIC|HEIF|ICB|ICN|ICO|" +
            "ICON|IIQ|IMG|J2C|J2K|JIF|JIFF|JP2|JPC|JPE|JPEG|JPF|JPG|JPS|JPX|JXR|KDK|" +
            "KMZ|KODAK|MOS|MRW|NCR|NEF|ORF|PAT|PBM|PCT|PCX|PDD|PDF|PDP|PEF|PGM|PICT|" +
            "PMG|PNG|PPM|PS|PSB|PSD|PSDC|PSID|PVR|PXR|RAF|RAW|RLE|RSR|SCT|SRF|TGA|TIF|" +
            "TIFF|TRIF|U3D|VDA|VST|WBMP|WDP|WEBP|WMP|X3F$", "i");
        files = folder.getFiles();
        for (i = 0; i < files.length; i++) {
            f = files[i];
            if (!f.hidden) {
                if (f instanceof Folder && subfolders) {
                    // Recursive (function calls itself).
                    results = results.concat(getFiles(f, subfolders));
                } else if (f instanceof File && pattern.test(f.name)) {
                    results.push(f);
                }
            }
        }
        return results;
    }

    function getLayers() {
        var kind;
        var layers = [];
        var searchLayers = function (o) {
            for (var i = 0; i < o.layers.length; i++) {
                if (o.layers[i].typename == "LayerSet") {
                    layers.concat(searchLayers(o.layers[i]));
                } else {
                    kind = o.layers[i].kind;
                    if (
                        kind == LayerKind.NORMAL ||
                        kind == LayerKind.SMARTOBJECT
                    ) {
                        if (!cbPixel.value) continue;
                    } else if (
                        kind == LayerKind.TEXT
                    ) {
                        if (!cbText.value) continue;
                    } else if (
                        kind == LayerKind.SOLIDFILL ||
                        kind == LayerKind.GRADIENTFILL ||
                        kind == LayerKind.PATTERNFILL
                    ) {
                        if (!cbFill.value) continue;
                    } else if (
                        kind == LayerKind.LEVELS ||
                        kind == LayerKind.CURVES ||
                        kind == LayerKind.COLORBALANCE ||
                        kind == LayerKind.BRIGHTNESSCONTRAST ||
                        kind == LayerKind.HUESATURATION ||
                        kind == LayerKind.SELECTIVECOLOR ||
                        kind == LayerKind.CHANNELMIXER ||
                        kind == LayerKind.GRADIENTMAP ||
                        kind == LayerKind.INVERSION ||
                        kind == LayerKind.THRESHOLD ||
                        kind == LayerKind.POSTERIZE ||
                        kind == LayerKind.PHOTOFILTER ||
                        kind == LayerKind.EXPOSURE ||
                        kind == LayerKind.BLACKANDWHITE ||
                        kind == LayerKind.VIBRANCE ||
                        kind == LayerKind.COLORLOOKUP
                    ) {
                        if (!cbAdjust.value) continue;
                    } else if (
                        kind == LayerKind.LAYER3D ||
                        kind == LayerKind.VIDEO
                    ) {
                        if (!cbOther.value) continue;
                    }
                    layers.push(o.layers[i]);
                }
            }
        };
        searchLayers(app.activeDocument);
        return layers;
    }

    function process() {
        var doc;
        var file;
        var fileName;
        var files;
        var i;
        app.displayDialogs = DialogModes.NO;
        progress.display("Initializing...");
        format = listFormat.selection.index;
        extension = extensions[format];
        try {
            if (rbProcessActive.value) {
                // Process Active image.
                processDoc(app.activeDocument);
            } else {
                // Process Folder.
                progress.display("Reading folder...");
                files = getFiles(folderInput, cbSubfolders.value);
                if (!files.length) {
                    doneMessage = "No files found in selected folder";
                    return;
                }
                progress.set(files.length);
                count = 0;
                for (i = 0; i < files.length; i++) {
                    file = files[i];
                    fileName = File.decode(file.name);
                    progress.increment();
                    progress.display(fileName);
                    try {
                        doc = app.open(file);
                    } catch (e) {
                        if (/User cancel/.test(e.message)) {
                            throw e;
                        }
                        // Can't open the file. Ignore it.
                        continue;
                    }
                    try {
                        processDoc(doc);
                    } finally {
                        doc.close(SaveOptions.DONOTSAVECHANGES);
                    }
                    count++;
                }
            }
        } finally {
            app.displayDialogs = DialogModes.ERROR;
        }
    }

    function processDoc(doc) {
        var baseName;
        var baseNameDoc;
        var docDupe;
        var docLayer;
        var i;
        var layer;
        var layerName;
        var layers;
        baseNameDoc = doc.name.replace(/\.[^\.]*$/, "");
        layers = getLayers();
        if (rbProcessActive.value) {
            progress.set(layers.length);
        }
        for (i = 0; i < layers.length; i++) {
            layer = layers[i];
            layerName = layer.name;
            baseName = baseNameDoc + "_" + layerName;
            // Replace invalid characters.
            baseName = baseName.replace(/[\\\/:"*?<>|]/g, "_");
            // Remove low non-printing ascii.
            baseName = baseName.replace(/[\x00-\x1F]/g, "");
            // Remove leading and trailing whitespace.
            baseName = baseName.trim();
            if (rbProcessActive.value) {
                progress.increment();
            }
            progress.display(baseName);
            docLayer = addDoc(doc);
            app.activeDocument = doc;
            if (cbGroups.value) {
                docDupe = doc.duplicate();
                try {
                    app.activeDocument = docDupe;
                    removeLayers(layer.id);
                    removeEmptyLayerSets();
                    docDupe.layers[0].duplicate(docLayer);
                } finally {
                    docDupe.close(SaveOptions.DONOTSAVECHANGES);
                }
            } else {
                layer.duplicate(docLayer);
            }
            app.activeDocument = docLayer;
            docLayer.layers[1].allLocked = false;
            docLayer.layers[1].remove();
            docLayer.layers[0].name = layerName;
            // Flatten.
            if (cbFlatten.value) {
                docLayer.flatten();
            }
            // Save.
            saveAndClose(docLayer, baseName);
        }
    }

    function removeEmptyLayerSets() {
        var searchLayerSets = function (o) {
            for (var i = o.layerSets.length - 1; i > -1; i--) {
                searchLayerSets(o.layerSets[i]);
                if (o.layerSets[i].layers.length == 0) {
                    o.layers[i].allLocked = false;
                    o.layerSets[i].remove();
                }
            }
        };
        searchLayerSets(app.activeDocument);
    }

    function removeLayers(keepId) {
        var searchLayers = function (o) {
            for (var i = o.layers.length - 1; i > -1; i--) {
                o.layers[i].visible = true;
                if (o.layers[i].typename == "LayerSet") {
                    searchLayers(o.layers[i]);
                } else {
                    if (o.layers[i].id == keepId) {
                        continue;
                    }
                    o.layers[i].allLocked = false;
                    o.layers[i].remove();
                }
            }
        };
        searchLayers(app.activeDocument);
    }

    function saveAndClose(doc, baseName) {
        var fileOutput;
        var fullPath;
        var nameOutput;
        var pathOutput;
        var saveOptions;
        try {
            // Set output path.
            pathOutput = folderOutput.fullName;
            // Add extension and build full path.
            nameOutput = baseName + "." + extension;
            fullPath = pathOutput + "/" + nameOutput;
            fileOutput = new File(fullPath);
            // Save.
            switch (format) {
                case 0: // JPG
                    saveOptions = new JPEGSaveOptions();
                    saveOptions.embedColorProfile = cbEmbedProfile.value;
                    saveOptions.formatOptions = FormatOptions.STANDARDBASELINE;
                    saveOptions.matte = MatteType.WHITE;
                    saveOptions.quality = Number(listQuality.selection.text);
                    saveOptions.scans = 3;
                    break;
                case 1: // PNG
                    // PNG always untagged 8-bit sRGB.
                    doc.bitsPerChannel = BitsPerChannelType.EIGHT;
                    doc.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC);
                    doc.colorProfileType = ColorProfile.NONE;
                    saveOptions = new PNGSaveOptions();
                    saveOptions.compression = 5;
                    saveOptions.interlaced = false;
                    break;
                case 2: // PSD
                    saveOptions = new PhotoshopSaveOptions();
                    saveOptions.alphaChannels = true;
                    saveOptions.annotations = true;
                    saveOptions.embedColorProfile = true;
                    saveOptions.layers = !cbFlatten.value;
                    saveOptions.spotColors = true;
                    break;
                case 3: // TIF
                    saveOptions = new TiffSaveOptions();
                    saveOptions.alphaChannels = true;
                    saveOptions.annotations = true;
                    saveOptions.embedColorProfile = true;
                    saveOptions.imageCompression = TIFFEncoding.TIFFLZW;
                    saveOptions.layerCompression = LayerCompression.ZIP;
                    saveOptions.layers = !cbFlatten.value;
                    saveOptions.spotColors = true;
                    break;
                default:
                    // None of the above.
                    throw new Error("Bad file format.");
            }
            doc.saveAs(fileOutput, saveOptions);
        } finally {
            doc.close(SaveOptions.DONOTSAVECHANGES);
        }
    }

})();
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell
Download
Save Layers

For help installing scripts, see How to Install and Use Scripts in Adobe Creative Cloud Applications.

IMPORTANT: scripts are developed for the latest Adobe Creative Cloud applications. Many scripts work in CC 2018 and later, even some as far back as CS6, but may not perform as expected, or run at all, when used in versions prior to 2018. Photoshop features Select Subject and Preserve Details 2.0 definitely fail prior to CC 2018 (version 19) as the features do not exist in earlier versions. For best results use the latest versions of Adobe Creative Cloud applications.

IMPORTANT: by downloading any of the scripts on this page you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.

IMPORTANT: fees paid for software products are the purchase of a non-exclusive license to use the software product and do not grant the purchaser any degree of ownership of the software code. Author of the intellectual property and copyright holder William Campbell retains 100% ownership of all code used in all software products regardless of the inspiration for the software product design or functionality.