发新话题
打印

[技术] 谈PS动作、脚本实际工作中的应用

谈PS动作、脚本实际工作中的应用

最近在做一些无聊游戏,具体就不多说了,谈实际碰到的两个例子:
1 d" ]; F. s( G6 w由于常常有人抱怨写的东西看不懂,所以废话多了点,讲的内容也比较粗浅,还望熟手见谅
; o( k4 \) v) n5 g4 Q9 f8 {( Q一、自己的麻将牌变对家的牌
' d; }% M( r' c% c) x$ ~. {做好一副麻将牌,需要把它上下颠倒过来。
- r* r2 ?, W! A% ?: ^/ w

% W, c; s8 c- b3 z# [$ p- Z1 _
点击放大

如果用垂直or水平反转,很明显字都是反的。直接旋转顺序不符合程序要求,且透视不对,于是要一张张牌单独处理。

由于是重复操作,且没有什么变数,于是我们采用动作处理:
  _; z  J& S/ v; Y. m5 C做选区,ctrl+t旋转180度,移动选区到下一个位置(1张牌的距离)

本来这个动作很简单,但是实际测试发现:
7 I% ?2 A  \1 `- b: r# Kctrl+t自由变换后,移动选区会带着图像走,不能单纯移动

想办法解决:自由变换后,先取消选区(ctrl+d),再恢复上一个选区(ctrl+shift+d)。这样,在移动选区,就不会带走图像了。

于是我们录制动作,一遍遍的执行,只要第一次选区准确、移动准确,就万事大吉了( \) d9 [" V- M# c
最近在做一些无聊游戏,具体就不多说了,谈实际碰到的两个例子:! T' d1 v0 `2 |1 D
由于常常有人抱怨写的东西看不懂,所以废话多了点,讲的内容也比较粗浅,还望熟手见谅: }2 u3 X! f$ {0 `6 P
一、自己的麻将牌变对家的牌
2 S3 {7 g" ]2 k, T做好一副麻将牌,需要把它上下颠倒过来。6 e0 P; E: {/ E  o6 Y


, C, j% Y4 V6 w3 T/ G# H; e点击放大

如果用垂直or水平反转,很明显字都是反的。直接旋转顺序不符合程序要求,且透视不对,于是要一张张牌单独处理。

由于是重复操作,且没有什么变数,于是我们采用动作处理:' }/ r1 T9 G* M1 H
做选区,ctrl+t旋转180度,移动选区到下一个位置(1张牌的距离)

本来这个动作很简单,但是实际测试发现:
7 s# o2 l- w& I! Z: N6 pctrl+t自由变换后,移动选区会带着图像走,不能单纯移动

想办法解决:自由变换后,先取消选区(ctrl+d),再恢复上一个选区(ctrl+shift+d)。这样,在移动选区,就不会带走图像了。

于是我们录制动作,一遍遍的执行,只要第一次选区准确、移动准确,就万事大吉了
8 |2 n0 ^; r8 n& T0 ?3 y

当然,如果重复数量十分庞大,懒得自己去点,还可以用 xiexienila的这个脚本 。
$ }' s7 s2 D7 Y9 X

当然,如果重复数量十分庞大,懒得自己去点,还可以用 xiexienila的这个脚本 。

TOP

二、找茬游戏中脚本的应用
8 {2 j. @/ a4 F5 D' {# A/ V项目要求:制作一大批图片。每两幅一组,两幅之间有十处不同,记录每处不同的矩形区域坐标。
+ J6 K9 X8 l8 m) G  Q首先是制作图片:其实就是简单的p图,制造一些差异。
1 g9 i0 `. l7 n- T; p4 ~2 O为了方便对比观察,避免错漏,使用了cs3的智能对象的堆栈功能(new)。
; j" L! b) b1 h4 Q* L4 t# E感觉这样比较方便比较,能在精确查看不同的同时ps图片的内容。
) o2 ^% q( }+ ]首先建立固定大小的文件,再把素材图拉进来,调整大小,然后ctrl+e合并到底层(合并是为了确保未来的智能对象大小和图像大小一致),ctrl+j 新建一层。我们只ps 新建出来的这一层,下面不动。
  t' O$ ?9 I% S! s( t因为有10处差异,所以光靠切换可视对比~比较辛苦,且容易错漏,[差值]对比效果也差又累。$ v$ g+ g3 m: a! v. C- l
