ご存じの方もいらっしゃると思いますが、Adobe Photoshop は JavaScript で制御することができます。
Photoshop 用の JavaScript ファイルのことを「JSX」と呼びます。拡張子は .jsx です。
Photoshop を使ってWeb制作されている方は多いかと思いますが、Photoshop は本来写真加工用のソフトでありWebデザイン用のソフトではないため、特にレイアウト機能が Fireworks、Illustrator、InDesign などに比べ貧弱です。
しかし、JSX を使用することにより Photoshop のレイアウト機能やWeb制作向けの機能をある程度向上させることができます。
JSX は昔から Photoshop に搭載されていましたが、書き方の参考になるような書籍やサイトが少ないためかそれ程注目されていなかった気がします。
しかし、最近は CSS Nite LP, Disk 22「Webデザインで使うPhotoshop」で有用な JSX が紹介されたり、Photoshopの便利なスクリプトや拡張機能のまとめ などの記事により JSX への注目が高まっている気がします。
そこでこの記事では、Web制作に有用なスクリプトを紹介するだけでなく、私が Photoshop によるWeb制作の効率を向上させるために制作した「数値を指定してシェイプを作成する」JSX と「数値を指定してシェイプのサイズや位置を変更する」JSX を通して、JSX の書き方やカスタマイズ方法をご紹介いたします。
私自身、JSX についてはまだまだ勉強中ですが、何かの参考になれば幸いです。
目次
- Web制作に役立ちそうなJSX
- JSXの使い方
- 自作JSXの紹介: 数値を指定してシェイプを作成、変更、複製
- JSXを書く
- JSXのリファレンス
- シェイプ作成JSXの解説
- シェイプ変更JSXの解説
- 参考文献
Web制作に役立ちそうなJSX
まずは、JSX を使うとどんなことができるか知るために、Web制作に有用な JSX をいくつかご紹介いたします。
選択範囲にあわせてガイドを引くJSX
名前の通り、現在の選択範囲の上下左右ぴったりにガイドを引いてくれます。
長方形だけでなく、複雑な選択範囲でもOKです。
Ctrl+A または command+A でカンバスをすべて選択してからこの JSX を実行すると、カンバスの上下左右にガイドが引かれます。地味に便利なスクリプトだと思います。
選択範囲にあわせてガイドを引く.jsx | 乱雑モックアップ
「ダウンロード」の下の「addGuide.jsx|選択範囲にあわせてガイドを引く.jsx」を右クリックして保存します。
オブジェクトを等間隔に整列させるJSX
例えばグローバルナビゲーションを制作する時など、文字数が違うものを等間隔に整列させることが Photoshop にはできません(Fireworks、Illustratorではできます)。
しかし、「Distribute Layer Spacing Horizontal」という JSX を使うと、それらを等間隔に整列させることができます。
Adobe Photoshop Scripts | Trevor Morris Photographics
「Download for: Photoshop CS3+」を右クリックして保存します。
複数のオブジェクトのカラーをまとめて変更
複数のシェイプやテキストのカラーをまとめて変更できる JSX です。
複数のシェイプやテキストを選択後、スクリプト実行すると Photoshop 標準のカラーピッカーが表示され、まとめてカラーを変更することができます。
サイト作るときに便利なPhotoShop JSX、ショートカット、アクション – Develo.org
ページ下の青い「Photoshop JSX」「webCreate Short cut Set」 ダウンロード を右クリックして保存します。
ZIP解凍後、「カラーピッカー」フォルダ内にある「colorPicker.jsx」を使用します。
(このZIPファイルには、他にも有用な JSX がいくつか入っています。使い方は↑のページに書かれています。)
JSXの使い方
ダウンロードした JSX は Photoshop の「Scripts」フォルダに入れることにより便利に使用することができます。
「Scripts」フォルダの場所は以下の通りです(Photoshop CS5 の場合)。
Windows の場合
C:¥Program Files¥Adobe¥Adobe Photoshop CS5¥Presets¥Scripts
(古いバージョンの場合、¥Presets¥Scripts のフォルダ名がカタカナで ¥プリセット¥スクリプト のようになっています。)
Mac の場合
Macintosh HD/アプリケーション/Adobe Photoshop CS5/Presets/Scripts/
Photoshop を起動し直すと、メニューの「ファイル>スクリプト」内に、先ほど Scripts フォルダに入れた JSX が表示されているはずです。
ここに表示される名前は JSX のファイル名になるので、わかりやすい名前をつけておくと良いかもしれません。
ファイル名を変更した際は Photoshop を再起動してください。
私の場合、作成系はファイル名の先頭に★、変更系は☆、書き出し系は▲などルールを決めてファイル名をつけています。
素早くスクリプトを実行するために、ショートカットキーを設定しておくと次回から楽です。
ショートカットキーは、メニューの「編集>ショートカットキー」で設定できます。
自作JSXの紹介: 数値を指定してシェイプを作成、変更、複製
JSXを使わずシェイプを座標指定で作成するには
私は Photoshop で長方形、角丸長方形などのシェイプを数値指定で作成したり、サイズを変更する作業は地味に面倒だと思っています。
標準の機能でシェイプを数値指定で作成し、座標を指定するには、例えば以下のような操作方法があります。
まず、サイズ指定で長方形シェイプを作成するには、ツールから「長方形ツール」を選び、オプションバーの▼をクリックして「固定」ラジオボタンをクリック、WとHのテキスト入力欄に「180 px」のように書き、画面上の任意の場所でクリックします。
次に座標を指定して移動するには、Ctrl+T または command+T キーを押し、オプションバーの基準点の位置で左上をクリックし(小さいのでかなりクリックしづらい)X と Y の欄に 100 px のように単位付きで数値を入力し、Enter キーを押して確定します。
(もし、もっと手軽な方法をご存じの方がいれば教えて頂けると幸いです。)
既に存在するシェイプ作成JSX
しかし、これでは正直めんどくさいです。
なので、これをどうにかする JSX はないかと探していたところ以下のようなものを見つけました。
Dearps ~Adobe Photoshopに関するTipsや便利な裏技を紹介するサイトです~ » 幅!高さ!角丸!サイズを指定してシェイプを作成!
これだ!と思ったのですが、この JSX では作成したシェイプは自動的にカンバスの中心に配置されるため、残念ながら X,Y 座標指定での配置ができません。
また、シェイプの W,H,R(幅、高さ、角丸)がレイヤー名に入るのは便利ですが、シェイプのサイズを変更してもそれがレイヤー名に反映されることはありません。
そこで、以下のような JSX を自分で書こうと思い立ちました。
- W,H,R(幅、高さ、角丸)を指定してシェイプを作成することができる
- X,Y 座標も指定して配置できる
- W,H,R がレイヤー名に入る(W 100 H 100 R 0 のような形式)
- W,H,R,X,Y 入力欄で四則演算が可能である(例: W 250*2+30 px 等)
それとは別に、シェイプのサイズや位置を数値指定で変更できる JSX も作る必要があります。
なぜかというと、「シェイプのサイズを変更した時に、レイヤー名を更新する必要がある」からです。
具体的には以下のような機能が必要です。
- W,H,X,Y を数値指定で変更できる(R も変更する方法が無いか考えたが無理そう)
- W,H を変更した際はレイヤー名が変更される
- X,Y を変更して移動する場合やサイズ変更の際は、常に基準点を左上に
- ついでに Illustrator のような数値指定で複製する機能も
私は jQuery ライブラリを使わない純粋な JavaScript はほとんど書いたことがなかったのですが、これは JavaScript を勉強してでも書いてみたいと思ったので、JavaScript の勉強をして JSX を書きました。
数値を指定してシェイプを作成、変更、複製するJSXのダウンロード
この JSX は以下からダウンロードできます。
数値を指定してシェイプを作成する JSX と、変更・複製する JSX のダウンロード
ZIP 解凍後、JSX ファイルを「Scripts」フォルダに入れてお使い下さい。
場所は以下の通りです(Photoshop CS5 の場合)。
Windows の場合
C:¥Program Files¥Adobe¥Adobe Photoshop CS5¥Presets¥Scripts
(古いバージョンの場合、¥Presets¥Scripts のフォルダ名がカタカナで ¥プリセット¥スクリプト のようになっています。)
Mac の場合
Macintosh HD/アプリケーション/Adobe Photoshop CS5/Presets/Scripts/
使い方
Photoshop を再起動し、メニューの「ファイル>スクリプト」から「シェイプ作成」を選ぶと以下のようなダイアログが表示されます。
W は幅、H は高さ、R は角丸(0だと長方形、1以上だと角丸長方形)です。
X はカンバス左端からの位置、Y はカンバス上端からの位置です。
px等の単位は入力する必要はありません。自動的に px 単位になります。
もちろん、四則演算も可能です。
足すには + 、引くには – 、かけるには * 、割るには / を半角で入力して下さい。
テキスト入力フォームは、いちいちマウスクリックで移動しなくても Tab キーで次の項目に移動可能です。
どのフォームが選択されている状態でも、Enter キーを押すと [作成] ボタンが押されたことになり、Esc キーを押すとキャンセルできます。
Enter キーを押すか [作成] ボタンを押すとシェイプが作成されます。
レイヤー名は、自動的に「W 100 H 100 R 0」のようになります。
次に、シェイプのサイズを数値指定で変更したり、位置を数値指定で移動したりするには「ファイル>スクリプト」メニューから「シェイプ変更」をクリックして下さい。
使い方は見ての通りだと思います。
R(角丸)を変更することはできません。また角丸長方形の W や H の数値を変更した場合、角丸が潰れてしまいますので、角丸長方形の場合は W や H の数値は変更しないで下さい。
(角丸を変更したい場合は、現在のシェイプを削除し、シェイプ作成 JSX を使って新規作成されることをおすすめします。)
[複製] ボタンをクリックすると、シェイプが複製されます。
W,H を変更して複製すればサイズ違いのものが複製され、X,Y だけを変更して複製すればサイズはそのままでシェイプが作成されます。
この2つの JSX は、ショートカットキーも設定しておくと便利です。
私は「シェイプ作成」を command+control+S に、「シェイプ変更」を command+control+T に割り当てています。
注意点ですが、まれに W,H,X,Y の数値が実際のサイズや位置と1pxずれることがあります(W 100, H 100, X 100, Y 100 で作成したはずなのに W 99, H 99, X 101, Y 101 のようになっているなど)。
その場合は、OK ボタンや Enter キーは押さず、Esc キーでキャンセルし、Ctrl+T または command+T でサイズ変更してください。
なお、この JSX は Adobe Photoshop CS5 Mac版ならびに CS6 Beta Windows版(いずれも日本語環境)で動作確認しています。
Photoshop CS6 ではレイヤーパネルに「レイヤー名で絞り込み」等の機能が付いていますので、「W 200」のようにサイズで絞り込んだり「R 4」のように角丸のあるものだけ絞り込んだりできるので便利ですね。
ちょっと Photoshop CS6 欲しくなって来ました。
もちろん商用利用可です。より便利にカスタマイズされた場合や機能追加された場合などはご自由に配布していただいて構いません。
(カスタマイズや機能追加されていない場合は、こちらの記事にリンクして頂けると幸いです。)
制限事項
- 角丸のあるシェイプをリサイズしないで下さい(移動のみならOK)
- 角丸の大きさを変えることはできません
- 万が一、シェイプ変更.jsx で W,H,X,Y の値にずれがあった場合は使用しないで下さい。
- シェイプ変更.jsx では、選択範囲を使ってサイズを取得しているため、既に選択範囲があった場合は解除されてしまいます。
- これらのスクリプトにより発生した、いかなる損害も賠償できません。
このJSXの使用例を動画で観る
ここでは、関電エネルギーソリューション のような、固めの企業サイトの下地を3分で作成するというテーマで制作しています。
カンバス内に見えている薄い破線はグリッドで、100px ごとに引かれています。
100px ごとにグリッドがあると、座標の計算がしやすいです。
「環境設定>ガイド・グリッド・スライス」で「スタイル: 点線」「グリッド線: 100 pixel」「分割数: 100」に設定しています。なお、この設定は CSS Nite LP22 で黒葛原さんがお話されていた設定値を参考にしました。
数値が決まっていれば、サクサク作れることがお分かり頂けるかと思います。
なぜ数値指定で作成すべきなのか
最近、複数の方から「デザイナーが PSD で作ったデータがコーディングしづらい」という話を聞きました。
詳しく聞いたところ、ボタンのサイズや間隔(マージン)が一定でないためだということです。
どうしてボタンのサイズや間隔が一定でないのか考えたところ、やはり Photoshop はサイズを指定してシェイプを作成したり、位置を調整することが面倒なので、フリーハンドで作成していることが原因ではないかと思いました。
Photoshop でも、Fireworks や Illustrator のようにサイズ指定でシェイプを作成したり移動できるようになれば、よりコーディングしやすい PSD が作成できるようになるのではないかと思います。
JSXを書く
自分で JSX を書けるようになると、さまざまなメリットがあります。例えば
- 案件に合わせて「もっとこうしたいな」と思った時に自分で書き換えることができる
- 機能を追加したり、あるいは余計な機能を削除して軽量化したりできる
- もし新しいバージョンの Photoshop で動作しなかった場合、自分で修正できる可能性がある
- JavaScript の勉強にもなる
というわけで、私はせっかく自分で JSX を書く機会があったので、ついでに JSX の書き方の解説も記事にしてしまおうと思いました。
ただ、私は jQuery ライブラリを使用しない JavaScript を書いたのはこれがほぼ初めてですので、説明や認識が間違っている可能性があります。
もし明らかに間違っている箇所がございましたら、 @Stocker_jp まで教えて頂けると幸いです。
準備
まず、Scripts フォルダに「test.jsx」等の名前で新規ファイルを作成します。
この時点では、「test.jsx」は空のファイルで構いません。
Scripts フォルダの場所は以下の通りです。
Windows の場合
C:¥Program Files¥Adobe¥Adobe Photoshop CS5¥Presets¥Scripts
(古いバージョンの場合、¥Presets¥Scripts のフォルダ名がカタカナで ¥プリセット¥スクリプト のようになっています。)
Mac の場合
Macintosh HD/アプリケーション/Adobe Photoshop CS5/Presets/Scripts/
Photoshop を再起動し、「ファイル>スクリプト」に「test」があることを確認します。
「編集>ショートカットキー」から「test」に適当なショートカットキーを設定しておきます。
次に、「test.jsx」をテキストエディタで開き、以下のようなスクリプトを書き、文字コード UTF-8 で保存します。
alert("テスト");
Photoshop で、「test」に指定したショートカットキーを押すか、「ファイル>スクリプト>test」で「test.jsx」を起動します。
ファイル名に変更がなければ Photoshop の再起動は必要ありません。
問題なければ、以下のようなダイアログが表示されるはずです。
もしうまくいかなければ、文字コードや改行コードをチェックしましょう。
私の環境だと、Mac の CotEditor では問題ありませんでしたが、NetBeans ではうまく動かないことがありました。
JSXのデバッグ
とある JavaScript が得意なエンジニアの方に「JSX は書かれないですか?」と聞いたところ「デバッグしづらそう」と言われたのでデバッグの方法についても解説しておきます。
まず、変数名の確認やスクリプトの一時停止はWebブラウザ向けの JavaScript 同様、alert で良いと思います。
// 変数 hoge を宣言し、100 を代入
var hoge = 100;
// 変数 hoge を表示
alert(hoge);
もし alert(hoge); の後にスクリプトがあった場合、そのスクリプトは alert のOKボタンを押してを閉じるまで実行されません。
オブジェクトのすべてのプロパティとメソッドを表示したい場合、私は以下のようにしました。
まず、適当にオブジェクトを作ります。
// オブジェクトを作る
var Hoge = function() {
this.a = 10;
this.b = 20;
}
// オブジェクトを利用する
var thisHoge = new Hoge;
alert(thisHoge);
しかしこれでは、alert は [object Object] のように表示されてしまい、プロパティとメソッドを表示することができません。
そこで、JSX を書く時は、以下のページを参考に指定したオブジェクトの全てのプロパティとメソッドを表示するスクリプトを書いておきました。
JavaScriptで指定したオブジェクトの全てのプロパティとメソッドを表示する – hoge256ブログ
// オブジェクトを作る
var Hoge = function() {
this.a = 10;
this.b = 20;
}
// オブジェクトを利用する
var thisHoge = new Hoge;
/* 指定したオブジェクトの全てのプロパティとメソッドを表示する関数 */
function printProperties(obj) {
var properties = '';
for (var prop in obj){
properties += prop + "=" + obj[prop] + "\n";
}
alert(properties);
}
// オブジェクト thisHoge を引数として関数を実行
printProperties(thisHoge);
これで無事に指定したオブジェクトの全てのプロパティとメソッドが表示されました。
どうやら、Photoshop では1行目が alert のタイトルとして大きく表示されるようです。
ちなみに、JSX を実行した時にエラーが出た場合、日本語で比較的わかりやすく表示してくれます。
ですので、JSX を書くのはそれほど苦には感じませんでした。
あと、実は下記の場所に JSX 開発ツールと思われる「ExtendScript Toolkit」というアプリが入っています。
(.jsx ファイルのダブルクリックで ExtendScript Toolkit が起動する場合もあります。)
Windowsの場合
C:\Program Files\Adobe\Adobe Utilities\ExtendScript Toolkit\ExtendScript Toolkit.exe
Macの場合
Macintosh HD/アプリケーション/ユーティリティ/Adobe ユーティリティ – CS5/ExtendScript Toolkit/ExtendScript Toolkit.app
ExtendScript Toolkit を使用すると、一応 JSX をステップ実行したりすることはできます。
ただ、ここから JSX を実行するとやたら Photoshop が落ちますし、console.log() すら効かないし何のための開発ツールなのでしょう…
もし有用な利用法をご存じの方は @Stocker_jp まで教えて頂けると幸いです。
JSXのリファレンス
JSX を書く時に参考になるような日本語のリファレンスは少ないです。
実は、
/Applications/Adobe Photoshop CS5/Scripting/Documents/
フォルダ内に
Photoshop CS5 Scripting Guide.pdf
Photoshop CS5 JavaScript Ref.pdf
などの英語のリファレンスは入っていますが、私にはいまいち理解できませんでした。
ネットで探したところ、以下のページがとても参考になりました。
Adobe Photoshop CS6自動化作戦
Adobe CS6 バージョンJavaScript ファレンス
※Photoshop だけではなく Adobe 製品全般なので、Photoshop では使えないメソッドもあります。
シェイプ作成JSXの解説
シェイプ作成 JSX の中身はこうなっています。
おそらく JavaScript を書きなれた方から見ると汚いコードだと思います…すみません。
/*==============================================================================
File Name: シェイプ作成.jsx
Title: シェイプ作成
Version: 1.0.0
Author: Stocker.jp
Author URI: https://stocker.jp/
==============================================================================*/
#target photoshop
// 単位を px に変更
preferences.rulerUnits = Units.PIXELS;
// 実行フラグ
var do_flag = 1;
/* ダイアログ関係 */
// ダイアログオブジェクト = new Window("dialog",タイトル,[左座標,上座標,右座標,下座標])
uDlg = new Window('dialog','シェイプ作成',[100,100,440,255]);
// ダイアログを画面に対して中央揃えに
uDlg.center();
// ラベル W
uDlg.sText = uDlg.add("statictext",[20,23,275,10+15], "W");
// テキスト入力 W
uDlg.w = uDlg.add("edittext",[40,20,110,15+25], "100");
// ラベル H
uDlg.sText = uDlg.add("statictext",[130,23,275,10+15], "H");
// テキスト入力 H
uDlg.h = uDlg.add("edittext",[150,20,220,15+25], "100");
// ラベル R
uDlg.sText = uDlg.add("statictext",[260,23,275,20+15], "R");
// テキスト入力 R
uDlg.r = uDlg.add("edittext",[280,20,320,15+25], "0");
// ラベル X
uDlg.sText = uDlg.add("statictext",[20,73,275,10+15], "X");
// テキスト入力 X
uDlg.x = uDlg.add("edittext",[40,70,110,0+90], "0");
// ラベル Y
uDlg.sText = uDlg.add("statictext",[130,70,275,10+15], "Y");
// テキスト入力 Y
uDlg.y = uDlg.add("edittext",[150,70,220,0+90], "0");
// OKボタン
uDlg.okBtn = uDlg.add("button",[70,115,160,115+25], "作成", { name:"ok"});
// キャンセルボタン
uDlg.cancelBtn = uDlg.add("button", [180,115,270,115+25], "キャンセル", {name: "cancel"});
// キャンセルボタンが押されたらキャンセル処理(ESCキー含む)
uDlg.cancelBtn.onClick = function() {
// 実行フラグに0を代入
do_flag = 0;
// ダイアログを閉じる
uDlg.close();
}
// ダイアログを表示
uDlg.show();
// =======================================================
/* シェイプを作成し、レイヤー名をリネーム */
// 実行フラグが1(キャンセルボタンが押されていない)であれば
if (do_flag == 1) {
// シェイプを作成する関数(75行目)を実行
draw_shape();
// アクティブレイヤーをリネームする関数(122行目)を実行
rename_layer();
}
// =======================================================
/* 以下関数 */
/* シェイプを作成する関数
* 88、92行目等: eval(変数) とすると、変数の値が "100+20" のような演算子入りのものであった場合計算する
*/
function draw_shape() {
var idMk = charIDToTypeID( "Mk " );
var desc58 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref31 = new ActionReference();
var idcontentLayer = stringIDToTypeID( "contentLayer" );
ref31.putClass( idcontentLayer );
desc58.putReference( idnull, ref31 );
var idUsng = charIDToTypeID( "Usng" );
var desc59 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var idsolidColorLayer = stringIDToTypeID( "solidColorLayer" );
desc59.putClass( idType, idsolidColorLayer );
var idShp = charIDToTypeID( "Shp " );
var desc60 = new ActionDescriptor();
var idTop = charIDToTypeID( "Top " );
var idRlt = charIDToTypeID( "#Rlt" );
// Y座標1
desc60.putUnitDouble( idTop, idRlt, eval(uDlg.y.text) );
var idLeft = charIDToTypeID( "Left" );
var idRlt = charIDToTypeID( "#Rlt" );
// X座標
desc60.putUnitDouble( idLeft, idRlt, eval(uDlg.x.text) );
var idBtom = charIDToTypeID( "Btom" );
var idRlt = charIDToTypeID( "#Rlt" );
// Y座標2
shapey = eval(uDlg.y.text) + eval(uDlg.h.text);
desc60.putUnitDouble( idBtom, idRlt, shapey );
var idRght = charIDToTypeID( "Rght" );
var idRlt = charIDToTypeID( "#Rlt" );
// X座標2
shapex = eval(uDlg.x.text) + eval(uDlg.w.text);
desc60.putUnitDouble( idRght, idRlt, shapex );
var idRds = charIDToTypeID( "Rds " );
var idRlt = charIDToTypeID( "#Rlt" );
// R角丸
desc60.putUnitDouble( idRds, idRlt, eval(uDlg.r.text) );
var idRctn = charIDToTypeID( "Rctn" );
desc59.putObject( idShp, idRctn, desc60 );
var idcontentLayer = stringIDToTypeID( "contentLayer" );
desc58.putObject( idUsng, idcontentLayer, desc59 );
executeAction( idMk, desc58, DialogModes.NO );
}
/* アクティブレイヤーのレイヤー名変更する関数 */
function rename_layer() {
// 右側が新しいレイヤー名(例: W 100 H 100 R 0)
activeDocument.activeLayer.name = "W " + parseInt(eval(uDlg.w.text)) + " H " + parseInt(eval(uDlg.h.text)) + " R " + parseInt(eval(uDlg.r.text));
}
この JSX を書く時に気を付けたのは「カスタマイズしやすいように書いておく」ということです。
極力機能ごとに関数にまとめておき、OK ボタンが押された時はそれらの関数が順次実行されるようにしています。
機能を関数ごとに分けておくことにより、今後別の JSX を書く際にコードの再利用性が高まると考えています。
あとは、他の方がカスタマイズしやすいように極力コメントを書いています。
ではここからソース解説。
// 単位を px に変更
preferences.rulerUnits = Units.PIXELS;
普段Web以外の仕事をされている方の場合、「編集>環境設定>単位・定規」で pixel 以外の単位が設定されていることも考えられます。
ですので、「この JSX では単位を pixel に統一」するために、最初に単位を指定しています。
もし指定しておかないと、pixel 以外の単位になっていた時にサイズがおかしくなる場合があります。
// 実行フラグ
var do_flag = 1;
[作成] ボタンが押されたのか [キャンセル] ボタンが押されたのか判定するための変数を宣言しています。
あとで使用します。
// ダイアログオブジェクト = new Window("dialog",タイトル,[左座標,上座標,右座標,下座標])
uDlg = new Window('dialog','シェイプ作成',[100,100,440,255]);
// ダイアログを画面に対して中央揃えに
uDlg.center();
// ラベル W
uDlg.sText = uDlg.add("statictext",[20,23,275,10+15], "W");
// テキスト入力 W
uDlg.w = uDlg.add("edittext",[40,20,110,15+25], "100");
/* 以下略 */
「uDlg = new Window(‘dialog’,’シェイプ作成’,[100,100,440,255]);」の行で新しいダイアログを定義し、ダイアログのタイトルバーに表示される文字列やサイズを指定しています。
「ラベル」とはダイアログに表示される文字列(W や H など)、「テキスト入力」はテキスト入力フィールドを定義しています。
テキスト入力フィールドにフォーカスした状態で、Tab キーを押すと次のテキスト入力フィールドに移動しますが、その順序はここで書いている順になります。
できれば「uDlg.w.focus()」のように感じで最初からフォーカスできればよかったのですが、Photoshop では focus メソッドは使用できないようです。
// キャンセルボタンが押されたらキャンセル処理(ESCキー含む)
uDlg.cancelBtn.onClick = function() {
// 実行フラグに0を代入
do_flag = 0;
// ダイアログを閉じる
uDlg.close();
}
特に何も指定しないと、[キャンセル] ボタンを押した時に何も起きないようです。
そこで、ここでは [キャンセル] ボタンを押した時や Esc キーが押された時に実行フラグに0を代入し、ダイアログを閉じるよう指定しています(指定がないとダイアログが閉じず、何も起きていないように見えます)。
// 実行フラグが1(キャンセルボタンが押されていない)であれば
if (do_flag == 1) {
// シェイプを作成する関数(75行目)を実行
draw_shape();
// アクティブレイヤーをリネームする関数(122行目)を実行
rename_layer();
}
この JSX の15行目で変数「do_flag」に1を代入しています。
そして、[キャンセル] ボタンが押された時は53行目で「do_flag」に0を代入しています。
ということは、変数「do_flag」の値が1であれば [キャンセル] ボタンは押されていない(つまり [作成] ボタンが押された)ということになり、if 文で「do_flag == 1」を条件にシェイプを作成し、レイヤー名を「W 100 H 100 R 0」などにリネームすれば良いことになります。
それぞれ「draw_shape()」関数と「rename_layer()」関数にシェイプ作成とレイヤー名のリネーム処理を書いており、それらの関数はこの後定義しています。
function draw_shape() {
/* 中略 */
// Y座標1
desc60.putUnitDouble( idTop, idRlt, eval(uDlg.y.text) );
var idLeft = charIDToTypeID( "Left" );
var idRlt = charIDToTypeID( "#Rlt" );
/* 後略 */
このあたり、かなりソースがごちゃごちゃしていて長いですよね…
どうしてこういうことになっているのかというと、どうやら「簡単にシェイプを作成するメソッド」があるわけではないようなのです。
他の方が作成された JSX のコードを見ていると、例えば
var idMk = charIDToTypeID( "Mk " );
var desc58 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref31 = new ActionReference();
var idcontentLayer = stringIDToTypeID( "contentLayer" );
ref31.putClass( idcontentLayer );
desc58.putReference( idnull, ref31 );
var idUsng = charIDToTypeID( "Usng" );
var desc59 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var idsolidColorLayer = stringIDToTypeID( "solidColorLayer" );
desc59.putClass( idType, idsolidColorLayer );
var idShp = charIDToTypeID( "Shp " );
var desc60 = new ActionDescriptor();
var idTop = charIDToTypeID( "Top " );
var idRlt = charIDToTypeID( "#Rlt" );
のような、人間が書いたとは思えない謎のコードがところどころで見られました。
これはどうやって書かれたのかなと思って調べてみると、これはどうやら操作を自動的に記録して JSX で書きだす「ScriptingListener.plugin」というプラグインで自動生成された JSX のようなのです。
「ScriptingListener.plugin」は Photoshop に標準で付いているプラグインですが、標準では有効になっていません。
有効にするには、以下のフォルダに入っている「ScriptingListener.plugin」をコピーし…
Adobe Photoshop CS5/Scripting/Utilities/ScriptingListener.plugin
以下のフォルダにペーストします。
Adobe Photoshop CS5/Plug-ins/Automate/ScriptingListener.plugin
Photoshop を再起動し、シェイプを作成するなど何か作業を行うと、デスクトップに「ScriptingListenerJS.log」というログファイル(中身はテキスト)ができます。
テキストエディタで開くと上記のような JSX が書かれており、作業ごとにコメント(// ===========)で区切られていることが分かります。
ここで、JSX として書き出したい作業を Photoshop で行うとデスクトップの「ScriptingListenerJS.log」に追記されるので、あとはそれをコピーして自分の JSX にペーストすれば良いというわけです。
改めて先ほどの箇所を見直すと、
function draw_shape() {
/* 中略 */
// Y座標1
desc60.putUnitDouble( idTop, idRlt, eval(uDlg.y.text) );
var idLeft = charIDToTypeID( "Left" );
var idRlt = charIDToTypeID( "#Rlt" );
/* 後略 */
のようになっています。
ここは本来、「eval(uDlg.y.text)」の箇所にシェイプの Y 座標が入っていたのですが、それをダイアログから入力した値「uDlg.y.text」に置換しました。
さらに、ダイアログから入力した値が「100+100」や「100*2」のような四則演算を含む値だった場合計算した結果になるよう eval で囲みました。
/* アクティブレイヤーのレイヤー名変更する関数 */
function rename_layer() {
// 右側が新しいレイヤー名(例: W 100 H 100 R 0)
activeDocument.activeLayer.name = "W " + parseInt(eval(uDlg.w.text)) + " H " + parseInt(eval(uDlg.h.text)) + " R " + parseInt(eval(uDlg.r.text));
}
最後に、アクティブなレイヤーのレイヤー名を変更する関数です。
「activeDocument.activeLayer.name = “新しいレイヤー”」のようにすると、アクティブなレイヤーのレイヤー名が「新しいレイヤー」になります。
今回は「W 100 H 100 R 0」のような形式になるようにダイアログから入力した値を eval で囲み、さらに文字列を整数に変換する parseInt で囲っています(これで囲わないと、0100 のように先頭に 0 が付いている値が8進数扱いになってしまうため)。
文字列の連結は + です。
シェイプ変更JSXの解説
シェイプ変更用の JSX は基本的にシェイプ作成用の JSX とされほど変わりませんが、多少異なる部分もありますのでそこを解説します。
基本的な処理の流れは以下のような感じです。
アクティブなレイヤーを移動する
アクティブなレイヤーをリサイズする
アクティブレイヤーをリネームする
ただし、レイヤー名が(W 100 H 100 R 0)のような形式でなければ、最後のリネームは行いません(例えば、ユーザーがレイヤー名を「ボタン」などにしていた場合はリネームを除外)。
/*==============================================================================
File Name: シェイプサイズ変更.jsx
Title: シェイプサイズ変更
Version: 1.1.0
Author: Stocker.jp
Author URI: https://stocker.jp/
==============================================================================*/
#target photoshop
// ドキュメントの解像度が 72ppi でなければアラート
if (activeDocument.resolution !== 72) {
alert("解像度が72ppiではありません。「編集>画像解像度」から解像度 72 pixel/inch にしてください。");
}
// 単位を px に変更
preferences.rulerUnits = Units.PIXELS;
// 実行フラグ
var do_flag = 1;
// 現在のレイヤーで選択範囲を作る
try {
var idsetd = charIDToTypeID( "setd" );
var desc85 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref57 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idfsel = charIDToTypeID( "fsel" );
ref57.putProperty( idChnl, idfsel );
desc85.putReference( idnull, ref57 );
var idT = charIDToTypeID( "T " );
var ref58 = new ActionReference();
var idPath = charIDToTypeID( "Path" );
var idPath = charIDToTypeID( "Path" );
var idvectorMask = stringIDToTypeID( "vectorMask" );
ref58.putEnumerated( idPath, idPath, idvectorMask );
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref58.putEnumerated( idLyr, idOrdn, idTrgt );
desc85.putReference( idT, ref58 );
var idVrsn = charIDToTypeID( "Vrsn" );
desc85.putInteger( idVrsn, 1 );
var idvectorMaskParams = stringIDToTypeID( "vectorMaskParams" );
desc85.putBoolean( idvectorMaskParams, true );
executeAction( idsetd, desc85, DialogModes.NO );
} catch(e) {
alert("選択されているレイヤーがありません。");
}
// 現在のレイヤーで選択範囲を作る ここまで
// アクティブレイヤーのサイズを求める
var layObj = activeDocument.selection.bounds;
var x1 = parseInt(layObj[0]);
var y1 = parseInt(layObj[1]);
var x2 = parseInt(layObj[2]);
var y2 = parseInt(layObj[3]);
// X,Y,W,H を取得
var x = x1;
var y = y1;
var w = eval(x2-x1);
var h = eval(y2-y1);
// 選択範囲を解除
activeDocument.selection.deselect();
// =======================================================
/* ダイアログ関係 */
// ダイアログオブジェクト = new Window("dialog",タイトル,[左座標,上座標,右座標,下座標])
uDlg = new Window('dialog','シェイプサイズ変更',[100,100,410,285]);
// ダイアログを画面に対して中央揃えに
uDlg.center();
// ラベル W
uDlg.sText = uDlg.add("statictext",[20,23,275,10+15], "W");
// テキスト入力 W
uDlg.w = uDlg.add("edittext",[40,20,110,15+25], w);
// ラベル H
uDlg.sText = uDlg.add("statictext",[130,23,275,10+15], "H");
// テキスト入力 H
uDlg.h = uDlg.add("edittext",[150,20,220,15+25], h);
// ラベル X
uDlg.sText = uDlg.add("statictext",[20,73,275,10+15], "X");
// テキスト入力 X
uDlg.x = uDlg.add("edittext",[40,70,110,0+90], x);
// ラベル Y
uDlg.sText = uDlg.add("statictext",[130,73,275,10+15], "Y");
// テキスト入力 Y
uDlg.y = uDlg.add("edittext",[150,70,220,0+90], y);
// 注意書き
uDlg.sText = uDlg.add("statictext",[20,110,285,10+15], "数値にずれがある場合は使用しないで下さい。");
// OKボタン
uDlg.okBtn = uDlg.add("button",[60,145,150,145+25], "OK", { name:"ok"});
// キャンセルボタン
uDlg.cancelBtn = uDlg.add("button", [170,145,260,145+25], "キャンセル", {name: "cancel"});
// 複製ボタン
uDlg.copyBtn = uDlg.add("button",[240,15,295,25+15], "複製", { name:"copy"});
// 複製ボタンが押されたら複製処理
uDlg.copyBtn.onClick = function() {
// 実行フラグに2を代入
do_flag = 2;
uDlg.close();
}
// キャンセルボタンが押されたらキャンセル処理(ESCキー含む)
uDlg.cancelBtn.onClick = function() {
// 実行フラグに0を代入
do_flag = 0;
uDlg.close();
}
// ダイアログを表示
uDlg.show();
// =======================================================
/* シェイプを移動し、レイヤー名をリネーム */
// 実行フラグが1(OKボタンが押された)であれば
if (do_flag == 1) {
// 現在のレイヤーの r の数値を取得する関数を実行
get_layer_r();
// シェイプを移動する関数(107行目)を実行
move_shape();
// シェイプをリサイズする関数(89行目)を実行
resize_shape();
// アクティブレイヤーをリネームする関数(142行目)を実行
rename_layer();
}
// 実行フラグが2(複製ボタンが押された)であれば
if (do_flag == 2) {
// 現在のレイヤーの r の数値を取得する関数を実行
get_layer_r();
// レイヤーを複製する関数を実行
layer_copy();
// シェイプを移動する関数(107行目)を実行
move_shape();
// シェイプをリサイズする関数(89行目)を実行
resize_shape();
// アクティブレイヤーをリネームする関数(142行目)を実行
rename_layer();
}
// =======================================================
/* 以下関数 */
/* 現在のレイヤーの r の数値を取得する関数 */
// グローバル変数 number_r と layer_name_flagを宣言
var number_r;
var layer_name_flag;
function get_layer_r() {
// アクティブなレイヤーのレイヤー名を取得
var layer_name = activeDocument.activeLayer.name;
// try {} の中を実行してみて、もしエラーが出れば catch {} の中を実行
try {
// 数値を取得し、変数 number に代入
var number = layer_name.match(/\d+/g);
// 変数 number の3つめの値を number_r に代入
number_r = number[2];
layer_name_flag = true;
} catch(error) {
// エラーが出た場合は layer_name_flag 変数に false を代入
layer_name_flag = false;
}
}
/* シェイプをリサイズする関数*/
function resize_shape() {
// 新しいWの%を求める(ダイアログで入力されたW÷現在のW×100)
var new_w_per = eval(eval(uDlg.w.text)/w*100);
// 新しいHの%を求める(ダイアログで入力されたH÷現在のH×100)
var new_h_per = eval(eval(uDlg.h.text)/h*100);
// try {} の中を実行してみて、もしエラーが出れば catch {} の中を実行
try {
// レイヤーのリサイズ(Wの%, Hの%)
activeDocument.activeLayer.resize(new_w_per, new_h_per);
} catch(error) {
alert('リサイズしたいレイヤーが選択されていません。');
}
}
/* レイヤーを複製する関数 */
function layer_copy() {
// 現在のレイヤーを複製
activeDocument.activeLayer.duplicate();
}
/* シェイプを移動する関数 */
function move_shape() {
// 相対値で何px移動すべきか求める
var new_x = 0;
var new_y = 0;
// X分岐フラグ = ユーザーが入力したX - 現在のX
// ユーザーに入力させた値は parseInt() で数値に(そうしないと文字列扱い→8進数扱いになる)
flag_x = parseInt(eval(uDlg.x.text)) - x;
// ユーザーが入力したX と 現在のX が等しくなければ
if (parseInt(eval(uDlg.x.text)) != x) {
// ユーザーが入力したX - 現在のX
new_x = parseInt(eval(uDlg.x.text) - x);
}
// Y分岐フラグ = ユーザーが入力したY - 現在のY
flag_y = parseInt(eval(uDlg.y.text)) - y;
// ユーザーが入力したY と 現在のY が等しくなければ
if (parseInt(eval(uDlg.y.text)) != y) {
// ユーザーが入力したY - 現在のY
new_y = parseInt(eval(uDlg.y.text) - y);
}
// サイズ変更された場合、その半分の値だけ移動(左上基準でサイズ変更)
var new_x2 = eval(eval(uDlg.w.text) - w) / 2;
var new_y2 = eval(eval(uDlg.h.text) - h) / 2;
new_x = new_x + new_x2;
new_y = new_y + new_y2;
// try {} の中を実行してみて、もしエラーが出れば catch {} の中を実行
try {
// レイヤーの移動(Xの相対値, Yの相対値)
activeDocument.activeLayer.translate(new_x, new_y);
} catch(error) {
alert('リサイズしたいレイヤーが選択されていません。');
}
}
/* アクティブレイヤーのレイヤー名を変更する関数 */
function rename_layer() {
// レイヤー名が「W 100 H 100 R 0」のような形式で、Rの数値を取得できていれば
if (layer_name_flag === true) {
// レイヤー名をリネーム: 右側が新しいレイヤー名(例: W 100 H 100 R 0)
activeDocument.activeLayer.name = "W " + parseInt(eval(uDlg.w.text)) + " H " + parseInt(eval(uDlg.h.text)) + " R " + parseInt(number_r);
}
}
ここからソース解説。
// ドキュメントの解像度が 72ppi でなければアラート
if (activeDocument.resolution !== 72) {
alert("解像度が72ppiではありません。「編集>画像解像度」から解像度 72 pixel/inch にしてください。");
}
ドキュメントの解像度が 72 ppi ではない(例: 72.009 ppi など)と、ずれが発生するのでアラートを出します。
// 現在のレイヤーで選択範囲を作る
try {
var idsetd = charIDToTypeID( "setd" );
/* 略 */
executeAction( idsetd, desc85, DialogModes.NO );
} catch(e) {
alert("選択されているレイヤーがありません。");
}
// 現在のレイヤーで選択範囲を作る ここまで
「現在のレイヤーで選択範囲を作る」の部分は意味不明なスクリプトが書いてありますが、ここは ScriptingListenerJS.log で取得したものをそのまま入れています。
もし、レイヤーが選択されていなければエラーが発生するので、エラーを catch したら「選択されているレイヤーがありません。」というアラートを表示します。
なぜそのようなことをするかというと、現在のレイヤーのサイズを取得しようとすると、たまにずれが発生するため、現在のレイヤーと同じサイズの選択範囲を作成し、その選択範囲のサイズを取得しています。
// X,Y,W,H を取得
var x = x1;
var y = y1;
var w = eval(x2-x1);
var h = eval(y2-y1);
後々ソースが見やすくなるよう変数 x,y,w,h を宣言し、そこに代入しておきます。
w と h の値は x2-x1 などにより求め、テキスト入力フィールドで四則演算が使えるよう eval で囲んでおきます。
/* 現在のレイヤーの r の数値を取得する関数 */
// グローバル変数 number_r と layer_name_flagを宣言
var number_r;
var layer_name_flag;
function get_layer_r() {
// アクティブなレイヤーのレイヤー名を取得
var layer_name = activeDocument.activeLayer.name;
// try {} の中を実行してみて、もしエラーが出れば catch {} の中を実行
try {
// 数値を取得し、変数 number に代入
var number = layer_name.match(/\d+/g);
// 変数 number の3つめの値を number_r に代入
number_r = number[2];
layer_name_flag = true;
} catch(error) {
// エラーが出た場合は layer_name_flag 変数に false を代入
layer_name_flag = false;
}
}
まず、現在のレイヤー名を取得します。
何のために取得するかというと、
- もしレイヤー名が「ボタン」や「グローバルナビ」など、シェイプ作成 JSX で作成したレイヤーでなかったり、ユーザーが分かりやすいようにとリネームしていた時に、それを「W 100 H 100 R 0」のようにリネームしてしまいたくないから
- R(角丸)の値を取得しておき、リネームする際はその値を使用したいから
です。
まずは、「activeDocument.activeLayer.name」でアクティブなレイヤーのレイヤー名を取得します。
そして、正規表現「/\d+/g」を使って数値を抜き出し、変数 number に代入します。
つまり、「W 100 H 100 R 0」というレイヤー名だったとして、変数 number の値は「100, 100, 0」のようになります。
ここで、最初の「100」は number[0] で参照でき、次の 「100」は number[1] で参照でき、最後の「0」は number[2] で参照できます。
ですので、number[2] を変数 number_r に代入すると、number_r には R の数値が入っているはずです。
ただ、もしレイヤー名が「W 100 H 100 R 0」のような形式でない場合(「ボタン」や「グローバルナビ」など)は、ここでエラーが出てしまいます。
Photoshop の JSX エラーメッセージは、JSX を書いている方には分かりやすいのですが一般の方にはわかりにくいので極力表示したくありません。
また、もしエラーの場合はフラグ用の変数 layer_name_flag に false という値を代入しておきたいです。
「エラーメッセージを表示したくない」そして「エラーが発生した場合は特定の動作をさせたい」この2つを同時にかなえてくれる JavaScript の構文があります。それが try {} catch(error) {} です。
まず try {} の {} 内に書かれた箇所を実行してみて、もしエラーが出たら、エラーは表示せず catch(error) {} の {} 内に書かれた箇所を実行します。
エラー内容は error 変数に代入されていますので、もしエラー内容を表示したければ catch(error) {} の {} 内で alert(error); と書けばエラー内容を表示することもできます。
今回は、もしエラーが出たら(=レイヤー名が「W 100 H 100 R 0」のような形式でなければ)、layer_name_flag 変数に false を代入しておきます。
/* 略 */
// try {} の中を実行してみて、もしエラーが出れば catch {} の中を実行
try {
// レイヤーの移動(Xの相対値, Yの相対値)
activeDocument.activeLayer.translate(new_x, new_y);
} catch(error) {
alert('リサイズしたいレイヤーが選択されていません。');
}
「activeDocument.activeLayer.translate(x, y)」でレイヤーの位置を移動することができますが、x, y は絶対位置ではなく相対位置です。
なので、面倒ですがその前の部分(178〜196行目)で絶対位置から相対位置を計算する処理(サイズを変更しつつ移動する場合にも対応、基準点は左上)で計算しています。
/* アクティブレイヤーのレイヤー名を変更する関数 */
function rename_layer() {
// レイヤー名が「W 100 H 100 R 0」のような形式で、Rの数値を取得できていれば
if (layer_name_flag === true) {
// レイヤー名をリネーム: 右側が新しいレイヤー名(例: W 100 H 100 R 0)
activeDocument.activeLayer.name = "W " + parseInt(eval(uDlg.w.text)) + " H " + parseInt(eval(uDlg.h.text)) + " R " + parseInt(number_r);
}
}
layer_name_flag 変数に true が代入されていれば(=レイヤー名が「W 100 H 100 R 0」のような形式で、R の値が取得できていれば)、「W 100 H 100 R 0」のような形式にリネーム処理を行います。
R の値は、予め number_r 変数に入っているはずなので、その値を使用します。
JSX の解説は以上です。
普段 JavaScript に慣れてないのでちょっと大変でしたが、実務でもそこそこ使えるものができたかなと思います。
この JSX はカスタマイズしやすいように書いたつもりですので、もしカスタマイズしたい方がいらっしゃいましたらぜひチャレンジしてみて下さい。
スクリプトをそのまま再配布したい場合は、なるべくこのページへリンクしていただけるとありがたいのですが、もしよりよい JSX にカスタマイズされた場合はご自身のサイトで配布していただいて構いません。
コメントやメッセージ、 @Stocker_jp までご連絡いただければ、この記事からリンクするかもしれません。
参考文献
この JSX を書くにあたり、下記書籍で JavaScript について学びました。
2冊とも、大変参考になりました。
よくわかるJavaScriptの教科書
パーフェクトJavaScript (PERFECT SERIES 4)
最後に、この JSX を書くにあたり、Twitter や Facebook でアドバイスを頂いた @YumiSora さん、@nog さん、@msdfjp さんらに感謝いたします。