使用JavaScript開發一個Photoshop插件

做爲前端開發者,咱們能夠利用Web技術在很是多的環境下開發應用,爲相關的用戶提供服務。其中,以Photoshop爲首的Adobe系列工具是咱們時長要去面對的一個平臺級應用。Photoshop在圖像處理上有着很強大的功能,用戶量也很是可觀,並且其功能在前端開發的一些狀況下也用獲得,所以筆者認爲Photoshop相關的Web技術具備很不錯的價值。javascript

本文將引導你們使用JavaScript開發一個Photoshop插件。css

CEP:通用擴展平臺

Adobe做爲一家歷史悠久的軟件公司,已經給開發者提供了相對成熟的擴展開發技術棧,被稱做CEP——Common Extensibility Platform(通用擴展平臺)。html

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4ad1319ec41f42d38321935ed74155ad~tplv-k3u1fbpfcp-zoom-1.image

CEP擴展基於Web技術,能夠在Adobe Photoshop、Adobe Illustrator、Adobe InDesign等全系列應用中運行,而且能夠訪問這些應用和外部操做系統環境的API。前端

CEP應用的結構能夠被分爲五個抽象層級:java

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3ed7204966194e19a54c5178d93682f4~tplv-k3u1fbpfcp-zoom-1.image

  1. 用戶層,用戶所獲得的、構建出來的應用外部;
  2. 宿主應用層,咱們的CEP擴展在宿主應用中被配置好後,會在嵌入的CEF(一個開源的、嵌入基於Chromium內核瀏覽器的簡單框架)中成功渲染;
  3. UI層,就是基於HTML文件構建的WEB頁面;
  4. Javascript層,運行在UI層頁面上的JavaScript腳本,比起通常Web應用的環境裏內置了更多的功能——能夠訪問Extendscript層與一些宿主應用原生功能的API以及本機中Nodejs的API
  5. Extendscript層,運行在宿主應用內部的腳本,具備訪問宿主應用的內部API的能力,能夠和JavaScript層之間通訊;

所謂宿主應用,就是咱們CEP擴展運行在的Adobe程序,例如Adobe Photoshop等,同時後文咱們也默認CEP的宿主程序是Adobe Photoshopreact

ExtendScript

做爲一個類Web應用,上面四層相對前端開發者來講都比較好理解,這一節咱們來看下ExtendScript層:git

ExtendScript腳本能夠用三套不一樣語言去編寫,分別爲JavaScript、VBScript和AppleScript。三種語言功能上沒有任何區別。鑑於本文面向的是各位前端工程師,咱們果斷選擇前者,同時文章的後文咱們也是默認選擇JavaScript版本的ExtendScript。github

ExtendScript有如下特色:chrome

  • 區別於 CEP 擴展中後綴爲.js的 JavaScript 文件,操做ExtendScript 的JavaScript文件後綴名爲.jsxjson

    這裏的.jsx文件和react用到的.jsx文件徹底不一樣,若是你在本身的CEP應用中引入了react,記得把它們分開以免混淆

  • ExtendScript在全局下內置了用來獲取和操做Adobe應用和文件內容的各類API

  • Adobe應用中,ExtendScript腳本和CEP中的JavaScript腳本運行於兩個不一樣的引擎,若是咱們選擇JavaScript語言接口的ExtendScript腳本,對應的引擎僅僅兼容至 ECMAScript3的標準

瞭解了Extendscript以後,咱們再來看一下CEP擴展各層級之間的橫向關係:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2f03872eddba4324902a6fa00b7528ed~tplv-k3u1fbpfcp-zoom-1.image

  • CEP擴展中的JavaScript代碼會在CEP JavaScript VM 中運行,比起通常的Web應用,增長了調用Node.js的API與操做系統交互,以及經過引入CSInterface.js調用ExtendScript的功能
  • 宿主程序中,做爲ExtendScript的JavaScript代碼會在另外一個環境下——Host JavaScript VM中被解析
  • 在這一個類Web應用中包含兩個腳本環境,兩個環境雖然都是JavaScript,並且能夠經過傳遞字符串相互通訊,可是是其上下文是相互隔離的,必定要區分開來

關於Photoshop中,ExtendScript具體能夠調用的API,咱們能夠直接看Adobe的官方手冊:ADOBE PHOTOSHOP SCRIPTING)

項目構建

在動手開發前,咱們先把運行CEP擴展的各類要素準備齊全。

