';

这里要介绍的 ExtendScript 是 Adobe 为旗下软件的提供的自动化脚本,前面已经提到过了,CEP 提供了的是扩展运行的环境和配套的界面方面的支持,而扩展要调用宿主的功能更多的还要依赖于使用 ExtendScript。

ExtendScript 也就是 PhotoShop 菜单里的“脚本”功能所用到的脚本

 

前面的文章已经多次提到过 ExtendScript 了,所以本文直接从 ExtendScript 具体的使用方法来介绍,其他的请参照先前文章:

「 1 」Hello World! – ExtendScript 调试

「 2 」CEP 技术概览 – ExtendScript

「 3 」API 的使用 – 执行 ExtendScript 代码

「 3 」API 的使用 – ExtendScript 事件监听

 

ExtendScript 通常可以使用 JavaScript、 AppleScript、VBScript 三种语言,其中 AppleScript 的版本功能要少一些,而 JavaScript 是最为常用的版本。CEP 中可以使用的也是 JavaScript 。

不同宿主的 ExtendScript 各有不同,毕竟 Photoshop、Illustrator 、InDesign 之间相差那么大,不过基本的编程风格是一样的,本文介绍的是 Photoshop 脚本。

本文只介绍 ExtendScript 的基本使用方法,和常用的操作,而调用宿主的各种功能请参照 Adobe 官方的参考文档(Reference),本文也翻译了一部分 Photoshop CC JavaScript Reference 供入门者查阅。

   Adobe 官方的参考文档​
Photoshop Scripting Photoshop CC Scripting Guide Photoshop CC JavaScript Reference
Illustrator Scripting Illustrator CC 2015 Scripting Guide Illustrator CC 2015 Reference: JavaScript
InDesign Scripting InDesign CS6 Scripting SDK [Windows] InDesign CS6 Scripting SDK [Mac]
After Effects Scripting After-Effects-CS6-Scripting-Guide After Effects CC 2015 Panel SDK

运行 ExtendScript

要调试和运行 ExtendScript  有  3 种方法:

  • 第一种就是把写好的代码保存为 .jsx 文件,使用 Photoshop 菜单里“文件 – 脚本 – 浏览”功能打开这个文件来运行。
  • 第二种是在 CEP 中使用 evalScript() 方法执行 ExtendScript  代码,这可以在 CEP 远程调试的控制台中使用,如果没有安装 Adobe ExtendScript Toolkit 的话,可以使用这种方法。
  • 第三种就是安装 Adobe ExtendScript Toolkit CC ,在 Adobe ExtendScript Toolkit CC 中运行 ExtendScript   代码。推荐使用第三种方法。

Adobe ExtendScript Toolkit

Adobe ExtendScript Toolkit

你可以使用 Adobe Creative Cloud 桌面程序下载 ExtendScript Toolkit,如果使用的非正版或者没有 Creative Cloud 可以使用下面下的载地址

Windows (94 MB) AdobeExtendScriptToolkit_4_LS22.exe [百度网盘]
OS X (96 MB) AdobeExtendScriptToolkit_4_LS22.dmg

Adobe ExtendScript Toolkit 的使用方法非常简单,选择好宿主程序,在编辑框中写代码再点击  运行即可。

选择宿主程序

另外一个有用的功能是 Data Browser 窗口可以实时查看宿主应用的 DOM,这可以让你查看对象中有什么成员:

Data Browser

Holle World

首先,就从一个 Holle World 示例来了解 ExtendScript 大体是如何操作 PhotoShop 的:

//创建一个尺寸为 400 像素 X 400 像素的文档,分辨率为 72 ,文档名称为:"一个脚本创建的文件"。
//并且用变量 newDoc 记录这个图层。
var newDoc = app.documents.add(UnitValue("400 px"), UnitValue("400 px"), 72 ,"一个脚本创建的文件");

// 在 newDoc 中添加一个图层。并且用变量 newLayer 记录这个图层。
var newLayer = newDoc.artLayers.add();

