// ExtendScript host for the AI Edit CEP panel. // ASCII only: ExtendScript guesses this file's encoding, so all localized // strings are passed in from the panel instead of written here. function aiEditHasDocument() { try { return app.documents.length > 0 ? "yes" : "no"; } catch (e) { return "no"; } } function aiEditShowComposite(doc) { try { doc.activeChannels = doc.componentChannels; } catch (e) { try { doc.activeChannels = [doc.channels[0]]; } catch (fallbackError) {} } } function aiEditPickImages(title, filter) { try { var picked = File.openDialog(title, filter, true); if (!picked) return ""; if (!(picked instanceof Array)) picked = [picked]; var paths = []; for (var i = 0; i < picked.length; i++) paths.push(picked[i].fsName); return paths.join("\n"); } catch (e) { return ""; } } // A native window is the reliable way to type CJK: the CEF panel does not // always receive IME composition events. function aiEditPromptDialog(title, label, okText, cancelText, initial) { try { var dialog = new Window("dialog", title); dialog.alignChildren = "fill"; dialog.margins = 14; dialog.spacing = 10; dialog.add("statictext", undefined, label); var field = dialog.add("edittext", undefined, initial || "", { multiline: true, wantReturn: true }); field.preferredSize = [420, 150]; var row = dialog.add("group"); row.alignment = "right"; row.spacing = 8; var cancel = row.add("button", undefined, cancelText, { name: "cancel" }); var confirm = row.add("button", undefined, okText, { name: "ok" }); var text = null; confirm.onClick = function () { text = field.text; dialog.close(1); }; cancel.onClick = function () { dialog.close(0); }; field.active = true; if (dialog.show() !== 1 || text === null) return "cancel"; // escape() keeps the result ASCII so evalScript can carry it back. return "ok:" + escape(text); } catch (e) { return "cancel"; } } function aiEditGetGenerationSize() { try { if (app.documents.length === 0) return ""; var doc = app.activeDocument; var width = doc.width.as("px"); var height = doc.height.as("px"); try { var bounds = doc.selection.bounds; width = bounds[2].as("px") - bounds[0].as("px"); height = bounds[3].as("px") - bounds[1].as("px"); } catch (noSelection) {} return Math.max(1, Math.round(width)) + "x" + Math.max(1, Math.round(height)); } catch (e) { return ""; } } function aiEditCaptureSelection() { var doc = null; var reference = null; try { if (app.documents.length === 0) return "none"; doc = app.activeDocument; aiEditShowComposite(doc); var bounds; try { bounds = doc.selection.bounds; } catch (selectionError) { return "none"; } var token = "AI Edit Selection " + new Date().getTime(); var channel = doc.channels.add(); channel.name = token; doc.selection.store(channel); aiEditShowComposite(doc); var width = Math.max(1, Math.round(bounds[2].as("px") - bounds[0].as("px"))); var height = Math.max(1, Math.round(bounds[3].as("px") - bounds[1].as("px"))); var boundsText = [ Math.round(bounds[0].as("px")), Math.round(bounds[1].as("px")), width, height ].join(","); // Avoid copy/copyMerged: Photoshop reports that command as unavailable // for some active layers and documents. A duplicate keeps the merged // visible pixels and can be cropped without using the clipboard. reference = doc.duplicate("AI Edit Reference", false); reference.flatten(); // Do not call selection.clear here. Photoshop rejects that command // when the duplicated document resolves to a locked background layer. // Cropping to the captured bounds keeps the selected image usable as // a reference without changing pixels through the clipboard/delete UI. reference.crop(bounds); var file = new File(Folder.temp.fsName + "/ai-edit-selection-" + new Date().getTime() + ".png"); var options = new PNGSaveOptions(); reference.saveAs(file, options, true, Extension.LOWERCASE); reference.close(SaveOptions.DONOTSAVECHANGES); app.activeDocument = doc; aiEditShowComposite(doc); return "ok:" + token + "|" + file.fsName + "|" + boundsText; } catch (e) { try { if (reference) reference.close(SaveOptions.DONOTSAVECHANGES); if (doc) { app.activeDocument = doc; aiEditShowComposite(doc); } } catch (closeError) {} return "error:" + e.toString(); } } function aiEditAddLayerMask(hasSelection) { var classicMask = new ActionDescriptor(); classicMask.putClass(charIDToTypeID("Nw "), charIDToTypeID("Chnl")); var classicTarget = new ActionReference(); classicTarget.putEnumerated( charIDToTypeID("Chnl"), charIDToTypeID("Chnl"), charIDToTypeID("Msk ") ); classicMask.putReference(charIDToTypeID("At "), classicTarget); classicMask.putEnumerated( charIDToTypeID("Usng"), charIDToTypeID("UsrM"), charIDToTypeID(hasSelection ? "RvlS" : "RvlA") ); try { executeAction(charIDToTypeID("Mk "), classicMask, DialogModes.NO); return; } catch (classicError) {} var modernMask = new ActionDescriptor(); var newChannel = new ActionReference(); newChannel.putClass(stringIDToTypeID("channel")); modernMask.putReference(charIDToTypeID("null"), newChannel); var currentLayer = new ActionReference(); currentLayer.putEnumerated( stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum") ); modernMask.putReference(charIDToTypeID("At "), currentLayer); modernMask.putEnumerated( stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID(hasSelection ? "revealSelection" : "revealAll") ); executeAction(charIDToTypeID("Mk "), modernMask, DialogModes.NO); } function aiEditPlaceImage(filePath, layerName, channelName, boundsText) { try { if (app.documents.length === 0) return "error:missing-document"; var file = new File(filePath); if (!file.exists) return "error:missing-file"; var doc = app.activeDocument; var selectionChannel = null; var restoreChannel = null; var hadCurrentSelection = false; var hasSelection = false; if (channelName) { try { try { doc.selection.bounds; hadCurrentSelection = true; restoreChannel = doc.channels.add(); restoreChannel.name = "AI Edit Restore " + new Date().getTime(); doc.selection.store(restoreChannel); } catch (noCurrentSelection) {} selectionChannel = doc.channels.getByName(channelName); doc.selection.load(selectionChannel); hasSelection = true; } catch (channelError) {} } var place = new ActionDescriptor(); place.putPath(charIDToTypeID("null"), file); place.putEnumerated(charIDToTypeID("FTcs"), charIDToTypeID("QCSt"), charIDToTypeID("Qcsa")); var offset = new ActionDescriptor(); offset.putUnitDouble(charIDToTypeID("Hrzn"), charIDToTypeID("#Pxl"), 0); offset.putUnitDouble(charIDToTypeID("Vrtc"), charIDToTypeID("#Pxl"), 0); place.putObject(charIDToTypeID("Ofst"), charIDToTypeID("Ofst"), offset); place.putBoolean(stringIDToTypeID("linked"), false); executeAction(charIDToTypeID("Plc "), place, DialogModes.NO); var placedLayer = app.activeDocument.activeLayer; if (layerName) placedLayer.name = layerName; if (boundsText) { var boundsValues = boundsText.split(","); if (boundsValues.length === 4) { var targetLeft = Number(boundsValues[0]); var targetTop = Number(boundsValues[1]); var targetWidth = Number(boundsValues[2]); var targetHeight = Number(boundsValues[3]); var placedBounds = placedLayer.bounds; var placedWidth = Math.max(1, placedBounds[2].as("px") - placedBounds[0].as("px")); var placedHeight = Math.max(1, placedBounds[3].as("px") - placedBounds[1].as("px")); placedLayer.resize( targetWidth / placedWidth * 100, targetHeight / placedHeight * 100, AnchorPosition.TOPLEFT ); placedBounds = placedLayer.bounds; placedLayer.translate( targetLeft - placedBounds[0].as("px"), targetTop - placedBounds[1].as("px") ); } } app.activeDocument.activeLayer = placedLayer; aiEditAddLayerMask(hasSelection); if (selectionChannel) { if (hadCurrentSelection && restoreChannel) doc.selection.load(restoreChannel); else doc.selection.deselect(); if (restoreChannel) restoreChannel.remove(); } aiEditShowComposite(doc); return "ok"; } catch (e) { try { aiEditShowComposite(app.activeDocument); } catch (restoreViewError) {} return "error:" + e.toString(); } }