Created
April 23, 2013 16:52
-
-
Save yorb/5445360 to your computer and use it in GitHub Desktop.
A Photoshop script to snap all path points on selected shape layers to nearest pixels.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Based on Adobe Community Forums member BlipAdobe's script: | |
// http://forums.adobe.com/message/3908198#3908198 | |
#target photoshop | |
// Constants | |
var QUANTIZE_PIXELS = 1; // The number of whole pixels we wish the path points to be quantized to | |
var PIXEL_RATIO = app.activeDocument.resolution / 72; // Standardize pixels and points | |
// Some helpers | |
function cTID(s) { return charIDToTypeID(s); }; | |
function sTID(s) { return stringIDToTypeID(s); }; | |
app.bringToFront(); | |
app.activeDocument.suspendHistory("Snap To Pixel", "main();"); | |
// Quantizes all points in the active layers' vector masks to the value specified by QUANTIZE_PIXELS. | |
function main() | |
{ | |
// Work in pixels | |
var OrigRulerUnits = app.preferences.rulerUnits; | |
var OrigTypeUnits = app.preferences.typeUnits; | |
app.preferences.rulerUnits = Units.PIXELS; | |
app.preferences.typeUnits = TypeUnits.PIXELS; | |
// Obtain the action manager indices of all the selected layers | |
var SelIndices = GetSelectedLayersIdx(); | |
if (SelIndices.length == 1) | |
{ | |
// Only a single layer is selected | |
QuantizeVectorMaskForActiveLayer(QUANTIZE_PIXELS); | |
} | |
else | |
{ | |
// More than one layer is selected | |
for (var i = 0; i < SelIndices.length; ++i) | |
{ | |
if (MakeActiveByIndex(SelIndices[i], false) != -1) | |
{ | |
QuantizeVectorMaskForActiveLayer(QUANTIZE_PIXELS); | |
} | |
} | |
} | |
// Restore original ruler units | |
app.preferences.rulerUnits = OrigRulerUnits; | |
app.preferences.typeUnits = OrigTypeUnits; | |
} | |
function QuantizeVectorMaskForActiveLayer(zQuantizeUnits) | |
{ | |
var doc = app.activeDocument; | |
var PathItems = doc.pathItems; | |
// Nothing to do if the active layer has no path | |
if (PathItems.length == 0) | |
return; | |
var QuantizedPathInfo = null; | |
for (var i = 0; i < PathItems.length; ++i) | |
{ | |
var Path = PathItems[i]; | |
// It would appear that the path for the selected layer is of kind VECTORMASK. If so, when does WORKPATH come into play? | |
if (Path.kind == PathKind.VECTORMASK) | |
{ | |
// Build a path info structure by copying the properties of this path, quantizing all anchor and left/right points | |
var QuantizedPathInfo = BuildPathInfoFromPath(Path, zQuantizeUnits); | |
if (QuantizedPathInfo.length > 0) | |
{ | |
// We've now finished with the original path so it can go | |
Path.remove(); | |
// Add our new path to the doc | |
var TempQuantizedPath = doc.pathItems.add("TempQuantizedPath", QuantizedPathInfo); | |
// Convert the new path to a vector mask on the active layer | |
PathtoVectorMask(); | |
// Finished with the path | |
TempQuantizedPath.remove(); | |
} | |
} | |
} | |
} | |
// Copies the specified path to a new path info structure, which is then returned. The function will | |
// optionally quantize all points to the specified number of whole units. Specify zQuantizeUnits = 0 | |
// if you do not wish to quantize the points. | |
function BuildPathInfoFromPath(zInputPath, zQuantizeUnits) | |
{ | |
// The output path will be an array of SubPathInfo objects | |
var OutputPathInfo = []; | |
// For each subpath in the input path | |
for (var SubPathIndex = 0; SubPathIndex < zInputPath.subPathItems.length; ++SubPathIndex) | |
{ | |
var InputSubPath = zInputPath.subPathItems[SubPathIndex]; | |
var OutputPointInfo = []; | |
// For each point in the input subpath | |
var NumPoints = InputSubPath.pathPoints.length; | |
for (var PointIndex = 0; PointIndex < NumPoints; ++PointIndex) | |
{ | |
var InputPoint = InputSubPath.pathPoints[PointIndex]; | |
var InputAnchor = InputPoint.anchor; | |
var InputAnchorQ = QuantizePoint(InputAnchor, zQuantizeUnits); | |
// Copy all the input point's properties to the output point info | |
OutputPointInfo[PointIndex] = new PathPointInfo(); | |
OutputPointInfo[PointIndex].kind = InputPoint.kind; | |
OutputPointInfo[PointIndex].anchor = QuantizePoint(InputPoint.anchor, zQuantizeUnits); | |
OutputPointInfo[PointIndex].leftDirection = QuantizePoint(InputPoint.leftDirection, zQuantizeUnits); | |
OutputPointInfo[PointIndex].rightDirection = QuantizePoint(InputPoint.rightDirection, zQuantizeUnits); | |
} | |
// Create the SubPathInfo for our output path, and copy properties from the input sub path | |
OutputPathInfo[SubPathIndex] = new SubPathInfo(); | |
OutputPathInfo[SubPathIndex].closed = InputSubPath.closed; | |
OutputPathInfo[SubPathIndex].operation = InputSubPath.operation; | |
OutputPathInfo[SubPathIndex].entireSubPath = OutputPointInfo; | |
} | |
return OutputPathInfo; | |
} | |
// Quantizes the specified point to the specified number of whole units | |
function QuantizePoint(zPoint, zQuantizeUnits) | |
{ | |
// Check for divide by zero (if zQuantizeUnits == 0 we don't quantize) | |
if (zQuantizeUnits == 0) | |
return [ zPoint[0]/PIXEL_RATIO, zPoint[1]/PIXEL_RATIO ]; | |
else | |
return [ Math.round(zPoint[0] / zQuantizeUnits)/PIXEL_RATIO * zQuantizeUnits, Math.round(zPoint[1] / zQuantizeUnits)/PIXEL_RATIO * zQuantizeUnits ]; | |
} | |
// Converts the current working path to a vector mask on the active layer. This function will fail | |
// if the active layer already has a vector mask, so you should remove it beforehand. | |
function PathtoVectorMask() | |
{ | |
var desc11 = new ActionDescriptor(); | |
var ref8 = new ActionReference(); | |
ref8.putClass( cTID("Path") ); | |
desc11.putReference( cTID("null"), ref8); | |
var ref9 = new ActionReference(); | |
ref9.putEnumerated(cTID("Path"), cTID("Path"), sTID("vectorMask")); | |
desc11.putReference(cTID("At "), ref9); | |
var ref10 = new ActionReference(); | |
ref10.putEnumerated(cTID("Path"), cTID("Ordn"), cTID("Trgt")); | |
desc11.putReference(cTID("Usng"), ref10); | |
executeAction(cTID("Mk "), desc11, DialogModes.NO ); | |
} | |
// Make a layer active by its action manager index | |
function MakeActiveByIndex(zIndex, zVisible) | |
{ | |
var desc = new ActionDescriptor(); | |
var ref = new ActionReference(); | |
ref.putIndex(cTID("Lyr "), zIndex) | |
desc.putReference(cTID( "null"), ref ); | |
desc.putBoolean(cTID( "MkVs"), zVisible ); | |
executeAction(cTID( "slct"), desc, DialogModes.NO ); | |
} | |
// Returns the AM index of the selected layers | |
function GetSelectedLayersIdx() | |
{ | |
var selectedLayers = new Array; | |
var ref = new ActionReference(); | |
ref.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Trgt') ); | |
var desc = executeActionGet(ref); | |
if (desc.hasKey(sTID('targetLayers'))) | |
{ | |
desc = desc.getList(sTID('targetLayers')); | |
var c = desc.count | |
var selectedLayers = new Array(); | |
for(var i = 0; i < c; ++i) | |
{ | |
selectedLayers.push(desc.getReference(i).getIndex()); | |
} | |
} | |
else | |
{ | |
var ref = new ActionReference(); | |
ref.putProperty(cTID('Prpr'), cTID('ItmI')); | |
ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt')); | |
selectedLayers.push(executeActionGet(ref).getInteger(cTID('ItmI'))); | |
} | |
return selectedLayers; | |
} |
I'm not sure why this keeps shifting the selection by one index - but here's the general idea:
// Select layers by their action manager indices
function SelectByIndices(indices)
{
var ref = new ActionReference();
var c = indices.length;
for(var i = 0; i < c; ++i)
{
ref.putIdentifier(cTID('Lyr '), indices[i]);
}
var desc = new ActionDescriptor();
desc.putReference(cTID('null'), ref);
desc.putBoolean(cTID('MkVs'), false);
executeAction(cTID('slct'), desc, DialogModes.NO);
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When multiple layers are selected, it would make sense to re-select the same layers before exiting the script.