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
Get more features
Batch Size And Mask
Download
Batch Web Images
Help me keep making new scripts by supporting my work. Click the PayPal button to contribute any amount you choose. Thank you. William Campbell

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.

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.

As of Photoshop version 23.5.0, Select Subject added the options Device (Quicker results) and Cloud (Detailed results). For most installations the default is Device, the only choice prior to version 23.5.0. The script uses whatever is the default method. To change the default method to the superior Cloud option, go to Preferences (Windows) or Settings (macOS), section Image Processing. Set the drop-down list Select Subject Processing to the desired default method.

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 each output file name. The characters entered must be legal to use in file names. Any illegal characters are automatically removed. Having no suffix is allowed, in which case output file names exactly match input file names.

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.

When files are output, any existing files of the same name in the output folder are replaced without alert.

Source code

(download button below)

/*

Batch Web Images
Copyright 2024 William Campbell
All Rights Reserved
https://www.marspremedia.com/contact

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 imgMoreFeatures;
    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 btnMoreFeatures;
    var btnOk;
    var cbCrop;
    var cbLowercase;
    var cbMaxPixels;
    var cbRemoveBackground;
    var cbRenameWeb;
    var cbSubfolders;
    var grpMaxPixels;
    var grpPercent;
    var grpQuality;
    var grpRemoveBg;
    var inpMaxPixelsHigh;
    var inpMaxPixelsWide;
    var inpMarginPercent;
    var inpSuffix;
    var listFormat;
    var listQualityJpg;
    var rbAlphaChannel;
    var rbPath;
    var rbSubject;
    var txtFolderInput;
    var txtFolderOutput;

    // SETUP

    // Created with script 'Encode Binary' from PNG.
    imgMoreFeatures = File.decode(
        "%C2%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%C2%AF%00%00%00%1F%08%06%00%00%0" +
        "0%C2%91X%C2%9D%00%00%00%00%09pHYs%00%00%0B%12%00%00%0B%12%01%C3%92%C3%9D~%C3%B" +
        "C%00%00%09%07IDATx%C2%9C%C3%AD%C2%9CAH#%C3%89%1A%C3%87%C3%BF%C2%BB%C2%BDD%5B%C" +
        "2%BA1%C2%99%C3%B0%02%C3%9D%C3%B8pH%C2%88%C2%B0Dpir%C2%9CD/9%05%C3%9C%C2%99%C3%" +
        "8Bf.%C3%AF%C3%B9%0E%1E%C2%969%C3%A9ed%0F%C2%BA%C3%B3%C3%B40%C3%8C%5C%C3%B4%C2%" +
        "B4%C3%8C!%07%C3%9D%C2%93%C2%99%C3%8B.%C2%82%C2%A7%5CF%C2%9D%C2%A3%C2%84%11%C2%" +
        "94%C2%85%C2%84%C2%84%C2%91'i%088%C2%89t%C2%B05%18%C3%A6%1D:UV%C2%9B%C3%AE%C3%9" +
        "8%C3%918%C3%AB@%C3%BF@0%C3%A9%C3%AA%C3%AA%C2%AF%C2%AA%C2%BE%C2%AA%C3%AF__w%C3%" +
        "A7%C2%9B%C3%8F%C2%9F?%C3%83%C3%85%C3%A5k%C3%A4;%C2%A7%05%13%0B%7B%13%00&%C3%AE" +
        "%C3%8C%12%17%17%C2%83%C2%8F%00%C3%9Ee%17%C3%87%3E%5EW%C3%B0%C2%9BN+oba%C3%AF!%" +
        "C2%80%17%00%1E%03%18%C3%AC%C2%89i..%C3%8E%C3%98%02%C2%B0%C2%92%5D%1C%C3%BB%C3%" +
        "93%C2%AE%C2%80%C2%AD%C3%B3&%16%C3%B6%5E%00%C3%B8%C3%B5n%C3%ACrqq%C3%8C%16%C2%8" +
        "0%C3%BFX%C2%AD%C3%84m%C3%8E%C2%9BX%C3%98%C3%B3%02X%05%C3%B0%C3%A3%C2%97%C2%B0%" +
        "C3%8C%C3%85%C3%85%01'%00&%C2%B2%C2%8Bc%1F%C3%98/%C2%BF%C2%B5(%C2%B8%0A%C3%97q%" +
        "5D%C3%AE%17%C2%83%00%C3%9E%25%16%C3%B6~%60%C2%BF49oK*%C2%B8%C2%8E%C3%ABr%1F%19" +
        "%04%C2%B0%C3%9AR%06%00%18%C3%A7mm%C3%8E%5C%C2%8D%C3%ABr%C2%9F%19%030C%3E%C2%B0" +
        "+%C3%AF%C2%8B/n%C2%8A%C2%8BK%C3%B7%C3%8C%C2%90%C3%95%C3%B7%5B%C2%80n%C3%92%C2%" +
        "A6%C3%BEV%C2%93%5C%5C%C2%9C1%08#uKoRL%C3%9C%C2%A66%C2%81%C3%A7%C3%9A%C2%BE%C2%" +
        "AB%C3%AB%C3%8D%C3%9BT%C3%A9%C2%98%C3%9F~%1E%01%00%C2%A4%C2%B3*rE%C2%AD%C2%ABs%" +
        "25%C2%9F%07%02%C3%8FA%C2%AD6%C2%A8%C2%BD%02%C3%8Fa:!%C2%A1%C2%AE7%C2%91%C3%8E%" +
        "C2%AA=%C2%B7%C2%97%C2%85%C3%B4%5B%C2%AF%C3%BAJ%09%C2%89%C2%98NH%00%C2%80go%C3%" +
        "B2%1D%C2%AFiw%C3%9D%C3%AB%C2%8EwK*%16%C3%80x%C3%84%C2%8B%7C%C3%B9%14+%1BG%C2%B" +
        "7%C2%AE%C2%AF%C3%85c%00%C2%AB%C3%84y%7F%C3%A8T%C2%B2%13%02%C3%8F%C3%A1%C2%8F_F" +
        "%C3%9B%C2%BE%C3%8F%155l%C3%AE~%C3%82%C3%B6A%C2%AD%C2%AB%C2%BA%C2%94%C2%A0%C2%8" +
        "8%5CIs%C3%94q%02%C3%8F!,%C3%B3%C3%86%C3%BF%C3%BD%C3%AD%13%C3%88%C2%8E%C2%B0%C3" +
        "%8Cc%3E%C3%B5%10%C2%92%C3%8Fc%C2%B2w%C3%A9%C3%AD!%C2%94%C2%A0%C2%88d%C3%94%0F%" +
        "00X%7F_%C2%B9%C2%B3I8%C2%9F%1AF%3Cb%C3%AC=%C3%92Y%15%C2%99%C2%9D%C3%8A%C2%AD%C" +
        "3%AB%14%C3%BA%C2%99%C3%BE%C3%A0%C2%B96%C3%9B%C2%AF%C2%8E%C3%95%C2%B37y%14%C3%8" +
        "A:%C3%BD%C2%AC%C2%84D%C2%BC%C2%9A%0A%C3%92%C3%8FO%5E%C3%AE%C3%9F%C2%BA%C3%BD%2" +
        "2cS%0F%C2%99%00.5%C3%AF%C2%8D%C2%9D%C2%97%C2%A5P%C3%96Q(%C3%ABP%C2%AB%0D(!%11%" +
        "C3%B3%C2%A9%C3%A1%C2%AE%0COF%C3%BD%C2%98O%0DC%09%C2%8A%C2%BD0%C3%87%C2%96%C3%9" +
        "7S!H%3E%C2%8F%C3%89%5E%C3%A9A%1F%C3%AAz%13%C3%9B%075%C2%A4%C2%B3*%C2%962%C2%87" +
        "w%1A=%C2%94%C2%A0H%C3%AB%1F%C2%8Fx%C2%AF)%7DG6%C2%84%C3%8C%C3%BD%1C%C2%8F%7C57" +
        "Q%07%C2%81K%C3%99%C3%90%C2%93%C3%9E%5B%C3%9F%C2%A9%60%C3%BB%C2%A0%06%C3%89%C3%" +
        "A7%C3%81%C3%AF%C2%B3%C3%9F%03%00$_%1F%0Ae%1D%C2%92%C3%8F%C2%83%C3%B8%C2%A8%C3%" +
        "974P%5B%075%C2%BA%C3%A2%C3%84#%5E%C2%BA%C3%A2=%C2%8D%05%C3%B04%16%C2%A0%C3%87%" +
        "05%C2%9EC2%C3%AA%C2%A7%C3%A7%C2%AA%C3%95sl%C3%AD%C2%9F%20W%C2%BA%C2%94%09%22%C" +
        "3%8Fa%3E5%0C%C3%89%C3%97%07M%C2%BF@:%C2%AB%C2%9AV%15B%3C%C3%A2%C2%A5%C2%A1%C3%" +
        "B1%C3%B9Z%C3%91$%17%00c@%C3%89u%C2%B6%0Fj%C2%A6P%C3%8C%C3%82%C2%86A%12%1A%C2%8" +
        "9m%C2%9B%C2%BB%C2%9F:J%C2%98d%C3%94%0F%C2%81%C3%A7Z%7D%C3%95%C2%87%C2%B0%C3%8C" +
        "#,%C3%B3%C3%94%5Er%C3%8D%C2%AD%C2%83%1A%C3%AAz%13%C3%B1%C3%88%20D%C3%BE;%C2%A8" +
        "%C3%95s,o%1C%C2%B5I%C2%9C%11y%00%C2%9A~%C2%81%5C%C2%A9%C3%9Eat.)%C2%94u%C2%84e" +
        "%1E%C3%A3%11%C2%AFi%C3%85WB%22=%C3%86%C2%A2%C2%84D%C3%84#%C2%83%18%C2%91%07%00" +
        "%00%C2%9A~a%C2%8A%C2%AA%02%C3%8F%C3%A1%C3%A9%C2%A3%00%C2%9D%0C%C3%84%16%C2%BBh" +
        "B%C3%BAK%C3%93/%C2%B0%C3%B4%C3%B6%C3%A6%C2%8B%C2%84%C3%A3%07s%C2%9Cb%C2%84%C3%" +
        "B2%01%C3%BAY%C2%AD%C2%9E%C3%93%C3%AF%C2%A7%13%12%C3%95%C2%97%C2%92%C3%8F%C2%83" +
        "%C3%A9%C2%84%04%C2%B1%C2%9F%C3%83%C3%BA%C3%BB%0A%C3%A6S%C3%83%C3%B4%1C%C3%92y%" +
        "C3%84%01%5EO%C2%85L%1D%1A%C2%96y%C3%A4%C3%8B:P%C2%BA%C2%BC%C3%AEtB2%C3%A9%C2%B" +
        "5%C3%97%C3%B2%00%C3%BE%C2%B5%C3%B2W%5B%C3%87%10%7B%00%C3%83%C2%89H%07S%C2%87%C" +
        "2%B8%12z%05%C2%9B%C2%B0%C2%A7%C3%A9%17%00%C2%80%C2%99%C3%89!$%C2%A3~%C3%9A%C2%" +
        "AEx%C3%84%C2%8Bx%C3%84%C2%8B%C2%B9%C2%B5%C2%92%C2%AD%03%C2%93%15.W%C2%ACC%C3%A" +
        "0/%1D%C2%898/%C2%B9&%C3%91%C3%A4l%C2%BB%C2%85~%0EskF%C3%83%C3%A7%7F%1A6%C2%AD%" +
        "C2%9E#L%C2%BFw%22,%C3%B3%C3%88%155(!%11%C2%92%C3%8FC#%C2%A5%C3%A4%C3%B3%20%C2%" +
        "B3_ikoX%C3%A6%C2%91%C2%8C%C3%BAM%C2%93K%09%C2%89X%C3%8A%18%13%7Cvr%08%C3%B1%C2" +
        "%88%C2%97%C3%B6%C2%81%12%12Q?%C2%B3v%C3%88T,@%C3%B7%14%C3%8B%1B%C3%BF%C2%BBUt%" +
        "C3%AB%C2%A9%C3%B3%C2%B2%0E%08%C3%80%C2%B4%C3%BA%15%C3%8A%C2%BAIc%C2%91F(!%11%C" +
        "3%A9%C2%AC%C2%8A'/%C3%B7%C2%A9%1E%5B%C3%8A%1CR%C3%9D%C2%9B%C2%8A%05%10%C2%96y%" +
        "C2%A8%C3%95%06%C3%A6V%C2%8B%C3%90%C3%8E%C2%9A4%C3%A4%C2%B3%03%C2%9B+iX%C3%9E8%" +
        "C2%82%C3%98%C3%8F%C3%A1%C3%B7%C3%99%C3%AF%C2%A9~%C2%BE%C2%AA%C2%B9%0Be%1D%C2%9" +
        "B%C2%BB%C3%87HF%C3%BD%C2%98NHHF%C3%BD%C3%98%C3%9C=%C2%B6%5D%25%C2%B6%0Fjx%C3%B" +
        "2%C3%92p%C3%82%11y%00%C2%AF%C2%A6%C2%82%C2%A8%C3%ABM%C2%ACl%1C%C3%91A%05%C2%80" +
        "%C2%B9%C3%95%22%C3%94j%C2%83%C2%B6+%C3%B5%C3%A8%1F%C2%96%C3%8E+%C3%B9%3C%C3%94" +
        "%C3%A1rE%0D%C3%92%C2%83%3E%00@%7C%C3%94%C3%9B%C2%B6A%14x%0E%C3%A9%C2%AC%C2%8A%" +
        "C3%8D%C3%9Dc%C2%8CG%C2%BC%C2%98%C2%99%1C%C2%82%12%12!%C3%B0%C2%9C%C2%A9%C2%9E%" +
        "C2%B9%C2%B5%12%C3%B2%C3%A5S%C3%9A&'%C3%A4JucE%1D5V_:%C2%A1Ju%C2%A4b%01S%C3%99%" +
        "C3%8CN%05%C2%B9%C2%A2F%C3%87%C2%8E%C3%A8%C3%B5%11%C2%99%C2%A7%C3%91%03%00%C3%A" +
        "DGv/%C3%81%12%C2%8Fx%C2%A9%C3%A3%3E_+ZF%C3%86n%C3%A8%C2%A9%C3%B3%C2%B2%C3%86%C" +
        "2%84e%1EO%1F%05%C2%B0%C2%BD_%C2%83Zm%18%C3%8E%C3%94%0A%C2%87%22%7F%C3%BDe%C3%8" +
        "9%C2%8C%14%5B%1B%C2%B1%5CQ%C2%83Zm%18%C3%97%C3%91%C3%9B%1B%C2%BD%C2%B5%7F%C2%8" +
        "2%C2%BA%C3%9ED%5DoZ%C2%86%3E%C2%96%C2%95%C2%8D#%14%C3%8A:R%C2%B1%00%C2%8D%00%C" +
        "2%92%C3%8Fc%C2%BB%1B%C2%AE%C3%ABM%08%3C%C2%87%C2%99%C3%89!%00%C3%86%C2%A4T%C2%" +
        "AB%0D%C3%84e/=%3E%C2%9Fzh:%C3%87%C2%AE%C2%8D%C3%B1Q%C3%A3%1C%C3%83%C3%86%01z%3" +
        "Eq%C3%86%C2%AB%0E%C2%BF%C2%B9%7B%C2%8C%C2%BA%C3%9E%C3%84%C3%96A%C2%8D%5E%1F%00" +
        "u%C2%98BY%C2%A7%C3%A7%C3%A4%C2%8A%1A%C3%A0%C3%94y%5Be%C2%95%C2%A0%C2%80%C3%8CN" +
        "%C2%85J%C2%86%7C%C3%B9%C2%B4%C2%AD%C2%AC%C3%A4%C3%B3%60%3C%C3%A2%C3%85%C3%AC%C" +
        "3%A4?%01%C2%B4g%C2%97%C3%B2%C3%A5S%C2%84e%1E%C3%93%09%09%C3%A3%11%C2%AFI%0E%12" +
        "%04%C2%9E%C3%83l%C3%8B%C3%BE%C3%A5V%C3%BF%C3%9F%C2%96%C2%9E:/%C3%91%C2%BC%C2%8" +
        "0%C2%91%C3%82%22+S:%C2%AB%C3%92%C3%90%C2%AFV%1B%C3%98%C3%9E%C2%AFAz%C3%A0%C3%A" +
        "9j3%C3%97%C3%AB%C3%8D%C3%93%C3%A6%C3%AE16w%C2%8F%C3%A9J%C2%99%C2%8C%C3%BA%C2%B" +
        "1%7Dpb%5B~vr%08%C2%92%C3%8FC%C3%8F%C3%AB%C2%84%C2%9D%13%00%C2%80%12%14%00%C2%9" +
        "0%C2%8C%C2%879R%C3%85#%C2%83%5D%C2%A7%C3%BBX%C3%88%C3%A4vZ%C2%B6P%C3%96%C2%A1%" +
        "C2%84%C2%8C%C3%AC%C2%8A%C3%A4%C3%B3%20%C2%BD%C3%9B%C2%9E%1A%C2%94%7C%1E%C3%BC%" +
        "C3%B6%C3%B3%08%04%C2%9E%C2%A3%C2%AB/%C2%914%C2%84%C2%95%C2%8D#%C2%A8%C3%95%06%" +
        "C2%92Q?%C3%95%C3%AFW%17%03%C2%B6%C3%BC%C3%B8%C3%A8%60WY(;H%C2%B6%C3%A1%C3%B65u" +
        "@%09%C2%89%C3%94Q%C3%A7V%C2%8BHgUC%C2%B3vY%07%C2%8BUn%C3%99%09lz%0D0B%22%C2%AB" +
        "5%C2%ADH%C3%85%02%C2%88%C2%B74);%20%C2%AC%C2%AE%7B%C2%BEV%C3%84%C2%B37y%C3%BAg" +
        "%C2%B5%C2%8A%C2%87e%C2%9E%C2%B6#%C2%B3S%C2%A1%7Fd%20o%C2%92u%60uq%C2%B7%C3%A7o" +
        "%C2%B5%C2%AEK%C2%A4%C2%8F%C3%95%C3%84%C2%89%C2%8F%1A%1B%C3%9CBY%C3%87%C3%9CZ%0" +
        "9%C3%A9%C2%ACj%C2%A9g3;%15%C3%BC%7B%C3%B9/*%7D%C3%88%C2%A6%C2%94%C2%A0V%1Bx%C3" +
        "%B6&oD%C2%AC%C2%88%C2%B7M%C2%9A%C3%9C%04%C2%B2%C3%B2~@%0F%1E%C3%88!Y%02%C2%A2%" +
        "C3%89%00%20_%C3%96%C2%A1~%C2%BA%C3%9C$%C2%A5b%01%C3%94%C3%B5&%C3%AD0%02%1B%C3%" +
        "AE%C2%93%C3%91%07%18%C2%91yhgF%C2%B8$%C2%BA%C3%B7%C3%95T%C2%90%C2%AE%16%C2%B9%" +
        "C2%A2%C2%86%C3%B5%C3%B7%C3%9D%C3%A7F%C2%95%C2%A0%C2%91%C3%823Rd%C3%A7t%C2%B7_%" +
        "C3%97%C2%9B%C3%88%C2%95%C2%B4%C2%B64%1D%09%C2%87%C2%80%C2%B1%C3%99#%C3%BFkgM%C" +
        "2%AA%05%C2%95%C2%90%C2%88%C3%B9%C2%9F%C2%86%C3%A9$%08%C3%8B%C2%BCe%C2%8E%C2%9B" +
        "8n%C2%A1%C2%AC%C2%B7%C3%A9%C3%9B?~%11iVEs%10e%C2%B6%0FjP%C2%AB%C2%86%C3%9C!%C3" +
        "%97&%C2%92%C3%84)D:%C2%90L%C3%87%C3%95%7D%04p%19%C3%B1%C2%88%C2%BC%12x%C2%8E%C" +
        "3%A6%C2%A7%09%C3%B3%C2%A9ah-%C3%89%C3%86%C3%9Exa%C2%A3%25%19%C3%9F%C2%95%C2%8D" +
        "#%C2%BC%C2%9A%0Ab:!%C2%99$O%C2%97%C2%9C%00%C2%97+%C3%AF%C2%87%0E%05%1D%C3%83%C" +
        "2%86%C2%8CBY%C3%87R%C3%A6%C2%B0%C3%95%C3%89%0D%C2%AA%C2%81%C2%92Q?%C2%92Q?%C3%" +
        "96%C3%9FW%C3%9A%C3%82%1C%C3%91wJH%C2%A43%C2%93%0C4%C3%99%11%13%C2%9D%C3%9AM%C2" +
        "%88d%C2%A9%C2%9F5%C2%A1V%1B%08%C3%8B%3C%C3%A2%11/%1D86m%C3%86B%C2%B4%25%00%C2%" +
        "BAb%C2%B0%C2%A9%C2%B1%C2%A5%C2%B7%C2%874%C2%A5F%C2%8E%C2%8D%C3%88%03%C2%96%C2%" +
        "AB%139g%C3%8B%22d%C2%92%C2%B4%C2%9F%12%12%1C%C2%B7e)%C3%B3%C2%91%C3%B6%17%C3%9" +
        "9xv%C3%93/%C3%84a%C3%ADl%02%C2%8C1%C3%89%155%08%3C%C2%87T%C3%8CH%C2%87YE%C2%95" +
        "d%C3%94%C2%8FT,@3/%C3%8B6%C3%BB%C2%87%5CQ%C2%A3%C2%BE039t%C3%93%08%C3%BA%0Eh=%" +
        "C2%8C%C3%9Ez%C2%B6%C2%A1z%C2%93Z%00%C3%A7%C2%B7%C2%87%05%C2%9E%C3%83%C2%88%3Cp" +
        "%C3%ADlSB%22%C3%B2%C3%A5%C3%93%C2%B6:H%C2%B8g%C3%85%C2%BE%C3%9D-V%C2%AB;LW%C2%" +
        "8F%C3%9B%C3%9D%C3%8D%C2%BBZ%C2%A7%C2%93%C3%B6%C2%91%C2%B6%C2%A9%C2%9F%C3%8Em%1" +
        "D%C3%A8%C2%BA%C3%9B%C3%81%C2%AC%C3%8DVe%C3%AD%C3%8Egs%C3%84W%C3%ABqb%C3%83%C3%" +
        "95%C3%B2Ve%C2%88%3C%C2%A1%12%C3%8B%C3%A2%1C%C2%BB%C3%B6%C3%9B%C3%9D%C3%AD%C2%B" +
        "3j%C2%8BCf%C2%B3%C2%8Bc+%C3%B4M%C2%8A%C3%84%C3%82%C3%9E*%C3%9C%C2%87s%5C%C3%AE" +
        "?'%00%1Ef%17%C3%87j%C3%AE#%C2%91._%1B+%C3%99%C3%85%C2%B1%1A%C3%80%3C%C3%8F%C3%" +
        "9Bz%C3%81%C3%AD%C2%BF%7F%C2%97E..%0E8%04%C2%B0B%3EX%C2%BD%C2%80%C3%B9%0E%C3%80" +
        "%C3%B8%C2%97%C2%B5%C3%89%C3%85%C3%A5Z%C3%9A%5E%C3%82%C2%B4z%01%C3%B31%C2%8C%C3" +
        "%97%C2%8D%5D%5C%C3%AE%0B%C2%96o%0F%C2%BB%C2%BF%C3%9B%C3%A0r%C3%9Fq%C3%BE%C2%BB" +
        "%0D,%C3%8C/%C3%A6%C2%B8Y%08%C2%97/%C3%8D%1E%C2%8C%C3%8D%C3%99%C2%AA%5D%C2%81%C" +
        "2%8E%C3%8EKh%C3%A5%C2%81'%60%3C%C2%B4%C3%AEE%C2%8F%1E%5Ewqa%C3%B8%C3%88%C3%BC%" +
        "C3%9D%C3%BE%C2%B7%C3%8A%5C%5C%C3%AE3%C3%BF%07%C3%BA%C3%A4%C2%B6%C3%8DKq%C3%85%" +
        "C2%AE%00%00%00%00IEND%C2%AEB%60%C2%82"
    );

    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.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 '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.width = 360;
    cbSubfolders = p.add("checkbox", undefined, "Include subfolders");

    // Panel 'Options'
    p = w.add("panel", undefined, "Options");
    p.alignChildren = "left";
    cbRemoveBackground = 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");
    rbAlphaChannel = 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:");
    inpMarginPercent = grpPercent.add("edittext");
    inpMarginPercent.preferredSize.width = 40;
    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.width = 60;
    grpMaxPixels.add("statictext", undefined, "w");
    inpMaxPixelsHigh = grpMaxPixels.add("edittext");
    inpMaxPixelsHigh.preferredSize.width = 60;
    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.width = 360;
    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:");
    listQualityJpg = 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");

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

    // More features
    g = w.add("group");
    g.alignment = "center";
    g.add("statictext", undefined, "Get more features");
    btnMoreFeatures = g.add("iconbutton", undefined, imgMoreFeatures);

    // SET UI VALUES

    txtFolderInput.text = "";
    cbSubfolders.value = false;
    cbRemoveBackground.value = true;
    rbPath.value = false;
    rbAlphaChannel.value = false;
    rbSubject.value = true;
    cbCrop.value = true;
    inpMarginPercent.text = "3";
    cbMaxPixels.value = true;
    inpMaxPixelsWide.text = "1200";
    inpMaxPixelsHigh.text = "800";
    txtFolderOutput.text = "";
    listFormat.selection = 1; // 0=JPG, 1=PNG
    listQualityJpg.selection = 5;
    inpSuffix.text = "";
    cbRenameWeb.value = true;
    cbLowercase.value = false;
    configureUi();

    // UI ELEMENT EVENT HANDLERS

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

    // Panel 'Options'
    cbRemoveBackground.onClick = configureUi;
    cbCrop.onClick = configureUi;
    inpMarginPercent.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.trim();
        // 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);
    };
    // Button 'More features'
    btnMoreFeatures.onClick = function () {
        var f;
        var url = "https://www.marspremedia.com/more-features?id=batch-web-images";
        if (Folder.fs == "Windows") {
            // Windows
            f = File(Folder.temp + "/9751c99557884d399a9eacba579e00fe.url");
            f.open("w");
            f.write("[InternetShortcut]\r\n" +
                "URL=" + url + "\r\n"
            );
            f.close();
        } else {
            // macOS
            f = File(Folder.temp + "/9751c99557884d399a9eacba579e00fe.webloc");
            f.open("w");
            f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
                "<plist version=\"1.0\">\n" +
                "<dict>\n" +
                "    <key>URL</key>\n" +
                "    <string>" + url + "</string>\n" +
                "</dict>\n" +
                "</plist>\n"
            );
            f.close();
        }
        f.execute();
        $.sleep(200);
        f.remove();
        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 = cbRemoveBackground.value;
        grpPercent.enabled = cbCrop.value;
        inpMarginPercent.prior = inpMarginPercent.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 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.increment();
                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");
                    continue;
                }
                try {
                    doc.flatten();
                    doc.changeMode(ChangeMode.RGB);
                    doc.bitsPerChannel = BitsPerChannelType.EIGHT;
                    doc.convertProfile("sRGB IEC61966-2.1", Intent.RELATIVECOLORIMETRIC);
                    // REMOVE BACKGROUND
                    if (cbRemoveBackground.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);
                    // Save.
                    switch (format) {
                        case 0: // JPG
                            saveOptions = new JPEGSaveOptions();
                            saveOptions.embedColorProfile = false;
                            saveOptions.formatOptions = FormatOptions.PROGRESSIVE;
                            saveOptions.matte = MatteType.WHITE;
                            saveOptions.quality = Number(listQualityJpg.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++;
            }
        } 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(inpMarginPercent.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 (rbAlphaChannel.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();
        }
    }

})();
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
Batch Web Images

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.