使用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同样,提供相关报错信息,实现断点调试的功能。

扩展阅读

相关文章
相关标签/搜索