1

I have a number of layers (hundreds) on a psd. I want to export each layer as a .png file, but I want each one to be the size of the canvas, but also to maintain it's current position in the psd.

At the moment I can select multiple layers and do "export as..." but then I have to manually resize each layer to be the size of the canvas. The trouble is every layer then becomes centred.

I need to maintain the position of each element as they're all varied sizes, but they are all related to an overall picture.

Ultimately it's a character builder and each layer is a part of a character. For instance there are multiple "hat" layers. Each hat however can be a different size and so is positioned slightly differently on the character. They're all positioned correctly on the PSD, but exporting ruins this positioning. I could address this in code in the final app, but it's making more work as I have to treat each hat as distinct rather than all as generic hats. Is there an easy way to do this in Photoshop?

2 Answers 2

0

Use File > Scripts > Export Layers to Files and make sure to deactivate Trim Layers. If you need transparency use PNG-24 as format.

1
  • Thank you. But what if I don't have this script at all in the latest Photoshop?
    – momijigari
    Nov 9, 2021 at 10:05
0

The feature of exporting layers to PNG without trimming migrated from the File > Scripts > Export Layers to Files menu to File > Export > Layers to Files. Note however that's its usability is still terrible:

  • It can't create new folders (will complain about destination not existing)
  • It's prohibitively slow. It duplicates documents, adds and removes layers one by one and performs some other crazy actions I don't understand. It easily takes 10-20 seconds per layer.

I don't enjoy sitting in front of my screen for 10 minutes watching this script run, so I wrote a simple script:

if (!Array.from) {
    Array.from = (function () {
        var toStr = Object.prototype.toString;
        var isCallable = function (fn) { return typeof fn === 'function' || toStr.call(fn) === '[object Function]' };
        var toInteger = function (value) {
            var number = Number(value);
            if (isNaN(number))
                return 0;
            if (number === 0 || !isFinite(number))
                return number;
            return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
        };
        var maxSafeInteger = Math.pow(2, 53) - 1;
        var toLength = function (value) { return Math.min(Math.max(toInteger(value), 0), maxSafeInteger) };
        return function from(arrayLike /*, mapFn, thisArg */) {
            var c = this;
            var items = Object(arrayLike);
            if (arrayLike == null)
                throw new TypeError("Array.from requires an array-like object - not null or undefined");
            var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
            var T;
            if (typeof mapFn !== 'undefined') {
                if (!isCallable(mapFn))
                    throw new TypeError("Array.from: when provided, the second argument must be a function");
                if (arguments.length > 2)
                    T = arguments[2];
            }
            var len = toLength(items.length);
            var a = isCallable(c) ? Object(new c(len)) : new Array(len);
            var k = 0;
            var kValue;
            while (k < len) {
                kValue = items[k];
                a[k] = mapFn ? (typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k)) : kValue;
                k += 1;
            }
            a.length = len;
            return a;
        };
    })();
}

if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement, fromIndex) {
        var k, o = Object(this), len = o.length >>> 0;
        if (len === 0)
            return -1;
        var n = +fromIndex || 0;
        if (Math.abs(n) === Infinity)
            n = 0;
        if (n >= len)
            return -1;
        k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
        while (k < len) {
            if (k in o && o[k] === searchElement)
                return k;
            k++;
        }
        return -1;
    };
}

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (callback, thisArg) {
        if (this === void 0 || this === null)
            throw new TypeError('Array.prototype.forEach called on null or undefined');
        if (callback.__class__ !== 'Function')
            throw new TypeError(callback + ' is not a function');
        var o = Object(this);
        var len = o.length >>> 0;
        var t = arguments.length > 1 ? thisArg : void 0;
        for (var i = 0; i < len; i++)
            if (i in o)
                callback.call(t, o[i], i, o);
    }
}