所以我们同时选中两个图层,右键-[转换为智能对象],然后菜单-图层-智能对象-堆栈模式-标准偏差: ]1 y) D- i& S2 q" X) Q

这时候我们就明显看到两层之间的差异了,
7 n+ t1 {8 _' e1 `# S我们可以双击图层面板的智能对象缩略图,就可以展开进入智能对象内部,继续编辑两个图层2 Y) G0 e& d8 |3 D# G' U

我们在智能对象内部编辑的时候,只要随手ctrl+s 保存一下,就可以看到原图(黑色那张)上的差异变化了。4 W& u* H- R$ {# W( e* f, d
图像处理完后,然后就是获得由差异区域的坐标:程序需要把差异区域作为一个个矩形,要得到每个区域左上角和右下角的坐标。4 L) U/ }7 ?4 F5 I. U( f% A! z) n

最开始,想把所有区域拉出选区,然后通过 cs3 的新增统计功能获得详细的选区数据,结果~~
. A- }9 `  L7 C2 g非常遗憾,统计可以同时获得所有选区的周长、长宽、面积甚至密度~ 就是不给出具体的每一组坐标(如下图所示)9 o* Y6 B3 F8 l$ A) a

于是只有改变做法,这时候想到了脚本 里面的 selection.bounds 获得选区坐标,. s- F: X. q4 @% ]! o5 }
遗憾的发现,似乎脚本里面没有获取多个区域选取数据的方法,只能获得总选区的左上角+右下角坐标
! \5 r1 V& y4 {1 [' ^, v( K也就是说10个区域当成一个大区域来看了。~9 I+ Z# ?) V+ h; \# S, b$ F
继续改变方案,改用图层,每个图层只记录一个区域,总可以了吧~~( z- Q  ~- w; i* {
因为之前的经验,确定图层的范围坐标是可以在脚本里通过 ArtLayer.bounds 获取的。' C1 [6 W5 x- K2 N9 A
所以接下来要做的事情就是手动建立一个个小矩形的图层。- }7 M: O* ], f* ^! o* ?
由于工作量巨大,不偷懒是不行的,所以录制了一个动作
6 T' r' E) n$ R# E2 |* e

可以看到,这个动作录制了3步:: R! t, ?4 W! z, v- x' v3 q1 x
  • 新建图层
  • 填充选区
  • 取消选区
而且这个动作设置了快捷键 F12 (双击动作名称,就可以设置快捷键)" {0 ^. \6 m' e! @6 A& y" ]
有了这个动作,我只要拉出一个选区,然后按一下 F12,就自动新建一个图层,并填充好。5 E2 \" y5 p. p5 D
这样就方便脚本获取每层的数据了。
! C, W  O  x0 y! v5 F3 o完成后的文件结构如下:& W, c' e9 _2 E7 b3 O  ~- @

上面是10层不同位置的矩形,最底层是一个智能对象(包含两个图层,上面一层为修改后的,下面一层为原图)  A6 m2 o# Z) l, `! [$ u9 _
文件格式ok,接下来就是脚本大显身手的地方了。

TOP

接下来我们开始编写脚本,为了通俗,这一步主要只是谈谈思路
6 X# W, }4 F  _4 t# L3 }( Z7 Z首先测试单个文件,脚本大致需要执行如下步骤:& W- Y* s4 H' _# H* J# G
  • 移动到最底层
  • 向上移动一层,利用 activeLayer.bounds 记录层范围坐标,并把坐标记录下来
  • 反复执行第2步,直到最顶层
  • 输出记录
有了以前一些脚本的经验,实现上面这个功能没有碰到什么难度,很容易搞定。+ B  J: ^$ D, n* E; N. B* h, s
然后就是批量处理了,借鉴 xiexienila 的变更尺寸脚本 ,该脚本已经有完善的批量打开、保存处理模块。局部Copy后稍加修改,就让我们的脚本实现了如下功能:
# t3 w9 z; x1 ]9 q: m/ k* D8 c
  • 用户选择待处理文件夹
  • 获取该文件夹下所有文件
  • 打开一个文件,获取并记录所有需要的坐标,关闭不保存
  • 重复第3步,直至处理完所有文件
  • 输出记录
测试成功后,想到:既然已经动用脚本获取了所有坐标,索性把两张不同的图片也输出保存好了。于是添加了一个保存位置选择。
' s0 R( j% G% X" E$ _* n并对单个文件内的操作作了修改:) C! ?) c. R: l( E
  • 移动到最底层
  • 打开最底层智能对象
  • 另存智能对象为jpg文档(xxx_1)
  • 隐藏智能对象里的最上层,也就是我们修改过的那层
  • 再次另存智能对象为jpg文档(xxx_0)
  • 不保存关闭智能对象(回到原文档)
  • 向上移动一层,利用 activeLayer.bounds 记录层范围坐标,并把坐标记录下来
  • 反复执行第7步,直到最顶层
这样不但记录了坐标数据,还顺便把智能对象里面的两个层都输出为jpg图像了。9 q/ ?4 a: L. Y6 A
后来,由于第二张图片和第一张图片很多相同的地方,导致游戏文件体积较大,所以想了一个解决办法,就是把相同的部分用黑色挡住,只保留不同的地方,这样jpg就小很多。也许是个笨办法吧,毕竟不太清楚别人怎么做的,这里只是给大家说说思路罢了。
% y0 F% r" Q% g* q1 J% M于是再次修改单个文件内的操作部分' H, k; r1 `, J8 X0 V2 l2 F( c
  • 移动到最底层
  • 向上移动一层,利用activeLayer.bounds 记录层范围坐标,并把坐标记录下来
  • 反复执行第2步,直到最顶层
  • 再次回到最底层
  • 打开最底层智能对象
  • 利用记录的坐标,建立选区(增加模式),全部选区增添完后,反选
  • 填充黑色(这时候是填充在智能对象内部最上层上,也就是我们修改过的那层)
  • 另存为智能对象为jpg文档(xxx_1)
  • 隐藏当前图层
  • 再次另存智能对象为jpg文档(xxx_0)
  • 不保存关闭智能对象(回到原文档)
这样我们就给修改过的图像增添了一个黑色部分,挡住了没动过的地方,只留下了差异处。为了减少jpg 保存可能对边缘造成的影响,所以黑色部分的填充范围缩小了2像素。后来为了修改方便,索性在面板上放了一个位置,可以手动输入缩小量。: k" o7 U7 U% C# @: U4 x
为了程序调用方便,脚本还顺便实现其他一些功能:
) E2 _/ x# t! D2 I/ J比如把文件名作些规范处理:按照数字大小排序文件(否则打开顺序会是1、11、12、2、21这样),并且把1.psd 、2.psd 之类的记录为 0001、0002;) _$ c" X5 p' h* a
用简单正则替换去掉坐标记录转换为字符串后的“ px”单位等等。
9 u( z4 n6 q( N& r4 W为了处理时对进度有个掌握,脚本界面上还放了进度条。1 ]6 x& V) _8 z+ c
最后,发现有时候不需要重新生成图像,只需要获取坐标。又在界面上加了一个 “仅查询坐标,不生成图片”的选项。如果勾选,就会跳过保存的步骤,以节省时间。: ~/ v, D) m3 L$ g# Q: X& J$ u4 r
以上的过程,最终就是如下这个脚本程序,处理我近百张图片也就3分多的样子,手工的话又容易出错又慢,程序的优势就这样体现出来了。7 C3 F6 m* S- r7 q

