Search code examples
javascriptphotoshopphotoshop-script

Photoshop scripting crop away x pixels from every side of document


I have a lot of images like this one which need to be processed as a batch:

unprocessed image

What I'm trying to accomplish is to crop away the border on every side, but here's the catch: trimming doesn't work because the color in every corner of the image is not black but white, so instead I'm trying to crop away about 5 pixels on every side. I've written some code to do just that but there seems to be an error somewhere resulting in funny, mirrored and wrongly cropped images:

function cropLayerToBounds(){
var layer = activeDocument.activeLayer; //Grab the currently selected layer

// get height and width
var actualHeight = layer.bounds[2]-layer.bounds[0]; //Grab the height
var actualWidth = layer.bounds[3]-layer.bounds[1]; //Grab the width

// calculate desired height and width
var desiredHeight = actualHeight - 5;
var desiredWidth = actualWidth - 5;

// not sure if necessary
    var doc = app.activeDocument;
    var halfWidth = (desiredWidth/2);
    var halfHeight = (desiredHeight/2);
    var centerX = (doc.width/2);
    var centerY = (doc.height/2);

// error seems to be here

   // tried this
   var bounds = [0,0,desiredHeight,desiredWidth];

   // and this
   var bounds = [(centerX-halfWidth),(centerY-halfHeight),(centerX+halfWidth),(centerY+halfHeight)];

    doc.crop(bounds);


}

The images i process this way look somewhat like this:

processed image


