Files
tools/Mac工具/Yeomai-Toolbox-CEP/jsx/scripts/1.图层工具/图片去黑底.jsx
T
2026-09-01 11:01:38 +08:00

238 lines
8.5 KiB
React

#target photoshop
/*
UnMult for Photoshop
------------------------------------------------------------
Reproduces the supplied After Effects plug-in's core operation:
a = max(R, G, B)
RGB = RGB / a (when 0 < a < 1)
alpha = alpha * a
Usage:
1. Open an RGB document and select the layer with a black background.
2. Run this file with File > Scripts > Browse...
The source layer is kept (and hidden). The result is placed directly
above it as a new raster layer. Set KEEP_ORIGINAL to false if preferred.
*/
(function () {
var KEEP_ORIGINAL = true;
var RESULT_SUFFIX = " - UnMult";
var SILENT_TEST = (typeof UNMULT_SILENT_TEST !== "undefined" && UNMULT_SILENT_TEST === true);
var tempDoc = null;
var sourceDoc = null;
var sourceLayer = null;
var visibilityState = [];
function c2t(s) { return charIDToTypeID(s); }
function s2t(s) { return stringIDToTypeID(s); }
function rememberAndHide(container) {
var i, item;
for (i = 0; i < container.layers.length; i++) {
item = container.layers[i];
visibilityState.push({ layer: item, visible: item.visible });
item.visible = false;
if (item.typename === "LayerSet") {
rememberAndHide(item);
}
}
}
function revealLayerPath(layer, doc) {
var item = layer;
while (item && item !== doc) {
item.visible = true;
item = item.parent;
}
}
function restoreVisibility() {
var i;
for (i = visibilityState.length - 1; i >= 0; i--) {
try { visibilityState[i].layer.visible = visibilityState[i].visible; }
catch (ignore) {}
}
}
function makeMaskFromSelection() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
desc.putClass(c2t("Nw "), c2t("Chnl"));
ref.putEnumerated(c2t("Chnl"), c2t("Chnl"), c2t("Msk "));
desc.putReference(c2t("At "), ref);
desc.putEnumerated(c2t("Usng"), c2t("UsrM"), c2t("RvlS"));
executeAction(c2t("Mk "), desc, DialogModes.NO);
}
function applyLayerMask() {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putEnumerated(c2t("Chnl"), c2t("Chnl"), c2t("Msk "));
desc.putReference(c2t("null"), ref);
desc.putBoolean(c2t("Aply"), true);
executeAction(c2t("Dlt "), desc, DialogModes.NO);
}
function removeBlackMatte() {
// Photoshop 2026 still registers this native Action Manager event.
// Calling it directly avoids relying on optional ExtendScript methods.
executeAction(s2t("removeBlackMatte"), undefined, DialogModes.NO);
}
function channelReference(doc, channel, componentName) {
var ref = new ActionReference();
if (componentName) {
ref.putEnumerated(s2t("channel"), s2t("channel"), s2t(componentName));
} else {
ref.putName(s2t("channel"), channel.name);
}
ref.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("merged"));
ref.putIdentifier(s2t("document"), doc.id);
return ref;
}
function calculateToNewChannel(doc, firstChannel, secondChannel, firstComponent, secondComponent,
blendType, invertFirst, invertSecond) {
// Equivalent to Image > Calculations, Result: New Channel.
var calculation = new ActionDescriptor();
calculation.putReference(s2t("to"), channelReference(doc, firstChannel, firstComponent));
calculation.putReference(s2t("source2"), channelReference(doc, secondChannel, secondComponent));
calculation.putBoolean(s2t("invert"), invertFirst === true);
calculation.putBoolean(s2t("invertSource2"), invertSecond === true);
calculation.putUnitDouble(s2t("opacity"), s2t("percentUnit"), 100.0);
calculation.putEnumerated(
s2t("calculation"),
s2t("calculationType"),
s2t(blendType)
);
var make = new ActionDescriptor();
make.putClass(s2t("new"), s2t("channel"));
make.putObject(s2t("using"), s2t("calculation"), calculation);
executeAction(s2t("make"), make, DialogModes.NO);
return app.activeDocument.activeChannels[0];
}
function invertChannel(doc, channel) {
doc.activeChannels = [channel];
executeAction(c2t("Invr"), undefined, DialogModes.NO);
}
function makeMaximumRGBSelection(doc, layer) {
var channels = doc.componentChannels;
if (!channels || channels.length < 3) {
throw new Error("RGB channels are not available.");
}
// Photoshop Calculations uses different polarity for component channels
// and alpha channels. Pass 1 yields 1-max(R,G), then is inverted. For
// pass 2, Darken with an inverted Blue source yields max(max(R,G),B).
doc.activeChannels = channels;
var maxRG = calculateToNewChannel(
doc, channels[0], channels[1], "red", "green",
"lighten", false, false
);
maxRG.name = "__UnMult_MaxRG__";
invertChannel(doc, maxRG);
var maxRGB = calculateToNewChannel(
doc, maxRG, channels[2], null, "blue",
"darken", false, true
);
maxRGB.name = "__UnMult_MaxRGB__";
doc.selection.load(maxRGB, SelectionType.REPLACE);
doc.activeChannels = channels;
maxRGB.remove();
maxRG.remove();
doc.activeLayer = layer;
}
try {
if (app.documents.length === 0) {
throw new Error("Open an image and select the black-background layer first.");
}
sourceDoc = app.activeDocument;
if (sourceDoc.mode !== DocumentMode.RGB) {
throw new Error("RGB mode is required. Convert the document to RGB first.");
}
if (sourceDoc.bitsPerChannel === BitsPerChannelType.THIRTYTWO) {
throw new Error("32-bit/channel is not supported by JSX selections. Convert to 16-bit or 8-bit first.");
}
sourceLayer = sourceDoc.activeLayer;
if (sourceLayer.typename !== "ArtLayer") {
throw new Error("Select a layer, not a layer group.");
}
// Copy the selected layer's rendered appearance only. This also safely
// handles smart objects, layer styles and pre-existing layer masks.
rememberAndHide(sourceDoc);
revealLayerPath(sourceLayer, sourceDoc);
sourceDoc.activeLayer = sourceLayer;
sourceDoc.selection.selectAll();
sourceDoc.selection.copy(true);
sourceDoc.selection.deselect();
restoreVisibility();
visibilityState = [];
tempDoc = app.documents.add(
sourceDoc.width,
sourceDoc.height,
sourceDoc.resolution,
"__UnMult_Temporary__",
NewDocumentMode.RGB,
DocumentFill.TRANSPARENT,
sourceDoc.pixelAspectRatio,
sourceDoc.bitsPerChannel
);
tempDoc.paste();
var workLayer = tempDoc.activeLayer;
makeMaximumRGBSelection(tempDoc, workLayer);
makeMaskFromSelection();
tempDoc.selection.deselect();
// Applying the max(R,G,B) mask creates alpha. Removing the black matte
// then performs RGB/max(R,G,B), which is the plug-in's unpremultiply.
applyLayerMask();
removeBlackMatte();
var originalName = sourceLayer.name;
var resultLayer = workLayer.duplicate(sourceDoc, ElementPlacement.PLACEATBEGINNING);
app.activeDocument = sourceDoc;
resultLayer.name = originalName + RESULT_SUFFIX;
resultLayer.move(sourceLayer, ElementPlacement.PLACEBEFORE);
resultLayer.visible = true;
if (KEEP_ORIGINAL) {
sourceLayer.visible = false;
} else {
sourceLayer.remove();
}
sourceDoc.activeLayer = resultLayer;
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
tempDoc = null;
if (!SILENT_TEST) alert("NewLayer: " + resultLayer.name);
} catch (err) {
restoreVisibility();
try {
if (sourceDoc) {
app.activeDocument = sourceDoc;
if (sourceLayer) sourceDoc.activeLayer = sourceLayer;
}
} catch (ignoreRestore) {}
try {
if (tempDoc) tempDoc.close(SaveOptions.DONOTSAVECHANGES);
} catch (ignoreClose) {}
if (SILENT_TEST) throw err;
alert("UnMult failed:\n" + err.message);
}
}());