Mini Kabibi Habibi
// (c) Copyright 2005-2007 Adobe Systems, Incorporated. All rights reserved.
/*
@@@BUILDINFO@@@ StackSupport.jsx 1.0.0.1
*/
//
// Support routines for Stack based imaging scripts. These
// routines mostly fill in for holes in the Document Object Model,
// and ideally should be replaced with DOM equivalents someday.
//
// on localized builds we pull the $$$/Strings from a .dat file
$.localize = true;
if (typeof typeNULL == "undefined")
$.evalFile(g_StackScriptFolderPath + "Terminology.jsx");
// Handy debugging function helps you figure out what descriptor keywords are.
function numToOSType( number )
{
function decodeByte(num)
{
const hex = '0123456789ABCDEF';
num = num & 0xFF;
var hi = hex[(num >> 4)];
var lo = hex[num & 0xF];
return decodeURI( "%" + hi + lo );
}
return decodeByte(number >> 24) + decodeByte(number >> 16) + decodeByte(number >> 8) + decodeByte(number);
}
// Returns true if the active layer has a layer mask
function hasLayerMask()
{
ref = new ActionReference();
args = new ActionDescriptor();
ref.putProperty( classProperty, keyUserMaskEnabled );
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
args.putReference( keyTarget, ref );
var resultDesc = executeAction( eventGet, args, DialogModes.NO );
return resultDesc.hasKey( keyUserMaskEnabled );
}
// Create a layer mask in the active document. Leaves layer mask the active channel
function createLayerMask()
{
if (hasLayerMask()) return;
var desc = new ActionDescriptor();
var ref = new ActionReference();
desc.putClass( keyNew, typeChannel );
ref.putEnumerated( typeChannel, typeChannel, classMask );
desc.putReference( keyAt, ref );
desc.putEnumerated( keyUsing, keyUserMaskEnabled, enumRevealAll );
executeAction( eventMake, desc, DialogModes.NO );
}
// Apply the layer mask, deleting it in the process.
function applyLayerMask()
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( typeChannel, typeOrdinal, enumTarget );
desc.putReference( typeNULL, ref );
desc.putBoolean( keyApply, true );
executeAction( eventDelete, desc, DialogModes.NO );
}
// Returns the name of the layer mask of the active layer
// (or "Alpha" if none...in English at least...)
function layerMaskName()
{
ref = new ActionReference();
args = new ActionDescriptor();
ref.putProperty( classProperty, keyChannelName ); // keyChannelName
ref.putEnumerated( classChannel, typeOrdinal, enumMask );
args.putReference( keyTarget, ref );
var resultDesc = executeAction( eventGet, args, DialogModes.NO );
return resultDesc.getString( keyChannelName );
}
// This is the same as app.activeDocument.activeLayer.translate( dx, dy ),
// which seems to be broken at the moment.
// Potential DOM FIX
function translateActiveLayer( deltaX, deltaY )
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
desc.putReference( typeNULL, ref );
var coords = new ActionDescriptor();
coords.putUnitDouble( enumHorizontal, unitPixels, deltaX );
coords.putUnitDouble( keyVertical, unitPixels, deltaY );
desc.putObject( keyTo, keyOffset, coords );
executeAction( eventMove, desc, DialogModes.NO );
}
// Gradient Fill operator.
// Note this is hardwired to Darken mode (among other things)
// Potential DOM FIX
function gradientFillLayerMask( fromPoint, toPoint )
{
function pointDesc( pt )
{
var desc = new ActionDescriptor();
desc.putUnitDouble( keyHorizontal, unitPixels, pt.fX );
desc.putUnitDouble( keyVertical, unitPixels, pt.fY );
return desc;
}
function stopDesc( location, midPoint )
{
var desc = new ActionDescriptor();
desc.putInteger( keyLocation, location );
desc.putInteger( keyMidpoint, midPoint );
return desc;
}
function grayDesc( grayValue )
{
var desc = new ActionDescriptor();
desc.putDouble( enumGray, grayValue );
return desc;
}
var args = new ActionDescriptor();
args.putObject( keyFrom, classPoint, pointDesc( fromPoint ) );
args.putObject( keyTo, classPoint, pointDesc( toPoint ) );
args.putEnumerated( keyMode, typeBlendMode, enumDarken );
args.putEnumerated( keyType, typeGradientType, enumLinear );
args.putBoolean( keyDither, true );
args.putBoolean( keyUseMask, true );
args.putBoolean( keyReverse, true );
var gradDesc = new ActionDescriptor();
gradDesc.putString( keyName, "White, Black" );
gradDesc.putEnumerated( typeGradientForm, typeGradientForm, enumCustomStops );
gradDesc.putDouble( keyInterpolation, 4096.000000 );
var colorList = new ActionList();
var stop = stopDesc( 0, 50 );
// stop.putEnumerated( keyType, typeColorStopType, enumForegroundColor );
stop.putObject( classColor, classGrayscale, grayDesc( 100 ) );
stop.putEnumerated( keyType, typeColorStopType, enumUserStop );
colorList.putObject( classColorStop, stop );
stop = stopDesc( 4096, 50 );
// stop.putEnumerated( keyType, typeColorStopType, enumBackgroundColor );
stop.putObject( classColor, classGrayscale, grayDesc( 0 ) );
stop.putEnumerated( keyType, typeColorStopType, enumUserStop );
colorList.putObject( classColorStop, stop );
gradDesc.putList( typeColors, colorList );
var xferList = new ActionList();
var xfer = stopDesc( 0, 50 );
xfer.putUnitDouble( keyOpacity, unitPercent, 100.000000 );
xferList.putObject( keyTransferSpec, xfer );
xfer = stopDesc( 4096, 50 );
xfer.putUnitDouble( keyOpacity, unitPercent, 100.000000 );
xferList.putObject( keyTransferSpec, xfer );
gradDesc.putList( keyTransparency, xferList );
args.putObject( keyGradient, classGradient, gradDesc );
executeAction( eventGradient, args, DialogModes.NO );
}
// Create polygon selection. The polygon is an array of point arrays,
// e.g. [[20,30][40,30][40,60][20,30]]
// Potential DOM FIX
function createPolygonSelection( polygon, addTo )
{
var i;
var desc = new ActionDescriptor();
var ref = new ActionReference();
var listDesc = new ActionDescriptor();
var pointList = new ActionList();
addTo = (typeof addTo === "undefined") ? false : addTo;
ref.putProperty( typeChannel, charIDToTypeID('fsel') ); // What's fsel?
desc.putReference( typeNULL, ref );
for (i in polygon)
{
var pointDesc = new ActionDescriptor();
pointDesc.putUnitDouble( keyHorizontal, unitPixels, polygon[i].fX );
pointDesc.putUnitDouble( keyVertical, unitPixels, polygon[i].fY );
pointList.putObject( classPoint, pointDesc );
}
listDesc.putList( keyPoints, pointList );
desc.putObject( keyTo, classPolygon, listDesc );
desc.putBoolean( keyAntiAlias, true );
var event = addTo ? stringIDToTypeID("addTo") : eventSet;
executeAction( event, desc, DialogModes.NO );
}
// Content aware fill an existing selection.
// Potential DOM FIX
function contentAwareFillSelection()
{
var desc = new ActionDescriptor();
desc.putEnumerated( keyUsing, typeFillContents, kcontentAwareStr );
desc.putUnitDouble( keyOpacity, unitPercent, 100.000000 );
desc.putEnumerated( classMode, typeBlendMode, enumNormal );
executeAction( typeFill, desc, DialogModes.NO );
app.activeDocument.selection.deselect();
}
function duplicateDocument( newName )
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( classDocument, typeOrdinal, enumFirst );
desc.putReference( typeNULL, ref );
desc.putString( keyName, newName );
executeAction( eventDuplicate, desc, DialogModes.NO );
}
// Set the exposure of the frontmost document
// Potential DOM FIX
function setFrontmostExposure( exposure, gamma )
{
if (typeof(gamma) == "undefined")
gamma = 1.0;
args = new ActionDescriptor();
args.putInteger( classVersion, 3 );
args.putEnumerated( keyMethod, app.stringIDToTypeID( 'hdrToningMethodType' ),
app.stringIDToTypeID( 'hdrtype2' ) );
args.putDouble( keyExposure, exposure );
args.putDouble( keyGamma, gamma );
executeAction( app.stringIDToTypeID('32BitPreviewOptions'), args );
}
// Since you can't assign to doc.resolution
// Potential DOM FIX
function setFrontmostResolution( dpi )
{
var desc = new ActionDescriptor();
desc.putUnitDouble( keyResolution, unitDensity, dpi );
executeAction( eventImageSize, desc, DialogModes.NO );
}
// Forces the frontmost document to save in Photoshop (.psd) format.
function resetFrontmostDocumentFormat()
{
var desc = new ActionDescriptor();
executeAction( kresetDocumentFormatStr, desc, DialogModes.NO );
}
// The only way to test for a custom option is to try accessing
// it and catch the exception if it fails.
// Potential DOM FIX
function getPSCustomOption( optionSet, optionType, optionID, defaultValue )
{
var result = defaultValue;
try {
var desc = app.getCustomOptions( optionSet );
result = eval( "desc.get" + optionType + "(" + optionID + ")" );
}
catch (e)
{}
return result;
}
// Some actions, like closing a window, need to be processed by the PS App
// before other actions are taken. In particular, when a window is closed and
// "open as tabs" is off, then app.activeDocument is left unset until the app is
// able to process the activate event of the next window coming forward.
function WaitForPhotoshopRedraw()
{
var desc = new ActionDescriptor();
desc.putEnumerated( keyState, typeState, enumRedrawComplete );
executeAction( eventWait, desc, DialogModes.NO );
}
// Set the lightroom "save" parameters for the frontmost document
// Potential DOM FIX
function setLightroomFileParams( lightroomID, bridgetalkID, lightroomDesc )
{
function setParameter( ID, data, method )
{
if (typeof(data) == "undefined") // Check for empty parameter
return;
var ref = new ActionReference();
var desc = new ActionDescriptor();
ref.putProperty( classProperty, ID );
ref.putEnumerated( classDocument, typeOrdinal, enumFirst );
desc.putReference( keyTarget, ref );
var toDesc = new ActionDescriptor();
eval( method );
desc.putObject( keyTo, typeNULL, toDesc );
executeAction( eventSet, desc, DialogModes.NO );
}
setParameter( klightroomDocIDStr, lightroomID, "toDesc.putString( ID, data );" );
setParameter( klightroomBridgetalkIDStr, bridgetalkID, "toDesc.putString( ID, data );" );
if ((typeof(lightroomDesc) != "undefined") && (lightroomDesc != null))
setParameter( klightroomSaveParamsStr, lightroomDesc, "toDesc.putObject( ID, ksaveStr, data );" );
}
// Scale the view on the frontmost document so it fits on the screen
function fitViewOnScreen()
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( classMenuItem, typeMenuItem, enumFitOnScreen );
desc.putReference( typeNULL, ref );
executeAction( eventSelect, desc, DialogModes.NO );
}
// Undo the last operation
// Potential DOM FIX
function undoLastEvent()
{
executeAction( eventUndo, undefined, DialogModes.NO );
}
// Purge this history states for the active document
// Potential DOM FIX
function purgeHistoryStates()
{
var desc = new ActionDescriptor();
desc.putEnumerated( typeNULL, typePurgeItem, enumHistory );
executeAction( eventPurge, desc, DialogModes.NO );
}
// Get flags controlling whether or not confirmation dialogs
// appear when changing color profiles.
// "settings" is one of kaskMismatchOpeningStr, kaskMismatchPastingStr, kaskMissingStr
function getColorProfileDialogSetting( setting, typeMethod )
{
if (typeof( typeMethod ) == "undefined")
typeMethod = "Boolean";
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putProperty( classProperty, kcolorSettingsStr );
ref1.putEnumerated( classApplication, typeOrdinal, enumTarget );
desc1.putReference( typeNULL, ref1 );
var result = executeAction( eventGet, desc1, DialogModes.NO );
if (result.hasKey( kcolorSettingsStr ))
{
var desc2 = result.getObjectValue( kcolorSettingsStr );
if (desc2.hasKey( setting ))
return eval( "desc2.get" + typeMethod +"( setting )" );
}
return null;
}
// Set flags controlling whether or not confirmation dialogs
// appear when changing color profiles.
// "settings" is one of kaskMismatchOpeningStr, kaskMismatchPastingStr, kaskMissingStr
function setColorProfileDialogSetting( setting, value, typeMethod )
{
if (typeof( typeMethod ) == "undefined")
typeMethod = "Boolean";
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putProperty( classProperty, kcolorSettingsStr );
ref1.putEnumerated( classApplication, typeOrdinal, enumTarget );
desc1.putReference( typeNULL, ref1 );
var desc2 = new ActionDescriptor();
eval( "desc2.put" + typeMethod + "( setting, value )" );
desc1.putObject( keyTo, kcolorSettingsStr, desc2 );
executeAction( eventSet, desc1, DialogModes.NO );
}
// Helper functions to make accessing the XMP metadata simpler.
function getXMPTagFromXML( tag, xmlData, numeric )
{
var s, xmp = new XML(xmlData);
// Ugly special case
if (tag == "ISOSpeedRatings")
s = String(xmp.*::RDF.*::Description.*::ISOSpeedRatings.*::Seq.*::li);
else
s = String(eval("xmp.*::RDF.*::Description.*::" + tag));
if (s.length == 0)
return numeric ? 0.0 : ""
return numeric ? eval(s) : s;
}
function getXMPTag( tag, numeric )
{
return getXMPTagFromXML( tag, app.activeDocument.xmpMetadata.rawData, numeric )
}
// Get / set the "Prefer Adobe Camera Raw for JPEG Files" flag.
// This feature was added at the last minute, and thus left out of app.preferences
// Potential DOM FIX
function setUseCameraRawJPEGPreference( flag )
{
//temporary workaround for #1756346
//var saveDesc = new ActionDescriptor();
//var ref = new ActionReference();
//ref.putProperty( classProperty, keyFileSavePrefs );
//ref.putEnumerated( classApplication, typeOrdinal, enumTarget );
//saveDesc.putReference( typeNULL, ref );
//var rawDesc = new ActionDescriptor();
//rawDesc.putBoolean( kcameraRawJPEGStr, flag );
//saveDesc.putObject( keyTo, classFileSavePrefs, rawDesc );
//executeAction( eventSet, saveDesc, DialogModes.NO );
}
function getUseCameraRawJPEGPreference()
{
//temporary workaround for #1756346
//var ref = new ActionReference();
//ref.putProperty( classProperty, keyFileSavePrefs );
//ref.putEnumerated( classApplication, typeOrdinal, enumTarget );
//// Descriptor with the actual prefs is buried one deep...
//var result = app.executeActionGet( ref );
//var desc = result.getObjectValue( result.getKey(0) );
//return desc.getBoolean( kcameraRawJPEGStr );
return false;
}
// Filters for the open dialog
function winFileSelection( f )
{
var suffix = f.name.match(/[.](\w+)$/);
var t;
if (suffix && suffix.length == 2)
{
suffix = suffix[1].toUpperCase();
for (t in app.windowsFileTypes)
if (suffix == app.windowsFileTypes[t])
{
// Ignore mac-generated system thumbnails
if (f.name.slice(0,2) != "._")
return true;
}
}
return false;
}
function macFileSelection( f )
{
var t;
for (t in app.macintoshFileTypes)
if (f.type == app.macintoshFileTypes[t])
return true;
// Also check windows suffixes...
return winFileSelection( f );
}
function isValidImageFile( f )
{
return ((File.fs == "Macintosh") && macFileSelection(f)) || ((File.fs == "Windows") && winFileSelection(f));
}
// Simple utilities for those lovely four-char-codes
function intToOStype(n)
{
return String.fromCharCode( (n >> 24) & 0xFF, (n >> 16) & 0xFF, (n >> 8) & 0xFF, n & 0xFF);
}
function osTypeToInt(os)
{
var n = 0;
for (i = 0; i < os.length; i++)
n |= os.charCodeAt(i) << ((3-i)*8);
return n;
}
// Open Photoshop's FileOpen dialog, and return a list of filenames
// Potential DOM FIX
function photoshopFileOpenDialog()
{
result = new Array();
var nlist = app.openDialog();
for (var i = 0; i < nlist.length; i++)
{
var s = decodeURI(nlist[i].toString());
s = s.replace(/^file:\/\//, "");
if ($.os.match(/^Windows.*/)) // Pull off ":" from drive letter
s = s.replace(/^\/(.):\//, "/$1/");
result.push(s);
}
return result;
}
// A viewless document doesn't have a window open in Photoshop,
// doesn't have a corresponding "document" object in the DOM,
// and isn't known to the DOM in general. Instead, it carries
// a pointer to the TImageDocument in PS, and manipulates it via
// PS events. The code supporting this is hiding in U3DCommands.cpp
function ViewlessDocument( desc, pathname )
{
var bppdict = { 1:BitsPerChannelType.ONE,
8:BitsPerChannelType.EIGHT,
16:BitsPerChannelType.SIXTEEN,
32:BitsPerChannelType.THIRTYTWO };
var modeDict = {};
modeDict[classBitmapMode] = DocumentMode.BITMAP;
modeDict[classGrayscaleMode] = DocumentMode.GRAYSCALE;
modeDict[classDuotoneMode] = DocumentMode.DUOTONE;
modeDict[classIndexedColorMode] = DocumentMode.INDEXEDCOLOR;
modeDict[classRGBColorMode] = DocumentMode.RGB;
modeDict[classCMYKColorMode] = DocumentMode.CMYK;
modeDict[classLabColorMode] = DocumentMode.LAB;
modeDict[classMultichannelMode] = DocumentMode.MULTICHANNEL;
this.height = new UnitValue( desc.getInteger( keyHeight ), "px" );
this.width = new UnitValue( desc.getInteger( keyWidth ), "px" );
this.bitsPerChannel = bppdict[desc.getInteger( keyDepth )];
this.xmpMetadata = new Object();
this.pixelAspectRatio = desc.getDouble( kaspectRatioStr );
this.layerCount = desc.getInteger( klayersStr );
this.isSimple = desc.getBoolean( kflatnessStr );
this.xmpMetadata.rawData = desc.getString( kXMPMetadataAsUTF8Str );
this.activeLayer = new Object();
// Note: "TEXT" here really DOES NOT necessarily mean text, it just
// means "not normal"
if (desc.getBoolean( kpixelStr ))
this.activeLayer.kind = LayerKind.NORMAL;
else
this.activeLayer.kind = LayerKind.TEXT;
this.mode = modeDict[desc.getClass( keyMode )];
this.viewlessDocPtr = desc.getData( kdocumentStr );
this.path = pathname;
this.isOpen = true;
}
ViewlessDocument.prototype.addToActiveDocument = function()
{
function isWierdMode( m )
{
var j, wierdModes = [DocumentMode.BITMAP, DocumentMode.INDEXEDCOLOR, DocumentMode.GRAYSCALE, DocumentMode.DUOTONE, DocumentMode.MULTICHANNEL];
for (j in wierdModes)
if (m == wierdModes[j])
return true;
return false;
}
// Bug 2510186: Favor non-whacky modes vs. whacky ones, or else the
// action below silently fails.
if (isWierdMode( app.activeDocument.mode ) && ! isWierdMode( this.mode ))
{
var thisModeTag = this.mode.toString().match(/DocumentMode[.](\w+)/)[1];
app.activeDocument.changeMode( eval( "ChangeMode." + thisModeTag ) );
}
var i, fileListDesc = new ActionList();
var ptrListDesc = new ActionList();
fileListDesc.putPath( new File( this.path ) );
ptrListDesc.putData( this.viewlessDocPtr );
var desc = new ActionDescriptor();
desc.putList( keyFileList, fileListDesc );
desc.putList( keyViewlessDoc, ptrListDesc );
executeAction( kaddLayerFromViewlessDocStr, desc, DialogModes.NO );
this.isOpen = false; // Adding the layer closes the document
}
ViewlessDocument.prototype.changeMode = function( mode )
{
alert("Assert - changemode not implemented yet");
}
ViewlessDocument.prototype.close = function( options )
{
if (this.isOpen)
{
var desc = new ActionDescriptor();
desc.putData( kdocumentStr, this.viewlessDocPtr );
executeAction( kcloseViewlessDocumentStr, desc );
this.isOpen = false;
}
}
// Open a document -without- a corresponding view
function openViewlessDocument( pathname )
{
var desc = new ActionDescriptor();
desc.putBoolean( kpreferXMPFromACRStr, true );
desc.putPath( app.charIDToTypeID('File'), new File( pathname ) );
var result = executeAction( kopenViewlessDocumentStr, desc, DialogModes.NO );
var psViewPtr = result.getData( kdocumentStr );
if (psViewPtr.length == 0)
{
return null;
}
return new ViewlessDocument( result, pathname );
}
// Return a list of color profile names corresponding to a given
// OSCode tag for the ACE_SelectorCode (defined in ACETypes.h)
// Potential DOM fix
function getColorProfileList( profileTagStr )
{
var profileTag = osTypeToInt( profileTagStr );
var args = new ActionDescriptor();
ref = new ActionReference();
ref.putProperty( classProperty, kcolorProfileListStr );
ref.putEnumerated( classApplication, typeOrdinal, profileTag );
args.putReference( keyTarget, ref );
args.putInteger( kprofileStr, profileTag );
var resultDesc = executeAction( eventGet, args, DialogModes.NO );
var profileList = resultDesc.getList( kcolorProfileListStr );
var i, profileStrings = [];
for (i = 0; i < profileList.count; ++i)
profileStrings.push( profileList.getString(i) );
return profileStrings;
}
// Apply a perspective transform to the current layer, with the
// corner TPoints given in newCorners (starts at top left, in clockwise order)
// Potential DOM fix
function transformActiveLayer( newCorners )
{
function pxToNumber( px )
{
return px.as("px");
}
var saveUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var i;
var setArgs = new ActionDescriptor();
var chanArg = new ActionReference();
chanArg.putProperty( classChannel, keySelection );
setArgs.putReference( keyNull, chanArg );
var boundsDesc = new ActionDescriptor();
var layerBounds = app.activeDocument.activeLayer.bounds;
boundsDesc.putUnitDouble( keyTop, unitPixels, pxToNumber( layerBounds[1] ) );
boundsDesc.putUnitDouble( keyLeft, unitPixels, pxToNumber( layerBounds[0] ) );
boundsDesc.putUnitDouble( keyRight, unitPixels, pxToNumber( layerBounds[2] ) );
boundsDesc.putUnitDouble( keyBottom, unitPixels, pxToNumber( layerBounds[3] ) );
setArgs.putObject( keyTo, classRectangle, boundsDesc );
executeAction( eventSet, setArgs );
var result = new ActionDescriptor();
var args = new ActionDescriptor();
var quadRect = new ActionList();
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[0] ) ); // ActionList put is different from ActionDescriptor put
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[1] ) );
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[2] ) );
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[3] ) );
var quadCorners = new ActionList();
for (i = 0; i < 4; ++i)
{
quadCorners.putUnitDouble( unitPixels, newCorners[i].fX );
quadCorners.putUnitDouble( unitPixels, newCorners[i].fY );
}
args.putList( krectangleStr, quadRect );
args.putList( kquadrilateralStr, quadCorners );
executeAction( eventTransform, args );
// Deselect
deselArgs = new ActionDescriptor();
deselRef = new ActionReference();
deselRef.putProperty( classChannel, keySelection );
deselArgs.putReference( keyNull, deselRef );
deselArgs.putEnumerated( keyTo, typeOrdinal, enumNone );
executeAction( eventSet, deselArgs );
app.preferences.rulerUnits = saveUnits;
}
const kAlignAuto = "Auto";
const kAlignCylindrical = "cylindrical";
const kAlignSpherical = "spherical";
const kAlignPerspective = "Prsp";
const kAlignTranslation = "translation";
const kAlignCollage = "sceneCollage";
// Convert plain string to the alignment key.
// Avoids clients having to load Terminology.jsx
function stringToAlignmentKey( alignMethod )
{
// Today's JavaScript lesson: You can not use the constants above in the table below, because
// the parser interns the symbol on the left of the ":" into a new ID, whether or not it's already
// defined in scope. The new interned symbols only work as object field IDs (table.xyz) not
// array indicies (table[xyz])
var table = {"interactive":kinteractiveStr, "Prsp":keyPerspectiveIndex, "Auto":keyAuto, "spherical":ksphericalStr, "cylindrical":kcylindricalStr, "translation":ktranslationStr, "sceneCollage":ksceneCollageStr};
if (typeof(alignMethod) == "string")
alignMethod = table[alignMethod];
return alignMethod;
}
// Align selected layers in the active document by content
// (uses SIFT registration in Photoshop core)
// Potential DOM FIX
function getActiveDocAlignmentInfo( alignmentKey, doTransform, flagList )
{
const kUserCancelledError = 8007;
function getUnitPoint( pointDesc, key )
{
var desc = pointDesc.getObjectValue( key );
var xType = desc.getUnitDoubleType( keyHorizontal );
var yType = desc.getUnitDoubleType( keyVertical );
var x = desc.getUnitDoubleValue( keyHorizontal );
var y = desc.getUnitDoubleValue( keyVertical );
return new TPoint( x, y );
}
alignmentKey = stringToAlignmentKey( alignmentKey );
var layerInfo = new Array();
var returnInfo = null;
var projection, numGroups = 1;
// Must match enums in AlignContentInfo.h, AlignContentInfo::transformation_type
const kXfmAuto =-1;
const kXfmProjective = 0;
const kXfmCylindrical = 1;
const kXfmSpherical = 2;
const kXfmTranslate = 3;
const kXfmEuclidean = 4;
const kXfmSimilarity = 5;
const kXfmAffine = 6;
const kXfmInteractive = 7;
const kXfmReposition = 8;
const kSceneCollage = 9; // Tries to scene collage based on Projective/Cylindrical/Spherical with a relaxation of allowing rotation and isotropic scaling
const kProjectiveRd2 = 10; // projective with two-parameter radial distortion
const kSphericalFisheye = 11;
try {
var saveUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var i,j, desc = new ActionDescriptor();
var result, ref = new ActionReference();
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
desc.putReference( typeNULL, ref );
desc.putEnumerated( keyUsing, typeAlignDistributeSelector, kADSContentStr );
if (alignmentKey)
desc.putEnumerated( keyApply, kprojectionStr, alignmentKey );
if ((typeof(doTransform) != "undefined") && doTransform)
desc.putBoolean( kgeometryRecordStr, true );
else
desc.putBoolean( kgeometryOnlyStr, true );
// Set any flags passed in (highQuality, lensCorrection, etc.)
if ((typeof(flagList) != "undefined") && (flagList.length > 0))
for (i in flagList)
desc.putBoolean( flagList[i], true );
result = executeAction( keyAlignment, desc, DialogModes.NO );
// projection = result.getEnumerationValue( kprojectionStr );
if (result.hasKey( kgroupStr ))
numGroups = result.getInteger( kgroupStr );
// Pick apart the data returned by the alignment engine.
// Clues are found in ULayerCommand.cpp, PostLayoutCommand.
var layerList = result.getList( klayerTransformationStr );
for (i = 0; i < layerList.count; ++i)
{
var layerXformDesc = layerList.getObjectValue( i );
var layerID = layerXformDesc.getInteger( klayerIDStr );
var groupNum = layerXformDesc.getInteger( kgroupStr );
// Note xformType here is the xform actually used (vs. what was requested)
var xformType = layerXformDesc.getInteger( ktransformStr );
var baseFlag = layerXformDesc.hasKey( kignoreStr ) ? layerXformDesc.getBoolean( kignoreStr ) : false;
var pts = new Array();
// JavaScript note 1: ignore the ":1"s below, it's just a way to get a set membership test.
// JavaScript note 2: Using symbolic constants for this fails.
// if (xformType in {kXfmProjective:1, kXfmTranslate:1, kXfmEuclidean:1, kXfmSimilarity:1, kXfmAffine:1, kXfmReposition:1})
if (xformType in {0:1, 3:1, 4:1, 5:1, 6:1, 8:1})
{
var quadType = layerXformDesc.getObjectType( keyTo );
var quadDescriptor = layerXformDesc.getObjectValue( keyTo );
pts[0] = getUnitPoint( quadDescriptor, kquadCorner0Str );
pts[1] = getUnitPoint( quadDescriptor, kquadCorner1Str );
pts[2] = getUnitPoint( quadDescriptor, kquadCorner2Str );
pts[3] = getUnitPoint( quadDescriptor, kquadCorner3Str );
}
// Note we depend on stackElement's order matching the sheet list.
layerInfo[i] = {"baseFlag":baseFlag, "corners":pts, "groupNum":groupNum, "layerID":layerID, "xformType":xformType};
}
}
catch (e)
{
app.preferences.rulerUnits = saveUnits;
numGroups = 0;
if (e.number == kUserCancelledError)
throw e;
}
app.preferences.rulerUnits = saveUnits;
if (numGroups == 0)
return null;
else
return {"numGroups":numGroups, "layerInfo":layerInfo}
}
// Convert a 32 bit HDR document to 8 or 16 bit (brings up the dialog)
// Potential DOM FIX
function convertFromHDR( newDepth )
{
args = new ActionDescriptor();
args.putInteger( keyDepth, newDepth );
// Put an empty "with" descriptor to ensure that the HDR dialog gets
// invoked even when the prefs say the ACR filter plugin should be used.
var toneDesc = new ActionDescriptor();
args.putObject( keyWith, khdrOptionsStr, toneDesc );
executeAction( eventConvertMode, args, DialogModes.ALL );
}
// Convert a 32 bit HDR document to 8 or 16 bit (doesn't bring up the dialog)
// Potential DOM FIX
function convertFromHDRNoDialog( newDepth, srcDesc )
{
function descCopyNum( key )
{
toneDesc.putDouble( key, srcDesc.getDouble( key ));
}
function descCopyFlag( key )
{
toneDesc.putBoolean( key, srcDesc.getBoolean( key ));
}
args = new ActionDescriptor();
args.putInteger( keyDepth, newDepth );
var toneDesc = new ActionDescriptor();
toneDesc.putInteger( kversionStr, 4 );
var method = srcDesc.getInteger( kmethodStr );
switch (method)
{
// Methods provided from the Dept. of Mismatched Constants.
case 0: // kHighlightCompression
{
toneDesc.putEnumerated( keyMethod, khdrToningMethodTypeStr, khdrToningType1Str );
break;
}
case 2: // kEqualizeHistogram
{
toneDesc.putEnumerated( keyMethod, khdrToningMethodTypeStr, khdrToningType3Str );
break;
}
case 1: // kExposureAndGamma
{
toneDesc.putEnumerated( keyMethod, khdrToningMethodTypeStr, khdrToningType2Str );
descCopyNum( kgammaStr );
descCopyNum( kexposureStr );
break;
}
case 3: // kLocalAdaptation
{
toneDesc.putEnumerated( keyMethod, khdrToningMethodTypeStr, khdrToningType4Str );
// Need special case handling for kexposure
descCopyNum( kradiusStr );
descCopyNum( kthresholdStr );
descCopyNum( ksaturationStr );
descCopyNum( kvibranceStr );
descCopyNum( kdetailStr );
descCopyNum( kshallowStr );
descCopyNum( khighlightsStr );
descCopyNum( kcontrastStr );
descCopyNum( kbrightnessStr );
descCopyFlag( ksmoothStr );
// Copy the toning curve as well
var srcCurve = srcDesc.getObjectValue( kclassContour );
var srcCurvePts = srcCurve.getList( keyCurve );
var pointList = new ActionList();
var i, curveDesc = new ActionDescriptor()
curveDesc.putString( keyName, "Default" );
for (i = 0; i < srcCurvePts.count; ++i)
{
var srcPt = srcCurvePts.getObjectValue(i);
var ptDesc = new ActionDescriptor();
ptDesc.putDouble( keyHorizontal, srcPt.getDouble( keyHorizontal ) );
ptDesc.putDouble( keyVertical, srcPt.getDouble( keyVertical ) );
ptDesc.putBoolean( keyContinuity, srcPt.getBoolean( keyContinuity ) );
pointList.putObject( classCurvePoint, ptDesc );
}
curveDesc.putList( keyCurve, pointList );
toneDesc.putObject( kclassContour, classShapingCurve, curveDesc );
break;
}
}
args.putObject( keyWith, khdrOptionsStr, toneDesc );
executeAction( eventConvertMode, args, DialogModes.NO );
}
// Use the core graph-cut facility to merge the layers. Assumes
// layers to be merged are selected.
// Potential DOM FIX
function advancedMergeLayers()
{
var idmergeAlignedLayers = stringIDToTypeID( "mergeAlignedLayers" );
var desc4 = new ActionDescriptor();
var idAply = charIDToTypeID( "Aply" );
var idautoBlendType = stringIDToTypeID( "autoBlendType" );
var idpanorama = stringIDToTypeID( "panorama" );
desc4.putEnumerated( idAply, idautoBlendType, idpanorama );
var idClrC = charIDToTypeID( "ClrC" );
desc4.putBoolean( idClrC, true );
var idautoTransparencyFill = stringIDToTypeID( "autoTransparencyFill" );
desc4.putBoolean( idautoTransparencyFill, false );
executeAction( idmergeAlignedLayers, desc4, DialogModes.NO );
// using the following line will be using the sticky settings of blendtype and autofill in PS, which is not what we want
// executeAction( kmergeAlignedLayersStr, undefined, DialogModes.NO );
}
// Create a new layer to put the merged layers
// Trim the transparent areas at canvas boundary
// Fill the transparent areas
function advancedMergeLayersAndAutoFill()
{
var idmergeAlignedLayers = stringIDToTypeID( "mergeAlignedLayers" );
var desc4 = new ActionDescriptor();
var idAply = charIDToTypeID( "Aply" );
var idautoBlendType = stringIDToTypeID( "autoBlendType" );
var idpanorama = stringIDToTypeID( "panorama" );
desc4.putEnumerated( idAply, idautoBlendType, idpanorama );
var idClrC = charIDToTypeID( "ClrC" );
desc4.putBoolean( idClrC, true );
var idautoTransparencyFill = stringIDToTypeID( "autoTransparencyFill" );
desc4.putBoolean( idautoTransparencyFill, true );
executeAction( idmergeAlignedLayers, desc4, DialogModes.NO );
}
// Select all of the layers, except the one named pluginName.
// Currently the DOM only allows for a single layer selected at one time.
// Potential DOM FIX
function selectAllLayers(doc, upToLayer)
{
var i;
app.activeDocument = doc;
// Select the first layer...
doc.activeLayer = doc.layers[0];
var desc = new ActionDescriptor();
var ref = new ActionReference();
if (typeof(upToLayer) == 'undefined')
upToLayer = 1;
// Note - the event indexing is the -reverse- of the JavaScript indexing,
// so "2" refers to the next to the last.
ref.putIndex( classLayer, upToLayer );
desc.putReference( typeNULL, ref );
desc.putEnumerated( kselectionModifierStr, kselectionModifierTypeStr, kaddToSelectionContinuousStr );
desc.putBoolean( keyMakeVisible, false );
executeAction( eventSelect, desc, DialogModes.NO );
}
// Return whether or not the active doc has a background layer.
// The DOM requires catching an exception to figure this out (ewww).
// Potential DOM FIX
function hasBackgroundLayer()
{
var ref = new ActionReference();
ref.putProperty( classProperty, khasBackgroundLayerStr );
ref.putEnumerated( classDocument, typeOrdinal, enumTarget );
var resultDesc = executeActionGet( ref );
return resultDesc.getBoolean( khasBackgroundLayerStr );
}
// Select one layer. Native JS is broken if multiple layers are
// already selected. If passed a number, select by index.
// Potential DOM FIX
function selectOneLayer(doc, layer)
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
app.activeDocument = doc;
if (typeof layer === "number") {
// Add offset; see TLayerElement::Make
var offset = hasBackgroundLayer() ? 0 : 1;
ref.putIndex( classLayer, layer + offset );
}
else
{
if (typeof(layer) != "string")
layer = layer.name;
ref.putName( classLayer, layer );
}
desc.putReference( typeNULL, ref );
desc.putBoolean( keyMakeVisible, false );
executeAction( eventSelect, desc, DialogModes.NO );
}
// There is a bug in UPluginSupport.cpp where the routine to build the descriptor
// for the layer is called with a target sheet *index* instead of the sheet's ID. Thus
// the "sheetID" in a plugin layer descriptor is really the index. This returns that
// index, properly offset so it matches up with the C++ code.
function getRawActiveLayerIndex()
{
var ref = new ActionReference();
ref.putProperty( classProperty, keyItemIndex );
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
var resultDesc = executeActionGet( ref );
return resultDesc.getInteger( keyItemIndex ) - 1;
}
// Align selected layers by content (uses SIFT registration in Photoshop core)
// Potential DOM FIX
function alignLayersByContent( alignMethod )
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
alignMethod = stringToAlignmentKey( alignMethod );
if (! alignMethod)
alignMethod = keyPerspectiveIndex;
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
desc.putReference( typeNULL, ref );
desc.putEnumerated( keyUsing, typeAlignDistributeSelector, kADSContentStr );
desc.putEnumerated( keyApply, kprojectionStr, alignMethod );
executeAction( keyAlignment, desc, DialogModes.NO );
}
// Invoke the graph-cut based blending method
// Potential DOM FIX
function graphCutMergeLayers()
{
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated( classLayer, typeOrdinal, enumTarget );
desc.putReference( typeNULL, ref );
executeAction( kmergeAlignedLayersStr, desc, DialogModes.NO );
}
// This is sleazy - check if a char/string ID is present by seeing if
// a "new" one is made when we ask for it.
// Note *THIS ONLY WORKS ONCE*. The second time it's called, it'll report
// the ID as valid even if it's not, since the act of testing it defines it.
function isIDDefined(idStr)
{
var d = new Date();
var bogusID = app.stringIDToTypeID( "s" + d.getTime().toString() );
var testID = app.stringIDToTypeID( idStr );
// If bogus and test are one apart, then they both defined new IDs.
// Otherwise, the ID already existed.
return !(testID - bogusID == 1)
}