前言

  最近因为各种原因严重拖更,不过还折腾了很多东西,主要是上一周遇到了让我吐血的需求。
  肝了一个多星期才算是勉勉强强解决了,实在是太难顶了,所以就一直拖更了。
  PS 的工具流程也是那个需求的一部分,

  最近接到一个需求,需要开发 PS 的工具来协助模型的贴图处理流程。
  尽管 Python 也有一些控制 PS 相关的库,但是考虑到 美术人员的使用环境问题。
  还是用原生的 PS JavaScript 进行开发较为稳妥。

开发文档

文档链接

  关于 Photoshop 开发相关的 JavaScript 使用,首先需要去查 Adobe 官方提供的文档以及编程规范。
  上面链接可以拿到各个不同版本的 脚本规范 PDF ,其实不同版本的 PDF 都是大同小异,重点需要关注两本。

Photoshop Scripting Guide
Photoshop JavaScript Reference

alt

  只有 JavaScript 支持跨平台,因此其他两种语言的 PDF 可以忽略掉
  但是只靠上面两个文档还是不够的,因为文档里面并没有包含 GUI 编程相关的内容。
  GUI 相关的内容在 ExtendScript Toolkit 下面。

ExtendScript Toolkit

javascript_tools_guide

  最初不知道有这个文档,还查了很长一段时间。
  从 ExtendScript Toolkit 的版本到了 CS5 就没有更新,可以看到用这个语言编写工具流程已经很久没有更新过了。

alt

  ExtendScript 是经过 Adobe 扩展过的 JavaScript 支持 Adobe 全家桶开发。
  使用 ExtendScript 的好处是能够一直兼容支持到 Adobe CS2 系列的产品。
  可以通过上面的 ExtendScript Toolkit 来调试代码。

  缺点就是 ExtendScript 是基于 ES3 的规范,所以很多 JavaScript 的新特性都将不支持。 stackoverflow 链接
  当然也正如 Stack Overflow 的链接提到的可以使用 polyfill 来实现前端的向前兼容。
  所以 Adobe 官方也推出了相关的 extendscriptr npm 模块,可以使用 node.js 来构建出兼容 ES3 的 js 代码 Github 链接

  另外 VScode 也有 ExtendScript 的扩展,功能完善,完全不需要安装老旧的 ExtendScript Toolkit CS5 来调试代码了。
  另外 VScode 的扩展也同样支持 将 jsx 编译成 jsxbin 格式来保护源代码。

alt

ExtendScript 编程

  如何使用 ExtendScript 来制作 PS 工具,可以先看看 Youtube 的一个老外的入门教程,浅显易懂。 Youtube 链接
  yotube 上还有很多相关的 Extendscript 教程,都是可以参考学习的。

GUI 编写

  关于 ExtendScript 语言特性自带的部分重点看 javascript_tools_guide 这个文档。
  可以跳过 第二个章节的 Toolkit 说明。
  其中 第四章 就是关于使用 ExtendScript 编写 GUI 的。

alt

1
2
3
4
// Create an empty dialog window near the upper left of the screen 
var dlg = new Window("dialog", "Alert Box Builder");
dlg.frameLocation = [100, 100];
dlg.show()

  上面代码可保存为 jsx 然后拖入到 PS 的灰色界面上,即可打开一个小窗口

alt

  由于没有关闭窗口,所以关闭需要通过 Esc 键来实现。
  基于上面的逻辑,可以编写出更为复杂一点的代码

1
2
3
4
5
6
7
8
9
var docRef = app.activeDocument;
var dlg = new Window('dialog', 'My first script!',[100,100,480,250],{closeButton:true});
dlg.btnPnl = dlg.add('panel', [25,15,365,125], 'Hello world!');
dlg.btnPnl.testBtn = dlg.btnPnl.add('button', [15,30,305,50], 'Finished', {name:'ok'});
dlg.btnPnl.testBtn.onClick = function () {
alert("Congratulations - it all worked!");
dlg.close();
};
dlg.show();

alt

  具体的对象参数可以参考文档,参数说明都有标注。

  除了使用 js 构建窗口,还支持 Resource specifications 来构建窗口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var windowResource = "dialog {  \
orientation: 'column', \
alignChildren: ['fill', 'top'], \
preferredSize:[300, 130], \
text: 'ScriptUI Window - dialog', \
margins:15, \
\
sliderPanel: Panel { \
orientation: 'row', \
alignChildren: 'right', \
margins:15, \
text: ' PANEL ', \
st: StaticText { text: 'Value:' }, \
sl: Slider { minvalue: 1, maxvalue: 100, value: 30, size:[220,20] }, \
te: EditText { text: '30', characters: 5, justify: 'left'} \
}, \
\
bottomGroup: Group{ \
cd: Checkbox { text:'Checkbox value', value: true }, \
cancelButton: Button { text: 'Cancel', properties:{name:'cancel'}, size: [120,24], alignment:['right', 'center'] }, \
applyButton: Button { text: 'Apply', properties:{name:'ok'}, size: [120,24], alignment:['right', 'center'] }, \
}\
}"

