Batch Web Images

Script for Adobe Photoshop

The script reads a folder of images and outputs JPG or PNG copies suitable for web use. While processing, backgrounds may be removed based on a path, alpha channel, or using the Photoshop Select Subject feature. When removing backgrounds, the script can also crop the image tighter to the subject. Pixels dimensions may be limited to keep file size manageable.

  • Convert folder of images to JPG or PNG
  • Remove background
  • Crop in on subject
  • Limit pixel dimensions
  • Adapt open source to customize or create other scripts

IMPORTANT: the script depends on the Photoshop feature Select Subject that was introduced in Photoshop CC 2018 (version 19). Earlier versions of Photoshop will not run this script correctly. For best results use the latest version of Photoshop as the Select Subject feature has improved greatly in recent years.

Download
Batch Web Images
Many scripts are free to download thanks to the support of users. Help me keep developing new scripts by supporting my work. Click the PayPal button to contribute any amount. Thank you.

How-to Video

How to use the script

The interface has three sections: Input, Options, and Output. Set desired options and click the OK button to begin. A progress bar is displayed as images are processed. Each image is flattened and converted to 8-bit sRGB color (web standard). Then images are transformed per the options chosen, and resolution is set (not resampled) to 72 pixels per inch. The result is copied to a new document so excess information from the original is omitted (metadata, EXIF, etc.). Then the image is saved in the chosen format to the selected location. If Include subfolders is enabled, the subfolder structure is preserved in the output folder. If problems are encountered, a log file is written to the output folder, and the user is notified.

The script depends on the accuracy of Photoshop’s Select Subject feature, which works well in most cases, but isn’t perfect 100% of the time. At a minimum, the user should open Bridge and scan through thumbnails of images output to ensure the results are satisfactory. For any images that fail to mask the subject correctly, open in Photoshop and mask the subject manually.

Section 1: Input

Folder — select a folder of images to convert.

Include subfolders — if enabled, images in subfolders are included. All subfolders in the input folder are created in the output folder so the subfolder structure is preserved.

Section 2: Options

Remove background — enable the checkbox and the script attempts to remove the background using the options next.

Path — the first path found is used to mask the image. If a path isn’t found, the script uses Select Subject instead.

Alpha channel — the first alpha channel found is used to mask the image. If an alpha channel isn’t found, the script uses Select Subject instead.

Select subject — the scripts uses Photoshop Select Subject to mask the image. The results depend on Photoshop’s ability to detect a subject. If Photoshop has trouble accurately masking an image, the script suffers the same.

Crop — when enabled, enter a percentage of the subject’s longest dimension to use as margins around the subject. Image area beyond the margins is removed, but margin is not added where it would exceed the original boundaries of the image.

Maximum pixels, width and height — enable the checkbox to limit the final pixel dimensions of the image, and enter a value in pixels for the width and/or height. It is acceptable to omit one of the dimensions. In that case, the entered dimension is limited and the other falls wherever it may. When both values are entered, neither dimension exceeds the value entered. One dimension may result in fewer pixels than entered if necessary to maintain proportions. In other words, setting both values does not define precise width and height; it only defines the upper limit of each dimension.

It is HIGHLY RECOMMENDED this option is used. Few website visitors view on screens exceeding HD resolution, 1920x1080 pixels. Most view on phones of far less pixel resolution. Website images exceeding HD resolution slow down page loading and give visitors good cause to click away to another site. Nothing is gained by the extra resolution as the browser down-samples images anyway. In most cases the default limit 1200x800 is a good choice that maintains quality without excessive file size.

Section 3: Output

Folder — select a folder to which converted copies of images are saved.

Format — choose JPG or PNG. JPGs are flattened, making any background removed become white. PNG retains transparency so when backgrounds are removed the area becomes transparent.

Original file name + — a suffix of characters appended to the each output file name. The characters entered must be legal to use in file names. Any illegal characters are automatically removed. It is acceptable to leave this option blank, naming the copy identical to the original file.

Rename for web — detects characters in the file name that require URL encoding and replaces them with a dash. Prevents URLs that look like “…/This%20is%20the%20File%20Name.jpg”. Instead the result is “…/This-is-the-File-Name.jpg”.

