本系列文章涉及的主題是PhotoShop plugin開發(fā)淆两。
要做的:
由兩篇文章組成:
基礎(chǔ)繪制篇--以PhotoShop的Document為舞臺断箫,在上面繪制文字和任意形狀
圖層操作篇--PS最強(qiáng)大的是圖層操作,通過操作圖層秋冰,我們可以完成各種想做的事情仲义,最經(jīng)典的就是用于UI自動切圖.
有了這些PSDom概念后,你會發(fā)現(xiàn)其實開發(fā)PS插件蠻簡單的,PSDom非常強(qiáng)大埃撵。
不做的:
1) 不涉及Channel操作赵颅,對位圖像素操作我們不關(guān)心
2) 不涉及PhotoShop界面編程。因為PS界面編程隨著版本變化暂刘,有好多種方式饺谬,一直在改變。
C++方式谣拣,強(qiáng)大募寨,但是難度較大,不是三言兩語說得清楚的
As3 Flex方式森缠,簡單易用拔鹰,和PSDom交互方便,但是適合Adobe CS系列
Html5方式贵涵,簡單應(yīng)用列肢,和PSDom交互方便,但是適合Adobe CC系列
但不管如何宾茂,核心的PhotoShop操作是通過PSDom API公開出來的瓷马,
這個不管版本如何變,其本身DOM是不會發(fā)生較大變化的跨晴。
因此才更有研究的價值
PSDom的開發(fā)環(huán)境--ExtendScript ToolKit:
在安裝Adobe cs/cc系列時欧聘,會自動安裝(我裝的是CS5.0)
如果在Windows下開發(fā),請見后綴名改為.jsx
如果使用.js后綴的話坟奥,可能會由Windows Scripting Host(WSH)來執(zhí)行树瞭,而不是CS 腳本解析器。防沖突爱谁!
通過這兩篇文章晒喷,希望能夠讓大家在PhotoShop中為所欲為!7玫小凉敲!
讓我們來關(guān)注第一篇吧:
1)PS DOM(文檔對象模型)用來操縱PS中的各個對象,可以使用AppleScript(mac專用),VBScript(windows專用),以及JavaScript(跨平臺寺旺,本文檔僅使用js代碼來演示).
2)PS DOM中關(guān)鍵類圖(豎線表示包含關(guān)系爷抓,且第三層開始的所有類都是所屬于Document):
3)與繪制相關(guān)的對象:
二維繪制可以歸類為三個繪制范疇:
1、位圖/圖像操作:
PS Dom通過Application.Document.Channel(通道)對象進(jìn)行某個通道的數(shù)據(jù)操作(例如你可以分別對位圖的四個通道R G B A 進(jìn)行操作)阻塑,這不是本文所關(guān)注的蓝撇。
2、文本繪制:
PS Dom通過使用Application.Document.ArtLayer.TextItem進(jìn)行文本的顯示和相關(guān)操作陈莽。
由上面的尋址關(guān)系可以看到TextItem文本對象是被附加到PS中的一個Layer上的渤昌。
3虽抄、矢量繪制:
PS Dom 通過使用:
Application.Document.PathItem
Application.Document.PathItem.SubPathItem Application.Document.PathItem.SubPathItem.PathPoint
這三個類來進(jìn)行相關(guān)操作的(實際上還有其他一些輔助類,具體我們在后面了解)
由上面的尋址關(guān)系可以看到PathItem路徑對象是并沒有像TextItem一樣是被附加到PS中的一個Layer上的独柑,而是獨立于layer對象的迈窟。
4)JS運行調(diào)試環(huán)境和API參考文檔:
安裝好PhostShop CS5或更高的版本后,在你的PS安裝目錄下可以看到如下目錄:
其中JavaScript Ref和Scripting Guide是你必須要進(jìn)行查閱的參考忌栅,因為PS DOM開發(fā)網(wǎng)絡(luò)上的信息非常少车酣,所以這兩篇文檔是你基本唯一的依靠
Utilities 目錄下:
scriptListener插件,用于PS Action錄制時候?qū)浿频牟襟E的js代碼記錄下來并輸出到桌面文件索绪。 是一個非常重要的具有開發(fā)功能插件湖员,以后會有專門文章,目前不關(guān)注瑞驱。
Sample Scripts 目錄下:
雙擊運行
里面都是js演示代碼:
雙擊后跳出Yes/No Alert界面:
Yes會打開PS并運行代碼破衔,顯示腳本運行后的效果
No會打開JS開發(fā)環(huán)境,可以進(jìn)行腳本開發(fā)钱烟,修改,運行嫡丙,debug等
5)常用的基礎(chǔ)代碼結(jié)構(gòu)流程:
1拴袭、js代碼文件的開始
//每個JS文件的開始部分,這些代碼基本可以說是固定的
// 如果有 #target photoshop曙博,就直接打開Ps運行js腳本
//如果沒有拥刻,則打開ps的腳本編輯器而不是直接在ps中運行
#target photoshop
// in case we double clicked the file
app.bringToFront();
2、單位標(biāo)尺等數(shù)據(jù)的保存與恢復(fù):
// 存儲當(dāng)前的狀態(tài)
var startRulerUnits = app.preferences.rulerUnits;
var startTypeUnits = app.preferences.typeUnits;
var startDisplayDialogs = app.displayDialogs;
//設(shè)置新的狀態(tài)父泳,我們使用像素來作為標(biāo)尺和文檔中各個對象的計量單位
//當(dāng)然也可以設(shè)置其他計量單位般哼,例如厘米,英寸等
app.preferences.rulerUnits = Units.PIXELS;
app.preferences.typeUnits = TypeUnits.PIXELS;
app.displayDialogs = DialogModes.NO;
//之所以這么做惠窄,是因為ps的開發(fā)是基于狀態(tài)機(jī)模式的
//所有圖形化操作基本都是狀態(tài)機(jī)蒸眠,opengl d3d gdi quartz2d.......
3、如果你PS打開了很多程序杆融,希望只顯示現(xiàn)在正在操作的那個文檔楞卡,那么可以使用下面代碼先關(guān)閉所有文檔
// 關(guān)閉所有打開的文檔
while (app.documents.length != 0)
{
app.activeDocument.close();
}
4、使用open全局函數(shù)脾歇,打開一個PSD文件蒋腮,并將該文件設(shè)置為當(dāng)前活動的Document
var doc = open(File(app.path + "/示例/mytest.psd"));
app.activeDocument = buttonDoc;
5、 針對各種文檔進(jìn)行相關(guān)的操作
針對文檔對象以及文檔中的各個對象進(jìn)行操作的代碼是需要你來實現(xiàn)的E焊鳌池摧!
6、操作完文檔后激况,需要恢復(fù)到原來的狀態(tài)(恢復(fù)到開始時記錄下來的狀態(tài)值)
// 恢復(fù)到修改前的狀態(tài)
app.preferences.rulerUnits = startRulerUnits;
app.preferences.typeUnits = startTypeUnits;
app.displayDialogs = startDisplayDialogs;
//狀態(tài)機(jī)恢復(fù)default狀態(tài)
- 文本操作(TextItem)
需求描述:
1作彤、打印出ps所有的Font對象的信息膘魄,目的是為了了解TextFont對象主要屬性: Family|name|postScriptName|style
function printAllInstalledTextFontInfo()
{
var allFonts = [];
for(var i = 0; i < app.fonts.length; i++)
{
var str = "{"+app.fonts[i].family+"|"+app.fonts[i].name+"|"
+app.fonts[i].postScriptName+"|"+app.fonts[i].style+"}";
allFonts.push (str);
}
alert(allFonts);
}
2、不要從現(xiàn)有的PSD載入宦棺,而是從無到有通過代碼創(chuàng)建文檔瓣距,層等
//創(chuàng)建一個Document對象
var docRef = app.documents.add(400, 300, 72);
//創(chuàng)建一個ArtLayer對象
var newTextLayer = docRef.artLayers.add();
//注意:一個文本對象必須要依附于一個Layer對象,并且Layer的kind必須是TEXT類型
newTextLayer.kind = LayerKind.TEXT;
3代咸、使用微軟雅黑并blod字體樣式
//當(dāng)設(shè)置為TEXT類型時蹈丸,PS自動會創(chuàng)建一個TextItem對象給Layer,因此可以直接引用
newTextLayer.textItem.font = "MicrosoftYaHeiUI-Blod";
//這里需要注意的是呐芥,TextItem.font的數(shù)據(jù)類型是字符串逻杖,該字符串是使用了TextFont
//對象中的postScriptName,這一點務(wù)必要注意。
//由這里看出思瘟,前面 printAllInstalledTextFontInfo函數(shù)的作用了荸百,你可以打印出來,查找
//postScriptName的名稱滨攻,然后記住是用這個名稱來設(shè)置字體name和style
4够话、繪制文字內(nèi)容為"隨風(fēng)而行的PSDOM Demo"
//設(shè)置要顯示的內(nèi)容
newTextLayer.textItem.contents = "隨風(fēng)而行的PSDOM Demo";
5、設(shè)置文字的大小
newTextLayer.textItem.size = 36;
6光绕、文字顏色為紅色(SolidColor對象)
var textColor = new SolidColor;
textColor.rgb.red = 255;
textColor.rgb.green = 0;
textColor.rgb.blue = 0;
newTextLayer.textItem.color = textColor;
//這樣就可以顯示文字了
其他的文字相關(guān)屬性請查閱文檔
7)路徑操作(PathItem)---核心操作
1女嘲、先來一段示例代碼,能夠?qū)athItem有個比較直觀的了解
我們編寫一個函數(shù)诞帐,用來顯示多邊形:
function DrawPolygon() {
//PS是狀態(tài)機(jī)欣尼,基于當(dāng)前選中的狀態(tài)進(jìn)行操作
var doc = app.activeDocument;
//獲取參數(shù)的數(shù)量,使用js可變參數(shù)
//因為多邊形參數(shù)不確定停蕉,例如三角形愕鼓,三個頂點,n邊形慧起,n個頂點
var y = arguments.length;
var i = 0;
var lineArray = [];
for (i = 0; i < y; i++) {
//創(chuàng)建一個PathPointInfo對象
//里面包含繪制點的相關(guān)信息
lineArray[i] = new PathPointInfo;
//多邊形是凸包菇晃,沒有任何曲線段表示,因此每個點都是CORNERPOINT類型
//如果是曲線的話蚓挤,那么每個點的類似是SMOOTHPOINT
lineArray[i].kind = PointKind.CORNERPOINT;
//要繪制的點的坐標(biāo)谋旦,來源于參數(shù),類型為[x,y];
//對于非曲線來說屈尼,leftDirection = rightDirection=anchor
lineArray[i].anchor = arguments[i];
lineArray[i].leftDirection = lineArray[i].anchor;
lineArray[i].rightDirection = lineArray[i].anchor;
}
//到此處册着,所有的繪制點的信息都保存在lineArray數(shù)組中
//創(chuàng)建一個SubPathInfo對象
var lineSubPathArray = new SubPathInfo();
//SubPathiInfo.entireSubPath指向了要繪制的頂點數(shù)據(jù)數(shù)組
lineSubPathArray.entireSubPath = lineArray;
//設(shè)置SubPathiInfo.closed為true,這樣在strokePath時候,會自動封閉整個路徑
//否則如果為false的話脾歧,那么會缺少最后一條線段甲捏,導(dǎo)致路徑非封閉狀態(tài)。
lineSubPathArray.closed = true;
//設(shè)置ShapeOperation為Add模式鞭执,疊加模式司顿,前景層直接覆蓋到背景層上
//還有其他也寫操作芒粹,可以理解為布爾操作,例如前景和背景取并集大溜,交集化漆,差集等
lineSubPathArray.operation = ShapeOperation.SHAPEADD;
//創(chuàng)建一個PathItem對象,使用的是doc.pathItems.add方法
//注意钦奋,我們會發(fā)現(xiàn)是doc而不像TextItem是屬于層對象的座云。
var myPathItem = doc.pathItems.add("myPath" , [lineSubPathArray]);
//調(diào)用PathItem的描邊函數(shù)
//矢量圖形繪制可以分為邊的繪制以及封閉形體的填充兩種操作
//strokePath用來進(jìn)行邊的繪制
//fillPath則用來進(jìn)行填充內(nèi)容
myPathItem.strokePath(ToolType.PENCIL);
//繪制好后,將PathItem去除掉付材,由于已經(jīng)描邊渲染了朦拖,所有所有效果都輸出到
//像素緩沖區(qū)了,因此不需要該PathItem了
//如果你需要后續(xù)進(jìn)行頂點級別的操作的話厌衔,那你也可以保留著璧帝,不要remove掉
myPathItem.remove();
}
2、測試多邊形繪制:
//從兩個點生成4個繪制點富寿,繪制Rect
function DrawRect(left,top,right,bottom)
{
DrawPolygon( [left,top], [right,top], [right,bottom], [left,bottom] );
}
//由于strokePath時使用的顏色是基于當(dāng)前的前景色的
//注意睬隶,如果是填充封閉路徑fillPath的話,則使用指定顏色作為參數(shù)页徐,但是描邊是基于前
//景色的操作
//為了防止干擾理疙,因此先記錄下當(dāng)前的前景色
var saveColor = app.foregroundColor;
//生成一個紅色的SolidColor對象
var newColor = new SolidColor;
newColor.rgb.red = 255;
newColor.rgb.green = 0;
newColor.rgb.blue = 0;
//設(shè)置前景色為紅色,并繪制三角形
app.foregroundColor = newColor;
DrawPolygon([250,10],[350,10],[250,100]);
//修改顏色為綠色
newColor.rgb.red = 0;
newColor.rgb.green = 255;
newColor.rgb.blue = 0;
//設(shè)置前景色為綠色泞坦,并繪制四邊形
app.foregroundColor = newColor;
DrawRect(10,100,100,200);
//修改顏色為藍(lán)色,繪制8角形
newColor.rgb.red = 0;
newColor.rgb.green = 0;
newColor.rgb.blue = 255;
app.foregroundColor = newColor;
DrawPolygon([36.9999885559082,13.9999985694885],[165.99999666214,13.9999985694885],[185.999989509583,33.9999973773956],[185.999989509583,61.9999945163727],[165.99999666214,81.9999992847443],[36.9999885559082,81.9999992847443],[16.9999957084656,61.9999945163727],[16.9999957084656,33.9999973773956]);
//完成后砖顷,將前景色恢復(fù)到以前記錄下來的顏色
app.foregroundColor = saveColor;
在PhotoShop顯示的結(jié)果如下:(我們使用Brush進(jìn)行繪制贰锁,如果線條的話,可以使用Pencil)
如果使用填充模式,在PhotoShop中獲得的效果:
3滤蝠、PathItem對象模型:
由此可見豌熄,PathItem對象的讀寫是使用不同的類來表示的,切記!物咳!
4锣险、貝塞爾曲線曲面研究:
pathItem中的PathPoint有三個很重要的屬性
pathPoint.anchor[x,y] The X and Y coordinates of the anchor point of the curve
pathPoint.leftDirection[a,b] The location of the left-direction endpoint (’in’position).
pathPoint.rightDirection[e,f] The location of the right-direction endpoint (’out’position).
For paths that are straight segments (not curved), the coordinates of all three points are the same.
如果是直線的話,leftDirection = rightDirection = anchor
但是如果是曲線的話览闰,文檔中的解釋是
For curved segements, the the coordinates are different. The difference between the anchor point and the left or right direction points determines the arc of the curve. You use the left direction point to bend the curve "outward" or make it convex; you use the right direction point to bend the curve "inward" or make it concave.
這些描述非常抽象芯肤,而且ps dom文檔只演示了非曲線曲面的demo,對于曲線曲面并沒有demo压鉴,而且網(wǎng)上也很難查到相關(guān)資料
因此只有抽取數(shù)據(jù)進(jìn)行查看了解其數(shù)據(jù)的格式以及猜測strokePath是如何繪制的
為了研究strokePath是如何繪制曲線的崖咨,必須先要取得矢量對象中所有曲線的點,看看是如何組成的油吭,所以先將矢量對象所有的點輸出到文件击蹲,以利于查看:
function saveFile(msg1,msg2,msg3,types)
{
//彈出saveFile對話框署拟,save類型為txt文件
var file = File.saveDialog("Saving TXT file.", "TXT Files: *.txt");
if ( file == null ) return;
//如果已經(jīng)存在,彈框確認(rèn)是否覆蓋重寫
if (file.exists) {
if(!confirm(file.fsName + " exists.\\\\rOver write?")) return;
}
//打開要寫的文件流歌豺,并且設(shè)置編碼為utf16格式存儲
file.open("w"); // open as write
file.encoding = "UTF16";
file.write("\\\\uFEFF"); // Unicode marker
//將anchor數(shù)據(jù)寫入文件流
file.write("pts:");
file.write(msg1);
//將left Direction points數(shù)據(jù)寫入文件流
file.write("lts:");
file.write(msg2);
file.write("rts:");
file.write(msg3);
//將right Direction points數(shù)據(jù)寫入文件流
file.write("tps:");
file.write(types);
file.write("\\\\n");
//關(guān)閉文件流推穷,寫入完成
file.close();
}
//將每個pathItem中的每個subPathItem中的pathPoint值輸出到文件參看具體數(shù)據(jù)
for(var i = 0; i < buttonDoc.pathItems.length; i++)
{
var subItems = buttonDoc.pathItems[i].subPathItems;
for(var j = 0; j < subItems.length; j++)
{
var subItem = subItems[j];
var pathPoints = subItem.pathPoints;
var pts = [];
var lefts = [];
var rights = [];
var types = []
for(var k = 0; k < pathPoints.length; k++)
{
pts.push(pathPoints[k].anchor);
lefts.push(pathPoints[k].leftDirection);
rights.push(pathPoints[k].rightDirection);
types.push(pathPoints[k].kind);
}
saveFile(pts,lefts,rights,types);
}
}
上面代碼運行后會在桌面生成一個文件,例如叫PathItem.txt
我們將一個圓角矩形輸出到文件类咧,并且對浮點數(shù)進(jìn)行四舍五入后獲得所有數(shù)據(jù)馒铃,進(jìn)行分析:
1、頂點的定義是左手系轮听,順時針方式 [0骗露,1,2血巍,3萧锉,4,5述寡,6柿隙,7]
如上圖所畫形狀。多邊形的面是有正反面之分,ps中按照左手順時針為正面鲫凶。
2禀崖、線段是以Line_Strip或Line_Strip_Loop方式連接的
上圖來源于opengl的圖元類型中的線圖源類型
ps使用的是:
Line_Strip(如果SubPathItem.closed = false)
Line_Strip_loop(如果SubPathItem.closed = true).
備注,OpenGL的頂點定義是右手系逆時針螟炫。而psdom中和d3d一樣使用左手系表示
Line_Strip n個頂點確定n-1條線段 n >= 2
Line_Strip_Loop n個頂點確定n條線段波附,最后一條線段首位相連 n>=2
3、PathItem貝塞爾3次曲線中四個頂點的含義
如果要繪制上面的曲線昼钻,則頂點如下:
pathPoint0.anchor = p0;
pathPoint0.leftDirection = p0;
pathPoint0.rightDirection = r0;
pathPoint1.anchor = p1;
pathPoint1.leftDirection = l1;
pathPoint1.rightDirection = r1;
pathPoint1.anchor = p2;
pathPoint1.leftDirection = l2;
pathPoint1.rightDirection = p0;
具體還是來看一個完整的繪制圓角矩形的例子吧
function DrawRoundRetange(left,top,right,bottom,radius)
{
if(radius <= 0.01)
{
DrawRetangle(left,top,right,bottom);
return;
}
if(radius>=(Math.min(right-left, bottom-top))/2.0)
{
alert("半徑太大");
//如何處理掸屡,要研究
//PS中、一般的情況下然评,是8個點
//但是在半徑太大或者其他一些情況下仅财,貌似用6個點
//目前還沒法逆向出來,以后再研究吧M胩省盏求!
return;
}
var doc = app.activeDocument;
var i = 0;
var lineArray = [];
//下面代碼以半徑和頂點為參數(shù),設(shè)置錨點和定位點
//具體算法上圖演示
//這些算法需要有一定數(shù)學(xué)基礎(chǔ)的亿眠,我正想開辟一個文章集碎罚,名為
//數(shù)學(xué)之美
lineArray[0] = new PathPointInfo;
lineArray[0].kind = PointKind.SMOOTHPOINT;
lineArray[0].anchor = [left+radius,top];
lineArray[0].leftDirection =lineArray[0].anchor;
lineArray[0].rightDirection = [left,top];
lineArray[1] = new PathPointInfo;
lineArray[1].kind = PointKind.SMOOTHPOINT;
lineArray[1].anchor = [right-radius,top];
lineArray[1].leftDirection = [right,top];
lineArray[1].rightDirection = lineArray[1].anchor;
lineArray[2] = new PathPointInfo;
lineArray[2].kind = PointKind.SMOOTHPOINT;
lineArray[2].anchor = [right,top+radius];
lineArray[2].leftDirection = lineArray[2].anchor;;
lineArray[2].rightDirection = [right,top];
lineArray[3] = new PathPointInfo;
lineArray[3].kind = PointKind.SMOOTHPOINT;
lineArray[3].anchor = [right,bottom-radius];
lineArray[3].leftDirection = [right,bottom];
lineArray[3].rightDirection = lineArray[3].anchor;
lineArray[4] = new PathPointInfo;
lineArray[4].kind = PointKind.SMOOTHPOINT;
lineArray[4].anchor = [right -radius,bottom];
lineArray[4].leftDirection = lineArray[4].anchor;
lineArray[4].rightDirection =[right,bottom];
lineArray[5] = new PathPointInfo;
lineArray[5].kind = PointKind.SMOOTHPOINT;
lineArray[5].anchor = [left+radius,bottom];
lineArray[5].leftDirection = [left,bottom];
lineArray[5].rightDirection = lineArray[5].anchor;
lineArray[6] = new PathPointInfo;
lineArray[6].kind = PointKind.SMOOTHPOINT;
lineArray[6].anchor = [left,bottom-radius];
lineArray[6].leftDirection = lineArray[6].anchor;
lineArray[6].rightDirection = [left,bottom];
lineArray[7] = new PathPointInfo;
lineArray[7].kind = PointKind.SMOOTHPOINT;
lineArray[7].anchor = [left,top+radius];
lineArray[7].leftDirection = [left,top];
lineArray[7].rightDirection = lineArray[7].anchor;
var lineSubPathArray = new SubPathInfo();
lineSubPathArray.closed = true;
lineSubPathArray.operation = ShapeOperation.SHAPEADD;
lineSubPathArray.entireSubPath = lineArray;
var myPathItem = doc.pathItems.add("myRoundedRetangle" ,
[lineSubPathArray]);
myPathItem.strokePath(ToolType.PENCIL);
//myPathItem.fillPath ();
myPathItem.remove();
}
我們來測試一下:
// 存儲當(dāng)前的狀態(tài)
var startRulerUnits = app.preferences.rulerUnits;
var startTypeUnits = app.preferences.typeUnits;
var startDisplayDialogs = app.displayDialogs;
//設(shè)置新的狀態(tài)
app.preferences.rulerUnits = Units.PIXELS;
app.preferences.typeUnits = TypeUnits.PIXELS;
app.displayDialogs = DialogModes.NO;
// 關(guān)閉所有打開的文檔
while (app.documents.length != 0) {
app.activeDocument.close();
}
//創(chuàng)建一個新的500*500大小 dpi=72的文檔對象,名稱為PathItemTest
//并且設(shè)定為當(dāng)前對象
var doc = app.documents.add(500,500,72, "PathItemTest");
app.activeDocument = doc;
//調(diào)用上面的函數(shù)
drawRoundRetange(20,20,200,200,50);
//恢復(fù)為系統(tǒng)初始化的狀態(tài)
app.preferences.rulerUnits = startRulerUnits;
app.preferences.typeUnits = startTypeUnits;
app.displayDialogs = startDisplayDialogs;
至此纳像,我們掌控Photoshop的第一篇結(jié)束魂莫,下一篇我們來關(guān)注如何進(jìn)行PS Layer的操作,這也是非常有趣的一個話題爹耗。
注: 關(guān)于參數(shù)曲線曲面耙考,其實還是有很多數(shù)學(xué)知識的谜喊,包括前段時間發(fā)的OpenGL太陽系Demo這篇bolg,并沒有很詳細(xì)的講解代碼倦始,因為都涉及到很多計算機(jī)圖形學(xué)的數(shù)學(xué)方法斗遏,因此隨風(fēng)計劃開辟一個名為數(shù)學(xué)之美的文集,使用基于javascript /typescript的Canvas2D,WebGL來演示各種動畫效果鞋邑,體驗數(shù)學(xué)之美之強(qiáng)大!
演示代碼將在第二篇層操作發(fā)布時候一起提交到github诵次,到時可以進(jìn)行下載。