初始化 Photoshop AI 编辑面板
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="9.0"
|
||||
ExtensionBundleId="com.aiedit.panel"
|
||||
ExtensionBundleName="AI Edit"
|
||||
ExtensionBundleVersion="1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.aiedit.panel.main" Version="1.0.0" />
|
||||
</ExtensionList>
|
||||
<ExecutionEnvironment>
|
||||
<HostList>
|
||||
<Host Name="PHXS" Version="[20.0,99.9]" />
|
||||
<Host Name="PHSP" Version="[20.0,99.9]" />
|
||||
</HostList>
|
||||
<LocaleList>
|
||||
<Locale Code="All" />
|
||||
</LocaleList>
|
||||
<RequiredRuntimeList>
|
||||
<RequiredRuntime Name="CSXS" Version="9.0" />
|
||||
</RequiredRuntimeList>
|
||||
</ExecutionEnvironment>
|
||||
<DispatchInfoList>
|
||||
<Extension Id="com.aiedit.panel.main">
|
||||
<DispatchInfo>
|
||||
<Resources>
|
||||
<MainPath>./index.html</MainPath>
|
||||
<ScriptPath>./host/host.jsx</ScriptPath>
|
||||
<CEFCommandLine>
|
||||
<Parameter>--enable-nodejs</Parameter>
|
||||
<Parameter>--mixed-context</Parameter>
|
||||
</CEFCommandLine>
|
||||
</Resources>
|
||||
<Lifecycle>
|
||||
<AutoVisible>true</AutoVisible>
|
||||
</Lifecycle>
|
||||
<UI>
|
||||
<Type>Panel</Type>
|
||||
<Menu>AI 生图</Menu>
|
||||
<Geometry>
|
||||
<Size>
|
||||
<Height>876</Height>
|
||||
<Width>390</Width>
|
||||
</Size>
|
||||
<MinSize>
|
||||
<Height>420</Height>
|
||||
<Width>300</Width>
|
||||
</MinSize>
|
||||
<MaxSize>
|
||||
<Height>2000</Height>
|
||||
<Width>2000</Width>
|
||||
</MaxSize>
|
||||
</Geometry>
|
||||
<Icons>
|
||||
<Icon Type="Normal">./icons/icon_N.png</Icon>
|
||||
<Icon Type="DarkNormal">./icons/icon_D.png</Icon>
|
||||
</Icons>
|
||||
</UI>
|
||||
</DispatchInfo>
|
||||
</Extension>
|
||||
</DispatchInfoList>
|
||||
</ExtensionManifest>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 568 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 535 B |
Binary file not shown.
|
After Width: | Height: | Size: 995 B |
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="styles/panel.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user