active layer 【CEP教程-8】Action Manager完全指南 - 上篇

日期: 2024-04-06 01:11:17|浏览: 114|编号: 42907

友情提醒:信息内容由网友发布,请自鉴内容实用性。

active layer 【CEP教程-8】Action Manager完全指南 - 上篇

早在PS4的年代,PS加入了(动作) 这个功能,对PS比较熟悉的小伙伴应该都了解这个东西,它可以记录你在PS上的各种操作,然后类似录像机一样把你的动作记录下来之后进行重放,就可以实现一些批量操作的任务。为了实现这样的机制,PS底层实现了一整套动作 - 事件机制,你在PS的每个操作,都会转化成一个(动作),在这个动作完成之后呢,又通过事件(Event)派发出来,给需要关心这个动作的其它组件进行监听,(我们前面文章讲到的事件机制就来此于此)。这套机制,是宿主引擎在C++层实现的,你依然可以在C++的SDK中看到它的影子

C++ SDK

后来,随着 推出,Adobe大佬将这套机制进行了封装,提供了JS的API,本质上也就是一个桥接,最后JS的解释器转化到底层C++部分,所以你看到的这种不常见的语法和使用方式,归根到底是底层C的语法结构。再后来,Adobe科学家 Ruark针对此写了一个非常有价值的插件,这个插件能够将你在PS中的操作,记录成一个动作脚本,将它输出出来,并且这个动作脚本是JS的方式提供的,也就是我们上面看到的那段代码,有了这个工具,大大降低了AM脚本获取的门槛(虽然门槛依然很高),它最大的价值,并不是给我们输出免费的代码,而是让我们可以窥探AM这个神秘的领域,打开了一扇窗户(嗯……不是门,一个很小的窗户)

这个插件的重要性,可以说是CEP/脚本开发领域必备工具了,有了它,可以在DOM API需求满足度40%,提高到80%,所以我们先来介绍一下它的安装和使用。

首先下载插件,为了大家方便,我已经打包好了放在下面的链接当中,点击下载后解压,会看到两个文件,.是mac上用的,.8li是上用的

将这个文件,拷贝到安装路径/plug-ins文件夹下,如下图

mac plug-ins

win plug-ins

安装完成之后,重启PS,接着你在PS上随便操作,就会发现在你的桌面上会生成一个.log文件,该文件记录下了你在PS的每一步操作对应的JS AM代码

.log

你把这一段段的代码拷贝出来,放到你的编辑器(如 )中运行,会发现它和你手动操作的行为是一模一样的。有了它,相当于有了一个学霸同桌,有地方抄作业了!

语法结构

好了,现在有了一个学霸同桌,接下来我们讲一下如何优雅的抄作业。

先看一个例子,我们用PS打开一个文件,就可以看到如下的代码输出

var idopen = stringIDToTypeID( "open" );
    var desc7 = new ActionDescriptor();
    var iddontRecord = stringIDToTypeID( "dontRecord" );
    desc7.putBoolean( iddontRecord, false );
    var idforceNotify = stringIDToTypeID( "forceNotify" );
    desc7.putBoolean( idforceNotify, true );
    var idnull = stringIDToTypeID( "null" );
    desc7.putPath( idnull, new File( "/Users/xiaoqiang/Desktop/Untitled-1.psd" ) );
    var iddocumentID = stringIDToTypeID( "documentID" );
    desc7.putInteger( iddocumentID, 224 );
executeAction( idopen, desc7, DialogModes.NO );

虽然我们还不太理解这段代码的详细意思,但是可以通过括号里头的参数半猜半蒙,open就是打开文件的意思,然后对应的文件路径在**new File(…)**里头,所以我们可以将这段代码封装一下,包装一个我们自己的文件打开函数,我们传进去一个可变的路径参数,这样就可以打开我们想要的文件了

function openDocument(filePath) {
    var idopen = stringIDToTypeID( "open" );
    var desc7 = new ActionDescriptor();
    var iddontRecord = stringIDToTypeID( "dontRecord" );
    desc7.putBoolean( iddontRecord, false );
    var idforceNotify = stringIDToTypeID( "forceNotify" );
    desc7.putBoolean( idforceNotify, true );
    var idnull = stringIDToTypeID( "null" );
    desc7.putPath( idnull, new File( filePath ) );
    var iddocumentID = stringIDToTypeID( "documentID" );
    desc7.putInteger( iddocumentID, 224 );
    executeAction( idopen, desc7, DialogModes.NO );   
}

