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
How-to Video
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 ignored.
The script interface only appears when the result of scaling the layer does not fill the height of the guides area. In other words, 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, middle, or bottom. If the scaled height reaches the guides before the width, there isn't a question of alignment because the script always centers the selection left to right.
The default vertical alignment is middle. If you prefer top or bottom instead, the script is open source you may alter in a text editor. Near the top is the variable vAlign. Set to 0 for top, 1 for middle, or 2 for bottom.
To use in an Action
Included in the download is a “Blind” version of the script, meaning it does not present an interface even when a wide subject is encountered. Edit the script code to select the preferred default for vertical alignment, which is then always the choice. This allows including the blind version of the script within an Action that performs other steps and can be used in standard Photoshop batch processing. The blind version does not stop to prompt the user unless an error occurs.
Source code
(download button below)
/*
Fit To Guides
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 vAlign;
// 0 = top
// 1 = middle
// 2 = bottom
vAlign = 1;
if (!/photoshop/i.test(app.name)) {
alert("Script for Photoshop", " ", false);
return;
}
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 maskEnabledRaster;
var maskEnabledVector;
var p;
var rbAlign;
var scale;
var scaleX;
var scaleY;
var translateX;
var translateY;
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.
rbAlign = [];
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";
rbAlign[0] = g.add("radiobutton", undefined, "Top");
rbAlign[1] = g.add("radiobutton", undefined, "Middle");
rbAlign[2] = g.add("radiobutton", undefined, "Bottom");
g = w.add("group");
g.alignment = "center";
btnOk = g.add("button", undefined, "OK");
btnCancel = g.add("button", undefined, "Cancel");
rbAlign[vAlign].value = true;
btnOk.onClick = function () {
if (rbAlign[0].value) {
w.close(0);
} else if (rbAlign[1].value) {
w.close(1);
} else if (rbAlign[2].value) {
w.close(2);
} else {
w.close(3);
}
};
btnCancel.onClick = function () {
w.close(3);
};
vAlign = w.show();
if (vAlign == 3) {
// User canceled.
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;
var list1;
var ref1;
desc1 = new ActionDescriptor();
ref1 = new ActionReference();
ref1.putName(charIDToTypeID("Lyr "), layer.name);
desc1.putReference(charIDToTypeID("null"), ref1);
desc1.putBoolean(charIDToTypeID("MkVs"), false);
list1 = new ActionList();
list1.putInteger(4);
desc1.putList(charIDToTypeID("LyrI"), list1);
executeAction(charIDToTypeID("slct"), desc1, DialogModes.NO);
})();
(function () {
// Is the layer masked?
var desc1;
var desc2;
var layerDesc;
var ref1;
ref1 = new ActionReference();
ref1.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
layerDesc = executeActionGet(ref1);
if (layerDesc.getBoolean(stringIDToTypeID("hasUserMask"))) {
// Layer has raster mask.
maskEnabledRaster = layerDesc.getBoolean(stringIDToTypeID("userMaskEnabled"));
if (maskEnabledRaster) {
// Disable raster mask.
desc1 = new ActionDescriptor();
ref1 = new ActionReference();
ref1.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc1.putReference(charIDToTypeID("null"), ref1);
desc2 = new ActionDescriptor();
desc2.putBoolean(charIDToTypeID("UsrM"), false);
desc1.putObject(charIDToTypeID("T "), charIDToTypeID("Lyr "), desc2);
executeAction(charIDToTypeID("setd"), desc1, DialogModes.NO);
}
} else if (layerDesc.getBoolean(stringIDToTypeID("hasVectorMask"))) {
// Layer has vector mask.
maskEnabledVector = layerDesc.getBoolean(stringIDToTypeID("vectorMaskEnabled"));
if (maskEnabledVector) {
// Disable vector mask.
desc1 = new ActionDescriptor();
ref1 = new ActionReference();
ref1.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc1.putReference(charIDToTypeID("null"), ref1);
desc2 = new ActionDescriptor();
desc2.putBoolean(stringIDToTypeID("vectorMaskEnabled"), 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 0: // Align top
translateY = Number(b2[1]) - Number(b1[1]);
break;
case 2: // Align bottom
translateY = Number(b2[3]) - Number(b1[3]);
break;
default: // Align middle
translateY = c2[1] - c1[1];
break;
}
layer.translate(translateX, translateY);
(function () {
var desc1;
var desc2;
var ref1;
// If layer mask was enabled, re-enable it.
if (maskEnabledRaster) {
// Enable raster mask.
desc1 = new ActionDescriptor();
ref1 = new ActionReference();
ref1.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc1.putReference(charIDToTypeID("null"), ref1);
desc2 = new ActionDescriptor();
desc2.putBoolean(charIDToTypeID("UsrM"), true);
desc1.putObject(charIDToTypeID("T "), charIDToTypeID("Lyr "), desc2);
executeAction(charIDToTypeID("setd"), desc1, DialogModes.NO);
} else if (maskEnabledVector) {
// Enable vector mask.
desc1 = new ActionDescriptor();
ref1 = new ActionReference();
ref1.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
desc1.putReference(charIDToTypeID("null"), ref1);
desc2 = new ActionDescriptor();
desc2.putBoolean(stringIDToTypeID("vectorMaskEnabled"), 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;
}
})();
Fit To Guides
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.