Solution

  • The following script utilizes a custom function named cropCanvas that will meet your requirement.

    It's not necessary to access the activeDocument.activeLayer, as cropping affects the whole document (i.e. canvas).

    Example gist:

    var document = app.activeDocument; // Assumes a document is active.
    
    // Obtain original ruler units prefs.
    var originalRulerUnits = app.preferences.rulerUnits;
    
    // Set the ruler units prefs to pixels.
    app.preferences.rulerUnits = Units.PIXELS;
    
    /**
     * Crops the canvas by x number of pixels equally from all sides.
     * @param {Number} [amountOfPixels=0] - Number of pixels to crop.
     */
    function cropCanvas(amountOfPixels) {
        amountOfPixels = amountOfPixels || 0; // Defaults to zero.
    
        // Obtain height and width of canvas.
        var canvasWidth = document.width.value;
        var canvasHeight = document.height.value;
    
        // Define the new bounds.
        var newBounds = [
            amountOfPixels,
            amountOfPixels,
            canvasWidth - amountOfPixels,
            canvasHeight - amountOfPixels
        ];
    
        // Crop the canvas. 
        document.crop(newBounds);
    }
    
    // Invoke the `cropCanvas` function passing
    // in the `amountOfPixels` value.
    cropCanvas(5);
    
    // Reset ruler prefs.
    app.preferences.rulerUnits = originalRulerUnits;
    

    Notes:

    1. The script initially obtains Photoshop's current ruler units via app.preferences.rulerUnits; and assigns it to the originalRulerUnits variable, before setting the ruler units to pixels.

    2. The cropCanvas(...) function has one parameter, namely amountOfPixels. This enables the function to be invoked with one argument specifying the amount of pixels to crop the image by. For example:

      cropCanvas(5); // Crops 5 x pixels from all sides.
      
      cropCanvas(25); // Crops 25 x pixels from all sides.
      
    3. Finally, Photoshop's ruler units are reset to its original unit.


    Additional considerations

    By not setting app.preferences.rulerUnits = Units.PIXELS; can result in incorrect cropping as per the example shown in your question. However, Photoshop's ruler origin can also produce unexpected results too when it's coordinates are not set to (0,0).

    To illustrate this potential issue carry out the following:

    1. Open an image PhotoShop.
    2. Show the rulers by typing Command+R(macOS), or Control+R(Windows)
    3. Change the ruler’s zero origin by positioning the pointer/cursor over the intersection of the rulers in the upper-left corner of the window, and drag diagonally down onto the image. A set of cross hairs will appear, marking the new origin for the rulers. Further explained here
    4. Then run the example gist provided above.

    As you can see, when the ruler's zero origin is not set to zero, undesired cropping can occur.

    The ruler origin can be reset to the default value, (i.e. 0,0), by double-clicking the upper-left corner of the rulers.

    Important: Now, this should not be too much of an issue for you as you mention in your question "...need to be processed as a batch.", which suggests your final script will be opening images one-by-one programmatically (i.e. automated). When images are opened the ruler origin defaults to 0,0 so no undesired cropping should occur.

    However, in a scenario whereby a feature of your script is to allow it to be run on an image which a user has opened and may have defined a new ruler origin. Then your script will also need to reset the ruler origin to zero (i.e. 0,0).

    Example gist for handling non-zero ruler origin:

    var document = app.activeDocument; // Assumes a document is active.
    
    // Obtain original ruler units prefs.
    var originalRulerUnits = app.preferences.rulerUnits;
    
    // Set the ruler units prefs to pixels.
    app.preferences.rulerUnits = Units.PIXELS;
    
    /**
     * Photoshop API doesn't provide a method to reset the ruler origin to [0, 0].
     * This get the cuurent ruler origin so we can offset the value.
     * @returns {Object} with properties `x` and `y` offset for rulers.
     */
    function getRulerOffset() {
        var ref = new ActionReference();
    
        ref.putEnumerated(
            charIDToTypeID("Dcmn"),
            charIDToTypeID("Ordn"),
            charIDToTypeID("Trgt")
        );
        var desc = executeActionGet(ref);
    
        var rulerPositionX = desc.getInteger(stringIDToTypeID('rulerOriginH')) / 65536;
        var rulerPositionY = desc.getInteger(stringIDToTypeID('rulerOriginV')) / 65536;
    
        return {
            x : rulerPositionX,
            y : rulerPositionY
        }
    }
    
    /**
     * Crops the canvas by x number of pixels equally from all sides.
     * @param {Number} [amountOfPixels=0] - Number of pixels to crop.
     */
    function cropCanvas(amountOfPixels) {
        amountOfPixels = amountOfPixels || 0; // Defaults to zero.
    
        // Obtain height and width of canvas.
        var canvasWidth = document.width.value;
        var canvasHeight = document.height.value;
    
        // Obtain current ruler x and y offset.
        var rulerOffsetX = getRulerOffset().x;
        var rulerOffsetY = getRulerOffset().y;
    
        // Define the new bounds.
        var newBounds = [
            amountOfPixels - rulerOffsetX,
            amountOfPixels - rulerOffsetY,
            canvasWidth - amountOfPixels - rulerOffsetX,
            canvasHeight - amountOfPixels - rulerOffsetY
        ];
    
        // Crop the canvas. 
        document.crop(newBounds);
    }
    
    // Invoke the `cropCanvas` function passing
    // in the `amountOfPixels` value.
    cropCanvas(5);
    
    // Reset ruler prefs.
    app.preferences.rulerUnits = originalRulerUnits;
    

    The revised script (above) now includes in addition to the previous gist:

    1. A new function, namely getRulerOffset, that returns the current x and y coordinates of the ruler origin. Unfortunately, Photoshop does not provide an API which can be called to reset the ruler origin so we have to obtain the current values instead.

    2. In the cropCanvas function we now additionally create two new variables (rulerOffsetX and rulerOffsetY), and their values are subtracted when defining the newBounds variable.


    Another solution utilizing the resizeCanvas() method

    Another way to meet your requirement is to utilize the resizeCanvas() method instead of the crop() method. I actually prefer this approach because the position of the ruler origin, (as discussed in the "Additional considerations" section), has no affect.

    Here is a gist for that:

    var document = app.activeDocument;
    
    // Obtain original ruler units prefs.
    var originalRulerUnits = app.preferences.rulerUnits;
    
    // Set the ruler units prefs to pixels.
    app.preferences.rulerUnits = Units.PIXELS;
    
    /**
     * Resizes canvas removing x number of pixels equally from all sides.
     * @param {Number} [amountOfPixels=0] - Number of pixels to remove.
     */
    function removePixelsFromAllSides(amountOfPixels) {
        amountOfPixels = amountOfPixels || 0;
    
        document.resizeCanvas(
           document.width.value - (amountOfPixels * 2),
           document.height.value - (amountOfPixels * 2),
           AnchorPosition.MIDDLECENTER
        );
    }
    
    // Invoke the function passing in the `amountOfPixels` value.
    removePixelsFromAllSides(5);
    
    // Reset ruler prefs.
    app.preferences.rulerUnits = originalRulerUnits;