//把图层 newLayer 的图层类型变为”文本“ ,图层转换为文本图层。
newLayer.kind = LayerKind.TEXT

//把图层 newLayer 的文本内容类型变为”文本框“。
newLayer.textItem.kind = TextType.PARAGRAPHTEXT

//设置图层 newLayer 的文本框宽度与高度。
newLayer.textItem.width = "300px"
newLayer.textItem.height = "150px"

//设置图层 newLayer 的文本框位置,横坐标 50 像素,纵坐标 100 像素。
newLayer.textItem.position= [UnitValue("50px"), UnitValue("100px")]

//设置 newLayer 的文本字体大小为“40 点”。
newLayer.textItem.size = UnitValue("40 pt")

//设置 newLayer 的文本内容。
newLayer.textItem.contents= "Holle World!"

//设置 newLayer 的文本框对齐方式为居中对齐。
newLayer.textItem.justification = Justification.CENTER

//创建一个色彩变量 c   ,颜色为 #77bb11。
var c = new SolidColor();
c.rgb.hexValue = "77bb11";

//设置 newLayer 的文本颜色为 c。
newLayer.textItem.color = c;

Holle World 示例的运行结果

 

另外 ExtendScript 没有控制台对象(console) ,不能使用 console.log() 这样的语句输出信息到控制台,不过 ExtendScript 中有起到同样作用的 $.writeln()  $.write()

 

DOM 操作

上面的示例,展现的是 ExtendScript 中最常用的用法:操作 DOM 中的对象来来操作文档。比如 app.documents 是所有打开的文档合集,可以通过索引号就能获得各个文档的对象: var doc = app.documents[0] 。

而一个对象有其“属性”和“方法”,修改属性可以修改类似于高度、宽度这样的数据:app.activeDocument.activeLayer.textItem.width = "300 px"

而方法通常是调用某个功能,比如这个调整图层亮度和对比度:app.activeDocument.activeLayer.adjustBrightnessContrast( 100, 45 ) 。

DOM 操作虽然不能完成全部 PhotoShop 的操作(用动作代理的方式能完成几乎全部操作),但是使用最为直观和简单,是 ExtendScript 的基础。

DOM 中的各种对象可以在 ExtendScript Toolkit 的 Data Browser 窗口中实时查看,而各种对象的具体使用方法,需要参考 Adobe 官方发布的 《Photoshop CC 2015 JavaScript Reference》文档,由于这是英文的,对入门有些阻碍所以我翻译了常用部分:「 6 」 Photoshop 中文脚本参考 。

ExtendScript Toolkit 的 Data Browser 窗口

另外一些官方参考文档中没有的对象,还可以在  ExtendScript Toolkit 的 Object Model Viewer (按 F1 调出)中找到简单的介绍。

单位

单位是要最先介绍的,因为各种属性都会用的单位,比如尺寸是像素(px)、英寸(in),还有颜色的表示。

尺寸单位

尺寸单位是使用 UnitValue 对象,使用起来很简单,用 UnitValue () 方法就能创建一个单位值。

可以直接在表达式里使用:

app.documents.add(UnitValue("400 px"), UnitValue("400 px"));

也可以先创建变量再使用:

var a = UnitValue("400 px")
app.documents.add(a, a);

用到的单位名可以是下面这些:

像素 px pt
英寸 in 派卡 pc
厘米 cm 百分比 %
毫米 mm

更多介绍请查看:「 6 」 Photoshop 中文脚本参考 – UnitValue

颜色

颜色相对于尺寸单位,使用起来要麻烦一些,必须用变量存储才可使用:

var color = new SolidColor();
color.rgb.red = 255;
color.rgb.green = 128;
color.rgb.blue = 200;

app.activeDocument.activeLayer.textItem.color = color //设置当前图层文本颜色

通常会用颜色十六进制值代码来减少代码量:

var color = new SolidColor();
color.rgb.hexValue  = "FFEEAA";

更多介绍请查看:「 6 」 Photoshop 中文脚本参考 –  SolidColor

 

 

图层遍历

