新增插件:图片去黑底
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
#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);
|
||||
}
|
||||
}());
|
||||
Reference in New Issue
Block a user