File name lowercase — converts the output file name to lowercase. Recommended for web resource file names. While the domain name of URLs is case-insensitive, the path is not. However, some hosts convert paths to lowercase, causing resource not found when letter case doesn't match. Using lowercase for web resources helps avoid these errors.

Replace existing output files — when enabled, existing output files are replaced. When disabled and an existing file is detected, an incremental number is added to the file name to prevent overwriting the existing file.

Source code

(download button below)

/*

Batch Web Images
Copyright 2022 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 = "Batch Web Images";

    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 log;
    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 cbCrop;
    var cbLowercase;
    var cbMaxPixels;
    var cbRemoveBg;
    var cbRenameWeb;
    var cbReplaceOutput;
    var cbSubfolders;
    var grpMaxPixels;
    var grpPercent;
    var grpQuality;
    var grpRemoveBg;
    var inpMaxPixelsHigh;
    var inpMaxPixelsWide;
    var inpPercent;
    var inpSuffix;
    var listFormat;
    var listJpgQuality;
    var rbAlpha;
    var rbPath;
    var rbSubject;
    var txtFolderInput;
    var txtFolderOutput;

    // SETUP

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

    // LOG

    log = {
        entries: [],
        file: null,
        // Default write log to user desktop.
        // Preferably set 'log.path' to a meaningful
        // location in later code once location exists.
        path: Folder.desktop,
        add: function (message) {
            this.entries.push(message);
        },
        addFile: function (fileName, entries) {
            this.add(File.decode(fileName));
            if (entries instanceof Array) {
                while (entries.length) {
                    log.add("----> " + entries.shift());
                }
            } else {
                log.add("----> " + entries);
            }
        },
        cancel: function () {
            if (log.entries.length) {
                log.add("Canceled.");
            }
        },
        write: function () {
            var contents;
            var d;
            var fileName;
            var padZero = function (v) {
                return ("0" + v).slice(-2);
            };
            if (!this.entries.length) {
                // No log entries to report.
                this.file = null;
                return;
            }
            contents = this.entries.join("\r") + "\r";
            // Create file name.
            d = new Date();
            fileName =
                title +
                " Log " +
                d.getFullYear() +
                "-" +
                padZero(d.getMonth() + 1) +
                "-" +
                padZero(d.getDate()) +
                "-" +
                padZero(d.getHours()) +
                padZero(d.getMinutes()) +
                padZero(String(d.getSeconds()).substr(0, 2)) +
                ".txt";
            // Open and write log file.
            this.file = new File(this.path + "/" + fileName);
            this.file.encoding = "UTF-8";
            try {
                if (!this.file.open("w")) {
                    throw new Error("Failed to open log file.");
                }
                if (!this.file.write(contents)) {
                    throw new Error("Failed to write log file.");
                }
            } catch (e) {
                this.file = null;
                throw e;
            } finally {
                this.file.close();
            }
            // Log successfully written.
            // log.file == true (not null) indicates a log was written.
        }
    };

    // CREATE PROGRESS WINDOW

    progress = new Window("palette", "Progress");
    progress.t = progress.add("statictext");
    progress.t.preferredSize = [450, -1];
    progress.b = progress.add("progressbar");
    progress.b.preferredSize = [450, -1];
    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 'Input'
    p = w.add("panel", undefined, "Input");
    p.alignChildren = "left";
    g = p.add("group");
    btnFolderInput = g.add("button", undefined, "Folder...");
    txtFolderInput = g.add("statictext", undefined, undefined, {
        truncate: "middle"
    });
    txtFolderInput.preferredSize = [360, -1];
    cbSubfolders = p.add("checkbox", undefined, "Include subfolders");

    // Panel 'Options'
    p = w.add("panel", undefined, "Options");
    p.alignChildren = "left";
    cbRemoveBg = p.add("checkbox", undefined, "Remove background:");
    grpRemoveBg = p.add("group");
    grpRemoveBg.margins = [24, 0, 0, 0];
    grpRemoveBg.orientation = "column";
    grpRemoveBg.alignChildren = "left";
    g = grpRemoveBg.add("group");
    rbPath = g.add("radiobutton", undefined, "Path");
    rbAlpha = g.add("radiobutton", undefined, "Alpha channel");
    rbSubject = g.add("radiobutton", undefined, "Select subject");
    g = grpRemoveBg.add("group");
    cbCrop = g.add("checkbox", undefined, "Crop:");
    grpPercent = g.add("group");
    grpPercent.add("statictext", undefined, "margin:");
    inpPercent = grpPercent.add("edittext");
    inpPercent.preferredSize = [40, -1];
    grpPercent.add("statictext", undefined, "percent of subject longest edge");
    g = p.add("group");
    cbMaxPixels = g.add("checkbox", undefined, "Maximum pixels:");
    grpMaxPixels = g.add("group");
    grpMaxPixels.margins = [1, 0, 0, 0];
    inpMaxPixelsWide = grpMaxPixels.add("edittext");
    inpMaxPixelsWide.preferredSize = [60, -1];
    grpMaxPixels.add("statictext", undefined, "w");
    inpMaxPixelsHigh = grpMaxPixels.add("edittext");
    inpMaxPixelsHigh.preferredSize = [60, -1];
    grpMaxPixels.add("statictext", undefined, "h");

    // Panel 'Output'
    p = w.add("panel", undefined, "Output");
    p.alignChildren = "left";
    g = p.add("group");
    btnFolderOutput = g.add("button", undefined, "Folder...");
    txtFolderOutput = g.add("statictext", undefined, "", {
        truncate: "middle"
    });
    txtFolderOutput.preferredSize = [360, -1];
    g = p.add("group");
    g.add("statictext", undefined, "Format:");
    listFormat = g.add("dropdownlist", undefined, formats);
    grpQuality = g.add("group");
    grpQuality.margins = [12, 0, 0, 0];
    grpQuality.add("statictext", undefined, "Quality:");
    listJpgQuality = grpQuality.add("dropdownlist", undefined, qualities);
    g = p.add("group");
    g.add("statictext", undefined, "Original file name +");
    inpSuffix = g.add("edittext");
    inpSuffix.preferredSize = [150, 23];
    g = p.add("group");
    cbRenameWeb = g.add("checkbox", undefined, "Rename for web");
    cbLowercase = g.add("checkbox", undefined, "File name lowercase");
    cbReplaceOutput = p.add("checkbox", undefined, "Replace existing output files");

    // 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 2022 William Campbell");

    // SET UI VALUES

    txtFolderInput.text = "";
    cbSubfolders.value = false;
    cbRemoveBg.value = true;
    rbPath.value = false;
    rbAlpha.value = false;
    rbSubject.value = true;
    cbCrop.value = true;
    inpPercent.text = "3";
    cbMaxPixels.value = true;
    inpMaxPixelsWide.text = "1200";
    inpMaxPixelsHigh.text = "800";
    txtFolderOutput.text = "";
    listFormat.selection = 1; // 0=JPG, 1=PNG
    listJpgQuality.selection = 5;
    inpSuffix.text = "";
    cbRenameWeb.value = true;
    cbLowercase.value = false;
    cbReplaceOutput.value = true;
    configureUi();

    // UI ELEMENT EVENT HANDLERS

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

    // Panel 'Options'
    cbRemoveBg.onClick = configureUi;
    cbCrop.onClick = configureUi;
    inpPercent.onChange = function () {
        var s;
        var v;
        s = this.text.replace(/[^0-9,]/g, "");
        if (s != this.text || Number(this.text) > 100) {
            alert("Invalid", " ", false);
            this.text = this.prior;
        } else {
            v = Math.round(Number(s) || 0);
            this.text = v.toString();
        }
    };
    cbMaxPixels.onClick = configureUi;
    inpMaxPixelsWide.onChange = validatePixels;
    inpMaxPixelsHigh.onChange = validatePixels;

    // 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;
    inpSuffix.onChange = function () {
        // Trim.
        this.text = this.text.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "");
        // Remove illegal characters.
        var s = this.text.replace(/[\/\\:*?"<>|]/g, "");
        if (s != this.text) {
            this.text = s;
            alert("Illegal characters removed", " ", false);
        }
    };

    // Action Buttons
    btnOk.onClick = function () {
        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();
            doneMessage = doneMessage || count + " files processed";
        } catch (e) {
            if (/User cancel/.test(e.message)) {
                log.cancel();
                doneMessage = "Canceled";
            } else {
                error = error || e;
                doneMessage = "An error has occurred.\nLine " + error.line + ": " + error.message;
            }
        }
        app.bringToFront();
        progress.close();
        try {
            log.path = folderOutput;
            log.write();
        } catch (e) {
            alert("Error writing log:\n" + e.message, title, true);
        }
        if (log.file) {
            if (
                confirm(doneMessage +
                    "\nAlerts reported. See log for details:\n" +
                    File.decode(log.file.fullName) +
                    "\n\nOpen log?", false, title)
            ) {
                log.file.execute();
            }
        } else {
            doneMessage && alert(doneMessage, title, error);
        }
    }

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

    function configureUi() {
        grpRemoveBg.enabled = cbRemoveBg.value;
        grpPercent.enabled = cbCrop.value;
        inpPercent.prior = inpPercent.text;
        grpMaxPixels.enabled = cbMaxPixels.value;
        grpQuality.visible = listFormat.selection && listFormat.selection.index == 0; // JPG
    }

    function getFiles(folder, subfolders, extensions) {
        // folder = folder object, not folder name.
        // subfolders = true to include subfolders.
        // extensions = string, extensions to include.
        // Combine multiple extensions with RegExp OR i.e. jpg|psd|tif
        // extensions case-insensitive.
        // extensions undefined = any.
        // Ignores hidden files and folders.
        var f;
        var files;
        var i;
        var pattern;
        var results = [];
        if (extensions) {
            pattern = new RegExp("\." + extensions + "$", "i");
        } else {
            // Any extension.
            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, extensions));
                } else if (f instanceof File && pattern.test(f.name)) {
                    // Ignore EPS if not raster.
                    if (/\.(eps)$/i.test(f.name)) {
                        // Determine if creator is Photoshop.
                        f.open("r");
                        // If Photoshop, creator will be early.
                        // Save time and read only first 512 characters.
                        var data = f.read(512);
                        f.close();
                        if (!/Adobe Photoshop/.test(data)) {
                            // NOT Photoshop EPS.
                            continue;
                        }
                    }
                    results.push(f);
                }
            }
        }
        return results;
    }

    function maskLayer() {
        // Mask active layer using document selection.
        var desc1 = new ActionDescriptor();
        var ref1 = new ActionReference();
        desc1.putClass(charIDToTypeID("Nw  "), charIDToTypeID("Chnl"));
        ref1.putEnumerated(charIDToTypeID("Chnl"), charIDToTypeID("Chnl"), charIDToTypeID("Msk "));
        desc1.putReference(charIDToTypeID("At  "), ref1);
        desc1.putEnumerated(charIDToTypeID("Usng"), charIDToTypeID("UsrM"), charIDToTypeID("RvlS"));
        executeAction(charIDToTypeID("Mk  "), desc1, DialogModes.NO);
    }

    function process() {
        var baseName;
        var bounds;
        var doc;
        var docClean;
        var f;
        var file;
        var fileName;
        var fileOutput;
        var fileVersion;
        var files;
        var fullPath;
        var i;
        var nameOutput;
        var pathOutput;
        var saveOptions;
        var subpath;
        // Preserve preferences.
        var preserve = {
            rulerUnits: app.preferences.rulerUnits
        };
        app.displayDialogs = DialogModes.NO;
        app.preferences.rulerUnits = Units.PIXELS;
        format = listFormat.selection.index;
        extension = extensions[format];
        progress.display("Reading folder...");
        try {
            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];
                subpath = File.decode(file.path).replace(File.decode(folderInput.fullName), "");
                fileName = File.decode(file.name);
                baseName = fileName.replace(/\.[^\.]*$/, "");
                if (cbRenameWeb.value) {
                    baseName = baseName.replace(/[^0-9a-zA-Z-\.\-]/g, "-").replace(/-{2,}/g, "-");
                }
                if (cbLowercase.value) {
                    baseName = baseName.toLowerCase();
                }
                progress.display(fileName);
                try {
                    doc = app.open(file);
                } catch (e) {
                    if (/User cancel/.test(e.message)) {
                        throw e;
                    }
                    log.addFile(file.fullName, "Cannot open the file");
                    progress.increment();
                    continue;
                }
                try {
                    doc.flatten();
                    doc.changeMode(ChangeMode.RGB);
                    doc.bitsPerChannel = BitsPerChannelType.EIGHT;
                    doc.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC);
                    // REMOVE BACKGROUND
                    if (cbRemoveBg.value) {
                        bounds = processDocRemoveBg(doc);
                        if (cbCrop.value && bounds) {
                            processDocCrop(doc, bounds);
                        }
                    }
                    // LIMIT PIXELS
                    if (cbMaxPixels.value) {
                        processDocMaxPixels(doc, inpMaxPixelsWide.text, inpMaxPixelsHigh.text);
                    }
                    // MAKE CLEAN DUPLICATE (to clear metadata, EXIF, etc.)
                    docClean = app.documents.add(doc.width, doc.height, 72, doc.name);
                    docClean.mode = DocumentMode.RGB;
                    docClean.colorProfileType = ColorProfile.NONE;
                    app.activeDocument = doc;
                    doc.layers[0].duplicate(docClean);
                    doc.close(SaveOptions.DONOTSAVECHANGES);
                    doc = docClean;
                    app.activeDocument = doc;
                    doc.layers[1].remove();
                    // Set output path.
                    pathOutput = folderOutput.fullName;
                    if (subpath) {
                        pathOutput += subpath;
                    }
                    f = new Folder(pathOutput);
                    if (!f.exists) {
                        f.create();
                    }
                    // Add extension and build full path.
                    nameOutput = baseName + inpSuffix.text + "." + extension;
                    fullPath = pathOutput + "/" + nameOutput;
                    fileOutput = new File(fullPath);
                    if (!cbReplaceOutput.value) {
                        // RESOLVE EXISTING FILES
                        fileVersion = 1;
                        while (fileOutput.exists) {
                            fileVersion++;
                            nameOutput = baseName + inpSuffix.text + "-" + fileVersion + "." + extension;
                            fullPath = pathOutput + "/" + nameOutput;
                            fileOutput = new File(fullPath);
                        }
                    }
                    // Save.
                    switch (format) {
                        case 0: // JPG
                            saveOptions = new JPEGSaveOptions();
                            saveOptions.embedColorProfile = false;
                            saveOptions.formatOptions = FormatOptions.PROGRESSIVE;
                            saveOptions.matte = MatteType.WHITE;
                            saveOptions.quality = Number(listJpgQuality.selection.text);
                            saveOptions.scans = 3;
                            break;
                        case 1: // PNG
                            saveOptions = new PNGSaveOptions();
                            saveOptions.compression = 5;
                            saveOptions.interlaced = false;
                            break;
                        default:
                            // None of the above.
                            throw new Error("Bad file format.");
                    }
                    doc.saveAs(fileOutput, saveOptions);
                } catch (e) {
                    if (/User cancel/.test(e.message)) {
                        throw e;
                    }
                    log.addFile(file.fullName, "Error line " + e.line + ": " + e.message);
                } finally {
                    doc.close(SaveOptions.DONOTSAVECHANGES);
                }
                count++;
                progress.increment();
            }
        } finally {
            // Restore preferences.
            app.preferences.rulerUnits = preserve.rulerUnits;
            app.displayDialogs = DialogModes.ERROR;
        }
    }

    function processDocCrop(d, b) {
        // d = document
        // b = selection bounds
        var w;
        var h;
        var margin;
        w = Number(b[2] - b[0]);
        h = Number(b[3] - b[1]);
        margin = Math.max(w, h) * (Number(inpPercent.text) / 100);
        b[0] = Math.max(0, b[0] - margin);
        b[1] = Math.max(0, b[1] - margin);
        b[2] = Math.min(d.width, b[2] + margin);
        b[3] = Math.min(d.height, b[3] + margin);
        d.crop(b);
    }

    function processDocMaxPixels(d, w, h) {
        var height = d.height;
        var scale;
        var scaleHeight = 1;
        var scaleWidth = 1;
        var width = d.width;
        var isEmpty = function (text) {
            return text.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "").length == 0;
        };
        if (!isEmpty(w)) {
            width = Number(w);
        }
        if (!isEmpty(h)) {
            height = Number(h);
        }
        if (width < d.width) {
            scaleWidth = width / d.width;
        }
        if (height < d.height) {
            scaleHeight = height / d.height;
        }
        scale = Math.min(scaleWidth, scaleHeight);
        if (scale < 1) {
            d.resizeImage(d.width * scale, d.height * scale, null, ResampleMethod.BICUBICSHARPER);
        }
    }

    function processDocRemoveBg(doc) {
        var bounds;
        var channel;
        var desc1;
        var i;
        var results;
        var selectSubject = rbSubject.value;
        results = [];
        if (rbPath.value) {
            if (doc.pathItems.length) {
                // Use first path found.
                doc.pathItems[0].makeSelection(0, true, SelectionType.REPLACE);
            } else {
                results.push("No paths found. Using select subject instead.");
                selectSubject = true;
            }
        } else if (rbAlpha.value) {
            for (i = 0; i < doc.channels.length; i++) {
                channel = doc.channels[i];
                if (channel.kind == ChannelType.MASKEDAREA || channel.kind == ChannelType.SELECTEDAREA) {
                    // Make selection from first alpha channel found.
                    (function () {
                        var desc1 = new ActionDescriptor();
                        var ref1 = new ActionReference();
                        ref1.putProperty(charIDToTypeID('Chnl'), stringIDToTypeID("selection"));
                        desc1.putReference(charIDToTypeID('null'), ref1);
                        var ref2 = new ActionReference();
                        ref2.putName(charIDToTypeID('Chnl'), channel.name);
                        desc1.putReference(charIDToTypeID('T   '), ref2);
                        executeAction(charIDToTypeID('setd'), desc1, DialogModes.NO);
                    })();
                    break;
                }
            }
            if (i == doc.channels.length) {
                results.push("No alpha channels found. Using select subject instead.");
                selectSubject = true;
            }
        }
        if (selectSubject) {
            try {
                desc1 = new ActionDescriptor();
                desc1.putBoolean(stringIDToTypeID("sampleAllLayers"), false);
                executeAction(stringIDToTypeID('autoCutout'), desc1, DialogModes.NO);
            } catch (e) {
                if (/User cancel/.test(e.message)) {
                    throw e;
                }
                results.push("Failed to select subject. Background remains.");
                log.addFile(doc.fullName.fullName, results);
                return null;
            }
        }
        bounds = doc.selection.bounds;
        try {
            maskLayer();
        } catch (_) {
            results.push("Failed to mask image. Background remains.");
            log.addFile(doc.fullName.fullName, results);
            return null;
        }
        if (results.length) {
            log.addFile(doc.fullName.fullName, results);
        }
        return bounds;
    }

    function validatePixels() {
        var s;
        var v;
        s = this.text.replace(/[^0-9]/g, "");
        if (s != this.text) {
            this.text = s;
            alert("Numeric input only. Non-numeric characters removed.", " ", false);
        }
        // Make integer.
        v = Math.round(Number(s) || 0);
        if (v == 0) {
            // Zero is equivalent to blank.
            this.text = "";
        } else {
            this.text = v.toString();
        }
    }

})();
Many scripts are free to download thanks to the support of users. Help me keep developing new scripts by supporting my work. Click the PayPal button to contribute any amount. Thank you.
Download
Batch Web Images

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

Custom solutions based on any script, or completely new ideas, are possible at reasonable cost. Contact William for more information.

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 products purchased from this site, or for programming custom solutions, are the purchase of a non-exclusive license to use the software and do not grant the purchaser any degree of ownership of the software. Author of the intellectual property and copyright holder William Campbell retains 100% ownership of all code used in all products and custom solutions.

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.