Photoshop脚本 环境搭建与运行方式

文件格式

ExtendScript 脚本使用 .jsx 扩展名(JavaScript eXtendScript)。

项目说明
后缀.jsx
引擎Adobe ExtendScript(ES3)
文件编码UTF-8(含 BOM 兼容性更佳)
支持版本所有支持脚本的 Photoshop 版本

开发工具

ExtendScript Toolkit(ESTK)

Adobe 官方提供的脚本 IDE,随 Creative Suite 安装(CS6 及更早版本包含)。功能:

  • 语法高亮、代码补全、断点调试
  • 直接运行和调试脚本
  • 对象模型查看器(Object Model Viewer)
  • JavaScript 控制台

如果找不到 ESTK,它通常安装在 Applications/Utilities/Adobe ExtendScript Toolkit/(macOS)或 C:\Program Files\Adobe\Adobe ExtendScript Toolkit\(Windows)。

VS Code

使用任何文本编辑器都可以编写 .jsx 文件。VS Code 安装 ExtendScript 扩展后可获得语法高亮和代码片段支持。由于 ExtendScript 只是普通的文本文件,记事本或 Sublime Text 也能胜任。

ES3 语法限制

ExtendScript 基于 ECMAScript 3,与现代 JavaScript 有显著差异:

javascript
// ✅ 正确——使用 var
var doc = app.activeDocument;
var layerName = "图层";

// ❌ 不支持 let(存在 bug)
// let count = 5;

// ❌ 错误——不支持箭头函数
// layers.forEach(l => alert(l.name));

// ✅ 正确——使用普通函数
function showLayerNames() {
  var i;
  for (i = 0; i < app.activeDocument.artLayers.length; i++) {
    alert(app.activeDocument.artLayers[i].name);
  }
}

// ❌ 错误——不支持模板字符串
// alert(`文档: ${doc.name}`);

// ✅ 正确——使用字符串拼接
alert("文档: " + doc.name);

ES3 环境限制

ES3 标准不包含 JSON 全局对象,因此 JSON.stringify()JSON.parse() 不可用:

javascript
// ❌ JSON 序列化不可用
// var json = JSON.stringify({name: "test"}); // ReferenceError

// ✅ 手动构造键值对字符串
function formatPairs(obj) {
  var parts = [];
  for (var key in obj) {
    parts.push(key + ": " + String(obj[key]));
  }
  return "{" + parts.join(", ") + "}";
}

替代方案:在脚本内自行实现轻量序列化函数。

运行脚本的方式

方法一:菜单浏览运行

文件 > 脚本 > 浏览,选择 .jsx 文件。Photoshop 立即执行脚本内容。

方法二:复制到脚本目录

.jsx 文件放入 Photoshop 的 Presets/Scripts 文件夹,重启 Photoshop 后出现在 文件 > 脚本 的子菜单中。

Windows 路径示例:

plaintext
C:\Program Files\Adobe\Adobe Photoshop CC\Presets\Scripts\

方法三:ESTK 运行

在 ExtendScript Toolkit 中打开 .jsx,点击运行按钮或按 F5,可以选择在 Photoshop 中执行。ESTK 提供 JavaScript 控制台,可配合 $.writeln() 输出调试信息。

方法四:跨应用脚本

通过 ESTK 连接可以控制任意运行的 Adobe 应用,甚至可以在一个脚本中同时操作 Photoshop 和 Illustrator。

调试方式

alert()

ExtendScript 中最简单的调试手段。弹出 Photoshop 对话框显示信息:

javascript
var doc = app.activeDocument;
alert("当前文档名称: " + doc.name);
alert("图层数量: " + doc.artLayers.length);

注意:doc.name 返回字符串,doc.path 返回 File 对象。当路径包含中文等非 ASCII 字符时,ExtendScript 会将它们进行 URL 编码

javascript
var doc = app.activeDocument;
alert(doc.name);                 // "example.psd"(纯 ASCII,正常)
alert(doc.path);                 // "C:/Users/Admin/%E4%B8%AD%E6%96%87"(中文被编码)

// 使用 decodeURI() 还原为可读的中文
alert(decodeURI(doc.name));      // "中文名.psd"
alert(decodeURI(doc.path));      // "C:/Users/Admin/中文"
alert(decodeURI(doc.path.fsName)); // "C:\Users\Admin\中文"

decodeURI() 是处理路径中非 ASCII 字符的必备工具,生产环境中广泛使用(File.nameFile.pathFile.fsNameFile.toString() 均受此影响)。

弹窗太多时,按住 Enter 键不放可快速跳过连续的 alert() 对话框。

写入日志文件

alert() 弹窗会阻塞脚本执行,不适用于循环中大量输出。如需记录运行日志,将输出写入文本文件更实用:

javascript
var logFile = new File("C:/Users/Administrator/Desktop/script-log.txt");
logFile.encoding = "UTF8";
logFile.open("a");                // "a" 追加模式
logFile.writeln("处理文件: " + decodeURI(app.activeDocument.name));
logFile.close();

全局 $ 对象