仔细观察,会发现上面的代码有些冗余,看起来费劲的很,我们可以把里头的局部变量做一下优化,变成这样

function openDocument(filePath) {
    var desc7 = new ActionDescriptor();
        desc7.putBoolean( stringIDToTypeID( "dontRecord" ), false );
        desc7.putBoolean( stringIDToTypeID( "forceNotify" ), true );
        desc7.putPath( stringIDToTypeID( "null" ), new File( filePath ) );
        desc7.putInteger( stringIDToTypeID( "documentID" ), 224 );
    executeAction( stringIDToTypeID( "open" ), desc7, DialogModes.NO );   
}

代码简化之后,我们大体就可以看清晰它的脉络,它的整个过程如下

创建了一个对象给这个对象设置了一堆属性执行一个open动作,把这个对象传给这个动作

open

关于这个类,我们可以在官方文档里头找到它的接口API,这个类有许多函数接口,我写了一个的

declare class ActionDescriptor {
    readonly count:number;  // The number of keys contained in the descriptor
    readonly typename: string;
    clear(): void;                                              // clear the descriptor
    erase(key: string): boolean;                                // erase a key from the descriptor
    getBoolean(key:number): boolean;                            // get the value of a key of type boolean
    getClass(key: number): number;                              // get the value of a key of type class
    getDouble(key: number): number;                             // get the value of a key of type double
    getEnumerationType(key: number): number;                    // get the enumeration value of a key
    getEnumerationValue(key: number): number;                   // get the enumeration value of a key
    getInteger(key: number): number;                            // get the value of a key of type integer 
    getKey(index: number): number;                              // get ID of the Nth key
    getList(key: number): ActionList;                           // get the value of a key of type list
    getObjectType(key: number): number;                         // get the class ID of an object in a key of type object
    getObjectValue(key: number): ActionDescriptor;              // get the value of a key of type object
    getPath(key: number): string;                               // get the value of a key of type Alias 
    getReference(num: number): ActionReference;                 // get the value of a key of type ActionReference
    getString(key: number): string;                             // get the value of a key of type string
    getType(key: number): DescValueType;                        // get the type of a key
    getUnitDoubleType(key: number): number;                     // 	get the unit type of a key of type UnitDouble
    getUnitDoubleValue(key: number): number;                    // get the value of a key of type UnitDouble
    getData(key: number): string;
    hasKey(key: number): boolean;                               // does the descriptor contain the provided key?
    isEqual(color: any): boolean;                               // return true if the provided color is visually equal to this color
    putBoolean(key: number,value: boolean): void;
    putClass(key: number,value: number):void;
    putDouble(key: number,value: number):void;
    putEnumerated(key: number,enumType: any,value: any):void
    putInteger(key: number,value: number):void;
    putList(key: number,value: any): void;
    putObject(key: number,classID: number,value: any):void;
    putPath(key: number,value: string): void;
    putReference(key: number,value: ActionReference): void;
    putString(key: number,value: string): void;
    putUnitDouble(key: number,unitID: number,value: number): void;
}

可是,那这里面的是啥意思?, 是做什么,为什么有一个null,我能猜到open是打开的意思,那关闭难道是close?那复制呢?从哪里可以找到这些命令的值呢?我们面临的问题还有很多,我们一个个来。

1. //

在所有AM的代码片段中,你都会看到这些函数

最常见的,应该是,该函数的参数,是一个4个字符长度的字符串缩写,比如Lyr 表示的是图层Layer的意思,Shw 表示的显示Show的含义。

// code sample 1
var desc1 = new ActionDescriptor();
var list1 = new ActionList();
var ref1 = new ActionReference();
ref1.putIdentifier(charIDToTypeID("Lyr "), this.id);;
list1.putReference(ref1);
desc1.putList(charIDToTypeID("null"), list1);
executeAction(charIDToTypeID("Shw "), desc1, DialogModes.NO);

你实验执行一下,下面的代码,会发现/的执行结果是一串数值

charIDToTypeID( "Opn " )    //1332768288 
stringIDToTypeID("open")    //1332768288

然后/就是反过来把数值转成可读的字符串

typeIDToCharID(1332768288)  // Opn
typeIDToStringID(1332768288) // open