if (!Array.prototype.filter) {
    Array.prototype.filter = function (callback, thisArg) {
        if (this === void 0 || this === null)
            throw new TypeError('Array.prototype.filter called on null or undefined');
        if (callback.__class__ !== 'Function')
            throw new TypeError(callback + ' is not a function');
        var o = Object(this);
        var len = o.length >>> 0;
        var a = [];
        var t = arguments.length > 1 ? thisArg : void 0;
        for (var i = 0; i < len; i++) {
            if (i in o) {
                var val = o[i];
                if (callback.call(t, val, i, o))
                    a.push(val);
            }
        }
        return a;
    };
}

if (!Array.prototype.some) {
    Array.prototype.some = function (callback, thisArg) {
        if (this === void 0 || this === null)
            throw new TypeError('Array.prototype.some called on null or undefined');
        if (callback.__class__ !== 'Function')
            throw new TypeError(callback + ' is not a function');
        var o = Object(this);
        var len = o.length >>> 0;
        var t = arguments.length > 1 ? thisArg : void 0;
        for (var i = 0; i < len; i++)
            if (i in o && callback.call(t, o[i], i, o))
                return true;
        return false;
    };
}

if (!Array.prototype.map) {
    Array.prototype.map = function (callback, thisArg) {
        if (this === void 0 || this === null)
            throw new TypeError('Array.prototype.map called on null or undefined');
        if (callback.__class__ !== 'Function')
            throw new TypeError(callback + ' is not a function');
        var o = Object(this);
        var len = o.length >>> 0;
        var t = arguments.length > 1 ? thisArg : void 0;
        var a = new Array(len);
        for (var i = 0; i < len; i++)
            if (i in o)
                a[i] = callback.call(t, o[i], i, o);
        return a;
    };
}

if (!Array.prototype.flat) {
    Array.prototype.flat = function (depth) {
        if (this === void 0 || this === null)
            throw new TypeError('Array.prototype.flat called on null or undefined');
        depth = typeof depth === 'undefined' ? 1 : Number(depth) || 0;
        var a = [];
        var forEach = a.forEach;
        var flatDeep = function (ai, di) {
            forEach.call(ai,
                function (val) {
                    if (di > 0 && Array.isArray(val))
                        flatDeep(val, di - 1);
                    else
                        a.push(val);
                });
        };
        flatDeep(this, depth);
        return a;
    };
} 

if (!String.prototype.repeat) {
    String.prototype.repeat = function (count) {
        var input = this;
        var result = "";
        for (var i = 0; i < count; i++)
            result += input;
        return result;
    }
}

if (!String.prototype.padStart) {
    String.prototype.padStart = function (targetLength, padString) {
        targetLength = targetLength >> 0;
        padString = String((typeof padString !== 'undefined' ? padString : ' '));
        if (this.length > targetLength)
            return String(this);
        else {
            targetLength = targetLength - this.length;
            if (targetLength > padString.length)
                padString += padString.repeat(targetLength / padString.length);
            return padString.slice(0, targetLength) + String(this);
        }
    };
}

