新增插件:图片去黑底
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