以后要修改、调整,只要修改psd文档,再用脚本重新生成 就很快完成工作。两个多小时的编写调试还是值得的 。
复制内容到剪贴板
代码:

#target photoshop
app.bringToFront();
res ="dialog { \
text:'找茬数据专用',\
        group: Group{orientation: 'column',alignChildren:'left',\
                folderO:Group{ orientation: 'row', \
                                b: Button {text:'待处理文件夹', properties:{name:'open'} ,helpTip:'选择您需要处理的文件所在的文件夹'},\
                                s: EditText  { text:'', preferredSize: [360, 20] },\
                                },\
                folderS:Group{ orientation: 'row', \
                                    b: Button {text:'输出图像至', properties:{name:'save'} ,helpTip:'选择您处理好的文件要保存至的文件夹'},\
                                    s: EditText  { text:'', preferredSize: [360, 20] },\
                                    },\
                meng:Group{ orientation: 'row', \
                                    c:Checkbox { text:' 启用黑色蒙版'} ,\
                                    s: StaticText { text:'| 蒙版收缩量(单位px):' }, \
                                    e: EditText  { text:'2', preferredSize: [20, 18]},\
                                    },\
                Quality: Group { orientation: 'row',  \
                                    c:Checkbox { text:' 仅查询坐标,不生成图片'} ,\
                                    s: StaticText { text:'| 生成JPG的压缩质量:' }, \
                                    d: DropDownList { alignment:'left', itemSize: [26,14] },\
                                    }, \
                gg: Group{orientation: 'column',alignChildren:'left' },\
                timeline:Progressbar{bounds:[0,0,400,10] , minvalue:0,maxvalue:100}\
                aa: Button { text:'START'}, \
        }\
}";
var mengPoint="";
var mengColor =new SolidColor;
mengColor.rgb.red =0;
mengColor.rgb.green =0;
mengColor.rgb.blue =0;
                        