var id = {
    name: 0,
    as: 0,
    method: 0,
    quick: 0,
    PNGInterlaceType: 0,
    PNGInterlaceNone: 0,
    PNGFilter: 0,
    PNGFilterAdaptive: 0,
    compression: 0,
    minDigits: 0,
    PNGFormat: 0,
    quality: 0,
    export_: 0,
    reverse: 0,
    animationFrameClass: 0,
    startNumber: 0,
    useDocumentSize: 0,
    useDocumentFrameRate: 0,
    allFrames: 0,
    renderAlpha: 0,
    alphaRendering: 0,
    straight: 0,
    Z3DPrefHighQualityErrorThreshold: 0,
    videoExport: 0,
    animationFramesFromLayers: 0,
    apply: 0, // "Aply"
    at: 0, // "At  "
    blendMode: 0, // "BlnM"
    blue: 0, // "Bl  "
    channel: 0, // "Chnl"
    color: 0, // "Clr "
    commandID: 0,
    delete_: 0, // "Dlt "
    directory: 0,
    enabled: 0, // "enab"
    frameFill: 0, // "FrFl"
    frameFX: 0, // "FrFX"
    frameStyle: 0, // "FStl"
    green: 0, // "Grn "
    invokeCommand: 0,
    kcanDispatchWhileModal: 0,
    layer: 0, // "Lyr "
    layerEffects: 0, // "Lefx"
    layerStyle: 0,
    level: 0, // "Lvl "
    make: 0, // "Mk  "
    makeFrameAnimation: 0,
    mask: 0, // "Msk "
    mode: 0, // "Md  "
    new_: 0, // "Nw  "
    normal: 0, // "Nrml"
    opacity: 0, // "Opct"
    ordinal: 0, // "Ordn"
    outsetFrame: 0, // "OutF"
    overprint: 0,
    paintType: 0, // "PntT"
    percentUnit: 0, // "#Prc"
    pixelsUnit: 0, // "#Pxl"
    PNGMethod: 0,
    present: 0,
    property: 0, // "Prpr"
    rasterizeItem: 0,
    rasterizeLayer: 0,
    red: 0, // "Rd  "
    RGBColor: 0, // "RGBC"
    scale: 0, // "Scl "
    sequenceRenderSettings: 0,
    set: 0, // "setd"
    showInDialog: 0,
    size: 0, // "Sz  "
    solidColor: 0, // "SClr"
    solidFill: 0, // "SoFi"
    style: 0, // "Styl"
    subdirectory: 0,
    target: 0, // "null"
    targetEnum: 0, // "Trgt"
    thresholdClassEvent: 0, // "Thrs"
    to: 0, // "T   "
    transparency: 0,
    userMaskEnabled: 0,
    using: 0, // "Usng"
    what: 0 // "What"
};

function setupPhotoshopIdentifiers() {
    for (var kid in id)
        id[kid] = stringIDToTypeID(kid.replace("_", ""));
    var classesWithPutMethods = {
        ActionDescriptor: {
            2: ['Boolean', 'Class', 'Double', 'Integer', 'LargeInteger', 'List', 'Path', 'Reference', 'String'],
            3: ['Enumerated', 'Object', 'UnitDouble']
        },
        ActionReference: {
            1: ['Class'],
            2: ['Identifier', 'Index', 'Name', 'Offset', 'Property'],
            3: ['Enumerated']
        }
    };
    var chainMethods = {
        1: function (f) {
            return function (a0) {
                this[f](a0);
                return this;
            };
        },
        2: function (f) {
            return function (a0, a1) {
                this[f](a0, a1);
                return this;
            };
        },
        3: function (f) {
            return function (a0, a1, a2) {
                this[f](a0, a1, a2);
                return this;
            };
        }
    }
    for (var className in classesWithPutMethods) {
        for (var argCount in classesWithPutMethods[className]) {
            for (var iMethod in classesWithPutMethods[className][argCount]) {
                var method = classesWithPutMethods[className][argCount][iMethod];
                this[className].prototype['set' + method] = chainMethods[argCount]('put' + method);
            }
        }
    }
}

function desc() {
    return new ActionDescriptor();
}

function ref() {
    return new ActionReference();
}

function execute(command, action) {
    executeAction(command, action || desc(), DialogModes.NO);
}

setupPhotoshopIdentifiers();

var folderPostfix = "-layers";

function savePng(path) {
    var opts = new ExportOptionsSaveForWeb();
    opts.format = SaveDocumentType.PNG;
    opts.PNG8 = false;
    opts.transparency = true;
    opts.interlaced = false;
    opts.quality = 100;
    doc.exportDocument(new File(path), ExportType.SAVEFORWEB, opts);
}

function makeFrameAnimation() {
    execute(id.makeFrameAnimation);
}

function animationFramesFromLayers() {
    execute(id.animationFramesFromLayers);
}

function reverseFrames() {
    execute(id.reverse, desc()
        .setReference(id.target, ref()
            .setEnumerated(id.animationFrameClass, id.ordinal, id.targetEnum)));
}