上面我们讲历时背景的时候提到了,底层是C的代码结构,我们看到的open, show等等命令,在引擎层就是一个,输出的数值就是该map的索引,计算机是不认识open这个单词的,所以我们通过函数将这个命令转成索引数值,在底层就可以找到对应的命令了。

我们还可以看到(“Opn “) == (“open”),所以两个都可以用,上面的code 1中也可以将替换掉,如下

var desc1 = new ActionDescriptor();
var list1 = new ActionList();
var ref1 = new ActionReference();
ref1.putIdentifier(stringIDToTypeID("layer"), this.id);;
list1.putReference(ref1);
desc1.putList(stringIDToTypeID("null"), list1);
executeAction(stringIDToTypeID("show"), desc1, DialogModes.NO);

我们会发现,char对应的是一个C层的一个字符,4个字节长度,所以它的参数都是4长度的字符串,而则是这个缩写的完整表达,为什么存在这两种功能相同表达方式不同的函数,这里头有历时原因,早期只有这个函数,通过命令字符串缩写来提供参数,后来随着PS的功能越来越多,只有4个字符串表示的缩写不够用了,于是推出了完整字符表示的这个函数,它通过完整的字符串表示命令,比如 (“”) ,于是我们可以通过以下两个函数来做转换,查看缩写命令和完整命令,比如你看到一个缩写命令(N ),想知道它到底是啥意思,可以将它的完整字符弄出来

var typeId = charIDToTypeID("N   ");
var value = typeIDToStringID(typeId);   // name

通过和,我们可以将缩写命令和完整命令之间进行转换。

有个注意的点是,不是所有的缩写命令和完整命令都能相互转换,有些缩写命令没有提供完整字符命令,有些完整字符命令没有缩写命令

我们还没有回答上面一个问题,这些命令的集合,从哪里能找到?

所有的完整字符命令,可以在C++SDK的其中一个头文件中找到,如下位置

.////.h

.h

然而有这样一个集合,并没有太多作用,这个文件定义了5k多个命令,你很难靠人肉去一个个看,看了也只能靠猜,效率低下,在后面一篇文章里头,我会讲述一个更好的办法,当我想要弄一个功能的时候,怎么能找到这个命令的值。

2.

我们再来看一个稍微复杂一点点的例子,调整图层的大小,我们在PS中通过图层的自由变换,可以改变元素的大小,会输出如下代码

var idtransform = stringIDToTypeID( "transform" );
    var desc31 = new ActionDescriptor();
    var idnull = stringIDToTypeID( "null" );
        var ref11 = new ActionReference();
        var idlayer = stringIDToTypeID( "layer" );
        var idordinal = stringIDToTypeID( "ordinal" );
        var idtargetEnum = stringIDToTypeID( "targetEnum" );
        ref11.putEnumerated( idlayer, idordinal, idtargetEnum );
    desc31.putReference( idnull, ref11 );
    var idfreeTransformCenterState = stringIDToTypeID( "freeTransformCenterState" );
    var idquadCenterState = stringIDToTypeID( "quadCenterState" );
    var idQCSAverage = stringIDToTypeID( "QCSAverage" );
    desc31.putEnumerated( idfreeTransformCenterState, idquadCenterState, idQCSAverage );
    var idoffset = stringIDToTypeID( "offset" );
        var desc32 = new ActionDescriptor();
        var idhorizontal = stringIDToTypeID( "horizontal" );
        var idpixelsUnit = stringIDToTypeID( "pixelsUnit" );
        desc32.putUnitDouble( idhorizontal, idpixelsUnit, 58.500000 );
        var idvertical = stringIDToTypeID( "vertical" );
        var idpixelsUnit = stringIDToTypeID( "pixelsUnit" );
        desc32.putUnitDouble( idvertical, idpixelsUnit, 31.500000 );
    var idoffset = stringIDToTypeID( "offset" );
    desc31.putObject( idoffset, idoffset, desc32 );
    var idwidth = stringIDToTypeID( "width" );
    var idpercentUnit = stringIDToTypeID( "percentUnit" );
    desc31.putUnitDouble( idwidth, idpercentUnit, 139.000000 );
    var idheight = stringIDToTypeID( "height" );
    var idpercentUnit = stringIDToTypeID( "percentUnit" );
    desc31.putUnitDouble( idheight, idpercentUnit, 152.500000 );
    var idinterfaceIconFrameDimmed = stringIDToTypeID( "interfaceIconFrameDimmed" );
    var idinterpolationType = stringIDToTypeID( "interpolationType" );
    var idbicubic = stringIDToTypeID( "bicubic" );
    desc31.putEnumerated( idinterfaceIconFrameDimmed, idinterpolationType, idbicubic );