win = new Window (res);
win.myText = win.group.gg.add("edittext",[0,0,500,300],'~~~',{multiline:true, readonly:false});
for (i=0;i<13;i++){ //初始化jpeg质量下拉
    win.group.Quality.d.add("item", i );
}
win.group.Quality.d.items[7].selected=true;
function lyFoot() { // 选中最下层
var id553 = charIDToTypeID( "slct" );
    var desc88 = new ActionDescriptor();
    var id554 = charIDToTypeID( "null" );
        var ref95 = new ActionReference();
        var id555 = charIDToTypeID( "Lyr " );
        var id556 = charIDToTypeID( "Ordn" );
        var id557 = charIDToTypeID( "Back" );
        ref95.putEnumerated( id555, id556, id557 );
    desc88.putReference( id554, ref95 );
    var id558 = charIDToTypeID( "MkVs" );
    desc88.putBoolean( id558, false );
executeAction( id553, desc88, DialogModes.NO );
}
function lyUp(){ //选中上一层
var id559 = charIDToTypeID( "slct" );
    var desc89 = new ActionDescriptor();
    var id560 = charIDToTypeID( "null" );
        var ref96 = new ActionReference();
        var id561 = charIDToTypeID( "Lyr " );
        var id562 = charIDToTypeID( "Ordn" );
        var id563 = charIDToTypeID( "Frwr" );
        ref96.putEnumerated( id561, id562, id563 );
    desc89.putReference( id560, ref96 );
    var id564 = charIDToTypeID( "MkVs" );
    desc89.putBoolean( id564, false );
executeAction( id559, desc89, DialogModes.NO );   
}
function openSm() { //打开智能对象
    var id216 = stringIDToTypeID( "placedLayerEditContents" );
    var desc43 = new ActionDescriptor();
executeAction( id216, desc43, DialogModes.NO );   
}
function lyHidden(){ //隐藏当前图层
    var id217 = charIDToTypeID( "Hd  " );
    var desc44 = new ActionDescriptor();
    var id218 = charIDToTypeID( "null" );
        var list1 = new ActionList();
            var ref24 = new ActionReference();
            var id219 = charIDToTypeID( "Lyr " );
            var id220 = charIDToTypeID( "Ordn" );
            var id221 = charIDToTypeID( "Trgt" );
            ref24.putEnumerated( id219, id220, id221 );
        list1.putReference( ref24 );
    desc44.putList( id218, list1 );
executeAction( id217, desc44, DialogModes.NO );   
}
function sm(name) { //保存结果图像
    lyFoot();
    openSm();   
    var smDoc=app.activeDocument;
    if (win.group.meng.c.value) meng(smDoc);
    var saveFolder = win.group.folderS.s.text+"/";
    saveOptions = new JPEGSaveOptions();
    saveOptions.quality =win.group.Quality.d.selection.index;; //获取jpg压缩质量
    smDoc.saveAs(new File(saveFolder + name + "_1.jpg"),saveOptions, true,Extension.LOWERCASE);   
    lyHidden();
    smDoc.saveAs(new File(saveFolder + name + "_0.jpg"),saveOptions, true,Extension.LOWERCASE);
    smDoc.close(SaveOptions.DONOTSAVECHANGES);
}
function selectBounds(name,a,b,c,d) { //做选区
  app.activeDocument.selection.select([[a, b],[ a, d ], [c, d], [ c, b]],SelectionType.EXTEND);
}
function meng(smDoc) { //添加蒙版
    nowPoint=mengPoint.split(",");
    for (var i=0;i<nowPoint.length-4;i+=4){
        selectBounds(smDoc,nowPoint[i],nowPoint[i+1],nowPoint[i+2],nowPoint[i+3],)
    }
// ==================================扩展n像素
var id32 = charIDToTypeID( "Expn" );
    var desc5 = new ActionDescriptor();
    var id33 = charIDToTypeID( "By  " );
    var id34 = charIDToTypeID( "#Pxl" );
    desc5.putUnitDouble( id33, id34, Number(win.group.meng.e.text) );
executeAction( id32, desc5, DialogModes.NO );
// ==================================反选
var id35 = charIDToTypeID( "Invs" );
executeAction( id35, undefined, DialogModes.NO );
//
    smDoc.selection.fill(mengColor); //填充蒙版色
}
// 打开文件夹的操作
var folderOpen=win.group.folderO
var folderSave=win.group.folderS
folderOpen.b.onClick = function() {
        var defaultFolder = folderOpen.s.text;
        var testFolder = new Folder(defaultFolder);
        if (!testFolder.exists) {
            defaultFolder = "~";
        }
        var selFolder = Folder.selectDialog("选择待处理文件夹", defaultFolder);
        if ( selFolder != null ) {
            folderOpen.s.text = selFolder.fsName;
            folderOpen.s.helpTip = selFolder.fsName.toString();
        }
}
folderSave.b.onClick = function() {
        var defaultFolder = folderSave.s.text;
        var testFolder = new Folder(defaultFolder);
        if (!testFolder.exists) {
            defaultFolder = "~";
        }
        var selFolder = Folder.selectDialog("选择要储存至的文件夹", defaultFolder);
        if ( selFolder != null ) {
            folderSave.s.text = selFolder.fsName;
            folderSave.s.helpTip = selFolder.fsName.toString();
        }
}
win.group.aa.onClick=function(){
    var myText="";   
    var openFolder = Folder(win.group.folderO.s.text);               
        var fileList = openFolder.getFiles() //获取open文件夹下所有文件
        win.group.timeline.value =0;
        var k=100/fileList.length;
        
        //调整文件顺序,按数字大小排序
        fileList.sort(function compare(a,b){return Number(a.name.substring(0, a.name.length-4))-Number(b.name.substring(0, b.name.length-4));})
        //
        for (i=0;i<fileList.length;i++){
            if (fileList[i] instanceof File && fileList[i].hidden == false){ //不处理隐藏文件
                var docRef =open(fileList[i]);
                var nowName =docRef.name.substring(0, docRef.name.length-4);
                while (nowName.length<4) {
                    nowName ="0"+nowName;
                }
                myText +=nowName+",";
                mengPoint="";
                lyFoot();                           
                for (j=1;j<docRef.layers.length;j++){   
                    lyUp();   
                    myText+=docRef.activeLayer.bounds+",";
                    mengPoint+=docRef.activeLayer.bounds+",";
                }               
                if (!win.group.Quality.c.value) sm(nowName);                    
                docRef.close(SaveOptions.DONOTSAVECHANGES);
                myText +="\r\n";
            }        
            win.group.timeline.value =win.group.timeline.value+k;
        }            
var re = / px/g; //要替换的“ px”
win.myText.text=myText.replace(re, "");
}
//////////////
win.center();
win.show();