function renderVideo(opts) {
    var pngFormat = desc()
        .setEnumerated(id.method, id.PNGMethod, id.quick)
        .setEnumerated(id.PNGInterlaceType, id.PNGInterlaceType, id.PNGInterlaceNone)
        .setEnumerated(id.PNGFilter, id.PNGFilter, id.PNGFilterAdaptive)
        .setInteger(id.compression, 6);
    var videoExport = desc()
        .setObject(id.sequenceRenderSettings, id.sequenceRenderSettings,
            desc().setObject(id.as, id.PNGFormat, pngFormat))
        .setPath(id.directory, new File(opts.directory))
        //.setString(id.subdirectory, opts.subdirectory)
        .setString(id.name, ".png")
        .setInteger(id.minDigits, 1)
        .setInteger(id.startNumber, 0)
        .setBoolean(id.useDocumentSize, true)
        .setBoolean(id.useDocumentFrameRate, true)
        .setBoolean(id.allFrames, true)
        .setEnumerated(id.renderAlpha, id.alphaRendering, id.straight)
        .setInteger(id.quality, 1)
        .setInteger(id.Z3DPrefHighQualityErrorThreshold, 5);
    execute(id.export_, desc()
        .setObject(id.using, id.videoExport, videoExport));
}

function getArtLayers(doc) {
    return Array.from(doc.artLayers);
}

function main() {
    // Prepare output folder
    var outFolder = new Folder(doc.path + "/" + doc.name.replace(".psd", folderPostfix));
    if (!outFolder.exists)
        outFolder.create();

    // Copy visible layers into new document
    var docCopy = app.activeDocument = doc.duplicate();
    try {
        getArtLayers(docCopy)
            .filter(function(layer) { return !layer.visible })
            .forEach(function(layer) { layer.remove() });

        // Generate file names from layer names
        var layerIndexLength = (docCopy.artLayers.length - 1 + "").length;
        var formatLayerFileName = function(layer, iLayer) { return (iLayer + "").padStart(layerIndexLength, "0") + " " + layer.name };
        var getOutPath = function(name) { return outFolder.fullName + "/" + name + ".png" };
        var layerFileNames = getArtLayers(docCopy).map(formatLayerFileName).map(getOutPath);

        // Confirm replacing existing files
        var existingFiles = layerFileNames.filter(function(fileName) { return new File(fileName).exists });
        if (existingFiles.length > 0 && !confirm(existingFiles.length + " files already exist in the target folder. Replace?"))
            return;

        // Export layers as video frames
        makeFrameAnimation();
        animationFramesFromLayers();
        reverseFrames();
        renderVideo({ directory: outFolder.fullName });

        // Rename sequental file names to proper layer names
        layerFileNames
            .forEach(function(fileName, iLayer) {
                new File(fileName).remove();
                new File(getOutPath(iLayer)).rename(fileName);
            });
    } finally {
        // Close document copy without saving
        docCopy.close(SaveOptions.DONOTSAVECHANGES);
        app.activeDocument = doc;
    }

    alert("Layers exported.");
}

var doc = app.activeDocument;
//doc.suspendHistory('Athari - Export Layers', 'main()');
main();

It doesn't provide a fancy user interface and probably fails in many cases, but it gets the job done 5 times faster. Feel free to adapt it to your needs.

Update
Based on suggestion from @joojaa, replaced export to PNG with export to PNG video frames. Now it's 5 times faster than before (3 layers per second in my case), doesn't create an entry in the undo history and even shows a progress bar. (I copy-pasted my includes right into the script, so it may look very long, but the actual code is just one page at the very end.)

3
  • Its actually faster to convert layers to frames and save the animation. As a bonus it is possible to do directly from the user interface.
    – joojaa
    Oct 22, 2021 at 19:22
  • @joojaa Do you mean TImeline > Create Frame Animation + Timeline > Make Frame From Layers + File > Export > Render Video? If it's considerably faster, a script can fix file names and will make toggling Timeline panel unnecessary. I'll try it. Thanks for the info.
    – Athari
    Oct 22, 2021 at 19:41
  • @joojaa Updated the script. Export to video is so much faster! 100 layers in 30 seconds.
    – Athari
    Oct 26, 2021 at 17:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.