PhotoShop Script（JSX）を使用して自動処理を実現しよう！

本記事は、[サムザップ Advent Calendar 2020 #2](https://qiita.com/advent-calendar/2020/sumzap2) の12/17の記事です。
昨日の記事は @IwakamiYuki さんの「クエリを見直してチューニングしてみよう！【変なSQL見える化作戦】」でした。

*****
# はじめに
Photoshopで自動処理と聞くと『アクションを作成してバッチ処理を行う』が簡単な例として挙げられます。
しかし、詳細まで指定してアクションを作ろうとすると痒いところに手が届かないことがあります。
そういった場合、PhotoShop Scriptを使用することでPhotoShop画面上で操作できることのほとんどの事を自動処理が可能なのです！

エンジニアの方はスクリプトと聞いても普段から見慣れているため抵抗ないと思いますが、
デザイナーさんなどの他の職種の方が聞くと「ちょっと敷居高いなぁ。。」と急に腰が重たくなって戦意喪失してしまう人もいると思います。
でも実はPhotoshopの機能を知り尽くしているデザイナーさんやイラストレーターさんの方が具体的にやりたいことがわかっているはずなので案外とっかかりやすいと思っています。
<font color="Gray">（機能を知らないと、どうリファレンスを検索したら良いかすら分からないため。）</font>

# 今回やること
今回はPhotoshop Scirptの実装を始めるまでの手順とちょっとした応用についてやっていきます。
この記事を読み終える頃には、あんなこといいな！できたらいいな！が思い浮かびモチベーションが
モリモリ上がっているかと思います！<font color="gray">そうあってほしい！</font><font color="silver"> そ、そうなるはず！</font>
Photoshopでスクリプトを組んでみたいという人の手助けになればと幸いです。

環境：
MacOS Mojave 10.14.6
Adobe Photoshop CC 2019 20.0.0
Visual Studio Code 1.52.0
 └ ExtendScript Debugger 1.1.2（プラグイン）

#そもそもPhotoShop Scriptとは？
Photoshop上で実行可能な言語となります。

一言でPhotoShop Scriptといっても実は３つの言語から選択することができます。

| (1) ExtendScript(JavaScript) | (2) AppleScript   | (3) VBScript |
|:-:|:-:|:-:|
簡単に言ってしまうとAppleScriptはMac向き、VBScriptはWindows向きとのことで
両OSでの使用を想定する場合はExtendScript(JavaScript)を使用するのが良さそうです。
JavaScriptで調べるとネット上に情報が多くあるということも利点かと思います。
細かく説明するとストレスで蕁麻疹が出てくると思うので今回は**(1)ExtendScript(JavaScript)**を選びます。
<font color="Red">（※JavaScriptの最新の機能を使用できる訳ではなくECMAScript3相当なので注意です。）</font>

# 必要なもの
下記の２つが必要です。インストールしましょう。
**・Adobe Photoshop CC**
　　これがなくては始まりません！
　　契約しているCreativeCloudなどからインストールしましょう。

**・Visual Studio Code：**https://azure.microsoft.com/ja-jp/products/visual-studio-code/
　　コードエディタです。なんと無料です。
　　『ExtendScript Debugger』というプラグインを別途導入する必要があります。
　　※元々はExtends Script ToolKitというツールをAdobeさんが公式に提供していましたが現在はサポートが停止しているため、今後は上記の環境を構築するのが良さそうです。

<font color="Silver">**・折れない心**
　　これがなくては途中でめんどくさくなってしまいます。自分にインストールしておきましょう！</font>
*****
# 1. Visual Studio Codeでの準備
## 1.1. ExtendScript Debuggerのプラグインを導入しましょう
インストール直後の初期状態だとPhotoshopScriptを開発するための機能が不足しているため
『ExtendScript Debugger』というプラグインを導入する必要があります。

Visual Studio Codeの画面の左にあるメニューからブロックのようなアイコンを選択してExtendsの画面を開きます。
検索箇所があるので『ExtendScript Debugger』と入力すると検索結果に出てきます。
<img width="423" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/f9f9616e-5692-68de-bc70-f7eb6664dd05.jpeg">
ExtendScript Debuggerを選択すると次のような画面が右側に表示されます。
Installのボタンを押してプラグインを導入しましょう。
<img width="680" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/b6523fcf-f0a3-73bb-2cce-73bc498e3c86.jpeg">
Installというボタンが下の画像のようにUninstallに切り替わっていれば導入できた証です。
※Disableというボタンを押してしまうと機能がオフになってしまうため押さないようにしましょう。
<img width="516" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/ac403471-920e-619f-810e-d75dfe6082d5.jpeg">

## 1.2.ExtendScript DebuggerとPhotoshopとの接続
書いたコードをPhotoshopで動作させるためにお互いを連携させる必要があります。
方法がいくつかあるのですが結論、下記の**（方法１）**をおすすめします。
**（方法１）Visual Studio Codeの画面右下に黄色の文字で表示されている『Select the target application』をクリックする。**
<img width="418" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/e16c3423-6d92-c0c6-c852-607c17b7be50.jpeg">
クリックすると画面上部にPCにインストールされているAdobe製品がリストで表示されます。
ここではPhotoshopを選択しましょう。
<font color="Silver">（実はPhotoshop以外のAdobe製品向けにもスクリプトを作成することができます。）</font>
<img width="641" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/62a653af-e60d-9ff1-bb39-1b2be3c66c25.jpeg">
画面右下で黄色で表示されていた文字が選択したPhotoshopのものに切り替わります。
<img width="418" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/7836fae1-f829-28c6-5565-65f511a7ac65.jpeg">


**（方法２）Visual Studio Codeの画面左下の歯車のアイコンから[Command Palette]、もしくはツールバーから[View]→[Command Palette]を選択し、表示される検索の箇所に『Select the target application』入力して出てきた項目を選択する。**
こちらの方法の場合、エラーメッセージが表示されてうまく接続できないことがありました。

```
Command 'Select the target application' resulted in an error(command 'extension.extendscript-debug.selectActiveTarget' not found)
```

プラグイン内の``selectActiveTarget``が見つからないとのエラーです。プラグイン導入済みなのに何故、、
（これから説明をするLaunch.jsonを作成下あたりから方法２でも接続がスムーズにできるようになりました。）

## 1.3. デバッグ構成ファイルを作成しよう
ちょっと面倒ですが接続の際に必要な設定ファイルを用意する必要があります。
形式や種類の記載がありますがテンプレートが下記の手順で作成されるので心配無用です！

（１）構成ファイルや実際のスクリプトを配置するフォルダを事前に作成しておきましょう。
今回はテスト用にTestExtendScriptとしました。名前は自由ですが用途に応じた名前が良いかと思います。
<img width="170" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/b93cc28b-8473-65b4-2029-ab8b56909784.jpeg">


（２）Visual Studio Codeの画面の左にあるメニューからてんとう虫と三角形のようなアイコンを選択してRunの画面を開きます。<font color="silver">（虫というとなんか嫌なので『てんとう虫』としておきます。。。）</font>
すると青文字で**『open a folder』**とあるので作成済みのフォルダを選択します。
<img width="370" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/a6e7c10f-698b-4306-f390-06619cad5a2b.jpeg">

（３）選択を終えて再度Runを表示すると内容が切り替わっています。
見ると青文字で**『create a launch.json file.』**とありますのでこちらをクリックします。
クリックすると画面中央にSelect Environmentというプルダウンが表示されますので
何も考えずに**ExtendScript Debug**を選択しましょう！
<img width="680" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/c960ba5c-a413-43d3-e74c-e3a41dc04a23.jpeg">

（４）すると、jsonファイルが作成されて下記のような画面が表示されます。やっとコードっぽいものが出てきました。これが**デバッグ構成ファイル**になります。
<font color="Red">（※上記で指定したフォルダを見ると空っぽですが、隠しフォルダでデバッグ構成ファイルなどが格納されています。フォルダを削除する際は注意です。）</font>
<img width="680" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/871a4b8c-283e-a44f-152f-2e6cfd31ea0d.jpeg">

（５）"name"の項目を分かりやすい名前にしましょう。
今回はTestExtendScriptとします。
『"Ask for script name"』→『"TestExtendScript"』に変更します。

```ruby:index.jsx
        "name": "TestExtendScript"
```

　
（６）てんとう虫のアイコンを押してまたRunの画面にします。
画面左上に`RUN ▷ TestExtendScript`とあり、先ほど変更した名前になっているかと思います。
再生ボタンがあると思うのでクリックしましょう。
すると画面中央に「Press 'Enter' to confirm your input or 'Escape' to cancel.」
と表示され、中に**index.jsx**とデフォルトで入力されています。
<img width="680" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/4916dffe-b27e-6b62-b7ee-0786e2efbac6.jpeg">

（７）このままEnterキーを押してしまうとファイルが見つからないというエラーが表示されます。
index.jsxファイルを準備していないため当然ですね。
ここではEnterキーは我慢して一旦、Escキーでキャンセルしましょう。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/25aa1ab8-4389-372b-d1bf-0efc90da3075.jpeg">

（８）**index.jsx**を作成しましょう。（今回はデフォルトの名前のままで設定します）
ツールバーの[File]→[New File]をクリックします。
**Untitled-1**というファイルが作成されるので、すぐに保存しましょう。
<img width="350" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/466b3702-4911-02b1-b033-0c808d9645b0.jpeg">
<img width="340" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/b8aeff79-36f5-63aa-7080-38d075ddf5e2.jpeg">
<img width="367" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/34f66d83-f0f5-6e14-6c82-9baa33a97232.jpeg">

（９）**index.jsx**を作成後にRunの画面の再生ボタンをもう一度クリックしてみましょう。**Photoshop**の画面に移動するはずです。
その後、何も起きませんがそれはindex.jsxに何も実装がないからなので全く問題ありません。
おめでとうございます！これでやっと実際にコードを記述することができます！
<font color="red">（エラーが発生した場合は、改めてPhotoshopとの接続を確認しましょう。</font>
[1.2. ExtendScript DebuggerとPhotoshopとの接続](#12extendscript-debuggerとphotoshopとの接続)）


*****

# ２. コードを書いてみよう！
## 2.1.まず簡単なコードを書いてみよう。
手始めにVisual Studio Codeのデバッグコンソールに文字を表示してみましょう。
index.jsxに下記を入力して保存します。

```ruby:index.jsx
$.writeln("ExtendScriptテスト");
```
この状態でRunから再生ボタンをクリックして再度、index.jsxを指定しましょう。
Photoshopの画面に移動した後にVisual Studio Codeに戻ってみると下記のように
**DEBUG CONSOLEのウィンドウ**内に先ほど入力したテキストが表示されているはずです。

このコンソール出力ですが開発時によく使うことになるので覚えておきましょう。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/1d509b4f-6530-b0bd-e049-98e533177946.jpeg">

## 2.2.Photoshop上にダイアログを表示してみよう！
index.jsxに下記を記載し保存します。

```ruby:index.jsx
alert("ExtendScriptテスト");
```
RUNから実行してみましょう。下記のようなダイアログが表示されるはずです。
<img width="493" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/9727eb01-e0ac-fc6f-52d3-f862991c8413.jpeg">
Photoshopが反応を見せてくれましたね！ちょっとテンションが上がったかと思います。

## 2.3.実際のドキュメントに変更を加えてみよう！
Photoshop上で新規ドキュメントを作成します。
今回は適当に横640px,縦480pxのドキュメントを作成しました。
<img width="413" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/151dc282-dd03-7c2b-aa13-11d45643e9a4.jpeg">
index.jsxを下記のように更新します。


```ruby:index.jsx
// 現在アクティブになっているドキュメントを取得
var doc = app.activeDocument;

// ドキュメントの名前を出力
$.writeln(doc.name);
```

上記ですが`app.activeDocument`でドキュメントの取得ができます。
その後にドキュメントの名前をデバッグコンソールに出力しています。
<img width="313" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/c3364fa8-2190-27cd-cc0f-6558572e6b90.jpeg">


## 2.4.もうちょっと複雑な事をやってみよう！
先ほどと同様にPhotoshop上で新規ドキュメントを作成します。
index.jsxを編集し下記を３つをやってみます。
・カンバスサイズを元の640x480(px)から200x200(px)に変更する。
・「NEW_LAYER」という名前で新規レイヤーを作成し非表示にする。
・「WHITE_LAYER」という名前でレイヤーを作成して選択範囲で全選択して透明度80%の白で塗りつぶす。
<font color="silver">（このぐらいならアクションでもできるかもしれないですが、、）</font>

```ruby:index.jsx
// 現在アクティブになっているドキュメントを取得
var doc = app.activeDocument;

// 左上を起点としてカンバスサイズを変更する。
doc.resizeCanvas(200, 200, AnchorPosition.TOPLEFT);

// 新規レイヤーを追加
var newLayer = doc.artLayers.add();
// レイヤー名を変更
newLayer.name = "NEW_LAYER";
// レイヤーを非表示
newLayer.visible = false;

// 新規レイヤーを追加
var whiteLayer = doc.artLayers.add();
// レイヤー名を変更
whiteLayer.name = "WHITE_LAYER";
// 選択範囲を全選択する
doc.selection.selectAll();
// 塗りつぶす色を設定する
var fillColor = new SolidColor();
fillColor.rgb.red = 255;
fillColor.rgb.green = 255;
fillColor.rgb.blue = 255;
// 選択範囲を塗りつぶす
doc.selection.fill(fillColor, ColorBlendMode.NORMAL, 80, false);
```
<img width="732" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/bea4f5e4-42b3-fe22-f913-87ab2be0ab0b.jpeg">
上記のindex.jsxをRUNで実行した結果が上の画像です。
３つのことが実現できています。
何もなかったドキュメントでしたがPhotoshopのツールを全く触らずに上記の画像のように変更できました。
このようにExtendScriptを使用することで人の手を介さずに自動で編集を行うことができます。
今回はとても簡単なことだけやってみましたが実際にはもっとたくさんの事をスクリプトで実現することが可能です！
ここまでくると、もうなんでもできる気になってきます！！！
*****
下記に公式のリファレンスがあります。
[ADOBE PHOTOSHOP SCRIPTING]
https://www.adobe.com/devnet/photoshop/scripting.html
今回はExtendScriptを使用しているので『JavaScript』と表記のあるお使いのPhotoshopのバージョンにあったものを参照しましょう。
`・Photoshop JavaScript Reference (PDF, *.* MB)`
全て英語表記なので日本語に訳す必要がありますが大抵のことはここに載っています。
　　
  
日本語でわかりやすく機能をまとめてくださっているページがありました。
[Adobe Photoshop CS6自動化作戦]
http://www.openspc2.org/book/PhotoshopCS6/
CS6の時の情報なので一部、仕様が変更されていますが公式のリファレンスと照らし合わせながらコードを記述すれば解決できるかと思います。

*****
#3 PhotoshopからJSXファイルを直接使用しよう
今回作成しているindex.jsxですが、Visual Studio Codeを介さなくてもPhotoshop上から直接使用することができます。
開発時だけVisual Studio Codeで作成して、完成したら各作業担当者にファイルを渡して直接実行してもらうということが可能です。
しかもindex.jsxだけを渡せばいいので使用する側は、Visual Studio Codeなどのインストールする必要もありません。
<font color="red">**（※実際に作成したJSXファイルを渡す際は機能に応じた適切なファイル名に変更しましょう。）**</font>

##3.1.JSXファイルを開く
Photoshopのツールバーから、[ファイル]→[スクリプト]→[参照...]から、作成したjsxファイルを開くだけです。
<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/dedbbcf7-f26e-35ff-2fe9-b4884790afc4.jpeg">
これだけです。とても簡単ですね。
ここでさらに、JSXを使用するアクションを登録して沢山のファイルにバッチ処理を走らせるといったことも可能です。

*****
#4 その他の超使える便利機能
もうなんでもできると思っていた私ですが、公式のリファレンスを見ながら対応を進めている最中、何度見返しても存在しない機能がありました。
具体的にしたかったことは「スライスを選択して設定されているファイル名を新しい名前にリネームする」というものでした。
Photoshopで直接操作すれば割とサクッとできるこの作業ですが、
1ファイル毎に変更が必要なスライスが30個ぐらいある＆作業が必要なPSDファイル数が1000を軽く超えていたので途方に暮れていました。
完全に戦意を喪失してお家に帰ろうかと思っていたところ解決策が見つかったためその紹介をしたいと思います。

##4.1.ScriptingListenerプラグインを使用する
このプラグインはPhotoshopで操作した内容を全てJavaScriptとしてテキストファイルにログ出力してくれるという最高のものでした。

（１）ADOBE公式サイトからダウンロードしインストールすることが可能です。
https://helpx.adobe.com/jp/photoshop/kb/downloadable-plugins-and-content.html
上記サイト内に、『ScriptingListener プラグイン』という項目があるのでそちらから、自分の環境にあったプラグインをダウンロードしましょう。

（２）ダウンロードが完了したら導入までの手順も一緒に記載されているのでその通りに進めてください。

（３）導入までが完了しPhotoshopを起動するとデスクトップに見慣れない『ScriptingListenerJS.log』というログファイルがちょこんと生成されていると思います。
<img width="122" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/b4d6da53-e3d5-6378-c7d2-f8a9619125c2.jpeg">

（４）上記ファイルをテキストエディタなどで開いてみます。

```ruby:ScriptingListenerJS.log(一部抜粋)
// =======================================================
var idslct = charIDToTypeID( "slct" );
    var desc29 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref6 = new ActionReference();
        var idzoomTool = stringIDToTypeID( "zoomTool" );
        ref6.putClass( idzoomTool );
    desc29.putReference( idnull, ref6 );
    var iddontRecord = stringIDToTypeID( "dontRecord" );
    desc29.putBoolean( iddontRecord, true );
    var idforceNotify = stringIDToTypeID( "forceNotify" );
    desc29.putBoolean( idforceNotify, true );
executeAction( idslct, desc29, DialogModes.NO );

// =======================================================
var idtoolModalStateChanged = stringIDToTypeID( "toolModalStateChanged" );
    var desc30 = new ActionDescriptor();
    var idLvl = charIDToTypeID( "Lvl " );
```
おおう、なんのこっちゃ、、、みたいな文字が大量に出力されています。
リファレンスにあるように綺麗な命令文が記載されているわけではありませんでした。
それとなく単語を拾って、なんとなくですが「この処理のことかな。」という憶測はつきます。
という状況ですが全ての動作がこちらに出力されるようです！
しかも、**一部を切り取ってExtendScriptとしてRUNさせると見事に動作を再現できました。**

（５）これはイケるのでは？と思ってPhotoshop上でスライスの名前変更をしてみるとそのログもしっかり出力してくれました。
下記が実際にログを元に作成したファンクションです。


<details><summary>**座標でスライス選択**</summary><div>

```
// 座標でスライス選択
function SelectSliceByPoint(x, y){
    var idslct = charIDToTypeID( "slct" );
    var desc119 = new ActionDescriptor();
    var idnull = charIDToTypeID( "null" );
        var ref33 = new ActionReference();
        var idslice = stringIDToTypeID( "slice" );
        ref33.putClass( idslice );
    desc119.putReference( idnull, ref33 );
    var idAt = charIDToTypeID( "At  " );
        var desc120 = new ActionDescriptor();
        var idHrzn = charIDToTypeID( "Hrzn" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc120.putUnitDouble( idHrzn, idPxl, x );
        var idVrtc = charIDToTypeID( "Vrtc" );
        var idPxl = charIDToTypeID( "#Pxl" );
        desc120.putUnitDouble( idVrtc, idPxl, y );
    var idPnt = charIDToTypeID( "Pnt " );
    desc119.putObject( idAt, idPnt, desc120 );
    var idaddToSelection = stringIDToTypeID( "addToSelection" );
    desc119.putBoolean( idaddToSelection, false );
    
    executeAction( idslct, desc119, DialogModes.NO );
}
```
</div></details>


<details><summary>**選択中のスライスに名前をつける**</summary><div>

```
// 選択中のスライスに名前をつける（※事前にSelectSliceで選択を行なっておくこと！）
function SetSliceName(name) {
	var idsetd = charIDToTypeID( "setd" );
	var desc205 = new ActionDescriptor();
	var idnull = charIDToTypeID( "null" );
	var ref130 = new ActionReference();
	var idslice = stringIDToTypeID( "slice" );
	var idOrdn = charIDToTypeID( "Ordn" );
	var idTrgt = charIDToTypeID( "Trgt" );
	ref130.putEnumerated( idslice, idOrdn, idTrgt );
	desc205.putReference( idnull, ref130 );
	var idT = charIDToTypeID( "T   " );
	var desc206 = new ActionDescriptor();
	var idNm = charIDToTypeID( "Nm  " );
	desc206.putString( idNm, name );
	var idsliceImageType = stringIDToTypeID( "sliceImageType" );
	var idImg = charIDToTypeID( "Img " );
	desc206.putEnumerated( idsliceImageType, idsliceImageType, idImg );
	var idslice = stringIDToTypeID( "slice" );
	desc205.putObject( idT, idslice, desc206 );
	executeAction( idsetd, desc205, DialogModes.NO );
}
```
</div></details>

コードを見ていてなんの略だろうと不明な箇所がありますが目的は無事達成できました。
今度こそ、もうなんでもできる気になってきますね！！！

#まとめ
いかがでしたでしょうか。

長い記事になってしまいましたがPhotoshopをスクリプトで動かすことができるようになったかと思います。
設定こそ面倒なものの一度やってしまえばそう抵抗なく手をつけられるのではないかと思います。
折れない心を持って最後まで読んでくださった方には感謝です。

あとはやりたいことをリファレンスで参照しながら機能を実装していくことになるかと思います。
使いようにもよりますが**色々なことを自動化できればミスも減り作業をとても楽にできるはず**です。
実装者のPhotoshopおよびJavaScriptのスキルによってできる範囲も変わってくるかと思いますが是非チャレンジしてみましょう。

元々、Extension化を行ってPhotoshopのツールとして登録する！というところまで
できたらよかったのですが、とても長くなってしまったため機会があればまた記事にしたいと思います。
<img width="299" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265488/dfa16ca7-e4f6-e78b-598e-3520e640711b.jpeg">
（Extension化までできればこんな感じでPhotoshopっと一体化できる！）

*****
明日は、@chrno001 さんの記事です、お楽しみに！