TOP

在编写脚本的时候,不能不提到的一个辅助工具就是“脚本侦听程序”
" R  T/ F0 _: \& ?0 M1 y3 i/ e+ k这个东西就在 cs3 安装目录下面的“脚本指南/实用工具”里面(英文版在 Scripting Guide\Utilities\)( R2 D) p8 n2 F2 ~. _- |
! U' J& x% ~$ f. k/ {% D) l. P
如果把它拷贝到“增效工具/自动”目录下(英文版为 Plug-Ins\Automate),再重新启动ps。你的ps就相当于安装了一个“**”,会把你所有的操作步骤像录制动作一样录制为脚本。只要你有可记录的动作,它就在桌面生成“ScriptingListenerJS”、“ScriptingListenerVB” 两个文本文件。其实就是 javascript 和 vbscript 两种规则记录的动作。  _6 X  ]( n0 s

虽然不像手工书写的代码易于理解和修改,但是很多直接操作的步骤都可以拷贝来用。
. e: O8 d5 @3 ~% ^3 J; H比如说上面的 “移动到最底层”“选中上一层”“隐藏当前层”“打开智能对象”“扩展n像素”“反选”等等动作就是通过脚本侦听录制下来 直接拷贝过来的。$ E, L( F( ]5 W9 C6 [
再结合自己的编写的其他逻辑语句,很容易写出你想要的东西。7 z: F+ Q$ m$ A- ?% w/ x7 |
最后,希望有点编程基础又有兴趣的朋友,
# e; h9 v1 B. b在处理重复、量大或者经常碰到的工作的时候,多多挖掘ps的潜力。' |: O  j; n" B- G6 c9 c! z, F
其实写一个简单的针对性脚本或者动作 并不是很难哦 ^_^
, {& @: ^. @  W& M$ ~0 A6 g提供jsx源文件+两个psd文档,分本是 F14和歼10,有兴趣的朋友可以试试看,注意cs3以上
, `  ^2 K1 m* p7 @+ }

源文件下载

鉴于实践表明

同样显示效果下 [另存为jpg] 比 [保存为web所用格式-jpg] 文件体积要大很多,所以最后替换了保存函数。 把saveAs,换成了exportDocument.,具体如下:

复制内容到剪贴板
代码:
function sm(name) { //保存结果图像
    lyFoot();
    openSm();   
    var smDoc=app.activeDocument;
    if (win.group.meng.c.value) meng(smDoc);
    var saveFolder = win.group.folderS.s.text+"/";
    saveOptions = new ExportOptionsSaveForWeb();
    saveOptions.format =SaveDocumentType.JPEG;
    saveOptions.quality=win.group.Quality.e.text;
    smDoc.exportDocument(new File(saveFolder + name + "_1.jpg"),ExportType.SAVEFORWEB,saveOptions);   
    lyHidden();
    smDoc.exportDocument(new File(saveFolder + name + "_0.jpg"),ExportType.SAVEFORWEB,saveOptions);
    smDoc.close(SaveOptions.DONOTSAVECHANGES);
}

最后完整程序下载

TOP

谢谢楼主````````

TOP

发新话题