使用JavaScript開發(fā)一個(gè)Photoshop插件
作為前端開發(fā)者,我們可以利用Web技術(shù)在非常多的環(huán)境下開發(fā)應(yīng)用,為相關(guān)的用戶提供服務(wù)。其中,以Photoshop為首的Adobe系列工具是我們時(shí)長要去面對的一個(gè)平臺級應(yīng)用。Photoshop在圖像處理上有著很強(qiáng)大的功能,用戶量也非??捎^,而且其功能在前端開發(fā)的一些情況下也用得到,因此筆者認(rèn)為Photoshop相關(guān)的Web技術(shù)具有很不錯(cuò)的價(jià)值。
本文將引導(dǎo)大家使用JavaScript開發(fā)一個(gè)Photoshop插件。
CEP:通用擴(kuò)展平臺
Adobe作為一家歷史悠久的軟件公司,已經(jīng)給開發(fā)者提供了相對成熟的擴(kuò)展開發(fā)技術(shù)棧,被稱作CEP——Common Extensibility Platform(通用擴(kuò)展平臺)。
CEP擴(kuò)展基于Web技術(shù),可以在Adobe Photoshop、Adobe Illustrator、Adobe InDesign等全系列應(yīng)用中運(yùn)行,并且可以訪問這些應(yīng)用和外部操作系統(tǒng)環(huán)境的API。
CEP應(yīng)用的結(jié)構(gòu)可以被分為五個(gè)抽象層級:
用戶層,用戶所得到的、構(gòu)建出來的應(yīng)用外部;
宿主應(yīng)用層,我們的CEP擴(kuò)展在宿主應(yīng)用中被配置好后,會在嵌入的CEF(一個(gè)開源的、嵌入基于Chromium內(nèi)核瀏覽器的簡單框架)中成功渲染;
UI層,就是基于HTML文件構(gòu)建的WEB頁面;
Javascript層,運(yùn)行在UI層頁面上的JavaScript腳本,比起一般Web應(yīng)用的環(huán)境里內(nèi)置了更多的功能——可以訪問Extendscript層與一些宿主應(yīng)用原生功能的API以及本機(jī)中Nodejs的API;
Extendscript層,運(yùn)行在宿主應(yīng)用內(nèi)部的腳本,具有訪問宿主應(yīng)用的內(nèi)部API的能力,可以和JavaScript層之間通信;
所謂宿主應(yīng)用,就是我們CEP擴(kuò)展運(yùn)行在的Adobe程序,例如Adobe Photoshop等,同時(shí)后文我們也默認(rèn)CEP的宿主程序是Adobe Photoshop
ExtendScript
作為一個(gè)類Web應(yīng)用,上面四層相對前端開發(fā)者來說都比較好理解,這一節(jié)我們來看下ExtendScript層:
ExtendScript腳本可以用三套不同語言去編寫,分別為JavaScript、VBScript和AppleScript。三種語言功能上沒有任何區(qū)別。鑒于本文面向的是各位前端工程師,我們果斷選擇前者,同時(shí)文章的后文我們也是默認(rèn)選擇JavaScript版本的ExtendScript。
ExtendScript有以下特點(diǎn):
區(qū)別于 CEP 擴(kuò)展中后綴為.js的 JavaScript 文件,操作ExtendScript 的JavaScript文件后綴名為.jsx
這里的.jsx文件和react用到的.jsx文件完全不同,如果你在自己的CEP應(yīng)用中引入了react,記得把它們分開以避免混淆
ExtendScript在全局下內(nèi)置了用來獲取和操作Adobe應(yīng)用和文件內(nèi)容的各種API
Adobe應(yīng)用中,ExtendScript腳本和CEP中的JavaScript腳本運(yùn)行于兩個(gè)不同的引擎,如果我們選擇JavaScript語言接口的ExtendScript腳本,對應(yīng)的引擎僅僅兼容至 ECMAScript3的標(biāo)準(zhǔn)
了解了Extendscript之后,我們再來看一下CEP擴(kuò)展各層級之間的橫向關(guān)系:
CEP擴(kuò)展中的JavaScript代碼會在CEP JavaScript VM 中運(yùn)行,比起一般的Web應(yīng)用,增加了調(diào)用Node.js的API與操作系統(tǒng)交互,以及通過引入 CSInterface.js[3] 調(diào)用ExtendScript的功能
宿主程序中,作為ExtendScript的JavaScript代碼會在另一個(gè)環(huán)境下——Host JavaScript VM中被解析
在這一個(gè)類Web應(yīng)用中包含兩個(gè)腳本環(huán)境,兩個(gè)環(huán)境雖然都是JavaScript,而且可以通過傳遞字符串相互通信,但是是其上下文是相互隔離的,一定要區(qū)分開來
關(guān)于Photoshop中,ExtendScript具體可以調(diào)用的API,我們可以直接看Adobe的官方手冊:ADOBE PHOTOSHOP SCRIPTING[1]
項(xiàng)目構(gòu)建
在動手開發(fā)前,我們先把運(yùn)行CEP擴(kuò)展的各種要素準(zhǔn)備齊全。
首先我們來看下CEP擴(kuò)展需要的目錄結(jié)構(gòu):
CSXS/manifest.xml:必需,項(xiàng)目的配置文件,配置CEP擴(kuò)展應(yīng)用的窗體大小、入口的html文件地址、入口jsx(ExtendScript)文件地址、版本兼容、啟動選項(xiàng)等信息,由于篇幅所限我們不在本文里具體展開,官方提供了配置文件的指南:Configure-your-extension-in-manifestxml[2]
client/index.html、client/index.js、client/style.css:CEP應(yīng)用相關(guān)頁面、腳本、樣式,就是我們CEP擴(kuò)展和Web相關(guān)的全部文件
client/CSInterface.js:Adobe官方提供的工具庫,需要在JavaScript層引入,封裝并提供了訪問ExtendScript層和一些原生功能的API,官方也在github上提供了文件CSInterface.js。
這個(gè)工具庫大概一千多行,其中很大一部分是描述各個(gè)函數(shù)功能的注釋,所以可以直接通過閱讀注釋來學(xué)習(xí)這個(gè)工具庫的用法。
CEP擴(kuò)展中的JavaScript環(huán)境下本身就內(nèi)置了調(diào)用ExtendScript環(huán)境的類,引入的CSInterface.js是對環(huán)境里調(diào)用ExtendScript環(huán)境的類進(jìn)行封裝使得開發(fā)者更便于調(diào)用而已,所以引入CSInterface.js并不是必要的。
host/index.jsx:ExtendScript的腳本,訪問宿主應(yīng)用的內(nèi)部API的能力,在CEP擴(kuò)展中,ExtendScript文件有兩種加載方式:
在CEP擴(kuò)展內(nèi)用JavaScript通過CSInterface.js封裝好的方法主動進(jìn)行加載
在manifest.xml中通過配置入口的jsx(ExtendScript)文件的腳本,在CEP擴(kuò)展應(yīng)用運(yùn)行的第一時(shí)間進(jìn)行加載
最后,我們要把建立好的CEP擴(kuò)展的目錄放到Photoshop指定的位置:
mac:~/Library/Application Support/Adobe/CEP/extensions
win:{Photoshop安裝路徑}\Required\CEP\extensions
這樣Photoshop就可以加載我們開發(fā)的擴(kuò)展,出現(xiàn)在其菜單欄中的「窗口」-「擴(kuò)展」中。
debug模式與調(diào)試
看了這么多概念,我們動手試試吧!
新建CEP擴(kuò)展的目錄之后,我們嘗試在Photoshop菜單欄的「窗口」-「擴(kuò)展」中運(yùn)行擴(kuò)展,就發(fā)現(xiàn)了一個(gè)問題:
這是因?yàn)槲覀冃陆ǖ腃EP擴(kuò)展沒有經(jīng)過簽名認(rèn)證。
為了繞過這個(gè)認(rèn)證,我們需要打開Photoshop的debug模式:
首先,我們要獲取自己當(dāng)前機(jī)器上Adobe CEP的版是CEP幾,關(guān)于Adobe不同應(yīng)用的種類和版本的簡稱,我們可以看官方提供的對應(yīng):Applications Integrated with CEP[4]
得到當(dāng)前CEP的版本后,我們可以通過下面的方法進(jìn)入debug模式(記得將下列CSXS.[n]中的[n]用你目前的CEP版本替換)
如果你是Windows用戶,你需要:
打開 regedit
找到HKEY_CURRENT_USER/Software/Adobe/CSXS.[n]
然后添加一個(gè)叫PlayerDebugMode的字段
設(shè)置值為string類型的"1"
如果你是macOS用戶,你需要:
打開終端輸入:defaults write com.adobe.CSXS.[n] PlayerDebugMode 1
你需要在終端輸入ps -axu $USER|grep cfprefsd,找到cfprefsd這個(gè)進(jìn)程的pid,然后用kill命令刪掉它(或者你也可以直接重新啟動你的機(jī)器)。
執(zhí)行完上面的操作后,你就可以在自己的Photoshop里運(yùn)行自己新建的擴(kuò)展了。
同時(shí),如果你想調(diào)試自己的擴(kuò)展,可以在目錄指定位置中添加.debug文件:
.debug文件中,我們指定開發(fā)的應(yīng)用可以在哪個(gè)宿主應(yīng)用和哪個(gè)端口進(jìn)行調(diào)試:
<ExtensionList><!-- 1 --><Extension Id="com.example.helloworld"><HostList><!-- 2 --><Host Name="PHXS" Port="8088"/><Host Name="PHSP" Port="8088"/></HostList></Extension></ExtensionList>
然后,我們訪問在chrome瀏覽器中訪問chrome://inspect/#devices,點(diǎn)擊「Port forwarding...」監(jiān)聽我們在.debug中設(shè)置的端口,我們可以看到的自己的應(yīng)用:
熟悉移動端調(diào)試的讀者一定對這個(gè)界面不陌生,我們找到自己的應(yīng)用并點(diǎn)開「inspect」,就可以在指定端口通過chrome的開發(fā)者工具來同步調(diào)試運(yùn)行的CEP擴(kuò)展了。
開發(fā)「獲取/刪除所有文字圖層」的Ps插件
我們從前文提到的「CEP應(yīng)用結(jié)構(gòu)的五個(gè)層級」自下向上來構(gòu)建:
1. 首先,在Extendscript層,我們先在全局定義好「獲取所有文字圖層」和「刪除所有文字圖層」的功能函數(shù):
function getAllLayers() {var out = [];var doc = app.activeDocument;getLayers(doc.layers);function getLayers(layers) {for (var i = 0; i < layers.length; i++) {if (layers[i].typename == "LayerSet") {//判斷是否是圖層組out.push(layers[i].name);getLayers(layers[i].layers);} else {out.push(layers[i].name);}}}return JSON.stringify(out);}function hideAllTextLayers() {var doc = app.activeDocument;var out = [];function getLayers(layers) {for (var i = 0; i < layers.length; i++) {if (layers[i] && layers[i].kind === LayerKind.TEXT) {out.push(layers[i]);}if (layers[i].typename == "LayerSet") {getLayers(layers[i].layers);}}}getLayers(doc.layers);for (var j = 0; j < out.length; j++) {out[j].remove();}return "{}";}
app作為Extendscript中的全局對象,有著獲取原生宿主程序各種功能的api,我們可以通過app.activeDocument.layers來獲取或者操作圖層
同時(shí),我們通過返回字符串類型的結(jié)果,讓CEP的JavaScript端得以獲取
由于在Extendscript環(huán)境下,JavaScript僅兼容ES3,而且ExtendScript和CEP JavaScript之間只能通過字符串進(jìn)行通信,所以我們要在ExtendScript的環(huán)境下引入JSON3[5]作為JSON功能的polyfill(注意這和CEP的JavaScript無關(guān))
2. 在CEP的JavaScript層,我們在utils/cs.js中使用Promise封裝好界面上用得到的的hideLayers和getLayers函數(shù)——調(diào)用Extendscript中已經(jīng)定義好在全局的方法,并處理返回的字符串:
const cs = new CSInterface();var c = cs.getSystemPath(SystemPath.EXTENSION) + "/jsx/";cs.evalScript(`$.evalFile("${c}json3.jsx")`);const evalJSXScript = (script) =>new Promise((resolve) => {cs.evalScript(script, (res) => {resolve(JSON.parse(res));});});export const getLayers = () => evalJSXScript("getAllLayers()");export const hideLayers = () => evalJSXScript("hideAllTextLayers()");
3. 在CEP的UI層(為了更直觀,這里我們用引入react來代替html展示UI),我們大致部署一下插件的界面,用兩個(gè)按鈕分別觸發(fā)「獲取所有文字圖層」和「刪除所有文字圖層」的功能。同時(shí)為了直觀一些,我們把獲取到的所有文字圖層在插件面板上顯示:
import React, { useState } from "react";import { hideLayers, getLayers } from "./utils/cs";import "./styles/main.css";export default () => {const [layers, setLayers] = useState(null);const handleGetLayers = async () => {const layers = await getLayers();setLayers(layers);};return (<div style={{ width: "100vw", height: "100vh", background: "#FFF" }}><button className="primary" onClick={handleGetLayers}>點(diǎn)擊獲取圖層</button><button className="primary" onClick={hideLayers}>點(diǎn)擊刪除全部文字圖層</button><div className="area">{layers && layers.length? layers.map((e, i) => (<div key={i} className="layer">{e}</div>)): "無"}</div></div>);};
在CEF層,我們根據(jù)上一小節(jié)「項(xiàng)目構(gòu)建」的步驟,配置好manifest.xml和整個(gè)項(xiàng)目的目錄結(jié)構(gòu),打開debug模式,并將整個(gè)CEP擴(kuò)展應(yīng)用的目錄放到相應(yīng)的路徑下;
在界面層,我們在Photoshop中隨便打開一個(gè)包含文字圖層的psd文件,然后在「窗口」-「擴(kuò)展」里面打開我們剛開發(fā)好的擴(kuò)展,就可以成功運(yùn)行了。
讓我們試試剛剛開發(fā)的功能,例如,當(dāng)我們點(diǎn)擊「點(diǎn)擊獲取圖層」的按鈕時(shí),得到了如下的結(jié)果:
然后我們點(diǎn)擊右側(cè)「刪除所有文字圖層」后,是不是可以發(fā)現(xiàn)打開的psd文件中的文字圖層都消失了呢?
我把實(shí)例的項(xiàng)目放在了Lumpychen/CEP-Test[6],大家有興趣可以自己嘗試。
簽名與發(fā)布
現(xiàn)在我們的應(yīng)用可以在記得Photoshop中跑起來了,但是如果想讓自己的擴(kuò)展可以在設(shè)計(jì)師同事的Photoshop里運(yùn)行,我們不能給讓每個(gè)用戶都開啟一下debug模式,這太麻煩了。
在沒有進(jìn)入debug模式的情況,Adobe CEP 擴(kuò)展必須有簽名才能正常運(yùn)行,簽名分為兩種:
商業(yè)簽名證書,可以在數(shù)字簽名提供商中購買
自簽名證書,可以通過Adobe官方的ZXPSignCmd 創(chuàng)建
具體如何獲取證書、簽名打包,Adobe也提供了官方的教程:package-distribute-install-guide[7]
同時(shí),Adobe官方也把下載、管理和更新CEP擴(kuò)展的功能集成到了Creative Cloud里,如果你安裝了Creative Cloud,它會連接Adobe Exchange——Adobe官方推出的擴(kuò)展市場,以獲取和更新我們安裝的擴(kuò)展。
如果你想把你自己開發(fā)的擴(kuò)展發(fā)布到Adobe Exchange上,Adobe官方也提供了Exchange Portal用來發(fā)布擴(kuò)展的渠道。
然而……
由于Adobe在中國的業(yè)務(wù)一直處于被閹割的狀態(tài),且國內(nèi)通過Creative Cloud購買正版Adobe應(yīng)用的用戶也相對有限,所以大家很少采用官方的渠道管理和獲取Adobe產(chǎn)品的CEP擴(kuò)展。
而國內(nèi)的Photoshop擴(kuò)展應(yīng)用的生態(tài)依然處于一個(gè)略微灰色的狀態(tài),很多擴(kuò)展的發(fā)布和都依賴第三方社區(qū)(知乎、微信公眾號、淘寶)或素材網(wǎng)站,當(dāng)然這樣的生態(tài)也催生了我國互聯(lián)網(wǎng)的歷史上一批又一批的ps大神。
輔助工具
文章的最后,如果你想要開發(fā)一個(gè)Adobe CEP擴(kuò)展,我這邊強(qiáng)烈推薦幾個(gè)輔助用的工具:
Script Listener[8]
Script Listener是Adobe社區(qū)里推出的輔助工具,可以隨時(shí)記錄用戶對Adobe宿主程序的操作,然后生成ExtendScript腳本文件在桌面上供用戶查看和選用——使用這種方式生成ExtendScript代碼,可以讓開發(fā)者省去很多學(xué)習(xí)Extendscript API的成本。
JSX.js[9]
JSX.js是提供給CEP應(yīng)用的JavaScript環(huán)境一個(gè)JS庫,可以代替原生的方法來引入ExtendScript的文件或執(zhí)行Extendscript的代碼,它解決了一個(gè)很重要的痛點(diǎn)——提供了執(zhí)行ExtendScript的報(bào)錯(cuò)信息(這比起原生調(diào)用ExtendScript代碼執(zhí)行得到一句evalScript error體驗(yàn)要強(qiáng)上很多倍)
ExtendScript Debugger[10]
這是目前Adobe官方提供的,當(dāng)前版本唯一用來調(diào)試ExtendScript的工具。它是一個(gè)VSCode Debugger插件,可以像其它的VScode Debugger一樣,提供相關(guān)報(bào)錯(cuò)信息,實(shí)現(xiàn)斷點(diǎn)調(diào)試的功能。
擴(kuò)展閱讀
ADOBE PHOTOSHOP SCRIPTING
https://www.adobe.com/devnet/photoshop/scripting.html
Configure-your-extension-in-manifestxml
https://github.com/Adobe-CEP/Getting-Started-guides#2-configure-your-extension-in-manifestxml
CSInterface.js
https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_10.x/CSInterface.js
Applications Integrated with CEP
https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_9.x/Documentation/CEP%209.0%20HTML%20Extension%20Cookbook.md#applications-integrated-with-cep
JSON3
https://github.com/bestiejs/json3
Lumpychen/CEP-Test
https://github.com/LumpyChen/CEP-Test
package-distribute-install-guide
https://github.com/Adobe-CEP/Getting-Started-guides/tree/master/Package%20Distribute%20Install#package-distribute-install-guide
Script Listener
https://helpx.adobe.com/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListenerplugin
JSX.js
https://creative-scripts.com/jsx-js/
ExtendScript Debugger
https://marketplace.visualstudio.com/items?itemName=Adobe.extendscript-debug
《CEP Intro》
https://github.com/Adobe-CEP/CEP-Resources
《nullice的Adobe CEP擴(kuò)展開發(fā)教程》
http://nullice.com/archives/category/note/%E8%BD%AF%E4%BB%B6%E6%95%99%E7%A8%8B/adobe-cep
《Photoshop Scripting Documentation》
https://www.notion.so/a908db4f72a74854b36c10e72a69b751
《Photoshop-CC-Javascript-Ref-2019》
https://wwwimages2.adobe.com/content/dam/acom/en/devnet/photoshop/pdfs/photoshop-cc-javascript-ref-2019.pdf













