初始化 Photoshop AI 编辑面板
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user