executeAction( idtransform, desc31, DialogModes.NO );

这坨代码不好阅读,我们做一下简化

var desc1 = new ActionDescriptor();
    var ref1 = new ActionReference();
        ref1.putEnumerated( stringIDToTypeID( "layer" ), stringIDToTypeID( "ordinal" ), stringIDToTypeID( "targetEnum" ) );
    desc1.putReference( stringIDToTypeID( "null" ), ref1 );
    desc1.putEnumerated( stringIDToTypeID( "freeTransformCenterState" ), stringIDToTypeID( "quadCenterState" ), stringIDToTypeID( "QCSAverage" ) );
        var desc2 = new ActionDescriptor();
        desc2.putUnitDouble( stringIDToTypeID( "horizontal" ), stringIDToTypeID( "pixelsUnit" ), 58.500000 );
        desc2.putUnitDouble( stringIDToTypeID( "vertical" ), stringIDToTypeID( "pixelsUnit" ), 31.500000 );
    desc1.putObject( stringIDToTypeID( "offset" ), stringIDToTypeID( "offset" ), desc2 );
    desc1.putUnitDouble( stringIDToTypeID( "width" ), stringIDToTypeID( "percentUnit" ), 139.000000 );
    desc1.putUnitDouble( stringIDToTypeID( "height" ), stringIDToTypeID( "percentUnit" ), 152.500000 );
    desc1.putEnumerated( stringIDToTypeID( "interfaceIconFrameDimmed" ), stringIDToTypeID( "interpolationType" ), stringIDToTypeID( "bicubic" ) );
executeAction( stringIDToTypeID( "transform" ), desc1, DialogModes.NO );

为了更好理解,我们将优化后的代码做了缩进,以便更清晰的看到它的层次结构,要理解AM的代码结构,我们应从下往上看,最后一行代码,执行了一个动作,动作命令是,我们可以很好理解他是自由变换,同时在执行这个命令的时候,传递给它一个参数,这个参数是一个(以下简称AD)对象,我们给整个AD对象设置了一堆参数,比如宽(width),高(),水平位置(),垂直位置()等等,我们对这个命令做一下拆解,可以得到这个图

通过上面的图,我们可以发现AM的代码组织是有对应的层次结构的,它由多个//等对象组织而成,无论你看到的代码多复杂,都是由这几个对象组成,每个对象设置相应的属性值,最后组成一个完整的动作命令,并执行这个命令。

我们继续解读这断代码,desc1是一个完整的动作描述,这个动作描述对象本质就描述了一句话: 对某个目标对象,设置一些属性。那目标对象的代码在哪里呢?如下

var ref1 = new ActionReference();
    ref1.putEnumerated( stringIDToTypeID( "layer" ), stringIDToTypeID( "ordinal" ), stringIDToTypeID( "targetEnum" ) );
desc1.putReference( stringIDToTypeID( "null" ), ref1 );

上面的代码定义了一个(简称AR)动作引用,给这个AR对象设置了枚举属性layer,对应的值是,从字面含义,我们就可以猜出来,这是当前选中的目标图层。然后把这个引用对象复制给desc1,这里为什么是null呢?,其实null的含义就是,你会发现

stringIDToTypeID("target") == charIDToTypeID("null")
typeIDToCharID(stringIDToTypeID("target")) // null

所以,含义就是我们desc1这个动作描述生效的目标对象就是当前选中的图层。有了操作对象了,然后是需要设置的一些属性,这些属性有一些是直接挂在desc1下的,有一些挂在另外一个AD下,比如这个属性,它包含了水平和垂直两个方向的数值,所以它是另外一个描述符对象,为了理解方便,我们将上面的结构用伪JSON来表示,一看就明白了

{
    "command": "transform",
    "descriptor": {
        "interfaceIconFrameDimmed": "bicubic",
        "width": 139,
        "height": 152,
        "offset": {
            "horizontal": 58,
            "vertical": 31
        },
        "freeTransformCenterState": "QCSAverage",
        "target": { 
            "layer": "targetNum"
        }
    }
}

