DTPab

DTPにまつわるあれこれ

SUIに画像を埋め込む方法

はじめに

ExtendScriptのScriptUIはアプリケーションによらずほぼ共通のコードでUIが生成できるのが便利です。ちょっとリッチなUIにしようとすると画像を扱いたくなってしまうのですが、そうなるとどうしてもpngファイルなど外部ファイルを扱う必要があり、特にスクリプトを配布する際(とりわけコードをjsxbinなどで難読化する場合)に苦労します。
そこで今回はSUIに画像ファイルを埋め込む方法を3つ紹介します。これらを利用すればスクリプトファイル一つで配布や公開が可能になるので、リッチなSUIを実装して楽しんでくださいね!

取り扱わないこと

そもそも外部ファイルとして画像を取り扱うことには触れません。先日紹介したPeter Kahrel先生のPDFに詳細が載っていますので、そちらを参照してください。

uske-s.hatenablog.com

画像ファイルを文字列化する

今回紹介するやりかたは3つです。

  1. ScriptUI Dialog Builder(SDB)を利用する
  2. 画像ファイルをBINARYで読み込んで、コードポイントとして落とし込む
  3. 画像ファイルをBINARYで読み込んで、Object.toSource()メソッドを使って文字列化する

以下、この順で説明していきますが、僕のオススメは3つめです。
1つ目はわざわざURLにアクセスして画像ファイルを投げて…という手順を踏まないといけないからで、ネット環境に接続している必要があります*1
2つ目は後述しますがソースコードが肥大化しやすいためです。画像データへの復号も直感的にわかりにくい。
3つ目はコード上の扱いが楽なのでこれがもっともオススメです。

SDBを使う

めちゃ簡単なので詳しい説明はしません。
SDBにアクセスしてimageコントロールを追加して、ダイアログをエクスポートしてもらえれば自動的にエンコードされた文字列として吐き出されます。マジでこれ天才では???
文字列化された画像データを改めて画像として復号するコードも内包されていますから、SDBで完結できるならそれはそれでアリです。
僕はこんなことを書きたくてこの記事を書いているんじゃぁないんですよw

BINARY読み込み、コードポイントとして落とし込む

ここからは画像データをどうやって手動で文字列化して、またそれを画像データとしてどうやって復号するかです。こういうのを書きたいんだよね!!

文字列化する

BINARYで読み出した情報は$.writeln()メソッドとかではうまく取得できないので、ちょっと工夫がいります。それがコードポイントへの落とし込みです。
まずはファイルをBINARYで読み出します。

var f = File("~/Desktop/icon.png"); //適宜ファイルパスを渡す
f.open("r");
f.encoding = "binary";
var res = f.read();
f.close();

これでres変数には画像データのバイナリデータが取得できます。これをコードポイントの羅列に落とし込んでしまうのがこの方法です。

var src = res.split("");
for (var i=0,len=src.length; i<len; i++) {
    src[i] = src[i].charCodeAt(0);
}
src = src.join(",");

こんな感じで1文字ずつコードポイントにして長たらしい文字列にしてしまいます。画像サイズなどにもよるでしょうがとーーーーーっても長くなります。これをテキストファイルなどに書き出してしまいましょう。

var txt = File("~/Desktop/img.txt");
txt.open("w");
txt.write(src);
txt.close();

画像データとして復号

今度はこうして用意した文字列からSUIの画像データへと復号してみます。
画像データの文字列がsrc変数に入っているとして、まずString.fromCharCode()メソッドを使ってコードポイントからBINARYの文字列に戻してやります。それをStringコンストラクタの関数呼び出しの引数として渡せばOKです。

var w = new Window("dialog");
w.add("image", undefined, String( String.fromCharCode(src) ));
w.show();

注意してほしいのはnew演算子を付けずにStringコンストラクタを関数として呼び出す、という1点です(コンストラクタの関数呼び出しについては後述)。こうすればダイアログに画像が表示されるはずです。

BINARY読み込み、toSource()メソッドで文字列化

先の方法に比べてファイルサイズが半分くらいで済み、なおかつ取り扱いが簡単なのでこっちがオススメです。

文字列化する

var f = File("~/Desktop/icon.png"); //適宜ファイルパスを渡す
f.open("r");
f.encoding = "binary";
var res = f.read();
f.close();

ここまでは先ほどと同じです。今度はtoSource()メソッドで文字列にします。

var src = res.toSource().replace(/^\(new String\("/, "").replace(/"\)\)$/, "");

やってみていただくと分かると思いますが、toSource()メソッドで文字列化すると(new String("~~~~~"))というふうになります。この(new String(""))replace()メソッドで取り除いた、というのがこのコードです。
これもまたテキストファイルなどに書き出してしまいましょう。

画像データとして復号

先ほどはStringコンストラクタの関数呼び出しなんていう見慣れない方法で画像データにしましたが、こっちのやり方だともう少し直感的にできます(またsrc変数に画像データの文字列が入っているとします)。

var w = new Window("dialog");
w.add("image", undefined, File.decode(src));

w.show();

File.decode()メソッドに入れてあげるだけで復号できます(SDBと同じ手法)。わかりやすい〜!
ちなみにStringコンストラクタの関数呼び出しでも同じように復号できます。

おわりに

こんな感じで簡単にSUIに画像データを埋め込むことができます。SUIのお供に画像データをぜひ使ってみてください。見栄えが全然違いますよ〜!

補足:Stringコンストラクタの関数呼び出し

なぜnew演算子を付けると画像が表示されないのか? ちょっと詳しく見ていきますが、あまり易しく説明してないかもしれないので、興味のある方だけどうぞ^^;;

さて下記のコードを見てください(src変数に画像データの文字列が入っているとする)。

var w = new Window("dialog");
w.add("image", undefined, String(src)); //OK
w.add("staticText", undefined, "---");
w.add("image", undefined, new String(src)); //NG

w.show();

new演算子を付けた方(---の下)は画像が表示されません。

f:id:uske_S:20200702204326p:plain
new演算子を付けると画像にならない

これは仮説になりますが、SUIのimageコントロールに要素として文字列を渡すとき、それをevalメソッドのような方法で実体化しているのではないか、ということです。

Stringコンストラクタをnew演算子なしで関数として呼び出す場合と、new演算子を付けてコンストラクタとして呼び出す場合、得られる結果が異なることは知られているかと思います*2

  • String(123):String型(プリミティブ値)
  • new String(123):Object型

これがeval()メソッドと組み合わさると、その仕様から異なる結果になるのです。

先ほど「SUIのimageコントロールに要素として文字列を渡すとき、それをevalメソッドのような方法で実体化しているのではないか」と書きましたが、その理由がこれです。eval()メソッドは文字列を渡すとそれをソースコードとして評価しますが、オブジェクトを渡すとそのまま同じオブジェクトを返します。
つまりStringコンストラクタの関数呼び出し(new演算子を付けない)でないと画像として表示されないのは、内部的にeval()メソッドのようなものが呼び出されているんだろうな、と推測したわけです。
以上、補足でした。

*1:Electronで開発されたデスクトップアプリ版もあるようですが未検証。興味があれば試してみてください

*2:MDNなどを参照:String - JavaScript | MDN