图层的合集有 artLayerslayerSets 和 layers 三种,其中 artLayers 是文档根目录的图层的集合,layerSets 是图层组的合集。而 layers 是所有图层和图层组的集合。

简单来说我们可以 artLayers 和 layerSets 取得找到文档根目录图层或图层组。如:

//输出文档根目录“图层”名到控制台:
for (var i =0; i< app.activeDocument.artLayers.length; i++)
    {
        $.writeln("图层:" + app.activeDocument.artLayers[i].name) //输出图层名到控制台
    }

//输出文档根目录“图层组”名到控制台:
for (var i =0; i< app.activeDocument.layerSets.length; i++)
 {
 $.writeln("图层组:" + app.activeDocument.layerSets[i].name) //输出图层组名到控制台
 }

不过这两种只能检索到根目录一层的图层和图层组,而要遍历包括放在图层组中的所有图层和图层组需要使用  layers 。遍历到图层组 layerSet 后,可以用 layerSet.layers  继续遍历图层组中包含的内容:

多层嵌套的图层和图层组

var doc = app.activeDocument
var out ="" //创建存储遍历结果的变量
getLayers(doc.layers, 0);//遍历图层
alert(out );//输出遍历结果

function getLayers(layers, count)
{
    for (var i =0; i<layers.length; i++)
    {
        if(layers[i].typename == "LayerSet")//判断是否是图层组
        {
            out = out + "\n " +  repeat("    ",count)   +"[" + layers[i].name +"]"; 
            getLayers(layers[i].layers, count+1);//递归
         }
        else
         {
              out = out + "\n " +  repeat("    ",count)   + layers[i].name; 
          }
    }
}

//重复字符串,这个用于生成缩进
function repeat(str, n) 
{
    var arr = new Array(n+1);
    return arr.join(str);
}

文件与路径

在 Extension 中使用文件(读取或写入)都需要使用 File 对象,使用起来很简单,不过要注意由于\在 Extension  中有转义符的作用,所以必须双写,像下面这样:

 var a = File("E:\\备份\\新D.txt")

另外要注意的是在 Extension 内部使用的文件路径是 URI 形式的,也就是会把非 ASCII 字符转义成 ASCII 字符,比如上面这个路径是这样的:

/e/%E5%A4%87%E4%BB%BD%E6%B3%A8%E5%86%8C%E8%A1%A8%E5%A4%87%E4%BB%BD%5B2013-8-31%5D.reg

要想得到当前平台风格的的路径,需要使用 a.fsName 。

各种类型路径示例
a.fsName 当前系统风格路径名 E:\备份\新D.txt
a.fullName 完整路径 /e/备份/新D.txt
a.displayName 文件显示名称 新D.txt
a.name 文件名 /e/%E5%A4%87%E4%BB%BD/%E6%96%B0D.txt
a.absoluteURI 绝对路径 URI /e/%E5%A4%87%E4%BB%BD/%E6%96%B0D.txt
a.relative 相对路径 URI /e/%E5%A4%87%E4%BB%BD/%E6%96%B0D.txt

 

动作代理

除了直接操作 DOM ,还有一种更加底层的操作宿主的方式:Action Manager ,与 DOM 不同,DOM 只是封装了宿主一部的操作,而动作代理几乎能完成所有宿主的操作。