var win = new Window(windowResource);

win.bottomGroup.cancelButton.onClick = function () {
return win.close();
};
win.bottomGroup.applyButton.onClick = function () {
return win.close();
};

win.show();

alt

  具体的定义也可以通过 文档查询 到。


  PS 的脚本执行除了拖入的方式之外,也可以使用 文件 -> 脚本 -> 浏览 选择一个 js 脚本来执行。

alt

PS API 调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// JavaScript Document

if (app.documents.length == 0) {
alert("You must have an open document to run this script.");
} else {
var docRef = app.activeDocument

// 设置 TGA 保存的设置
var saveOptions = new TargaSaveOptions();
saveOptions.alphaChannels = true;
saveOptions.resplution = TargaBitsPerPixels.TWENTYFOUR;

// 设置 TGA 保存的路径
var saveName = docRef.fullName.toString();
saveName = saveName.replace('.psd', '.tga')
docRef.saveAs(new File(saveName), saveOptions, true, Extension.LOWERCASE)

alert("保存成功")
}

  上面的脚本就是基于 Ps 的 API 实现将当前 psd 文件存储为 tga 格式的脚本。
  具体的操作方式都可以通过文档查询到。

  结合上面的 GUI 界面就可以构建出 按钮触发保存 TGA 图片的效果。

PS Script listener 插件

  虽然 PS 的很多操作都可以看 API 来实现,但是手动去查 API 还是非常麻烦。
  可不可以像 Maya 一样自动回显命令,这样直接抄代码就可以了。
  这个官方也出了一个 C++ 插件来实现相应的功能 插件列表
  上面的链接里面有很多 插件 ,找到 ScriptingListener 插件,按照说明安装到对应的目录即可。

  安装完成重开 PS 之后,在 PS 进行任意的操作会在桌面上生成 ScirptingListenerJS.log 的文件
  里面会将 PS 的操作转换为 js 代码。
  不过代码大概是从 C++ 的命令里面转换过来的,所以自动生成的代码比较不简洁。

alt

  自动生成的代码里面包含很多特殊设置,文档也没有说明,因此通过自动生成的代码可以实现更多操作。

ExtendScript 配合 动作 自动化调用流程

  虽然 ExtendScirpt 可以直接拖入来执行。
  但是操作起来还是非常不方便,我测试脚本的时候通常会制作执行脚本的动作。
  然后通过这个动作来快速调用脚本。
  于是我想到通过这个方法自动化部署美术人员的工具。

  经过网上的一番操作 xtools 老牌 jsx 工具集里面有将 PS 的 atn 动作文件和 xml 互转的工具。

alt

  我可以预先设置好一个 atn 转换成 xml 的 xml 模板,然后留下 jsx 脚本路径的特殊标记。
  然后通过 install.jsx 来读取 xml 替换标记的路径为当前路径,然后通过 xml 生成 atn 文件使用 PS 读取 atn 动作。
  这样做好的工作,无论放置到哪里都可以正确安装调用到相关的 jsx 脚本。

alt


  具体的调用代码可以参考这里 链接

保持 ExtendScript 窗口响应

  使用 ExtendScript 写界面有个非常麻烦的问题。
  界面一启动就会将 PS 完全占用,无法像 Maya 一样界面和操作互不干扰。
  这个问题其实 PS 自带的操作都是如此。

  最初我想可能是 dialog 类型导致的。
  于是尝试在 PS 里面使用 palette 类型的窗口,
  没想到执行起来的就是一闪而过,窗口立刻就被关闭了。

  后来经过大量查询,在网上找到了解决方案 链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
var isDone, s2t, waitForRedraw, win, windowResource;

// Shortcut function
s2t = function (stringID) {
return app.stringIDToTypeID(stringID);
};

waitForRedraw = function () {
var d;
d = new ActionDescriptor();
d.putEnumerated(s2t('state'), s2t('state'), s2t('redrawComplete'));
return executeAction(s2t('wbait'), d, DialogModes.NO);
};

//sentinel variable
isDone = false;

// palette same as before
windowResource = "palette { \
orientation: 'column', \
alignChildren: ['fill', 'top'], \
preferredSize:[300, 130], \
text: 'ScriptUI Window - palette', \
margins:15, \
\
sliderPanel: Panel { \
orientation: 'row', \
alignChildren: 'right', \
margins:15, \
text: ' PANEL ', \
st: StaticText { text: 'Value:' }, \
sl: Slider { minvalue: 1, maxvalue: 100, value: 30, size:[220,20] }, \
te: EditText { text: '30', characters: 5, justify: 'left'} \
}, \
\
bottomGroup: Group{ \
cd: Checkbox { text:'Checkbox value', value: true }, \
cancelButton: Button { text: 'Cancel', properties:{name:'cancel'}, size: [120,24], alignment:['right', 'center'] }, \
applyButton: Button { text: 'Apply', properties:{name:'ok'}, size: [120,24], alignment:['right', 'center'] }, \
}\
}";

win = new Window(windowResource);

