从PS插件源码入手:手把手教你读懂并修改那个‘秋色效果’的JSX脚本
从零解析PS脚本:如何拆解并定制"秋色效果"滤镜的JSX代码
当你第一次看到Photoshop脚本代码时,那些密密麻麻的ActionDescriptor和cTID函数调用可能让你望而生畏。但别担心,每个PS脚本高手都曾经历过这个阶段。本文将带你像侦探破案一样,逐行解剖一个完整的"秋色效果"脚本,不仅让你理解它的工作原理,还能教会你如何修改它来创造属于自己的独特滤镜效果。无论你是设计师想扩展PS能力,还是开发者想进入插件创作领域,这篇指南都将成为你的实用手册。
1. 脚本开发基础:理解PS的JSX环境
Photoshop的脚本系统基于ECMAScript(类似JavaScript)的扩展版本,称为ExtendScript。与网页中的JavaScript不同,PS脚本可以直接调用Photoshop的各种功能,从简单的图层操作到复杂的图像处理都能实现。
1.1 脚本文件基础结构
每个PS脚本通常包含以下几个核心部分:
- 初始化函数:脚本的入口点,如示例中的
StartAutumnColours.main - 工具函数:如
cTID和sTID这类辅助转换函数 - 操作步骤:按顺序执行的一系列PS命令,示例中的
step1到step5 - 错误处理:
try/catch块用于捕获执行中的问题
// 典型的PS脚本结构示例 function mainFunction() { // 步骤1 // 步骤2 // ... } mainFunction.main = function() { mainFunction(); }; mainFunction.main();1.2 关键工具函数解析
让我们先看看脚本开头的两个神秘函数:
cTID = function(s) { return app.charIDToTypeID(s); }; sTID = function(s) { return app.stringIDToTypeID(s); };这两个函数是PS脚本中的常客:
cTID:将4字符ID转换为PS内部使用的类型IDsTID:将字符串ID转换为PS内部类型ID
提示:PS内部使用4字符代码(如'Lyr '代表图层)和字符串ID(如'mergeLayersNew')来标识各种操作和属性。这些转换函数让代码更简洁。
2. 逐行拆解"秋色效果"的实现逻辑
现在,让我们深入脚本的每个步骤,看看它如何一步步创造出秋色效果。
2.1 第一步:设置基础图层属性
function step1(enabled, withDialog) { if (enabled != undefined && !enabled) return; var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); var desc1 = new ActionDescriptor(); var ref1 = new ActionReference(); ref1.putProperty(cTID('Lyr '), cTID('Bckg')); desc1.putReference(cTID('null'), ref1); var desc2 = new ActionDescriptor(); desc2.putString(cTID('Nm '), "Base Layer"); desc2.putUnitDouble(cTID('Opct'), cTID('#Prc'), 100); desc2.putEnumerated(cTID('Md '), cTID('BlnM'), cTID('Nrml')); desc1.putObject(cTID('T '), cTID('Lyr '), desc2); executeAction(cTID('setd'), desc1, dialogMode); };这段代码做了以下工作:
- 创建一个ActionDescriptor(动作描述符)来定义图层属性
- 设置图层名称为"Base Layer"
- 设置不透明度为100%
- 设置混合模式为"Normal"
- 执行
setd(set descriptor)动作应用这些设置
2.2 第二步:复制基础图层
function step2(enabled, withDialog) { if (enabled != undefined && !enabled) return; var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); var desc1 = new ActionDescriptor(); var ref1 = new ActionReference(); ref1.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt')); desc1.putReference(cTID('null'), ref1); desc1.putString(cTID('Nm '), "Base Layer copy"); desc1.putInteger(cTID('Vrsn'), 5); executeAction(cTID('Dplc'), desc1, dialogMode); };这一步创建了基础图层的副本,关键点包括:
ref1.putEnumerated指定了当前目标图层Dplc动作代表"duplicate"(复制)- 新图层被命名为"Base Layer copy"
2.3 第三步:创建调整图层
function step3(enabled, withDialog) { if (enabled != undefined && !enabled) return; var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); var desc1 = new ActionDescriptor(); var ref1 = new ActionReference(); ref1.putClass(cTID('AdjL')); desc1.putReference(cTID('null'), ref1); var desc2 = new ActionDescriptor(); var desc3 = new ActionDescriptor(); desc3.putEnumerated(sTID("presetKind"), sTID("presetKindType"), sTID("presetKindDefault")); desc3.putBoolean(cTID('Clrz'), false); desc2.putObject(cTID('Type'), cTID('HStr'), desc3); desc1.putObject(cTID('Usng'), cTID('AdjL'), desc2); executeAction(cTID('Mk '), desc1, dialogMode); };这里创建了一个色相/饱和度调整图层:
AdjL代表Adjustment Layer(调整图层)HStr指Hue/Saturation(色相/饱和度)Mk是"make"的缩写,表示创建操作
2.4 第四步:设置色相/饱和度参数
这是实现秋色效果的核心步骤:
function step4(enabled, withDialog) { if (enabled != undefined && !enabled) return; var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); var desc1 = new ActionDescriptor(); var ref1 = new ActionReference(); ref1.putEnumerated(cTID('AdjL'), cTID('Ordn'), cTID('Trgt')); desc1.putReference(cTID('null'), ref1); var desc2 = new ActionDescriptor(); desc2.putEnumerated(sTID("presetKind"), sTID("presetKindType"), sTID("presetKindCustom")); var list1 = new ActionList(); var desc3 = new ActionDescriptor(); desc3.putInteger(cTID('LclR'), 3); desc3.putInteger(cTID('BgnR'), 24); desc3.putInteger(cTID('BgnS'), 105); desc3.putInteger(cTID('EndS'), 135); desc3.putInteger(cTID('EndR'), 195); desc3.putInteger(cTID('H '), -95); desc3.putInteger(cTID('Strt'), 0); desc3.putInteger(cTID('Lght'), 0); list1.putObject(cTID('Hst2'), desc3); desc2.putList(cTID('Adjs'), list1); desc1.putObject(cTID('T '), cTID('HStr'), desc2); executeAction(cTID('setd'), desc1, dialogMode); };关键参数解析:
| 参数代码 | 含义 | 示例值 | 作用 |
|---|---|---|---|
| LclR | 本地化范围 | 3 | 控制色彩调整的范围 |
| BgnR | 开始范围 | 24 | 色相调整起始点 |
| BgnS | 开始饱和度 | 105 | 饱和度调整起始点 |
| EndS | 结束饱和度 | 135 | 饱和度调整结束点 |
| EndR | 结束范围 | 195 | 色相调整结束点 |
| H | 色相偏移 | -95 | 产生暖色调的关键参数 |
| Strt | 开始亮度 | 0 | 亮度调整起始点 |
| Lght | 亮度 | 0 | 亮度调整量 |
这些参数的组合产生了典型的秋色效果:暖色调、高饱和度的黄色和橙色。
2.5 第五步:合并图层
function step5(enabled, withDialog) { if (enabled != undefined && !enabled) return; var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); var desc1 = new ActionDescriptor(); executeAction(sTID('mergeLayersNew'), desc1, dialogMode); };这一步简单地将所有图层合并,完成效果应用。
3. 自定义你的专属滤镜效果
理解了脚本的工作原理后,我们可以开始修改它来创建不同的色彩效果。
3.1 创建青橙色调效果
青橙色调是另一种流行的色彩风格,我们可以通过修改step4中的参数来实现:
// 修改step4中的desc3部分 desc3.putInteger(cTID('LclR'), 3); desc3.putInteger(cTID('BgnR'), 180); // 调整为青色范围 desc3.putInteger(cTID('BgnS'), 90); desc3.putInteger(cTID('EndS'), 240); // 调整为橙色范围 desc3.putInteger(cTID('EndR'), 30); desc3.putInteger(cTID('H '), 20); // 适度调整色相 desc3.putInteger(cTID('Strt'), 0); desc3.putInteger(cTID('Lght'), 5); // 稍微提亮关键调整点:
- 将
BgnR(开始范围)设为180,对应青色区域 EndR(结束范围)设为30,对应橙色区域- 调整
H(色相)为正值,增强冷暖对比 - 轻微提高
Lght(亮度)使效果更通透
3.2 创建复古胶片效果
复古效果通常需要降低某些颜色的饱和度并添加轻微色偏:
desc3.putInteger(cTID('LclR'), 2); // 减小调整范围 desc3.putInteger(cTID('BgnR'), 30); desc3.putInteger(cTID('BgnS'), 80); // 降低饱和度起始值 desc3.putInteger(cTID('EndS'), 210); desc3.putInteger(cTID('EndR'), 60); desc3.putInteger(cTID('H '), -15); // 轻微暖色偏 desc3.putInteger(cTID('Strt'), 0); desc3.putInteger(cTID('Lght'), -5); // 稍微降低亮度3.3 参数调整参考表
下表列出常见色彩风格的关键参数设置:
| 效果类型 | LclR | BgnR | EndR | H | BgnS | EndS | Lght |
|---|---|---|---|---|---|---|---|
| 秋色效果 | 3 | 24 | 195 | -95 | 105 | 135 | 0 |
| 青橙色调 | 3 | 180 | 30 | 20 | 90 | 240 | 5 |
| 复古胶片 | 2 | 30 | 60 | -15 | 80 | 210 | -5 |
| 冷色调 | 3 | 180 | 240 | 30 | 70 | 120 | 10 |
| 粉彩效果 | 4 | 0 | 360 | 0 | 30 | 60 | 15 |
4. 进阶技巧:提升脚本的实用性和灵活性
4.1 添加用户界面控制
原始脚本的所有参数都是硬编码的,我们可以扩展它,添加简单的用户界面:
// 添加对话框函数 function showDialog() { var dlg = new Window('dialog', '自定义色彩效果'); // 添加控制元素 dlg.hueSlider = dlg.add('slider', undefined, 0, -180, 180); dlg.hueSlider.text = '色相偏移:'; dlg.satSlider = dlg.add('slider', undefined, 100, 0, 200); dlg.satSlider.text = '饱和度:'; // 确定和取消按钮 dlg.btnGroup = dlg.add('group'); dlg.btnGroup.add('button', undefined, '确定'); dlg.btnGroup.add('button', undefined, '取消'); if (dlg.show() == 1) { return { hue: dlg.hueSlider.value, saturation: dlg.satSlider.value }; } return null; } // 修改主函数 StartAutumnColours.main = function() { var settings = showDialog(); if (settings) { // 使用用户设置的值 globalHueValue = settings.hue; globalSatValue = settings.saturation; StartAutumnColours(); } };4.2 添加错误处理和日志功能
增强脚本的健壮性:
var log = []; function logStep(stepName) { log.push(stepName + ' - 执行时间: ' + new Date().toLocaleTimeString()); } function handleError(e, stepName) { log.push('错误在 ' + stepName + ': ' + e.message); alert('执行 ' + stepName + ' 时出错: ' + e.message); } // 修改try-catch块 try{ logStep('step1'); step1(); } catch(e){ handleError(e, 'step1'); };4.3 创建可重用的脚本框架
将通用功能抽象出来,方便创建新效果:
// 基础效果框架 function PS_Effect(name) { this.name = name; this.steps = []; this.log = []; this.addStep = function(stepFunc, stepName) { this.steps.push({ func: stepFunc, name: stepName || ('步骤' + (this.steps.length + 1)) }); }; this.execute = function() { this.log.push('开始执行效果: ' + this.name); for (var i = 0; i < this.steps.length; i++) { try { this.log.push('执行: ' + this.steps[i].name); this.steps[i].func(); } catch(e) { this.log.push('错误: ' + e.message); break; } } this.log.push('效果执行完成'); }; } // 使用框架创建秋色效果 var autumnEffect = new PS_Effect('秋色效果'); autumnEffect.addStep(step1, '设置基础图层'); autumnEffect.addStep(step2, '复制图层'); autumnEffect.addStep(step3, '创建调整层'); autumnEffect.addStep(step4, '设置色彩参数'); autumnEffect.addStep(step5, '合并图层'); autumnEffect.execute();