首先咱們來看下CEP擴展須要的目錄結構:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0734eeef146f4c4084c9993c5b66c265~tplv-k3u1fbpfcp-zoom-1.image

  • CSXS/manifest.xml必需,項目的配置文件,配置CEP擴展應用的窗體大小、入口的html文件地址、入口jsx(ExtendScript)文件地址、版本兼容、啓動選項等信息,因爲篇幅所限咱們不在本文裏具體展開,官方提供了配置文件的指南:Configure-your-extension-in-manifestxml

  • client/index.htmlclient/index.jsclient/style.css:CEP應用相關頁面、腳本、樣式,就是咱們CEP擴展和Web相關的所有文件

  • client/CSInterface.js:Adobe官方提供的工具庫,須要在JavaScript層引入,封裝並提供了訪問ExtendScript層和一些原生功能的API,官方也在github上提供了文件CSInterface.js

    這個工具庫大概一千多行,其中很大一部分是描述各個函數功能的註釋,因此能夠直接經過閱讀註釋來學習這個工具庫的用法。

    CEP擴展中的JavaScript環境下自己就內置了調用ExtendScript環境的類,引入的CSInterface.js是對環境裏調用ExtendScript環境的類進行封裝使得開發者更便於調用而已,因此引入CSInterface.js並非必要的。

  • host/index.jsx:ExtendScript的腳本,訪問宿主應用的內部API的能力,在CEP擴展中,ExtendScript文件有兩種加載方式:

    • 在CEP擴展內用JavaScript經過CSInterface.js封裝好的方法主動進行加載
    • manifest.xml中經過配置入口的jsx(ExtendScript)文件的腳本,在CEP擴展應用運行的第一時間進行加載

最後,咱們要把創建好的CEP擴展的目錄放到Photoshop指定的位置:

mac:~/Library/Application Support/Adobe/CEP/extensions

win:{Photoshop安裝路徑}\Required\CEP\extensions

這樣Photoshop就能夠加載咱們開發的擴展,出如今其菜單欄中的「窗口」-「擴展」中。

debug模式與調試

看了這麼多概念,咱們動手試試吧!

新建CEP擴展的目錄以後,咱們嘗試在Photoshop菜單欄的「窗口」-「擴展」中運行擴展,就發現了一個問題:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7a1ee442e8c248cfa156a7dc27c3c2c5~tplv-k3u1fbpfcp-zoom-1.image

這是由於咱們新建的CEP擴展沒有通過簽名認證。

爲了繞過這個認證,咱們須要打開Photoshop的debug模式:

  • 首先,咱們要獲取本身當前機器上Adobe CEP的版是CEP幾,關於Adobe不一樣應用的種類和版本的簡稱,咱們能夠看官方提供的對應: Applications Integrated with CEP
  • 獲得當前CEP的版本後,咱們能夠經過下面的方法進入debug模式(記得將下列CSXS.[n]中的[n]用你目前的CEP版本替換)
    • 若是你是Windows用戶,你須要:
      • 打開 regedit
      • 找到HKEY_CURRENT_USER/Software/Adobe/CSXS.[n]
      • 而後添加一個叫PlayerDebugMode的字段
      • 設置值爲string類型的"1"
    • 若是你是macOS用戶,你須要:
      • 打開終端輸入: defaults write com.adobe.CSXS.[n] PlayerDebugMode 1
      • 你須要在終端輸入ps -axu $USER|grep cfprefsd,找到cfprefsd這個進程的pid,而後用kill命令刪掉它(或者你也能夠直接從新啓動你的機器)。

執行完上面的操做後,你就能夠在本身的Photoshop裏運行本身新建的擴展了。

同時,若是你想調試本身的擴展,能夠在目錄指定位置中添加.debug文件:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cd2207a6e6b2404883fc070f92f6519e~tplv-k3u1fbpfcp-zoom-1.image

.debug文件中,咱們指定開發的應用能夠在哪一個宿主應用和哪一個端口進行調試:

<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,點擊「Port forwarding...」監聽咱們在.debug中設置的端口,咱們能夠看到的本身的應用:

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/89d1147fa6f74979a58fd30add531d35~tplv-k3u1fbpfcp-zoom-1.image

熟悉移動端調試的讀者必定對這個界面不陌生,咱們找到本身的應用並點開「inspect」,就能夠在指定端口經過chrome的開發者工具來同步調試運行的CEP擴展了。

Case:開發一個能夠「獲取/刪除全部文字圖層」的Photoshop插件