// Button listeners
win.bottomGroup.cancelButton.onClick = function () {
return isDone = true;
};
win.bottomGroup.applyButton.onClick = function () {
return isDone = true;
};

// don't forget this one!
win.onClose = function () {
return isDone = true;

};

win.show();

while (isDone === false) {
app.refresh(); // or, alternatively, waitForRedraw();
}

alt

  通过上面的文章可以知道 palette 一闪而过是因为窗口的生命周期依赖于脚本。
  脚本执行完毕之后窗口就会被清理掉。
  所以需要不断执行 app.refresh() 来保持相应,其实这个做法和我开发 mpdb 的时候用 Qt 的 QApplication.processEvents() 有异曲同工的地方。

  尽管如此,这个方案也还是有些许瑕疵,比如使用选区工具就会因为不断刷新看不到蚂蚁线了。

Adobe CEP 流程

  经过上面的 ExtendScript 的一系列折腾之后。
  项目组的另一个 TA 有提到 PS 开发支持 HTML 的了。
  我发现我这个流程完全没有用上 HTML 呀~
  于是又查了一圈发现了新大陆。

  自从 Adobe CC 系列推出之后,Adobe 推出了全新的扩展开发流程
  CEP (Common Extensibility Platform) 流程,具体文档可以查看 Cookbook

CEP 结构

  关于如何使用上,Adobe 的团队有一个专门的仓库有很详细的描述 CEP Getting Start

alt

  CEP 开发其实非常像小程序,需要根据特定的目录结构和配置文件来定义扩展的信息。

alt

  通过上面的目录结构来配置插件可以实现如上面动图一样启动插件扩展。

Bootstrap 快速开发

  通过上面的目录层级结构就可以实现界面上调用 Html 来开发界面了。
  背后的原理大概是通过 node.js 开端口让 PS 进行界面渲染。

  没有仔细测试,不过浏览器的大部分功能都是支持的。
  理论上可以使用前端主流的 Vue 或者 React 框架开发出一个静态网站,作为页面进行挂载。
  就日常开发的简单工具而言,上面两个框架无疑让复杂度上去了,毕竟写个界面又不是做商城。
  所以我更加推荐使用 ES5 时代的 JQuery + Bootstrap 流程。
  使用 Bootstrap 可以减少 css 代码的编写,提高开发的效率。

  最重要的是 Bootstrap 有类似 QtDesigner 的工具,开发效率直线上升。 layoutit链接

alt

  通过 bootstrap 简单开发的效果如下

alt

安装 & 签名

alt

  cookbook 里面有提到关于 extension 安装目录,只要按照上面的 CEP 结构,将文件夹放置到上图的特定目录上。
  PS 启动的时候会自动加载。

alt

  但是默认按照流程走的 extension 扩展启动时候遇到扩展未签名的问题。
  官方提供了 debug 模式来忽略未签名问题, windows 上只需要在注册表加一个键值即可。 操作链接

alt

  但是没有签名无法使用还是让人非常蛋疼,毕竟不是所有的环境都支持注册表的修改。


  签名文件需要下载 ZXPSignCmd 命令行工具来进行签名操作。 发布文档

alt

  使用 ZXPSignCmd 生成一个证书,类似于发布 Android 打包时候定义的证书一样。

1
2
# 生成 p12 验证文件
ZXPSignCmd.exe -selfSignedCert zh guangdong tencent timmyliang 123456 test.p12

alt

  然后再使用 -sign 命令将扩展插件打包成 zxp 文件。

1
2
# 生成 zxp 打包文件
ZXPSignCmd.exe -sign C:\Users\timmyliang\AppData\Roaming\Adobe\CEP\extensions\Flickr test.zxp test.p12 123456

  安装上发布文档提供了三种不同的方式。
  不过 zxp 文件本质上是个压缩文件,其实也可以用 7z 解压文件,然后将解压目录移动到 extension 的固定目录下。
  这样的插件扩展就是已经完成了签名的。

CSTK 工具补充

https://github.com/Trevor-/CSTK

  Github 上有人开发了支持 Adobe 都款软件的 JSX 运行工具,调试代码会方便简单了很多,十分推荐使用。
  关于 PS 开发的一些个人总结可以参考我的 Github 仓库记录 链接

CEP 案例补充

  https://github.com/Adobe-CEP/Samples 这里面有很多参考案例
  其中 CEP HTML Test Extension 非常有用,包含各种 API 的调用

总结

  上述就是 PS 工具流程开发最近的一些发现。
  但是 PS 毕竟不是命令行工具,而且很多算法封装实现,在程序批处理调用上非常不灵活。
  我最近还花了大量的时间研究 终极命令行图形处理工具 imagemagick
  不愧是服务器领域的 PS , C & C++ 语言编写,兼顾极致的效率,同时又能实现 PS 的各种功能。
  无论是 绘图 还是 图像处理,亦或是更甚的 核函数自定义 脚本编写 都可以灵活实现。
  如此强大的工具,没想到 Maya 的 bin 目录下就有了,后续一定要好好总结一下 imagemagick
  虽然没有图形界面,但是比 PS 要强大得多~