投稿日:2022年11月27日

先日、2022年11月8日の皆既月食はご覧になりましたでしょうか?
私は地元、所沢市の狭山湖堰堤から観望しました。

▲一眼でタイムラプスを撮影。望遠鏡にiPhone6sを装着して拡大写真を撮りました。

▲タイムラプス動画です。
iPhoneで撮影したものがサムネールになっています。

さて、ニュースサイトなどでよく見かける比較明合成という画像ですが、Photoshopのレイヤーモード「カラー比較(明)」を使用すれば作れるということを最近知りました。
ある程度の間隔を空けて撮影した画像を選び、合成したのが下図になります。

▲24コマ置きに合成しました。

数枚置きに画像を選択といっても2,000枚近い画像から選ぶとなると、数え違いが起きたり、大変な労力と時間が費やされます。

そこでPhotoshopのJSXで自動処理できないかと考察し、作ってみました。
下図がその動きです。

▲1,777枚の画像を格納したフォルダを選択し、25枚置きに合成しています。
レイヤーモードを「カラー比較(明)」にし、レイヤー複製で最初の画像に配置します。

JSXの編集

JSXのソースコードは以下です。

/*
仕様
2022/11/26 記述
ダイアログを開き、処理するファイルが格納されたフォルダを選択。
「いくつおきに処理しますか?」のダイアログで何枚おきに処理するかの数値を入力します。
「おきに」とは「置きに」と書きます。
例;
「0」と入力
1、2、3、4、5、6、7、8、9、10.....
「1」と入力
1、3、5、7、9、11、13、15、17.....
「2」と入力
1、4、7、10、13、16、19、22.....
「10」と入力
1、12、23、34、45、56、78、89.....
「100」と入力
1、102、203、304、405、506、607、708、809、910.....
入力した数値が、開くファイルの間の数になります。
ファイルを開いた後、レイヤー名をファイル名(拡張子なし)に、レイヤーモードを「カラー比較(明)」にし、一番最初のファイルにレイヤー複製を行います。
一番最初のファイルだけ残し、開いたファイルは保存せずに閉じます。
レイヤーの順番は昇順に並びます。
ファイルは保存せずに開いた状態にします。
*/
MAIN: { //ラベル
var preFolder = Folder.selectDialog("処理するフォルダを選択してください");
if (!preFolder) {
alert("処理を中断します。");
break MAIN; //キャンセルの場合処理を抜ける
}
var preFiles = new Array;
var preFiles = preFolder.getFiles(); //処理前のフォルダから全てのファイルを取得
var firstFileName = preFiles[1].name; //最初のファイル名を取得
var flag = false; //フラグの初期化
while (flag == false) {
var myDialog = new Window('dialog', '数値入力', [830, 480, 1090, 580]); //見出し
myDialog.center();
myDialog.staticText = myDialog.add("statictext", [10, 5, 275, 25], "いくつおきに処理しますか?"); //固定テキスト
myDialog.inputNum = myDialog.add("edittext", [10, 35, 100, 45], "0"); //入力欄。デフォルトは「0」
myDialog.okBtn = myDialog.add("button", [135, 70, 220, 35], "OK", {
name: "ok"
}); //OKボタン
myDialog.cancelBtn = myDialog.add("button", [50, 70, 135, 35], "キャンセル", {
name: "cancel"
}); //キャンセルボタン
var bottomFlag = myDialog.show(); //ダイアログを表示し、OK、キャンセルボタンの結果を取得
var flag = true;
if (bottomFlag == 2) { //キャンセルの場合処理を抜ける
alert("処理を中断します。");
break MAIN;
}
if (isNaN(myDialog.inputNum.text) == true) { //数値以外が入力されたら繰り返す ※入力値はstringになる
var flag = false;
alert("整数を入力してください。");
}
}
var inputNum = Number(myDialog.inputNum.text); //数値へ型変換
for (var i = 1, preFilesLength = preFiles.length; i < preFilesLength - 1; i = i + (inputNum+1)) { //一度全てのファイルを開ききる
open(preFiles[i]);
var doc = app.activeDocument, //アクティブドキュメント
fileName = doc.name, //ファイル名を取得
fileNameResult = fileName.split("."), //ファイル名を小数点で分割
layer = app.activeDocument.activeLayer;
layer.name = fileNameResult[0]; //レイヤー名をファイル名にする
// ▼▼▼ ScriptListenerの処理
// =======================================================
// レイヤーモードを「カラー比較(明るい)」に設定
var idset = stringIDToTypeID("set");
var desc1080 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref205 = new ActionReference();
var idlayer = stringIDToTypeID("layer");
var idordinal = stringIDToTypeID("ordinal");
var idtargetEnum = stringIDToTypeID("targetEnum");
ref205.putEnumerated(idlayer, idordinal, idtargetEnum);
desc1080.putReference(idnull, ref205);
var idto = stringIDToTypeID("to");
var desc1081 = new ActionDescriptor();
var idmode = stringIDToTypeID("mode");
var idblendMode = stringIDToTypeID("blendMode");
var idlighterColor = stringIDToTypeID("lighterColor");
desc1081.putEnumerated(idmode, idblendMode, idlighterColor);
var idlayer = stringIDToTypeID("layer");
desc1080.putObject(idto, idlayer, desc1081);
executeAction(idset, desc1080, DialogModes.NO);
}
for (var j = 0, docLength = app.documents.length; j < docLength - 1; j++) { //開いてから処理を開始する
// ▼▼▼ ScriptListenerの処理
// =======================================================
// レイヤーを複製
var idduplicate = stringIDToTypeID("duplicate");
var desc687 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref136 = new ActionReference();
var idlayer = stringIDToTypeID("layer");
var idordinal = stringIDToTypeID("ordinal");
var idtargetEnum = stringIDToTypeID("targetEnum");
ref136.putEnumerated(idlayer, idordinal, idtargetEnum);
desc687.putReference(idnull, ref136);
var idto = stringIDToTypeID("to");
var ref137 = new ActionReference();
var iddocument = stringIDToTypeID("document");
ref137.putName(iddocument, firstFileName); //最初のファイルに複製
desc687.putReference(idto, ref137);
var iddestinationDocumentID = stringIDToTypeID("destinationDocumentID");
desc687.putInteger(iddestinationDocumentID, 283);
var idversion = stringIDToTypeID("version");
desc687.putInteger(idversion, 5);
executeAction(idduplicate, desc687, DialogModes.NO);
//▼保存しないで閉じる
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
var layerLength = app.activeDocument.layers.length; //レイヤー数を取得
//▼最下部のレイヤーを最上部に移動
app.activeDocument.layers[layerLength - 1].move(app.activeDocument.layers[0], ElementPlacement.PLACEBEFORE);
alert("処理が終わりました");
}

▲71〜116行目、Adobeが配布している無料プラグイン ScriptingListener から書き出されるソースコードを使用しています。

JSXの編集はVisual Studio Codeを使用します。
以前の紹介記事 ではExtendScript Debugger 1.x系の紹介をしていましたが、ver2.x系 ではlaunch.jsonが不要など大きく仕様が変更されたようです。

▲VS Codeのextension、ExtendScript Debugger ver2.0.3

▲プログラムの実行には、実行 > デバッグの開始を選択します。

▲もしくはアクティビティバー実行とデバッグから開始します。

▲表示されるデバッグモードからLaunchを選択します。
翻訳は以下です。
Launch
ホストアプリケーションでアクティブなスクリプトをデバッグします。
スクリプトを直接デバッグするのに便利です。

Attach
ホストアプリケーションにアタッチします。すぐにスクリプトが実行されるわけではありません。
CEP および ScriptUI のデバッグに便利です。

▲続いて実行プログラムを選択します。今回はPhotoshopを選択します。

▲対象のアプリケーションが起動してないと上図のような警告が表示されます。

ScriptingListener プラグイン

Adobeのサイト からダウンロードします。
ダウンロードしたZIPを解凍し下図の階層にインストールします。
Photoshopを再起動します。

▲許可を求める画面が表示されるので許可します。

▲Photoshopを起動するとセキュリティに関する警告が表示されるのでキャンセルをクリックします。

システム設定プライバシーとセキュリティからインストールしたScriptListener.pliginを探し「このまま許可」をクリックします。

▲Photoshopを起動すると再度警告が表示されるので開くをクリックします。

▲起動するとデスクトップにlogファイルができます。
このファイルに実行した内容が記述されていきます。
Photoshop 2019 以前のバージョンでは記述のON、OFFができましたが、Photoshop 2020以降はできない仕様のようです。
以下は記述の一部です。

// =======================================================
var idfeatureInfo = stringIDToTypeID( "featureInfo" );
    var desc1 = new ActionDescriptor();
    var idactive = stringIDToTypeID( "active" );
    desc1.putBoolean( idactive, true );
    var idcommand = stringIDToTypeID( "command" );
    desc1.putString( idcommand, """getFeatureActive""" );
    var iddontRecord = stringIDToTypeID( "dontRecord" );
    desc1.putBoolean( iddontRecord, true );
    var idforceNotify = stringIDToTypeID( "forceNotify" );
    desc1.putBoolean( idforceNotify, true );
executeAction( idfeatureInfo, desc1, DialogModes.NO );

// =======================================================
var idhostFocusChanged = stringIDToTypeID( "hostFocusChanged" );
    var desc2 = new ActionDescriptor();
    var idactive = stringIDToTypeID( "active" );
    desc2.putBoolean( idactive, true );
    var iddontRecord = stringIDToTypeID( "dontRecord" );
    desc2.putBoolean( iddontRecord, true );
    var idforceNotify = stringIDToTypeID( "forceNotify" );
    desc2.putBoolean( idforceNotify, true );
executeAction( idhostFocusChanged, desc2, DialogModes.NO );

// =======================================================
var idLoadedPluginsNames = stringIDToTypeID( "LoadedPluginsNames" );
    var desc3 = new ActionDescriptor();
    var idpolygon = stringIDToTypeID( "polygon" );
        var list1 = new ActionList();
        list1.putString( """広角補正...""" );
        list1.putString( """平均""" );
        list1.putString( """Camera Raw""" );
        list1.putString( """Camera Raw フィルター""" );
        list1.putString( """ピクチャパッケージフィルター...""" );
        list1.putString( """Matlab 操作""" );
        list1.putString( """Cineon""" );
        list1.putString( """雲模様 1""" );
        list1.putString( """雲模様 2""" );
        list1.putString( """角度補正して切り抜き""" );

// 〜〜〜〜〜〜〜〜〜〜〜 以下省略 〜〜〜〜〜〜〜〜〜〜〜

▲起動しただけで3,000行弱のソースが書き出されます。
読み込んだプラグインも全て書き出されるようです。
ScriptingListenerJS.log 自体を削除し、Photoshopをアクティブにしても「アクティブにした」というログが記述されます。
実行したコマンドの箇所を見定めJSXに追記するのがコツです。

上記のソースコードのScriptListenerの箇所です。

var idset = stringIDToTypeID("set");
var desc1080 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref205 = new ActionReference();
var idlayer = stringIDToTypeID("layer");
var idordinal = stringIDToTypeID("ordinal");
var idtargetEnum = stringIDToTypeID("targetEnum");
ref205.putEnumerated(idlayer, idordinal, idtargetEnum);
desc1080.putReference(idnull, ref205);
var idto = stringIDToTypeID("to");
var desc1081 = new ActionDescriptor();
var idmode = stringIDToTypeID("mode");
var idblendMode = stringIDToTypeID("blendMode");
var idlighterColor = stringIDToTypeID("lighterColor");
desc1081.putEnumerated(idmode, idblendMode, idlighterColor);
var idlayer = stringIDToTypeID("layer");
desc1080.putObject(idto, idlayer, desc1081);
executeAction(idset, desc1080, DialogModes.NO);

レイヤーモードを「カラー比較(明るい)」に設定
14行目の lighterColor が「カラー比較(明るい)」に該当します。

var idduplicate = stringIDToTypeID("duplicate");
var desc687 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref136 = new ActionReference();
var idlayer = stringIDToTypeID("layer");
var idordinal = stringIDToTypeID("ordinal");
var idtargetEnum = stringIDToTypeID("targetEnum");
ref136.putEnumerated(idlayer, idordinal, idtargetEnum);
desc687.putReference(idnull, ref136);
var idto = stringIDToTypeID("to");
var ref137 = new ActionReference();
var iddocument = stringIDToTypeID("document");
ref137.putName(iddocument, firstFileName); //最初のファイルに複製
desc687.putReference(idto, ref137);
var iddestinationDocumentID = stringIDToTypeID("destinationDocumentID");
desc687.putInteger(iddestinationDocumentID, 283);
var idversion = stringIDToTypeID("version");
desc687.putInteger(idversion, 5);
executeAction(idduplicate, desc687, DialogModes.NO);

レイヤーを複製(対象のファイルに複製)
13行目の第2引数に対象のファイルを記述しています。変数 firstFileName に最初に開いたファイル名を定義しています。

まとめ

以前検証したExtendScript DebuggerScriptListener
いつの間にか仕様も変更され再検証の必要がありました。

Adobeソフトの自動化からも離れていましたが、必要に応じて検証してみたいと思いました。
ScriptListenerを使った箇所も、できればJSXで記述したかったのですが、力及びませんでした。
他にもアクションを併用し柔軟な自動化を行えればと思います。

最後まで読んでくださりありがとうございました。

お断りとして、紹介しているソースコードはご自由に使用していただいても構いませんが、生じたトラブル、損害などについては、本サイト、紹介しているサイトさま共に、一切の責任を追わないものとします。

参考にしたサイト

古籏 一浩さまのサイト

Adobe Photoshop CC JavaScript Reference

関連記事
Pocket