比如下面展示使用动作代理改变当前文字图层消除锯齿方式为 “Windows LCD” 这使用  DOM 操作是无法完成的,DOM 的常数 antiAliasMethod 里没有收录 “Windows LCD”:

 var desc = new ActionDescriptor();

 var ref = new ActionReference();
 ref.putProperty( charIDToTypeID( "Prpr" ), charIDToTypeID( "AntA" ) );
 ref.putEnumerated( charIDToTypeID( "TxLr" ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Trgt" ) ); 
 
 
desc.putReference( charIDToTypeID( "null" ), ref); // 向描述符中添加 ActionReference 类型的数据。
desc.putEnumerated( charIDToTypeID( "T " ), charIDToTypeID( "Annt" ), stringIDToTypeID( "antiAliasPlatformLCD" ) );
 
executeAction( charIDToTypeID( "setd" ), desc, DialogModes.NO );

看起来这比直接操作 DOM 要复杂多了,

这里来解释一下:

动作代理的关键方法是 executeAction( 事件, 动作描述符, 对话框模式 ) 这个函数会执行一个动作事件。事件是一个 TypeID (实际运行 ID),它可以用 charIDToTypeID() 或 stringIDToTypeID 从特定的字符串转换得来。比如上面这个示例就是执行了一个 "setd" 事件,这个是代表“设置”事件。

另一个关键是动作描述符: ActionDescriptor 它存放有动作用到的各种参数。各种参数作为键值对存放在  ActionDescriptor 中,比如上面例子中向描述符 desc 中添加了一个 ActionReference 类型数据,和一个 Enumerated 类型的数据。一个 ActionDescriptor 可以有许多键值对,一个特殊的值类型是  ActionReference , 这种类型会存放多种指定的请求类的数据。另外还有一种类型是 ActionList 它是用处类似于数组的数据类型。

可以简单理解 ActionDescriptor、ActionReference、ActionList 都是一种容器,用来存放各种指定数据类型的数据,这是直接调用 PhotoShop 底层接口的中间层,作为各种指令在 ExtendScript 与 PhotoShop 底层之间的代理。

操作监听器

关于动作代理的资料,Adobe 公布的很少,不过 Adobe 发布了一个操作监听插件,可以监听 PhotoShop 的每一步操作,并输出为 ExtendScript 的动作代理代码,这样就可以很方便的获得想要得到的操作的动作代理代码,用于修改。

 

动作监听器输出的内容

动作监听器下载地址 :Scripting Listener Plug-in for Windows

动作监听器的使用很简单,把下载下来的监听器的 ScriptListener.8li 文件放入 PhotoShop 的 Plug-ins 目录,再重启 PhotoShop 就行了。

ScriptListener.8li 文件放入 PhotoShop 的 Plug-ins 目录

运行中的监听器没有界面,其输出的内容会放在桌面上的一个名为 ScriptingListenerJS.log 文本文件中,如果你在桌面上找到了这个文件,说明监听器运行成功了。要注意的是这个监听器是无法关闭的,要关闭只有把 ScriptListener.8li 文件从 Plug-ins 目录删除或者移出,不需要使用时记得要删除它,否则当你使用 Photoshop 时它会一直记录,输出的脚本文件体积越来越大,直到撑满硬盘。

在 Photoshop 中每一步操作都会被输出到这个文本文件中,所以为了查看输出的内容推荐使用一个可以实时监控文件内容的文本编辑器,比如 Everedit、虹吸墨等:

文本编辑器虹吸墨的监视模式(虹吸墨窗口顶部的右键菜单-监视模式)

 

常量列表

在动作代理会用到许多 ID ,比如 charIDToTypeID( "Prpr" ) ,这是 PhotoShop 的内部常量,每一个常量会有 charID(字符 ID,4 个字节)、StringID(字符串 ID,不定长度)、typeID(实际运行 ID,数字)。使用时要使用 typeID ,不过为了方便记忆和书写(或者随版本变化实际运行 ID 改变了),通常会用使用字符 ID 再通过 charIDToTypeID( ) 、 stringIDToTypeID ( )、 typeIDToCharID()typeIDToStringID( ) 这几个函数来转化。

详细的常量列表可以参考:「 附录 · 1 」PhotoShop ExtendScript 常数表

 

示例:通过动作代理获取所有选中图层

上面的示例比较简单,只是简单的执行一个命令,这里展示一个执行命令并从返回值中获取数据的例子。这也是一个常用但无法直接操作 DOM 来完成的功能,ExtendScript 中可以用  app.activeDocument.activeLayer 获得当前选中图层,但是无法在多选情况下获得多选的图层,这个例子就是获取所有选中图层:


//获取所有选中图层的 ItemIndex ,存放在变量 selectedLayers 中:
var selectedLayers = getSelectedLayerItemIndex ();

//遍历图层名:
$.writeln("---------------------");
for (var i = 0; i < selectedLayers.length; ++i)
{
  $.writeln("选中图层 ["+selectedLayers[i]+"]: " + layerItemIndexToName(selectedLayers[i]) );
}

//遍历图层(返回图层对象):
$.writeln("---------------------");
for (var i = 0; i < selectedLayers.length; ++i)
{
  $.writeln(getArtLayerByItemIndex(selectedLayers[i]));
}


//取得所有被选择中的图层的 ItemIndex 。
getSelectedLayerItemIndex = function()
{
   var resultLayerItemIndexs = []; //存放结果的数组
   var backgroundIndexOffset = 0;
    
  //取选中的图层:
  var ref = new ActionReference();
  var args = new ActionDescriptor();
  ref.putProperty( charIDToTypeID('Prpr') , stringIDToTypeID("targetLayers") );
  ref.putEnumerated( charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Trgt') );
  args.putReference( charIDToTypeID('null'), ref );
  var resultDesc = executeAction( charIDToTypeID('getd'), args, DialogModes.NO );

  //只选中了一个图层:
  if (! resultDesc.hasKey( stringIDToTypeID("targetLayers") ))
  {
    resultLayerItemIndexs.push( app.activeDocument.activeLayer.itemIndex);
    return resultLayerItemIndexs; 
  }

 //选中了多个图层:
  var selIndexList = resultDesc.getList( stringIDToTypeID("targetLayers") );

  for (i = 0; i < selIndexList.count; ++i)
  {
      //ItemIndex 是从 1 开始,而 PS 内部序号是从 0 开始,所以这里需要 + 1。
    resultLayerItemIndexs.push(selIndexList.getReference(i).getIndex( charIDToTypeID('Lyr ') )  + 1 );
      $.writeln("r: " + selIndexList.getReference(i).getIndex( charIDToTypeID('Lyr ') )  + "+" + (1));
  }

  return resultLayerItemIndexs;  
  }


//根据图层 ItemIndex 获取图层名称
layerItemIndexToName = function( ItemIndex )
{
   // 判断背景图层是否存在。背景图层无论存在与否始终占有是 0  位,所如果背景图层不存在,第一个图层就是内部序号就是 1 而不是 0 ,所以要 + 1。
   backgroundIndexOffset = 1;
  try {
    if (app.activeDocument.backgroundLayer)
      backgroundIndexOffset = 0;
  }
  catch (err)
  {}
    
  var ref = new ActionReference();
  ref.putProperty( app.charIDToTypeID('Prpr'), stringIDToTypeID("name") );
     //ItemIndex 是从 1 开始,而 PS 内部序号是从 0 开始,所以这里需要 - 1。
  ref.putIndex( charIDToTypeID('Lyr '), ItemIndex -1 + backgroundIndexOffset );
  var resultDesc = executeActionGet( ref );
  return resultDesc.getString(stringIDToTypeID("name"));
}



 //根据 ItemIndex 返回图层或图层组对象
getArtLayerByItemIndex = function(ItemIndex)
{
    //要找的图层不在根目录,遍历所有图层组去寻找
    return  findInLayers(app.activeDocument.layers);//遍历图层

    function findInLayers(layers)
    {   
        for (var i =0; i<layers.length; i++)
        {
            if(layers[i].typename == "LayerSet")//判断是否是图层组
            { 
                //要找到图层图层就是本图层组,直接返回结果:
                if( layers[i].itemIndex == ItemIndex)
                 {          
                    return  layers[i];
                 }
                 //要找到图层不是本图层组,寻找图层组中的图层,找到就返回结果。如果没有找到这执行 catch ,如果找到多个同名图层则忽略 catch:
                 try
                 {
                    var tryF = layers[i].layers.getByName (layerItemIndexToName(ItemIndex))
                    if(tryF.itemIndex == ItemIndex){ return  tryF;}      
                 }
                catch (err)
                 { 
                    //要找的图层不是图层组中的图层,接着寻找本图层组中的图层组:
                    var r = findInLayers(layers[i].layerSets); 
                    if(r != undefined){return r;}
                    continue;
                 }

              //找到多个同名图层, 比较每个图层的 ItemIndex :
               var r = findInLayers(layers[i].artLayers); 

               if(r != undefined){return r;}
               }

            else
             {
                // 比较图层 ItemIndex :
                 if( layers[i].itemIndex == ItemIndex)
                 {       
                     return  layers[i];
                 }
              }
       }

    }
}

 

 

xtools

xtools 是一个第三方的 PhotoShop ExtendScript 开发工具包,有可直接使用的脚本(xapps)和用于开发的库(xlibs),很值得使用和参考。

下载:官方主页 | 网盘

 

 

 

23条评论

  1. 路人   •  

    博主你好,請教個問題。以下代碼,為甚麽在 PS CC 2015下没有效果,但在 ExtendScript 里面就正常?================================var res2 = “dialog{ list: ListBox{ properties: { multiselect: true, numberOfColumns: 3, showHeaders: true, columnTitles: [‘column1’, ‘column2’, ‘column3’] }, }, btnCancel: Button{ properties: {name: ‘cancel’}, text: ‘取 消’, }}”;var win = new Window(res2, ‘title’);win.show();=============================== 謝謝

    • 不知语冰   •     作者

      这是 Photoshop 的问题,CC 2014 以下应该是正常的,而 Photoshop CC 2015 换了新界面界面,有的控件使用方式有变化,导致以前的 ScriptUI 代码很多地方都没法用,你仔细查看下 Object Model Viewer 吧,我没用过 ScriptUI 做界面,都是使用 HTML 做界面,不是很熟。

  2. 卡拉卡   •  

    很6

  3. 牛奶面包   •  

    你好楼主,我想学这个软件,您能教吗?或者您知不知道哪里能学?能加我qq聊吗?跪求,多谢,qq1540963011

  4. 黄伟华   •  

    666!这方面的资料真心少,好好研究方,到时候要和楼主吹吹牛。

  5. Mentalist   •  

    你好,请问 PS脚本侦听输出的代码在extendscript中运行报错该如何修正呢?

  6. 花花   •  

    博主,Scripting Listener Plug-in for Windows 可以监听 illustrator CC吗

    • 不知语冰   •     作者

      PS 的 Scripting Listener 只能监听 PS ,illustrator 有没有相同的东西我也没找过

  7. 常磊   •  

    大神,请问插件UI部分可否嵌入FLASH?我尝试过在html上放flash不行。。。

  8. 常磊   •  

    大神,我一直想获取图层的内容,可我发现除了kind是Text的能获取到文字内容,其他的图层类型,没从layer的api上找到相应的方法。。。。请问用什么方式获取里面的内容呢?还有我用layer.bounds给的值给ps里的值不一样,请问如何获取正确的呢?谢谢

    • 不知语冰   •     作者

      内容是指什么?像素吗? extendScript 没法直接获取像素数据的,一般是保存临时文件,或者用 generator(https://github.com/adobe-photoshop/generator-core),这种间接的方法。layer.bounds 会不一样? bounds 获得的数组 [0,1,2,3] 分别是 0:左侧左边距 ,1:顶侧顶边距 ,2:右侧左边距 ,3:底侧顶边距。

      • 常磊   •  

        好的,我再试试,我把bounds数组里最后两个以为是宽高呢。。。这下明白了,谢谢!

      • 常磊   •  

        大哥我又来麻烦你了,我获取到LayerKind是SOLIDFILL,我用什么方式能获取到这个图层里的图形对象并得到他的宽高颜色圆角度数什么的(我画了个圆角矩形)

  9. 苏州刘勇   •  

    厉害 学习了ActionDescriptor这个资料真是极少啊,没想到在这里看到了。现在理解了一些,依然有疑惑。能再开个帖子详细解释下里面参数的设置方法吗?

  10. 疯子   •  

    大神,能加个微信吗,最近在开发一个设计的辅助插件,但是这方面的资料好少,碰到问题好难解决。 不会占用您很多时间哈,微信号 q578405741。 拜托拜托~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注