A closeup of an eye in black and white, with a blue-green iris in color

Many design tools now feature workflows for exporting Retina images, but only for the current document; bulk conversion of assets has tended to be the role of complex command-line utilities like ImageMagick or task runners like Grunt. PhotoShop Actions can be used for batch processing, but they can be somewhat finicky to record, play back, and modify.

For designers, there’s a nice intermediate solution between using a familiar GUI and the power of a CLI: most Adobe products have had the power to run JavaScript under the hood for years. Creating a script action provides complete customisation for your project needs, and acts as a potential stepping stone to stand-alone systems like Gulp.

The Problem

I’m currently rebuilding and redesigning this site for the fifth time. Retina images aren’t the problem, per se: I’ve been serving them for years… but I’ve been serving them to everyone, which is incredibly wasteful. By using srcset, I can deliver double-resolution images to screens that need it, and standard resolution to the rest, decreasing load time for the average user:

<img src="milky-way-cave-1x.jpg" srcset="milky-way-cave-1x.jpg 1x, milky-way-cave-2x.jpg 2x" alt="Two figures silhouetted in a cave mouth against a starry sky">

I decided to tackle the easiest part of the problem – the article thumbnails – first. These images are used everywhere: on the archive page, in the features bar, and on social networks. By default, they are 500px × 500px, but occupy several different roles and sizes.

After a little research, I determined that I’d need four versions of every thumbnail image:

Image sizeDescriptionFilename
500px × 500pxThe largest Retina sizeimage-2x.jpg
250px × 250pxNormal sizeimage-1x.jpg
125px × 125pxIcon size, for Retinaimage-icon-2x.jpg
75px × 75pxStandard icon sizeimage-icon-1x.jpg

The 500px × 500px images already existed, and could be easily renamed using NameChanger. The problem was generating the other images, since the originals existed in different formats and at differing quality levels.

The Solution

Building the solution requires some basic JavaScript skill and a little knowledge of the PhotoShop API. You can create the script using the editor of your choice, or use the ExtendScript Toolkit that comes with the complete Adobe Creative Cloud package. The primary advantage of the latter is the ability to test your script live in the application and instantly see results.

The script to address my problem I developed as the following:

var inputFolder = Folder.selectDialog("Select a folder to process"),
outputFolder = Folder.selectDialog("Select a folder for the output files"),
imageSizes = [
    ["250px", "250px", "1x"],
    ["125px", "125px", "icon-2x"],
    ["75px", "75px", "icon-1x"]
numImageSizes = imageSizes.length;

if (inputFolder != null && outputFolder != null) {  
    var fileList = inputFolder.getFiles(/\.(jpg|jpeg|png|gif)$/i);
    for(var i=0; i<fileList.length; i++) {
        var doc = app.open( fileList[i] );
         for (var j = 0; j < imageSizes.length; j++) {
            var currentImageSize = imageSizes[j],
            currentImageWidth = currentImageSize[0],
            currentImageHeight = currentImageSize[1],
            currentImageVersion = currentImageSize[2],
            fullname = doc.name,
            filename = fullname.substr(0, fullname.lastIndexOf(".")) || fullname,
            extension = fullname.split(".").pop(),
            exportOptionsSaveForWeb = new ExportOptionsSaveForWeb();
            doc.resizeImage(currentImageWidth, currentImageHeight);
            exportOptionsSaveForWeb.includeProfile = true;
            exportOptionsSaveForWeb.optimized = true;
                if (extension == "jpg" || extension == "jpeg") {
                    exportOptionsSaveForWeb.format = SaveDocumentType.JPEG;
                    exportOptionsSaveForWeb.includeProfile = true;
                    exportOptionsSaveForWeb.quality = 25;
                 if (extension == "png") {
                    exportOptionsSaveForWeb.format = SaveDocumentType.PNG;
                 if (extension == "gif") {
                    exportOptionsSaveForWeb.format = SaveDocumentType.GIF;
                    var documentPath = decodeURI(outputFolder) + "/" + filename + "-" + currentImageVersion + "." + extension,
                    file = new File(documentPath);
                    doc.exportDocument (file, ExportType.SAVEFORWEB, exportOptionsSaveForWeb);

I’ve also left the code as a Gist to pick up and experiment with. A quick explanation:

  • when run, the script will automatically prompt you for an input folder (i.e. the location of the source images) and an output folder. I would recommend keeping the two separate.
  • imageSizes contains an array of the output image sizes, together with the appropriate appends to their filename.
  • There are two for loops: one loop that goes through the total array of all images found, and inside of that, a loop going through each option contained in the imageSizes array.
  • the script will determine the image type from its file extension and save versions at appropriate sizes, together with the correct extension. JPEGs will be saved at 25% quality by default. A color profile will be included with each image.

Once it’s ready to go, save the file as batch-resize.jsx (note the extension) and turn to PhotoShop. Load the script using the the File / Scripts / Browse menu option. (If you’ve used the ExtendScript Toolkit application, you’ll have been prompted to save the script inside an Adobe Scripts folder in your Documents folder.)

Running through the 900 thumbnails took PhotoShop about five minutes; at the end of the process, I had a set of several thousand “Retinafied” images ready to be integrated into the new codebase of the site.

A few concluding points and tips:

  1. As a rule, always work using images at their largest possible size and highest quality, using them to generate smaller images: graphics editors tend to produce better results with “thrown away” excess pixels than having to “make them up”.
  2. The script did choke on two thumbnail images in the collection, I assume from corrupted encoding in the image data; do be careful with correct formatting.

As always, you are very welcome to leave any questions or feedback in the comments section below!

Photograph by Rachel Patterson, used under a Creative Commons Attribution-NonCommercial-NoDerivs 2.0 Generic license

Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.