Fit To Guides

Script for Adobe Photoshop

Scale and move a layer to fit selected area inside guides.

  • Choice of vertical alignment when height is short
  • Adapt open source to customize or create other scripts
Fit To Guides screen
Download
Fit To Guides

You decide. Reward the author an
amount the solution is worth to you.

How to use the script

Add guides to the image, top, bottom, left, and right, then make or load a selection. Run the script, and the active layer is scaled and moved to fit inside the guides. If there isn't a selection, the script transforms the entire layer.

From top to bottom, and from left to right, the script reads all guides, no matter the number, and considers the highest and lowest to be top and bottom. Likewise, the guides most left and right are the side boundaries of the target area. Additional guides between the others are no problem, as they are ignored.

The script interface only appears when the result of scaling the layer does not fill the height of the guides area. Or to say another way, the scaled width reaches the guides before the height does. In this case, a window appears asking how to vertically align the scaled layer. Choices are top, center, or bottom. If the scaled height reaches the guides before the width, there isn't a question of alignment because the script always aligns left and right centered within the guides.

How does it work? Study the code below and find out. Feel free to alter so it fits your needs.

Source code

(download button below)

/*
Fit To Guides
Copyright 2021 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.
*/

//@target photoshop

(function () {
    var b1 = [];
    var b2 = [];
    var b3 = [];
    var btnCancel;
    var btnOk;
    var c1 = [];
    var c2 = [];
    var doc;
    var g;
    var guidesX = [];
    var guidesY = [];
    var i;
    var isBackgroundLayer;
    var isBackgroundVisible;
    var layer;
    var layerBottom;
    var layerOffset = [0, 0];
    var maskEnabled;
    var p;
    var rbBottom;
    var rbCenter;
    var rbTop;
    var scale;
    var scaleX;
    var scaleY;
    var translateX;
    var translateY;
    var vAlign = 2; // Default to center
    var w;
    if (!app.documents.length) {
        alert("Open a document", " ", false);
        return;
    }
    app.preferences.rulerUnits = Units.PIXELS;
    doc = app.activeDocument;
    // Create array of vertical guides, sorted from left to right.
    for (i = 0; i < doc.guides.length; i++) {
        if (doc.guides[i].direction === Direction.VERTICAL) {
            guidesX.push(parseInt(doc.guides[i].coordinate, 10));
        }
    }
    guidesX.sort(function (a, b) {
        return a - b;
    });
    // Create array of horizontal guides, sorted from top to bottom.
    for (i = 0; i < doc.guides.length; i++) {
        if (doc.guides[i].direction === Direction.HORIZONTAL) {
            guidesY.push(parseInt(doc.guides[i].coordinate, 10));
        }
    }
    guidesY.sort(function (a, b) {
        return a - b;
    });
    // Check for enough guides.
    if (guidesX.length < 2 || guidesY.length < 2) {
        alert("Not enough guides!", " ", true);
        return;
    }
    // Get bound of selection (or document if none).
    try {
        b1 = doc.selection.bounds;
    } catch (_) {
        // Probably nothing selected.
        // Use doc bounds.
        b1 = [0, 0, doc.width, doc.height];
    }
    // Get bounds of guides.
    b2 = [
        guidesX[0],
        guidesY[0],
        guidesX[guidesX.length - 1],
        guidesY[guidesY.length - 1]
    ];
    // Calculate scaling to fit selection into guides.
    scaleY = Number(b2[3] - b2[1]) / Number(b1[3] - b1[1]);
    scaleX = Number(b2[2] - b2[0]) / Number(b1[2] - b1[0]);
    if (scaleX < scaleY) {
        // Scaled width reaches guides.
        // Ask user to choose vertical alignment.
        w = new Window("dialog", "Fit To Guides");
        w.alignChildren = "fill";
        p = w.add("panel");
        p.add("statictext", undefined, "Vertical alignment:");
        g = p.add("group");
        g.orientation = "column";
        g.alignChildren = "left";
        rbTop = g.add("radiobutton", undefined, "Top");
        rbCenter = g.add("radiobutton", undefined, "Center");
        rbBottom = g.add("radiobutton", undefined, "Bottom");
        g = w.add("group");
        g.alignment = "center";
        btnOk = g.add("button", undefined, "OK");
        btnCancel = g.add("button", undefined, "Cancel");
        rbCenter.value = true;
        btnOk.onClick = function () {
            if (rbTop.value) {
                w.close(1);
            } else if (rbBottom.value) {
                w.close(3);
            } else {
                // Default center.
                w.close(2);
            }
        };
        btnCancel.onClick = function () {
            w.close(0);
        };
        vAlign = w.show();
        if (vAlign === 0) {
            // User cancelled.
            return;
        }
    }
    doc.selection.deselect();
    // Handle bottom layer.
    layerBottom = doc.layers[doc.layers.length - 1];
    // Preserve bottom layer properties.
    isBackgroundVisible = layerBottom.visible;
    isBackgroundLayer = layerBottom.isBackgroundLayer;
    // Make bottom layer not background and visible.
    if (!layerBottom.isBackgroundLayer) {
        layerBottom.visible = true;
    }
    layerBottom.isBackgroundLayer = false;
    // Use lowest of X and Y scale.
    scale = Math.min(scaleX, scaleY);
    // Scale bounds.
    b1[0] *= scale;
    b1[1] *= scale;
    b1[2] *= scale;
    b1[3] *= scale;
    // Get center of bounds.
    c1[0] = Number(b1[0]) + (Number(b1[2] - b1[0]) / 2);
    c1[1] = Number(b1[1]) + (Number(b1[3] - b1[1]) / 2);
    // Get center of guides.
    c2[0] = Number(b2[0]) + (Number(b2[2] - b2[0]) / 2);
    c2[1] = Number(b2[1]) + (Number(b2[3] - b2[1]) / 2);
    // Get active layer.
    layer = doc.activeLayer;
    // Script fails if no layer is selected.
    // But when none are selected, 'doc.activeLayer' returns the highest layer.
    // Select the active layer to ensure script succeeds.
    // If not the intended layer, result should be obvious.
    // User needs to select the correct layer.
    (function () {
        // Select active layer.
        var desc1 = new ActionDescriptor();
        var ref1 = new ActionReference();
        ref1.putName(charIDToTypeID('Lyr '), layer.name);
        desc1.putReference(charIDToTypeID('null'), ref1);
        desc1.putBoolean(charIDToTypeID('MkVs'), false);
        var list1 = new ActionList();
        list1.putInteger(4);
        desc1.putList(charIDToTypeID('LyrI'), list1);
        executeAction(charIDToTypeID('slct'), desc1, DialogModes.NO);
    })();
    (function () {
        // Is the layer masked?
        var ref1 = new ActionReference();
        ref1.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
        var layerDesc = executeActionGet(ref1);
        if (layerDesc.getBoolean(stringIDToTypeID("hasUserMask"))) {
            // Has layer mask.
            maskEnabled = (function () {
                // Is mask enabled?
                var ref = new ActionReference();
                ref.putProperty(charIDToTypeID('Prpr'), stringIDToTypeID("userMaskEnabled"));
                ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
                return executeActionGet(ref).getBoolean(stringIDToTypeID("userMaskEnabled"));
            })();
            if (maskEnabled) {
                (function () {
                    // Toggle layer mask enabled.
                    var desc1 = new ActionDescriptor();
                    var ref1 = new ActionReference();
                    ref1.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
                    desc1.putReference(charIDToTypeID('null'), ref1);
                    var desc2 = new ActionDescriptor();
                    desc2.putBoolean(charIDToTypeID('UsrM'), false);
                    desc1.putObject(charIDToTypeID('T   '), charIDToTypeID('Lyr '), desc2);
                    executeAction(charIDToTypeID('setd'), desc1, DialogModes.NO);
                })();
            }
        }
    })();
    // Get layer bounds.
    b3 = layer.bounds;
    if (b3[0] > 0 || b3[1] > 0) {
        // Layer does not reach doc bounds.
        // Store offset and translate to doc origin.
        layerOffset = [b3[0], b3[1]];
        try {
            layer.translate(-layerOffset[0], -layerOffset[1]);
        } catch (_) {
            alert("Cannot translate selected layer", " ", false);
            return;
        }
    }
    // Resize the layer.
    try {
        layer.resize(scale * 100, scale * 100, AnchorPosition.TOPLEFT);
    } catch (_) {
        alert("Cannot resize selected layer.", " ", false);
        return;
    }
    if (layerOffset[0] || layerOffset[1]) {
        // Add back offset scaled.
        layer.translate(layerOffset[0] * scale, layerOffset[1] * scale);
    }
    // Translate layer to put content inside guides.
    // X always center.
    translateX = c2[0] - c1[0];
    // Y based on vAlign value.
    switch (vAlign) {
        case 1: // Align top
            translateY = Number(b2[1]) - Number(b1[1]);
            break;
        case 3: // Align bottom
            translateY = Number(b2[3]) - Number(b1[3]);
            break;
        default: // Align center
            translateY = c2[1] - c1[1];
            break;
    }
    layer.translate(translateX, translateY);
    // If layer mask was enabled, re-enable it.
    if (maskEnabled) {
        (function () {
            // Toggle layer mask enabled.
            var desc1 = new ActionDescriptor();
            var ref1 = new ActionReference();
            ref1.putEnumerated(charIDToTypeID('Lyr '), charIDToTypeID('Ordn'), charIDToTypeID('Trgt'));
            desc1.putReference(charIDToTypeID('null'), ref1);
            var desc2 = new ActionDescriptor();
            desc2.putBoolean(charIDToTypeID('UsrM'), true);
            desc1.putObject(charIDToTypeID('T   '), charIDToTypeID('Lyr '), desc2);
            executeAction(charIDToTypeID('setd'), desc1, DialogModes.NO);
        })();
    }
    // Restore bottom layer properties.
    layerBottom.isBackgroundLayer = isBackgroundLayer;
    if (doc.layers.length > 1) {
        layerBottom.visible = isBackgroundVisible;
    }
})();
Download
Fit To Guides

License details included in download

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

IMPORTANT: by downloading the script you agree that the software is provided without any warranty, express or implied. USE AT YOUR OWN RISK. Always make backups of important data.