// (c) Copyright 2011. Adobe Systems, Incorporated. All rights reserved. // // Straighten.jsx - Straighten and crop a photo, based on the ruler position. // Added prototype Layer transform via shift modifier-steveg, 2011 // // John Peterson, Adobe Systems, 2009 // /* @@@BUILDINFO@@@ Straighten.jsx 3.0.0.4 */ /* // BEGIN__HARVEST_EXCEPTION_ZSTRING $$$/JavaScripts/Straighten/Menu=Straighten to Ruler automate // END__HARVEST_EXCEPTION_ZSTRING */ // on localized builds we pull the $$$/Strings from a .dat file $.localize = true; var g_StackScriptFolderPath = app.path + "/"+ localize("$$$/ScriptingSupport/InstalledScripts=Presets/Scripts") + "/" + localize("$$$/private/Exposuremerge/StackScriptOnly=Stack Scripts Only/"); $.evalFile( g_StackScriptFolderPath + "Geometry.jsx"); $.evalFile(g_StackScriptFolderPath + "Terminology.jsx"); // Create a base object to scope the rest of the functions in the file function Straightener() { this.pluginName = "Straighten"; } straighten = new Straightener(); straighten.getUnitPoint = function( desc ) { var x = desc.getUnitDoubleValue( kxStr ); var y = desc.getUnitDoubleValue( kyStr ); return new TPoint( x, y ); } // Special version of crop supporting the hidePixels option (DOM FIX) straighten.hideCrop = function( cropRect, hidePixels ) { if (typeof hidePixels == "undefined") hidePixels = true; // Hiding pixels does not work on simple documents if ((app.activeDocument.layers.length == 1) && (app.activeDocument.activeLayer.isBackgroundLayer)) hidePixels = false; var desc = new ActionDescriptor(); var cropDesc = new ActionDescriptor(); cropDesc.putUnitDouble( enumTop, unitPixels, cropRect.fTop ); cropDesc.putUnitDouble( enumLeft, unitPixels, cropRect.fLeft ); cropDesc.putUnitDouble( keyBottom, unitPixels, cropRect.fBottom ); cropDesc.putUnitDouble( enumRight, unitPixels, cropRect.fRight ); desc.putObject( keyTo, classRectangle, cropDesc ); desc.putUnitDouble( enumAngle, unitAngle, 0.0 ); if (hidePixels) desc.putBoolean( eventHide, true ); executeAction( eventCrop, desc, DialogModes.NO ); } // Remove the ruler and update the display (DOM FIX) straighten.clearRuler = function() { desc = new ActionDescriptor(); var eventClearRuler = app.stringIDToTypeID("clearRuler"); executeAction( eventClearRuler, desc, DialogModes.NO ); } // Get the endpoints from the ruler (DOM FIX) straighten.getRulerEndpoints = function() { var desc1 = new ActionDescriptor(); var ref1 = new ActionReference(); ref1.putProperty( classProperty, krulerPointsStr ); ref1.putEnumerated( classDocument, typeOrdinal, enumTarget ); desc1.putReference( typeNULL, ref1 ); var result = executeAction( eventGet, desc1, DialogModes.NO ); if (result.hasKey( kpointsStr )) { var i, ptList = result.getList( kpointsStr ); // The middle item in the list is an unused "midpoint" (currently == p0) var p0 = this.getUnitPoint( ptList.getObjectValue(0) ); var p1 = this.getUnitPoint( ptList.getObjectValue(2) ); if (p0.fX < p1.fX) return [p0, p1]; else return [p1, p0]; } else return []; } // Given two ruler endpoints, compute the rotation (in radians) // needed to make the ruler horizontal or vertical. straighten.getRotationAngle = function( p0, p1 ) { // Perfectly horizontal or vertical - no rotation if ((p0.fX == p1.fX) || (p0.fY == p1.fY)) return 0.0; var a, t, v = p1 - p0; if (Math.abs(v.fY) > Math.abs(v.fX)) { // If the line is mostly vertical, adjust the angle to // straighten to the vertical axis. t = v.fX; v.fX = v.fY; v.fY = t; if (v.fX < 0) { v.fX = - v.fX; a = v.vectorAngle(); } else a = -v.vectorAngle(); } else a = v.vectorAngle(); return -a; } // Given an angle and a rectangle, compute the rotated // rectangle and a new bounding rectangle for it. straighten.computeRotation = function( angle, rect ) { // Rotate rect about the origin, and find the bounds of the rotated rectangle. rect.setCenter( TPoint.kOrigin ); var i; this.fRotatedPoints = rect.getCornerPoints(); this.fRotatedBounds = new TRect(0,0,0,0); for (i in this.fRotatedPoints) { this.fRotatedPoints[i] = this.fRotatedPoints[i].rotate(angle); this.fRotatedBounds.extendTo( this.fRotatedPoints[i] ); } // Move the origin back to top left corner of the enclosing rect, // the new center is based on that. this.fRotatedBounds.offset( -this.fRotatedBounds.getTopLeft() ); this.fNewCenter = this.fRotatedBounds.getCenter(); for (i in this.fRotatedPoints) this.fRotatedPoints[i] += this.fNewCenter; } // Given an rectange and a rotation angle, find a new rectangle with the // same proportions that fits inside the original. straighten.getCropRect = function( angle, rect ) { this.computeRotation( angle, rect ); rect.setCenter( this.fNewCenter ); var cropPts = []; // So sign test works below if (angle < -Math.PI) angle += 2*Math.PI; var off1 = (angle > 0) ? 3 : 0; var off2 = (angle > 0) ? 0 : 1; // Draw lines from the center through the corners of the original rect. // Record where those lines intersect the rotated rect. var ii, rectPoints = [rect.getTopLeft(), rect.getTopRight(), rect.getBottomRight(), rect.getBottomLeft()]; for (ii in rectPoints) { i = Number(ii); // Whacky Javascript uses strings, not numbers, for index cropPts[i] = TPoint.lineSegmentIntersect( this.fRotatedPoints[(i + off1) % 4], this.fRotatedPoints[(i + off2) % 4], this.fNewCenter, rectPoints[i] ); } // The new crop is based on the minimum bounds of the intersections found above. var cropRect = new TRect( Math.max(cropPts[0].fX, cropPts[3].fX), Math.max(cropPts[0].fY, cropPts[1].fY), Math.min(cropPts[1].fX, cropPts[2].fX), Math.min(cropPts[2].fY, cropPts[3].fY) ); return cropRect; } straighten.getRulerAngle = function() { var rulerPts = this.getRulerEndpoints(); if (rulerPts.length == 0) return 0.0; return this.getRotationAngle( rulerPts[0], rulerPts[1] ); } // Straighten the layer to the ruler with transform. Returns the rotation angle, // or zero if there was no rotation (or no ruler). straighten.rotateLayerToRuler = function() { var angle = this.getRulerAngle(); if (angle == 0.0) return; if(!app.activeDocument.activeLayer.visible){ alert(localize("$$$/Straighten/StraightenHiddenLayer=Could not straighten the layer because the target layer is hidden.")); return; } layerRef = app.activeDocument.activeLayer; layerRef.rotate( angle * 180.0 / Math.PI ); return angle; } // Straighten the image to the ruler. Returns the rotation angle, // or zero if there was no rotation (or no ruler). straighten.rotateCanvasToRuler = function() { var angle = this.getRulerAngle(); app.activeDocument.rotateCanvas( angle * 180.0 / Math.PI ); return angle; } // Crop the image to the bounds, rotated by angle (in radians) straighten.cropRotatedImage = function( angle, bounds ) { var newBounds = this.getCropRect( angle, bounds ); // Ideally we'd report an error here, but since it's past RC, we'll just silently fail. if (newBounds.isValid()) this.hideCrop( newBounds, true ); // app.activeDocument.crop( [UnitValue(newBounds.fLeft, 'px'), UnitValue(newBounds.fTop, 'px'), // UnitValue(newBounds.fRight, 'px'), UnitValue(newBounds.fBottom, 'px')] ); } // Original functionality, now not used. straighten.rotateAndCropCanvas = function () { var angle, bounds = new TRect( 0, 0, app.activeDocument.width.as('px'), app.activeDocument.height.as('px') ); angle = straighten.rotateCanvasToRuler(); straighten.cropRotatedImage( angle, bounds ); } // Main interactive entry. straighten.doInteractive = function() { var angle, bounds = new TRect( 0, 0, app.activeDocument.width.as('px'), app.activeDocument.height.as('px') ); if (app.activeDocument.activeLayer.isBackgroundLayer == true) app.activeDocument.activeLayer.isBackgroundLayer = false; angle = straighten.rotateLayerToRuler() // alert(localize("$$$/Straighten/Script/NoBGLayer=Operation cannot be performed on background layer")); if (angle != 0.0) straighten.clearRuler(); } // Set runstraightenFromScript if you want to load this script w/o running it. if ((typeof(runstraightenFromScript) == 'undefined') || (runstraightenFromScript==false)) straighten.doInteractive();