$ 是 ExtendScript 的全局工具对象,无需声明即可直接使用。常用属性和方法:

成员用途
$.evalFile(file)运行时加载并执行另一个 .jsx 文件
$.fileName当前脚本自身的文件路径
$.os操作系统名称(如 "Windows""Macintosh"
$.writeln(text)写入 ESTK 控制台(仅在 ESTK 中可见)
javascript
$.writeln("当前脚本: " + $.fileName);  // ESTK 控制台输出
alert("操作系统: " + $.os);             // 弹窗显示系统信息

抑制对话框

自动化运行时可禁用所有弹窗:

javascript
app.displayDialogs = DialogModes.NO;  // 不显示任何对话框(包括 alert)
// app.displayDialogs = DialogModes.ERROR;  // 仅显示错误
// app.displayDialogs = DialogModes.ALL;   // 显示所有对话框(默认)

错误处理

生产脚本中需要处理各种异常情况——文档不存在、文件损坏、操作被取消等。ExtendScript 支持标准的 try-catch

javascript
try {
  var doc = app.activeDocument;
  if (!doc) throw new Error("没有打开的文档");

  var layer = doc.artLayers.add();
  layer.name = "新图层";

} catch (err) {
  alert("错误: " + err);
}

常见模式:

javascript
// 模式 1:检查对象是否存在再操作
var file = new File("C:/Users/Administrator/Desktop/target.psd");
if (file.exists) {
  try {
    var doc = app.open(file);
    // 处理文档...
    doc.close(SaveOptions.SAVECHANGES);
  } catch (err) {
    alert("打开文件失败: " + err);
  }
} else {
  alert("文件不存在");
}

// 模式 2:静默处理可选操作(如删除可能不存在的图层)
try {
  doc.artLayers.getByName("临时图层").remove();
} catch (err) {
  // 图层不存在,无需处理
}

// 模式 3:确保文档关闭(即使出错)
var doc = app.open(file);
try {
  // 执行可能出错的操作
  doc.resizeImage(1920, null, 72, ResampleMethod.BICUBIC);
} catch (err) {
  alert("处理出错: " + err);
} finally {
  doc.close(SaveOptions.DONOTSAVECHANGES);
}

ExtendScript 还提供 Error 对象用于主动抛出异常:

javascript
if (!app.documents.length) {
  throw new Error("没有打开任何文档");
}

Error 对象的 message 属性可包含中文错误信息,通过 alert() 展示给用户。

第一个脚本

创建新文档并保存为 PSD:

javascript
app.displayDialogs = DialogModes.NO;

var doc = app.documents.add(400, 300, 72, "测试文档", NewDocumentMode.RGB);
var layer = doc.artLayers.add();
layer.name = "我的图层";

var saveFile = new File("C:/Users/Administrator/Desktop/测试文档.psd");
doc.saveAs(saveFile, new PhotoshopSaveOptions(), true, Extension.LOWERCASE);
alert("保存完成: " + decodeURI(saveFile.fsName));

模块化脚本组织

随着脚本规模增长,将代码拆分到多个文件中是良好的实践。ExtendScript 提供两种代码复用方式:

#include 指令(编译时包含)

#include 是一个预处理指令,在脚本编译时将被包含文件的全部内容插入到当前文件位置:

javascript
// main.jsx —— 入口文件
#include "config.jsxinc"
#include "lib/helpers.jsxinc"
#include "lib/export.jsxinc"

// 编译后,上面三个文件的内容会直接插入此处,
// 其中定义的所有函数和变量都可以直接使用
processAllFiles();

包含的文件通常使用 .jsxinc 后缀(非强制,仅为标识代码片段而非独立脚本):

javascript
// utils.jsxinc —— 不包含独立执行逻辑,仅供 #include 使用
function resizeToWidth(doc, targetWidth) {
  doc.resizeImage(targetWidth, null, 72, ResampleMethod.BICUBIC);
}

function saveAsJPEG(doc, filePath, quality) {
  var opts = new JPEGSaveOptions();
  opts.quality = quality || 10;
  doc.saveAs(new File(filePath), opts, true, Extension.LOWERCASE);
}
javascript
// main.jsx
#include "utils.jsxinc"

// 直接使用 utils.jsxinc 中定义的函数
var doc = app.activeDocument;
resizeToWidth(doc, 1920);
saveAsJPEG(doc, "C:/Users/Administrator/Desktop/output.jpg", 12);

注意事项:

  • 路径相对于当前脚本文件所在目录
  • 文件内容在编译期插入,执行期与主脚本无区别
  • 同一个文件可以使用多个 #include 分层组织

$.evalFile(运行时加载)

$.evalFile() 在脚本执行过程中动态加载并执行另一个 .jsx 文件:

javascript
// 运行时加载配置脚本
$.evalFile(new File("C:/Users/Administrator/Desktop/config.jsx"));
// 此时 config.jsx 中定义的变量和函数生效

选择参考

场景推荐方式
静态库、工具函数#include
用户自定义配置$.evalFile
插件式功能模块$.evalFile
项目依赖的基础模块#include