1131 lines
35 KiB
React
1131 lines
35 KiB
React
#target photoshop
|
||
|
||
/*
|
||
Photoshop RGBA Channel Tools 5.7
|
||
|
||
使用方法:
|
||
1. 在 Photoshop 中打开一个 RGB 文档,并选中目标图层。
|
||
2. 选择“文件 > 脚本 > 浏览”,运行本 JSX 文件。
|
||
3. 在弹窗中选择需要执行的功能:
|
||
- 互换 R、G、B、A 中任意两个通道;
|
||
- 把当前图层的 R、G、B 或 A 复制为纯白透明图层。
|
||
|
||
通道交换:
|
||
- R/G/B 沿用 Photoshop 原生“通道混合器”算法;
|
||
- 与 A 组合时,输出透明度 = 所选 R/G/B 通道 × 当前图层 A;
|
||
- 结果像素为对应通道的纯色,其他通道区域及原透明区域保持透明;
|
||
- 只读取运行脚本时选中的图层,A 表示该图层自身透明度;
|
||
- 结果是当前文档、当前图层正上方的独立普通像素层;
|
||
- 最终结果层禁止使用剪切蒙版;无法确认时中止并删除结果层;
|
||
- 不修改原图层,也不建立图层蒙版或链接;
|
||
- 计算用的临时文档会自动关闭,不会留下新文档副本。
|
||
|
||
复制通道:
|
||
- 读取当前图层的指定通道;
|
||
- R/G/B 的输出透明度 = 对应通道 × 当前图层 A;
|
||
- A 的输出透明度 = 当前图层 A;
|
||
- 仅在结果非透明区域填充纯白像素;
|
||
- 不使用图层蒙版或剪切蒙版,也不链接其他图层;
|
||
- 名称为“原图层名称_R/G/B/A”。
|
||
|
||
窗口:
|
||
- “执行”和“取消”都会显式关闭 ScriptUI 对话框;
|
||
- 请求系统标题栏显示关闭按钮,并把 Esc 明确绑定到取消;
|
||
- 开始图像处理前再次安全释放窗口,避免留下空白窗口残影。
|
||
*/
|
||
|
||
(function () {
|
||
var SCRIPT_TITLE = "RGBA 通道工具";
|
||
var CHANNEL_LABELS = [
|
||
"R(红)",
|
||
"G(绿)",
|
||
"B(蓝)",
|
||
"A(透明度)"
|
||
];
|
||
var COPY_CHANNEL_LABELS = [
|
||
"R(红)",
|
||
"G(绿)",
|
||
"B(蓝)",
|
||
"A(当前图层透明度)"
|
||
];
|
||
var CHANNEL_NAMES = ["R", "G", "B", "A"];
|
||
|
||
if (app.documents.length === 0) {
|
||
alert("请先打开一个 RGB 图像文档。", SCRIPT_TITLE, true);
|
||
return;
|
||
}
|
||
|
||
var doc = app.activeDocument;
|
||
|
||
if (doc.mode !== DocumentMode.RGB) {
|
||
alert(
|
||
"当前文档不是 RGB 颜色模式。\n请先转换为 RGB 模式后再运行脚本。",
|
||
SCRIPT_TITLE,
|
||
true
|
||
);
|
||
return;
|
||
}
|
||
|
||
var dialog = new Window(
|
||
"dialog",
|
||
SCRIPT_TITLE,
|
||
undefined,
|
||
{ closeButton: true }
|
||
);
|
||
dialog.orientation = "column";
|
||
dialog.alignChildren = ["fill", "top"];
|
||
dialog.spacing = 12;
|
||
dialog.margins = 18;
|
||
|
||
var operationPanel = dialog.add("panel", undefined, "功能");
|
||
operationPanel.orientation = "column";
|
||
operationPanel.alignChildren = ["left", "center"];
|
||
operationPanel.margins = 14;
|
||
|
||
var swapRadio = operationPanel.add(
|
||
"radiobutton",
|
||
undefined,
|
||
"互换两个 R/G/B/A 通道(输出到当前文档)"
|
||
);
|
||
var copyRadio = operationPanel.add(
|
||
"radiobutton",
|
||
undefined,
|
||
"复制指定通道为纯白透明图层"
|
||
);
|
||
swapRadio.value = true;
|
||
|
||
var swapPanel = dialog.add("panel", undefined, "互换通道");
|
||
swapPanel.orientation = "column";
|
||
swapPanel.alignChildren = ["fill", "center"];
|
||
swapPanel.spacing = 10;
|
||
swapPanel.margins = 14;
|
||
|
||
var firstRow = swapPanel.add("group");
|
||
firstRow.orientation = "row";
|
||
firstRow.add("statictext", undefined, "第一个通道:");
|
||
var firstDropdown = firstRow.add(
|
||
"dropdownlist",
|
||
undefined,
|
||
CHANNEL_LABELS
|
||
);
|
||
firstDropdown.preferredSize.width = 180;
|
||
firstDropdown.selection = 0;
|
||
|
||
var secondRow = swapPanel.add("group");
|
||
secondRow.orientation = "row";
|
||
secondRow.add("statictext", undefined, "与此通道互换:");
|
||
var secondDropdown = secondRow.add(
|
||
"dropdownlist",
|
||
undefined,
|
||
CHANNEL_LABELS
|
||
);
|
||
secondDropdown.preferredSize.width = 180;
|
||
secondDropdown.selection = 1;
|
||
|
||
var modeText = swapPanel.add(
|
||
"statictext",
|
||
undefined,
|
||
"与 A 互换:透明度 = 所选颜色通道 × 当前图层 A。"
|
||
);
|
||
modeText.alignment = "fill";
|
||
|
||
var modeText2 = swapPanel.add(
|
||
"statictext",
|
||
undefined,
|
||
"只处理当前选中图层;结果为独立非剪切图层。"
|
||
);
|
||
modeText2.alignment = "fill";
|
||
|
||
var copyPanel = dialog.add("panel", undefined, "复制通道");
|
||
copyPanel.orientation = "column";
|
||
copyPanel.alignChildren = ["fill", "center"];
|
||
copyPanel.spacing = 10;
|
||
copyPanel.margins = 14;
|
||
|
||
var copyRow = copyPanel.add("group");
|
||
copyRow.orientation = "row";
|
||
copyRow.add("statictext", undefined, "要复制的通道:");
|
||
var copyDropdown = copyRow.add(
|
||
"dropdownlist",
|
||
undefined,
|
||
COPY_CHANNEL_LABELS
|
||
);
|
||
copyDropdown.preferredSize.width = 210;
|
||
copyDropdown.selection = 0;
|
||
|
||
var copyHint = copyPanel.add(
|
||
"statictext",
|
||
undefined,
|
||
"R/G/B 会与当前图层 A 相乘;黑色和原透明区域保持透明。"
|
||
);
|
||
copyHint.alignment = "fill";
|
||
|
||
var copyHint2 = copyPanel.add(
|
||
"statictext",
|
||
undefined,
|
||
"最终层禁止剪切蒙版;名称:原图层名称_R/G/B/A。"
|
||
);
|
||
copyHint2.alignment = "fill";
|
||
|
||
var buttons = dialog.add("group");
|
||
buttons.alignment = "right";
|
||
var cancelButton = buttons.add(
|
||
"button",
|
||
undefined,
|
||
"取消",
|
||
{ name: "cancel" }
|
||
);
|
||
var executeButton = buttons.add(
|
||
"button",
|
||
undefined,
|
||
"执行",
|
||
{ name: "ok" }
|
||
);
|
||
|
||
// 明确声明键盘与系统窗口按钮的默认行为。
|
||
dialog.cancelElement = cancelButton;
|
||
dialog.defaultElement = executeButton;
|
||
dialog.onClose = function () {
|
||
return true;
|
||
};
|
||
|
||
swapRadio.onClick = updateOperationUI;
|
||
copyRadio.onClick = updateOperationUI;
|
||
cancelButton.onClick = function () {
|
||
dialog.close(0);
|
||
};
|
||
executeButton.onClick = function () {
|
||
dialog.close(1);
|
||
};
|
||
updateOperationUI();
|
||
|
||
dialog.center();
|
||
|
||
var dialogResult = dialog.show();
|
||
|
||
if (dialogResult !== 1) {
|
||
disposeScriptUIDialog(dialog);
|
||
return;
|
||
}
|
||
|
||
function updateOperationUI() {
|
||
swapPanel.enabled = swapRadio.value;
|
||
copyPanel.enabled = copyRadio.value;
|
||
}
|
||
|
||
function disposeScriptUIDialog(dialogReference) {
|
||
if (dialogReference === null || dialogReference === undefined) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
dialogReference.hide();
|
||
} catch (hideDialogError) {
|
||
// 点击标准按钮后窗口通常已关闭;此处只负责兼容残留外壳。
|
||
}
|
||
|
||
try {
|
||
dialogReference.close();
|
||
} catch (closeDialogError) {
|
||
// 已关闭的 ScriptUI 对话框无需重复处理。
|
||
}
|
||
|
||
try {
|
||
app.refresh();
|
||
} catch (refreshUIError) {
|
||
// 某些旧版 Photoshop 没有公开 refresh;关闭窗口本身仍有效。
|
||
}
|
||
}
|
||
|
||
var operationIsSwap = swapRadio.value;
|
||
var firstIndex = firstDropdown.selection.index;
|
||
var secondIndex = secondDropdown.selection.index;
|
||
var copyIndex = copyDropdown.selection.index;
|
||
|
||
// 选项已保存为普通数值;切换文档和处理像素前彻底释放窗口。
|
||
disposeScriptUIDialog(dialog);
|
||
dialog = null;
|
||
|
||
if (operationIsSwap && firstIndex === secondIndex) {
|
||
alert("请选择两个不同的通道。", SCRIPT_TITLE, true);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
app.activeDocument = doc;
|
||
|
||
var resultLayer;
|
||
|
||
if (operationIsSwap) {
|
||
resultLayer = swapChannelsToCurrentDocument(
|
||
doc,
|
||
firstIndex,
|
||
secondIndex
|
||
);
|
||
alert(
|
||
"已交换 " +
|
||
CHANNEL_NAMES[firstIndex] +
|
||
" 与 " +
|
||
CHANNEL_NAMES[secondIndex] +
|
||
" 通道,并放入当前文档的新图层:\n" +
|
||
resultLayer.name,
|
||
SCRIPT_TITLE
|
||
);
|
||
} else {
|
||
resultLayer = copyChannelToWhiteAlphaLayer(doc, copyIndex);
|
||
alert(
|
||
"已把 " +
|
||
CHANNEL_NAMES[copyIndex] +
|
||
" 通道写入纯白图层的透明度:\n" +
|
||
resultLayer.name,
|
||
SCRIPT_TITLE
|
||
);
|
||
}
|
||
} catch (error) {
|
||
try {
|
||
app.activeDocument = doc;
|
||
} catch (activateError) {
|
||
// 文档状态异常时保留原始错误信息。
|
||
}
|
||
|
||
var details = error.message;
|
||
if (error.number !== undefined) {
|
||
details += "\n错误编号:" + error.number;
|
||
}
|
||
if (error.line !== undefined) {
|
||
details += "\n脚本行号:" + error.line;
|
||
}
|
||
|
||
alert(
|
||
(operationIsSwap ? "通道交换失败。\n\n" : "通道复制失败。\n\n") +
|
||
details +
|
||
"\n\n临时文档已自动关闭,当前文档不会留下未完成的新图层。",
|
||
SCRIPT_TITLE,
|
||
true
|
||
);
|
||
}
|
||
|
||
function swapChannelsToCurrentDocument(
|
||
sourceDocument,
|
||
channelA,
|
||
channelB
|
||
) {
|
||
var anchorLayer = sourceDocument.activeLayer;
|
||
var temporaryDocument = null;
|
||
var placedLayer = null;
|
||
var outputLayer = null;
|
||
var outputName =
|
||
"交换_" +
|
||
CHANNEL_NAMES[channelA] +
|
||
"-" +
|
||
CHANNEL_NAMES[channelB];
|
||
|
||
try {
|
||
// 只把当前选中图层隔离为临时像素层;源文档及其他图层不参与。
|
||
temporaryDocument = createIsolatedLayerDocument(
|
||
sourceDocument,
|
||
makeTemporaryName("Swap")
|
||
);
|
||
app.activeDocument = temporaryDocument;
|
||
deselectSafely(temporaryDocument);
|
||
activateRGBComposite(temporaryDocument);
|
||
|
||
if (channelA < 3 && channelB < 3) {
|
||
// R/G/B 之间继续使用 Photoshop 原生通道混合器算法。
|
||
outputLayer = temporaryDocument.activeLayer;
|
||
applyRGBSwap(outputLayer, channelA, channelB);
|
||
} else {
|
||
// 涉及 A 时,仅提取“所选颜色通道 × 原图层 A”。
|
||
// 例如 G-A:只有 G × A 的部分显示纯绿色,其余透明。
|
||
var sourceLayer = temporaryDocument.activeLayer;
|
||
var storedChannels = captureRGBAChannels(
|
||
temporaryDocument,
|
||
sourceLayer,
|
||
"__RGBA_Tools_Swap_"
|
||
);
|
||
var colorChannelIndex =
|
||
channelA === 3 ? channelB : channelA;
|
||
var outputTransparencyChannel =
|
||
createCopyTransparencyChannel(
|
||
temporaryDocument,
|
||
storedChannels,
|
||
colorChannelIndex
|
||
);
|
||
|
||
outputLayer = createPrimaryColorAlphaLayer(
|
||
temporaryDocument,
|
||
outputTransparencyChannel,
|
||
colorChannelIndex
|
||
);
|
||
}
|
||
|
||
outputLayer.name = outputName;
|
||
placedLayer = placeStandaloneLayer(
|
||
outputLayer,
|
||
sourceDocument,
|
||
anchorLayer,
|
||
outputName
|
||
);
|
||
return placedLayer;
|
||
} catch (swapError) {
|
||
removeLayerSafely(placedLayer);
|
||
placedLayer = null;
|
||
throw swapError;
|
||
} finally {
|
||
closeDocumentSafely(temporaryDocument);
|
||
restoreTargetDocument(
|
||
sourceDocument,
|
||
placedLayer !== null ? placedLayer : anchorLayer
|
||
);
|
||
}
|
||
}
|
||
|
||
function copyChannelToWhiteAlphaLayer(documentReference, channelIndex) {
|
||
var anchorLayer = documentReference.activeLayer;
|
||
var sourceName = anchorLayer.name;
|
||
var temporaryDocument = null;
|
||
var placedLayer = null;
|
||
|
||
try {
|
||
temporaryDocument = createIsolatedLayerDocument(
|
||
documentReference,
|
||
makeTemporaryName("Copy")
|
||
);
|
||
app.activeDocument = temporaryDocument;
|
||
|
||
var sourcePixelLayer = temporaryDocument.activeLayer;
|
||
var storedChannels = captureRGBAChannels(
|
||
temporaryDocument,
|
||
sourcePixelLayer,
|
||
"__RGBA_Tools_Copy_"
|
||
);
|
||
var outputName = sourceName + "_" + CHANNEL_NAMES[channelIndex];
|
||
var outputTransparencyChannel = createCopyTransparencyChannel(
|
||
temporaryDocument,
|
||
storedChannels,
|
||
channelIndex
|
||
);
|
||
var outputLayer = createWhiteAlphaLayer(
|
||
temporaryDocument,
|
||
outputTransparencyChannel
|
||
);
|
||
outputLayer.name = outputName;
|
||
|
||
placedLayer = placeStandaloneLayer(
|
||
outputLayer,
|
||
documentReference,
|
||
anchorLayer,
|
||
outputName
|
||
);
|
||
return placedLayer;
|
||
} catch (copyError) {
|
||
removeLayerSafely(placedLayer);
|
||
placedLayer = null;
|
||
throw copyError;
|
||
} finally {
|
||
closeDocumentSafely(temporaryDocument);
|
||
restoreTargetDocument(
|
||
documentReference,
|
||
placedLayer !== null ? placedLayer : anchorLayer
|
||
);
|
||
}
|
||
}
|
||
|
||
function applyRGBSwap(pixelLayer, channelA, channelB) {
|
||
// 每一行表示输出 R、G、B 从输入 R、G、B 获取的百分比;
|
||
// 第四个数值是常数,交换通道时保持为 0。
|
||
var matrix = [
|
||
[100, 0, 0, 0],
|
||
[0, 100, 0, 0],
|
||
[0, 0, 100, 0]
|
||
];
|
||
var temporaryRow = matrix[channelA];
|
||
matrix[channelA] = matrix[channelB];
|
||
matrix[channelB] = temporaryRow;
|
||
pixelLayer.mixChannels(matrix, false);
|
||
}
|
||
|
||
function createIsolatedLayerDocument(sourceDocument, temporaryName) {
|
||
var temporaryDocument = sourceDocument.duplicate(temporaryName, false);
|
||
|
||
try {
|
||
app.activeDocument = temporaryDocument;
|
||
deselectSafely(temporaryDocument);
|
||
activateRGBComposite(temporaryDocument);
|
||
|
||
// 整个文档副本只用来保持尺寸、位深和颜色配置一致。
|
||
// 把镜像的当前图层复制进临时组并合并,可得到独立普通像素层。
|
||
var mirroredSourceLayer = temporaryDocument.activeLayer;
|
||
var wrapperGroup = temporaryDocument.layerSets.add();
|
||
wrapperGroup.name = "__RGBA_Tools_Isolate__";
|
||
wrapperGroup.visible = true;
|
||
wrapperGroup.opacity = 100;
|
||
wrapperGroup.blendMode = BlendMode.NORMAL;
|
||
|
||
var isolatedCopy = mirroredSourceLayer.duplicate();
|
||
prepareLayerForIsolation(isolatedCopy);
|
||
isolatedCopy.move(wrapperGroup, ElementPlacement.INSIDE);
|
||
|
||
temporaryDocument.activeLayer = wrapperGroup;
|
||
var pixelLayer = wrapperGroup.merge();
|
||
pixelLayer.name = "__RGBA_Tools_Source__";
|
||
|
||
// 合并结果位于最上方;移除其余镜像图层,只保留当前图层内容。
|
||
while (temporaryDocument.layers.length > 1) {
|
||
var lowerLayer = temporaryDocument.layers[
|
||
temporaryDocument.layers.length - 1
|
||
];
|
||
unlockLayerSafely(lowerLayer);
|
||
lowerLayer.remove();
|
||
}
|
||
|
||
prepareStandaloneArtLayer(pixelLayer);
|
||
temporaryDocument.activeLayer = pixelLayer;
|
||
activateRGBComposite(temporaryDocument);
|
||
return temporaryDocument;
|
||
} catch (isolateError) {
|
||
closeDocumentSafely(temporaryDocument);
|
||
throw isolateError;
|
||
}
|
||
}
|
||
|
||
function prepareLayerForIsolation(layerReference) {
|
||
unlockLayerSafely(layerReference);
|
||
layerReference.visible = true;
|
||
layerReference.opacity = 100;
|
||
|
||
try {
|
||
layerReference.blendMode = BlendMode.NORMAL;
|
||
} catch (blendModeError) {
|
||
// 合并临时组时仍会转换为普通像素层。
|
||
}
|
||
|
||
enforceNoClippingMask(layerReference);
|
||
}
|
||
|
||
function captureRGBAChannels(
|
||
documentReference,
|
||
sourceLayer,
|
||
channelNamePrefix
|
||
) {
|
||
var componentChannels = [
|
||
documentReference.channels[0],
|
||
documentReference.channels[1],
|
||
documentReference.channels[2]
|
||
];
|
||
var storedChannels = [];
|
||
var componentIndex;
|
||
|
||
for (componentIndex = 0; componentIndex < 3; componentIndex++) {
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
loadChannelSelectionSafely(
|
||
documentReference,
|
||
componentChannels[componentIndex]
|
||
);
|
||
storedChannels[componentIndex] = storeSelectionAsAlphaChannel(
|
||
documentReference,
|
||
channelNamePrefix + CHANNEL_NAMES[componentIndex]
|
||
);
|
||
}
|
||
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
documentReference.activeLayer = sourceLayer;
|
||
|
||
try {
|
||
loadActiveLayerTransparency();
|
||
} catch (emptyTransparencyError) {
|
||
// 完全透明图层可能报告“没有选中像素”,按空 A 通道处理。
|
||
deselectSafely(documentReference);
|
||
}
|
||
|
||
storedChannels[3] = storeSelectionAsAlphaChannel(
|
||
documentReference,
|
||
channelNamePrefix + "A"
|
||
);
|
||
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
documentReference.activeLayer = sourceLayer;
|
||
return storedChannels;
|
||
}
|
||
|
||
function storeSelectionAsAlphaChannel(documentReference, channelName) {
|
||
var storageChannel = documentReference.channels.add();
|
||
storageChannel.name = channelName;
|
||
|
||
try {
|
||
// 没有选区时 Photoshop 可能抛错;新建通道保持全黑即可。
|
||
documentReference.selection.store(
|
||
storageChannel,
|
||
SelectionType.REPLACE
|
||
);
|
||
} catch (emptySelectionError) {
|
||
deselectSafely(documentReference);
|
||
}
|
||
|
||
activateRGBComposite(documentReference);
|
||
return storageChannel;
|
||
}
|
||
|
||
function createCopyTransparencyChannel(
|
||
documentReference,
|
||
storedChannels,
|
||
channelIndex
|
||
) {
|
||
// 复制 A 时直接使用当前图层自身透明度。
|
||
if (channelIndex === 3) {
|
||
return storedChannels[3];
|
||
}
|
||
|
||
var colorChannel = storedChannels[channelIndex];
|
||
var alphaChannel = storedChannels[3];
|
||
var colorRange = inspectChannelRange(colorChannel);
|
||
var alphaRange = inspectChannelRange(alphaChannel);
|
||
var resultName =
|
||
"__RGBA_Tools_Copy_" +
|
||
CHANNEL_NAMES[channelIndex] +
|
||
"_x_A";
|
||
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
|
||
// 任一输入全黑,乘积必定全黑;直接建立空通道。
|
||
if (
|
||
(colorRange.known && colorRange.allBlack) ||
|
||
(alphaRange.known && alphaRange.allBlack)
|
||
) {
|
||
return storeSelectionAsAlphaChannel(
|
||
documentReference,
|
||
resultName
|
||
);
|
||
}
|
||
|
||
try {
|
||
if (colorRange.known && colorRange.allWhite) {
|
||
// 255 × A = A。
|
||
documentReference.selection.load(
|
||
alphaChannel,
|
||
SelectionType.REPLACE
|
||
);
|
||
} else if (alphaRange.known && alphaRange.allWhite) {
|
||
// 通道 × 255 = 通道。
|
||
documentReference.selection.load(
|
||
colorChannel,
|
||
SelectionType.REPLACE
|
||
);
|
||
} else {
|
||
documentReference.selection.load(
|
||
colorChannel,
|
||
SelectionType.REPLACE
|
||
);
|
||
// Photoshop 对灰度选区执行“交叉”,得到逐像素乘积:
|
||
// 输出 Alpha = 颜色通道 × 当前图层 Alpha。
|
||
documentReference.selection.load(
|
||
alphaChannel,
|
||
SelectionType.INTERSECT
|
||
);
|
||
}
|
||
} catch (intersectError) {
|
||
throw new Error(
|
||
"无法计算 " +
|
||
CHANNEL_NAMES[channelIndex] +
|
||
" 通道与当前图层透明度的交集:" +
|
||
intersectError.message
|
||
);
|
||
}
|
||
|
||
return storeSelectionAsAlphaChannel(
|
||
documentReference,
|
||
resultName
|
||
);
|
||
}
|
||
|
||
function buildLayerFromRGBAChannels(
|
||
documentReference,
|
||
storedChannels,
|
||
mapping
|
||
) {
|
||
var outputLayer = documentReference.artLayers.add();
|
||
prepareStandaloneArtLayer(outputLayer);
|
||
documentReference.activeLayer = outputLayer;
|
||
activateRGBComposite(documentReference);
|
||
|
||
// 先得到不透明黑底,再逐一写入三个颜色通道。
|
||
fillEntireLayer(documentReference, makeGrayColor(0));
|
||
|
||
var componentChannels = [
|
||
documentReference.channels[0],
|
||
documentReference.channels[1],
|
||
documentReference.channels[2]
|
||
];
|
||
var outputIndex;
|
||
|
||
for (outputIndex = 0; outputIndex < 3; outputIndex++) {
|
||
writeStoredChannelToComponent(
|
||
documentReference,
|
||
outputLayer,
|
||
componentChannels[outputIndex],
|
||
storedChannels[mapping[outputIndex]]
|
||
);
|
||
}
|
||
|
||
writeStoredChannelToLayerTransparency(
|
||
documentReference,
|
||
outputLayer,
|
||
storedChannels[mapping[3]]
|
||
);
|
||
prepareStandaloneArtLayer(outputLayer);
|
||
documentReference.activeLayer = outputLayer;
|
||
activateRGBComposite(documentReference);
|
||
deselectSafely(documentReference);
|
||
return outputLayer;
|
||
}
|
||
|
||
function writeStoredChannelToComponent(
|
||
documentReference,
|
||
outputLayer,
|
||
targetComponentChannel,
|
||
sourceStorageChannel
|
||
) {
|
||
var range = inspectChannelRange(sourceStorageChannel);
|
||
documentReference.activeLayer = outputLayer;
|
||
activateRGBComposite(documentReference);
|
||
deselectSafely(documentReference);
|
||
|
||
if (range.known && range.allBlack) {
|
||
return;
|
||
}
|
||
|
||
documentReference.activeChannels = [targetComponentChannel];
|
||
|
||
if (range.known && range.allWhite) {
|
||
documentReference.selection.selectAll();
|
||
} else {
|
||
activateRGBComposite(documentReference);
|
||
loadChannelSelectionSafely(
|
||
documentReference,
|
||
sourceStorageChannel
|
||
);
|
||
documentReference.activeChannels = [targetComponentChannel];
|
||
|
||
if (!range.known && !hasSelection(documentReference)) {
|
||
activateRGBComposite(documentReference);
|
||
deselectSafely(documentReference);
|
||
return;
|
||
}
|
||
}
|
||
|
||
documentReference.selection.fill(
|
||
makeGrayColor(255),
|
||
ColorBlendMode.NORMAL,
|
||
100,
|
||
false
|
||
);
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
}
|
||
|
||
function writeStoredChannelToLayerTransparency(
|
||
documentReference,
|
||
outputLayer,
|
||
sourceStorageChannel
|
||
) {
|
||
var range = inspectChannelRange(sourceStorageChannel);
|
||
documentReference.activeLayer = outputLayer;
|
||
activateRGBComposite(documentReference);
|
||
deselectSafely(documentReference);
|
||
|
||
if (range.known && range.allWhite) {
|
||
return;
|
||
}
|
||
|
||
if (range.known && range.allBlack) {
|
||
documentReference.selection.selectAll();
|
||
documentReference.selection.clear();
|
||
deselectSafely(documentReference);
|
||
return;
|
||
}
|
||
|
||
loadChannelSelectionSafely(documentReference, sourceStorageChannel);
|
||
|
||
if (!range.known && !hasSelection(documentReference)) {
|
||
documentReference.selection.selectAll();
|
||
documentReference.selection.clear();
|
||
deselectSafely(documentReference);
|
||
return;
|
||
}
|
||
|
||
documentReference.selection.invert();
|
||
|
||
if (range.known || hasSelection(documentReference)) {
|
||
documentReference.selection.clear();
|
||
}
|
||
|
||
deselectSafely(documentReference);
|
||
}
|
||
|
||
function createWhiteAlphaLayer(
|
||
documentReference,
|
||
sourceStorageChannel
|
||
) {
|
||
return createSolidColorAlphaLayer(
|
||
documentReference,
|
||
sourceStorageChannel,
|
||
makeGrayColor(255)
|
||
);
|
||
}
|
||
|
||
function createPrimaryColorAlphaLayer(
|
||
documentReference,
|
||
sourceStorageChannel,
|
||
colorChannelIndex
|
||
) {
|
||
return createSolidColorAlphaLayer(
|
||
documentReference,
|
||
sourceStorageChannel,
|
||
makePrimaryColor(colorChannelIndex)
|
||
);
|
||
}
|
||
|
||
function createSolidColorAlphaLayer(
|
||
documentReference,
|
||
sourceStorageChannel,
|
||
fillColor
|
||
) {
|
||
var outputLayer = documentReference.artLayers.add();
|
||
prepareStandaloneArtLayer(outputLayer);
|
||
documentReference.activeLayer = outputLayer;
|
||
activateRGBComposite(documentReference);
|
||
deselectSafely(documentReference);
|
||
|
||
var range = inspectChannelRange(sourceStorageChannel);
|
||
|
||
// 全黑结果保持为空图层:没有任何可见像素。
|
||
if (range.known && range.allBlack) {
|
||
return outputLayer;
|
||
}
|
||
|
||
if (range.known && range.allWhite) {
|
||
documentReference.selection.selectAll();
|
||
} else {
|
||
loadChannelSelectionSafely(
|
||
documentReference,
|
||
sourceStorageChannel
|
||
);
|
||
|
||
if (!range.known && !hasSelection(documentReference)) {
|
||
deselectSafely(documentReference);
|
||
return outputLayer;
|
||
}
|
||
}
|
||
|
||
// 从透明空层开始,使用来源通道的灰度作为输出透明度;
|
||
// 非透明像素只写入指定纯色,不创建蒙版或剪切关系。
|
||
documentReference.selection.fill(
|
||
fillColor,
|
||
ColorBlendMode.NORMAL,
|
||
100,
|
||
false
|
||
);
|
||
prepareStandaloneArtLayer(outputLayer);
|
||
documentReference.activeLayer = outputLayer;
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
return outputLayer;
|
||
}
|
||
|
||
function inspectChannelRange(channelReference) {
|
||
var result = {
|
||
known: false,
|
||
allBlack: false,
|
||
allWhite: false
|
||
};
|
||
|
||
try {
|
||
var histogram = channelReference.histogram;
|
||
if (histogram === undefined || histogram.length < 2) {
|
||
return result;
|
||
}
|
||
|
||
var hasNonBlackPixels = false;
|
||
var hasNonWhitePixels = false;
|
||
var lastIndex = histogram.length - 1;
|
||
var index;
|
||
|
||
for (index = 0; index < histogram.length; index++) {
|
||
if (histogram[index] > 0) {
|
||
if (index > 0) {
|
||
hasNonBlackPixels = true;
|
||
}
|
||
if (index < lastIndex) {
|
||
hasNonWhitePixels = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
result.known = true;
|
||
result.allBlack = !hasNonBlackPixels;
|
||
result.allWhite = !hasNonWhitePixels;
|
||
} catch (histogramError) {
|
||
// 极少数旧版本无法读取 Alpha 直方图时,后续回退到选区检测。
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function loadChannelSelectionSafely(
|
||
documentReference,
|
||
channelReference
|
||
) {
|
||
deselectSafely(documentReference);
|
||
activateRGBComposite(documentReference);
|
||
|
||
try {
|
||
documentReference.selection.load(
|
||
channelReference,
|
||
SelectionType.REPLACE
|
||
);
|
||
} catch (loadSelectionError) {
|
||
deselectSafely(documentReference);
|
||
}
|
||
}
|
||
|
||
function placeStandaloneLayer(
|
||
sourceLayer,
|
||
targetDocument,
|
||
anchorLayer,
|
||
layerName
|
||
) {
|
||
var placedLayer = null;
|
||
|
||
try {
|
||
placedLayer = sourceLayer.duplicate(targetDocument);
|
||
app.activeDocument = targetDocument;
|
||
placedLayer.move(anchorLayer, ElementPlacement.PLACEBEFORE);
|
||
prepareStandaloneArtLayer(placedLayer);
|
||
placedLayer.name = layerName;
|
||
enforceNoClippingMask(placedLayer);
|
||
|
||
targetDocument.activeLayer = placedLayer;
|
||
activateRGBComposite(targetDocument);
|
||
return placedLayer;
|
||
} catch (placeError) {
|
||
removeLayerSafely(placedLayer);
|
||
throw placeError;
|
||
}
|
||
}
|
||
|
||
function prepareStandaloneArtLayer(layerReference) {
|
||
unlockLayerSafely(layerReference);
|
||
layerReference.visible = true;
|
||
layerReference.opacity = 100;
|
||
|
||
try {
|
||
layerReference.fillOpacity = 100;
|
||
} catch (fillOpacityError) {
|
||
// 某些旧版本或图层类型没有 fillOpacity。
|
||
}
|
||
|
||
try {
|
||
layerReference.blendMode = BlendMode.NORMAL;
|
||
} catch (blendModeError) {
|
||
// 输出层最终为 ArtLayer;保留此兼容保护。
|
||
}
|
||
|
||
enforceNoClippingMask(layerReference);
|
||
}
|
||
|
||
function enforceNoClippingMask(layerReference) {
|
||
if (layerReference.typename !== "ArtLayer") {
|
||
return;
|
||
}
|
||
|
||
var isClippingLayer;
|
||
|
||
try {
|
||
// 对 grouped 已为 false 的普通图层再次赋值,在部分 Photoshop
|
||
// 版本中会触发“释放剪贴蒙版当前不可用”。必须先判断再执行。
|
||
isClippingLayer = layerReference.grouped;
|
||
} catch (readClippingError) {
|
||
throw new Error(
|
||
"无法确认结果图层是否为剪切蒙版,已取消生成。"
|
||
);
|
||
}
|
||
|
||
if (isClippingLayer === true) {
|
||
try {
|
||
layerReference.grouped = false;
|
||
} catch (releaseClippingError) {
|
||
throw new Error(
|
||
"无法解除结果图层的剪切蒙版,已取消生成。"
|
||
);
|
||
}
|
||
}
|
||
|
||
try {
|
||
isClippingLayer = layerReference.grouped;
|
||
} catch (verifyClippingError) {
|
||
throw new Error(
|
||
"无法验证结果图层的剪切状态,已取消生成。"
|
||
);
|
||
}
|
||
|
||
if (isClippingLayer === true) {
|
||
throw new Error(
|
||
"结果图层仍处于剪切蒙版状态,已取消生成。"
|
||
);
|
||
}
|
||
}
|
||
|
||
function unlockLayerSafely(layerReference) {
|
||
if (layerReference === null || layerReference === undefined) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
layerReference.allLocked = false;
|
||
} catch (allLockedError) {
|
||
// 继续尝试解除各项锁定。
|
||
}
|
||
try {
|
||
layerReference.pixelsLocked = false;
|
||
} catch (pixelsLockedError) {
|
||
// 图层组没有此属性。
|
||
}
|
||
try {
|
||
layerReference.positionLocked = false;
|
||
} catch (positionLockedError) {
|
||
// 旧版本可能不支持。
|
||
}
|
||
try {
|
||
layerReference.transparentPixelsLocked = false;
|
||
} catch (transparentPixelsLockedError) {
|
||
// 图层组没有此属性。
|
||
}
|
||
}
|
||
|
||
function activateRGBComposite(documentReference) {
|
||
documentReference.activeChannels = [
|
||
documentReference.channels[0],
|
||
documentReference.channels[1],
|
||
documentReference.channels[2]
|
||
];
|
||
}
|
||
|
||
function loadActiveLayerTransparency() {
|
||
var descriptor = new ActionDescriptor();
|
||
var selectionReference = new ActionReference();
|
||
selectionReference.putProperty(
|
||
charIDToTypeID("Chnl"),
|
||
charIDToTypeID("fsel")
|
||
);
|
||
descriptor.putReference(
|
||
charIDToTypeID("null"),
|
||
selectionReference
|
||
);
|
||
|
||
var transparencyReference = new ActionReference();
|
||
transparencyReference.putEnumerated(
|
||
charIDToTypeID("Chnl"),
|
||
charIDToTypeID("Chnl"),
|
||
charIDToTypeID("Trsp")
|
||
);
|
||
descriptor.putReference(
|
||
charIDToTypeID("T "),
|
||
transparencyReference
|
||
);
|
||
|
||
executeAction(
|
||
charIDToTypeID("setd"),
|
||
descriptor,
|
||
DialogModes.NO
|
||
);
|
||
}
|
||
|
||
function hasSelection(documentReference) {
|
||
try {
|
||
var bounds = documentReference.selection.bounds;
|
||
return bounds !== undefined && bounds !== null;
|
||
} catch (selectionError) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function deselectSafely(documentReference) {
|
||
try {
|
||
documentReference.selection.deselect();
|
||
} catch (deselectError) {
|
||
// 没有选区时无需处理。
|
||
}
|
||
}
|
||
|
||
function fillEntireLayer(documentReference, color) {
|
||
documentReference.selection.selectAll();
|
||
documentReference.selection.fill(
|
||
color,
|
||
ColorBlendMode.NORMAL,
|
||
100,
|
||
false
|
||
);
|
||
deselectSafely(documentReference);
|
||
}
|
||
|
||
function makeGrayColor(value) {
|
||
var color = new SolidColor();
|
||
color.rgb.red = value;
|
||
color.rgb.green = value;
|
||
color.rgb.blue = value;
|
||
return color;
|
||
}
|
||
|
||
function makePrimaryColor(channelIndex) {
|
||
var color = new SolidColor();
|
||
color.rgb.red = channelIndex === 0 ? 255 : 0;
|
||
color.rgb.green = channelIndex === 1 ? 255 : 0;
|
||
color.rgb.blue = channelIndex === 2 ? 255 : 0;
|
||
return color;
|
||
}
|
||
|
||
function makeTemporaryName(operationName) {
|
||
return "__RGBA_Tools_" + operationName + "_" + new Date().getTime();
|
||
}
|
||
|
||
function restoreTargetDocument(documentReference, activeLayerReference) {
|
||
try {
|
||
app.activeDocument = documentReference;
|
||
activateRGBComposite(documentReference);
|
||
|
||
if (
|
||
activeLayerReference !== null &&
|
||
activeLayerReference !== undefined
|
||
) {
|
||
documentReference.activeLayer = activeLayerReference;
|
||
}
|
||
} catch (restoreError) {
|
||
// 清理阶段不覆盖原始结果或错误。
|
||
}
|
||
}
|
||
|
||
function closeDocumentSafely(documentReference) {
|
||
if (documentReference !== null && documentReference !== undefined) {
|
||
try {
|
||
documentReference.close(SaveOptions.DONOTSAVECHANGES);
|
||
} catch (closeError) {
|
||
// 清理阶段不覆盖原始结果或错误。
|
||
}
|
||
}
|
||
}
|
||
|
||
function removeLayerSafely(layerReference) {
|
||
if (layerReference !== null && layerReference !== undefined) {
|
||
try {
|
||
layerReference.remove();
|
||
} catch (removeLayerError) {
|
||
// 清理阶段不覆盖原始结果或错误。
|
||
}
|
||
}
|
||
}
|
||
})();
|