Adobe JSX を作って知ったこと

# Adobe JSX とは
JavaScript で Adobe 製品を操る仕組みのこと。

今回「Photoshop のレイヤーカラーラベルを変更できるスクリプト」を作ってみようと思い立ったのだが、あまり情報がない。
もとい、有用な情報があまりない。
検索すると色々出てくるんだけど「これをコピペしよう」とかが多く実際に利用できそうなスクリプトがないし資料もない。
なので、他の記事ではあまり触れられておらず苦労した部分だけ書いた。
※Adobe JSX といいつつ Photoshop についてのみ記載…

# あまり他の記事に書かれていないこと

## 普通の JavaScript でいい

拡張子を jsx にするだけ。
上から順に実行される。
Script フォルダに入れておけば自動的に [ファイル]→[スクリプト] に表示される

Script フォルダはデフォルトであれば、ここ。

```console
C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Scripts\
```

## Photoshop の JSX は２種類の操作方法がある

1. 一般的な DOM オブジェクトによる操作
1. [Action](https://helpx.adobe.com/jp/photoshop/kb/39.html) をスクリプト化したもの

１番の DOM オブジェクトによる操作ができる場合は問題無い。
いつも通り DOM オブジェクトの操作で済む。
例えば、アクティブレイヤーの表示・非表示を切り替える場合は

```javascript
// app はビルトインのグローバル変数でここから DOM をたどれる
app.activeDocument.activeLayer.visible = false;
```

こんな感じ。

問題は、DOM として提供されていないプロパティを変更する場合。

* ActionReference オブジェクト
* ActionDescriptor オブジェクト
* executeAction / executeActionGet 関数

を使って Action として処理する。
これがとにかく面倒くさい。
例えば、下記のコードは現在アクティブなレイヤーのカラーラベルを取得する処理。

```javascript
  // 現在の Color Label の取得
  var ref = new ActionReference(); 
  ref.putEnumerated(
    charIDToTypeID("Lyr "), // 4文字ID型式を Photoshop が理解する形に変換 Lyr = Layer
    charIDToTypeID("Ordn"), // 4文字ID型式を Photoshop が理解する形に変換 Ordn = Ordinal
    charIDToTypeID("Trgt")  // 4文字ID型式を Photoshop が理解する形に変換 Trgt = TargetEnum
  );

  var desc = executeActionGet(ref); // Action を実行して ActionDescripter を取得
  var colorID = desc.getEnumerationValue(stringIDToTypeID('color')); // color プロパティを取得
  var curColor = typeIDToStringID(colorID); // 人間が解る形に変換
```

ここで出てくる ```Lyr ``` や ```Ordn``` といった謎の４文字については[オフィシャル](https://www.adobe.com/content/dam/acom/en/devnet/photoshop/pdfs/photoshop-javascript-ref-2020.pdf#page=218)にも全ては載っていない。
全てが載っているページは↓の WaybackMachine にある

[Photoshop Constants Rosetta Stone](http://web.archive.org/web/20140121053819/http://www.pcpix.com/Photoshop/char.htm)

また、そもそも Action を使った処理を人間の手で書いている人はあまりいない。
というのも、Adobe が提供している [ScriptingListener](https://helpx.adobe.com/jp/photoshop/kb/downloadable-plugins-and-content.html#ScriptingListener%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3) というプラグインを使うと Action を JavaScript として保存できるようになるため。
必要な処理を Action として記録して Script 化して使っている人が多いのが実情。
JSX で○○するにはこうするんだよ～的なスクリプトが沢山公開されているが、変数名が ```foo1```, ```foo2``` みたいな連番ぽい数字が付いているものは ScriptingListener を使って作ったもの。
全然参考にならない。

そんな訳なので実は ActionReference と ActionDescriptor についてはあまり良く理解できていない。
恐らく

* [ActionReference](https://www.adobe.com/content/dam/acom/en/devnet/photoshop/pdfs/photoshop-javascript-ref-2020.pdf#page=43) 操作対象
* [ActionDescriptor](https://www.adobe.com/content/dam/acom/en/devnet/photoshop/pdfs/photoshop-javascript-ref-2020.pdf#page=37) 操作に必要なプロパティなどを保存する Key-Value ストレージ

という感じかなあと思っている。

## JavaScriptResource

このスクリプトの各種属性を記述しておく所。
ちなみに、Photoshop の起動時１回だけ読み込む。
変更した場合は Photoshop を再起動しないといけない。

詳しくは [JavaScript Reference](https://www.adobe.com/content/dam/acom/en/devnet/photoshop/pdfs/photoshop-javascript-ref-2020.pdf#page=191) を。

今回指定した XML は↓こんな感じ。

```xml
/*
<javascriptresource>
  <name>色ラベルの変更 / Toggle Color Label</name>
  <about>
    Toggle Color Label
    Version 1.00
    Copyright (c) 2021 @pik.
  </about>
</javascriptresource>
*/
```

name, about タグだけ説明すると

#### name
メニューに表示されるテキストを指定するところ。
<img width="400", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/564a6cb8-8920-da9f-9af6-d6e0690db3a3.png">

#### about 
[ヘルプ]→[プラグインについて]で表示されるもの。
上記のコードのように途中で改行を入れればそのまま改行されて表示される。
<img width="400", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/1966e44d-e7a3-ae2e-3a96-f0b2bfb2df3c.png">
<img width="300", src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/403d439c-dffe-1772-26d6-4c03f534f28e.png">

他にも色々設定できて、メニューの Enable/Disable を状態に応じて自動的に変更できたりするらしいが、方法は解らず。

## Target 宣言

↓こんな感じでターゲットを指定しておける。

```javascript
#target photoshop
```

jsx ファイルをダブルクリックして起動するときとかに役立つ？

## デバッグレベル
デバッグレベルは

* 0: disable
* 1: break on error
* 2: break at beginning

の３つあるが、どれもエラーになった時適切に行番号を表示してくれるので 1, 2 を使う事はあまり無さそう。

```javascript
$.level = 0; // デバッグレベルの設定
```

## ローカライズ

一般的な JavaScript のローカライズ機構しか使えない

```javascript
$.localize = true; // 自動的にローカライズ文字列を使う設定
const msg = {en:"test", ja:"テスト"}; // ロケールとテキストを持つオブジェクト
alert(msg); // ロケールに応じて自動的にテキストが選択される
```

一番悩んだのはここ。
Adobe が最初から提供している Script はメニューに各国の言葉が表示されている。
JavaScriptResource でメニューに表示されるテキストを指定できるが、ここを動的に変更しないといけない。
だが JavaScriptResource は動的に変更できないようだ。

Adobe が最初から提供している Script については ```Adobe format for localized Strings``` という特別な型式（ZString）を使っているらしい…が、ZString は Internal use only とあって情報が全然無い。

詳しくは↓
[Global localize function](https://www.adobe.com/content/dam/acom/en/devnet/Images/JavaScript-Tools-Guide-CC.pdf#page=226)

結局仕方がないので ```色ラベルの変更 / Toggle Color Label``` と日本語と英語を並記した。

# 作った物

呼び出す度にカラーラベルが切り替わるスクリプト。
ショートカットを指定して呼び出すと便利かもしれない。
…でも単純に右クリックメニューからやった方が速いんじゃないかなあ…（奥さんの要望で作ったものの、奥さんはペンタブのペンのボタンを「ブラシの設定」「Ctrl+Z」に割り当ててる奇特な人なので必要だった）

<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/51a54362-50c1-3511-a3a8-95c1b96a5ed4.gif">

## ソース

ソースは無名関数型式にしているけど、その必要は全くない。
単純に必要なオブジェクトが取れなかった時に処理をやめさせたかっただけ。
（インデントが深くならないように）

一応 Gist にも同じ物を。
https://gist.github.com/freeonterminate/666d4f47b83594d82c9915f5e0098815

```javascript
// Toggle Color Label
//
// ABOUT
//   Change color label.
//   None → Red → Orange → Yellow → Green 
//     → Blue → Violet → Gray → None
//
// LICENSE
//   Copyright (c) 2021 twitter:@pik
//   Released under the MIT license
//   http://opensource.org/licenses/mit-license.php
//
// HISTORY
//   2021/05/06  1.00  Release.
//

/*
<javascriptresource>
  <name>色ラベルの変更 / Toggle Color Label</name>
  <about>
    Toggle Color Label
    Version 1.00
    Copyright (c) 2021 @pik.
  </about>
</javascriptresource>
*/

#target photoshop

$.level = 0;
$.localize = true;

const msgErrBackground = {
  en: "Color labels cannot be set for the background layer", 
  ja: "背景レイヤーには色ラベルを設定できません"
};

(function(){
try 
{
  var doc = app.activeDocument;
  if (doc.activeLayer.isBackgroundLayer)
  {
    alert(msgErrBackground);
    return;
  }
} 
catch(e) 
{
  return;
}

const colors = [
  "none",
  "red" ,
  "orange",
  "yellowColor",
  "grain",
  "blue" ,
  "violet",
  "gray",
];

try
{
  // 現在のカラーラベルの取得
  var ref = new ActionReference();
  ref.putEnumerated(
    charIDToTypeID("Lyr "), // Layer
    charIDToTypeID("Ordn"), // Ordinal
    charIDToTypeID("Trgt")  // TargetEnum
  );

  var desc = executeActionGet(ref);
  var curColor = 
    typeIDToStringID(desc.getEnumerationValue(stringIDToTypeID('color')));

  // 設定するカラーラベルの取得
  var index = 0;
  var len = colors.length;
  for (var i = 0; i < colors.length; i++)
  {
    if (curColor == colors[i])
    {
      index = i + 1;
      break;
    }
  }

  if (index == len)
    index = 0;

  var newColor = colors[index];

  // カラーラベルの設定
  var descColor = new ActionDescriptor();
  descColor.putEnumerated( 
    charIDToTypeID('Clr '), // Color
    charIDToTypeID('Clr '), // Color
    stringIDToTypeID(newColor)
  );

  var target = new ActionDescriptor();
  target.putReference(charIDToTypeID('null'), ref);
  target.putObject( 
    charIDToTypeID('T   '), // To
    charIDToTypeID('Lyr '), // Layer
    descColor 
  );

  executeAction(charIDToTypeID('setd'), target, DialogModes.NO);
}
catch (e)
{
  alert(e.message);
}
})();
```

# 最後に
Adobe JSX を完全に理解するのは難しそう。
特に charID とかリファレンスないの酷すぎる。

一番参考になったのは↓このページ

https://community.adobe.com/t5/photoshop/determine-layer-color-label-from-layer-id/td-p/8708075

特に役に立ったのが ```grain``` という色についてのところ。
知らなければ ```green``` のタイポだと思うところだった。
<img width=600 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12977/a2bacf57-b723-4447-4f15-eda6b2ddc0a7.png">
ちなみに完全に理解すると↓こういう感じで商売ができるっぽい

MightyPlugins.cc / MagicScripts
http://bereza.cz/ps