咱們從前文提到的「CEP應用結構的五個層級」自下向上來構建:

  1. 首先,在Extendscript層,咱們先在全局定義好「獲取全部文字圖層」和「刪除全部文字圖層」的功能函數:

    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來獲取或者操做圖層
    • 同時,咱們經過返回字符串類型的結果,讓CEP的JavaScript端得以獲取

    因爲在Extendscript環境下,JavaScript僅兼容ES3,並且ExtendScript和CEP JavaScript之間只能經過字符串進行通訊,因此咱們要在ExtendScript的環境下引入JSON3做爲JSON功能的polyfill(注意這和CEP的JavaScript無關)

  2. 在CEP的JavaScript層,咱們在utils/cs.js中使用Promise封裝好界面上用獲得的的hideLayersgetLayers函數——調用Extendscript中已經定義好在全局的方法,並處理返回的字符串:

    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),咱們大體部署一下插件的界面,用兩個按鈕分別觸發「獲取全部文字圖層」和「刪除全部文字圖層」的功能。同時爲了直觀一些,咱們把獲取到的全部文字圖層在插件面板上顯示:

    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}> 點擊獲取圖層 </button> <button className="primary" onClick={hideLayers}> 點擊刪除所有文字圖層 </button> <div className="area"> {layers && layers.length ? layers.map((e, i) => ( <div key={i} className="layer"> {e} </div> )) : "無"} </div> </div>
      );
    };
    複製代碼
  4. 在CEF層,咱們根據上一小節「項目構建」的步驟,配置好manifest.xml和整個項目的目錄結構,打開debug模式,並將整個CEP擴展應用的目錄放到相應的路徑下;

  5. 在界面層,咱們在Photoshop中隨便打開一個包含文字圖層的psd文件,而後在「窗口」-「擴展」裏面打開咱們剛開發好的擴展,就能夠成功運行了。

    讓咱們試試剛剛開發的功能,例如,當咱們點擊「點擊獲取圖層」的按鈕時,獲得了以下的結果:

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/849faacafe1f49e08a2db4e3c2e0d9aa~tplv-k3u1fbpfcp-zoom-1.image

    而後咱們點擊右側「刪除全部文字圖層」後,是否是能夠發現打開的psd文件中的文字圖層都消失了呢?

    我把實例的項目放在了Lumpychen/CEP-Test,你們有興趣能夠本身嘗試。

簽名與發佈

如今咱們的應用能夠在記得Photoshop中跑起來了,可是若是想讓本身的擴展能夠在設計師同事的Photoshop裏運行,咱們不能給讓每一個用戶都開啓一下debug模式,這太麻煩了。

在沒有進入debug模式的狀況,Adobe CEP 擴展必須有簽名才能正常運行,簽名分爲兩種:

  • 商業簽名證書,能夠在數字簽名提供商中購買
  • 自簽名證書,能夠經過Adobe官方的 ZXPSignCmd 建立

具體如何獲取證書、簽名打包,Adobe也提供了官方的教程:package-distribute-install-guide

同時,Adobe官方也把下載、管理和更新CEP擴展的功能集成到了Creative Cloud裏,若是你安裝了Creative Cloud,它會鏈接Adobe Exchange——Adobe官方推出的擴展市場,以獲取和更新咱們安裝的擴展。

若是你想把你本身開發的擴展發佈到Adobe Exchange上,Adobe官方也提供了Exchange Portal用來發布擴展的渠道。

然而……

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/30cabcce8b9f48fbad2728775a4f2f4b~tplv-k3u1fbpfcp-zoom-1.image

因爲Adobe在中國的業務一直處於被閹割的狀態,且國內經過Creative Cloud購買正版Adobe應用的用戶也相對有限,因此你們不多采用官方的渠道管理和獲取Adobe產品的CEP擴展。

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a54915078caf49c78aa420bbdf5e8b79~tplv-k3u1fbpfcp-zoom-1.image

而國內的Photoshop擴展應用的生態依然處於一個略微灰色的狀態,不少擴展的發佈和都依賴第三方社區(知乎、微信公衆號、淘寶)或素材網站,固然這樣的生態也催生了我國互聯網的歷史上一批又一批的ps大神。

輔助工具

文章的最後,若是你想要開發一個Adobe CEP擴展,我這邊強烈推薦幾個輔助用的工具:

  • Script Listener

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acea7b23566a482ea3bd022e70c33bef~tplv-k3u1fbpfcp-zoom-1.image

    Script Listener是Adobe社區裏推出的輔助工具,能夠隨時記錄用戶對Adobe宿主程序的操做,而後生成ExtendScript腳本文件在桌面上供用戶查看和選用——使用這種方式生成ExtendScript代碼,可讓開發者省去不少學習Extendscript API的成本。

  • JSX.js

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e2fd8b1a34144c28509ed41b6547cb3~tplv-k3u1fbpfcp-zoom-1.image

    JSX.js是提供給CEP應用的JavaScript環境一個JS庫,能夠代替原生的方法來引入ExtendScript的文件或執行Extendscript的代碼,它解決了一個很重要的痛點——提供了執行ExtendScript的報錯信息(這比起原生調用ExtendScript代碼執行獲得一句evalScript error體驗要強上不少倍)

  • ExtendScript Debugger

    https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d3faece536d4c638dbbf6f42e096838~tplv-k3u1fbpfcp-zoom-1.image

    這是目前Adobe官方提供的,當前版本惟一用來調試ExtendScript的工具。它是一個VSCode Debugger插件,能夠像其它的VScode Debugger同樣,提供相關報錯信息,實現斷點調試的功能。

擴展閱讀

相關文章
相關標籤/搜索