在设置属性值的时候,desc1设置属性值的方法有很多/等,这些通过目标属性值就可以判断出来,其中第二个参数目标值的单位或者属性类型,这些类型大体是可枚举的

数值型,/,参数类型是PS提供的数值类型,比如表示像素,表示英寸,还有百分比等枚举类型,,参数类型对应的是一个这样(这里就是底层C++的某个结构体的体现)对象类型,,比如AD/AR/AL等,这种场景,一般参数类型就是自己本身,比如

desc1.putObject( stringIDToTypeID( "offset" ), stringIDToTypeID( "offset" ), desc2 );

3. DOM VS

在我们常规开发的时候,DOM和AM是可以结合使用的,反正大家都是JS代码,所可以混合在一起来达成目的,你可以优先用DOM API来完成操作,如果发现某个需求没有相应的API,则可以通过查看的代码输出,拷贝出来结合使用。通常,我们将拷贝出来的AM代码包装成一个函数,然后把里头可变的值作为参数传进去,以便后续调用

function transform(width, height, offsetX, offsetY) {
    var desc1 = new ActionDescriptor();
    var ref1 = new ActionReference();
    ref1.putEnumerated( stringIDToTypeID( "layer" ), stringIDToTypeID( "ordinal" ), stringIDToTypeID( "targetEnum" ) );
    desc1.putReference( stringIDToTypeID( "null" ), ref1 );
    desc1.putEnumerated( stringIDToTypeID( "freeTransformCenterState" ), stringIDToTypeID( "quadCenterState" ), stringIDToTypeID( "QCSAverage" ) );
    var desc2 = new ActionDescriptor();
    desc2.putUnitDouble( stringIDToTypeID( "horizontal" ), stringIDToTypeID( "pixelsUnit" ), offsetX );
    desc2.putUnitDouble( stringIDToTypeID( "vertical" ), stringIDToTypeID( "pixelsUnit" ), offsetY );
    desc1.putObject( stringIDToTypeID( "offset" ), stringIDToTypeID( "offset" ), desc2 );
    desc1.putUnitDouble( stringIDToTypeID( "width" ), stringIDToTypeID( "percentUnit" ), width );
    desc1.putUnitDouble( stringIDToTypeID( "height" ), stringIDToTypeID( "percentUnit" ), height );
    desc1.putEnumerated( stringIDToTypeID( "interfaceIconFrameDimmed" ), stringIDToTypeID( "interpolationType" ), stringIDToTypeID( "bicubic" ) );
    executeAction( stringIDToTypeID( "transform" ), desc1, DialogModes.NO );
}
// 对某个图层进行变换
var layer = activeDocument.artLayers.getByName("Layer 1");
activeDocument.activeLayer = layer;
transform(200, 300, 20, 30);

插件输出的代码,冗余度很高,可读性很差,我们可以通过这个工具来进行代码优化

parse code

parse code

通常,大多数开发者都会根据自己的需要,将这些常见的AM代码做封装,以弥补DOM API的接口不足,通过实际验证我们发现AM的脚本执行效率要比DOM API高的多,所以推荐大家少用DOM API,尽量使用AM脚本来完成诉求。为了开发方便,我在前些年将这些AM脚本做了封装和抽象,包装了一套类似DOM API的库,比如对的常用操作,对Layer的常用操作,可以快速提升开发效率,该项目目前开源,并且在升级2.0版本,详情见

//--api

api总结

以上,就是本篇的主要内容,讲述了如何通过插件来获取PS操作输出AM代码,并且对其中的代码做了一些介绍,让大家能够对这种不常见的语法结构做一些了解,希望阅读本篇文章之后,你看到那一坨AM代码,心理就不太怵了。但是这篇文章并没有解决一个很实际又很重要的问题:我现在只学会了抄作业,不会自己写!

我们发现输出的代码,基本都是我们在PS里头做了实际操作,它才会输出,做操作的过程是一个SET的过程,那如果我想GET一些数据,就无法从插件的输出里头获取到了,比如我想获取当前PS的主题颜色,获取当前选中的多个图层等,就无从下手了。还有,不是所有的PS操作,都会在中有输出

提醒:请联系我时一定说明是从浚耀商务生活网上看到的!