Photoshopの「書き出し形式」風に、複数ファイルを処理するツールを作ってみた話。

画像を複数の解像度設定で書き出す機能「書き出し形式」
レスポンシブに対応した画像を書き出すのに素晴らしい機能です！

しかし、対象となるのは開いている画像のうち、アクティブになっている1枚だけ。。。
複数の枚数を処理するとなると、都度、開いて、書き出してと非常に手間がかかります。

Photoshopにはアクションの他に、バッチ、ドロップレットなど自動化には色々な機能が搭載されていますが「書き出し形式」はどれにも組み込めないようです。

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/18b58e4e-ec51-05e2-6b15-88406599d9ff.png)
▲「書き出し形式」の画面


そこでJavaScriptで複数ファイルを処理できるツールを作ってみました。

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/ca6b3617-8439-3378-6912-bb55d6642f93.png)
▲実行すると上のダイアログが表示されます。
数値は「書き出し形式」を見習い100%のときは「1」を、75%のときは「0.75」と入力するようにしました。
「書き出し形式」では10まで設定できますが、今回は4つまでにしました。
2つ、3つしか入力していなくても、入力した数だけ処理するようにしました。

![WS001.JPG](https://qiita-image-store.s3.amazonaws.com/0/122812/b78408cb-2d7b-4f79-1a00-16768e90424d.jpeg)
▲ファイル形式はPNG、JPEG、GIFの3種類。
保存時の設定は下図のようにPhotoshopで保存するときのデフォルトの設定値にしてあります。

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/2374f812-490a-5953-987d-32c92d47c865.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/bdb179ff-cee0-099e-f6c6-d77324062fc9.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/b30d4885-3de8-38c1-5a9b-499018f550b4.png)


ファイル形式と解像度を設定しOKを押下すると処理前のフォルダを選択するダイアログが表示されます。
このフォルダに複数のファイルを入れておきます。

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/e42fab26-9462-0ab1-361f-50f30fb61192.png)

続いて処理後のファイルを保存するフォルダを選択します。

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/771c1e7b-15d1-e485-a455-dd9072ce6c52.png)

ファイル名は、「書き出し形式」のデフォルト設定を見習い
ファイル名 + "@+0,5x" + 拡張子 のようにしておきました。
「1」（100%）の場合は ファイル名 + 拡張子 にしてあります。 

![image](https://qiita-image-store.s3.amazonaws.com/0/122812/0a73b69f-9bdd-5e14-5732-283b88803a63.png)


ソースコードは以下

```javascript:filesAutoExpot.jsx
MAIN: { //ラベル
    /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    入力ダイアログ表示
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
    var flag = false; //フラグの初期化
    while (flag == false) {
        var myDialog = new Window('dialog', '書き出し設定', [800, 250, 1060, 450]); //見出し
        myDialog.staticText = myDialog.add("statictext", [10, 5, 275, 25], "保存するファイル形式を選択してください。"); //固定テキスト
        myDialog.dropDownList = myDialog.add("dropdownlist", [10, 30, 250, 50], ["PNG", "JPEG", "GIF"]); //ドロップダウンリスト
        myDialog.dropDownList.selection = 0; //デフォルトで一番上のものを選択
        myDialog.staticText = myDialog.add("statictext", [10, 55, 275, 75], "数値を入力してください。"); //固定テキスト
        myDialog.editText01 = myDialog.add("edittext", [40, 80, 100, 35], "1"); //入力欄1
        myDialog.editText02 = myDialog.add("edittext", [40, 100, 100, 35], "0.5"); //入力欄2
        myDialog.editText03 = myDialog.add("edittext", [40, 120, 100, 35], "0.75"); //入力欄3
        myDialog.editText04 = myDialog.add("edittext", [40, 140, 100, 35], "0.25"); //入力欄4
        myDialog.okBtn = myDialog.add("button", [135, 170, 220, 35], "OK!", {
            name: "ok"
        }); //OKボタン
        myDialog.cancelBtn = myDialog.add("button", [50, 170, 135, 35], "キャンセル!", {
            name: "cancel"
        }); //キャンセルボタン

        var bottomFlag = myDialog.show(); //ダイアログを表示し、OK、キャンセルボタンの結果を取得
        var flag = true;
        if (bottomFlag == 2) { //キャンセルの場合処理を抜ける
            alert("処理を中断します。");
            break MAIN;
        }
        if (myDialog.editText01.text.match(/[^\d+(\.\d+)?]/) || myDialog.editText02.text.match(/[^\d+(\.\d+)?]/) || myDialog.editText03.text.match(/[^\d+(\.\d+)?]/) || myDialog.editText04.text.match(/[^\d+(\.\d+)?]/)) { //数値以外が入力されたら繰り返す
            var flag = false;
            alert("整数、または小数を入力してください。");
        }
    }

    var preFolder = Folder.selectDialog("処理するフォルダを選択してください");
    if (!preFolder) {
        alert("処理を中断します。"); //キャンセルの場合処理を抜ける
        break MAIN;
    }
    var afterFolder = Folder.selectDialog("保存するフォルダを選択してください");
    if (!afterFolder) {
        alert("処理を中断します。"); //キャンセルの場合処理を抜ける
        break MAIN;
    }
    var preFiles = new Array;
    var preFiles = preFolder.getFiles(); //処理前のフォルダから全てのファイルを取得

    for (var i = 0, preFilesLength = preFiles.length; i < preFilesLength; i++) { //一度全てのファイルを開ききる
        open(preFiles[i]);
    }
    /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    リサイズと保存処理
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
    for (var j = 0, preFilesLength = preFiles.length; j < preFilesLength; j++) { //開いてから処理を開始する
        var doc = app.activeDocument; //ドキュメント
        var fileName = doc.name; //ファイル名
        var fileName = fileName.split("."); //ファイル名を小数点で分割
        //var docPath = activeDocument.path; //ディレクトリ
        var docWidth = activeDocument.width.value; //横幅
        var docHeight = activeDocument.height.value; //高さ

        var textResult01 = myDialog.editText01.text, //小数点をカンマに変換する関数を実行し、変数に格納
            textResult02 = myDialog.editText02.text,
            textResult03 = myDialog.editText03.text,
            textResult04 = myDialog.editText04.text;

        var array = [textResult01, textResult02, textResult03, textResult04]; //配列に追加
        var textArray = []; //配列を準備
        for (var k = 0, arrayLength = array.length; k < arrayLength; k++) {
            if (array[k] != "") {
                textArray.push(array[k]); //空欄以外の入力値を配列に追加
            }
        }

        for (var l = 0, textArrayLength = textArray.length; l < textArrayLength; l++) {
            doc.resizeImage(docWidth * textArray[l], docHeight * textArray[l]); //指定された数値でリサイズ
            if (textArray[l] != 1) {
                var fileObj = new File(afterFolder + "/" + fileName[0] + "@" + dot2Com(textArray[l]) + "x"); // 1以外のファイル名の処理
            } else {
                var fileObj = new File(afterFolder + "/" + fileName[0]); // 1の場合のファイル名の処理
            }

            var n = myDialog.dropDownList.selection; //ドロップダウンリストで選ばれたものを関数に格納

            //▼保存
            switch (n + 0) {
            case 0:
                pngOutput(); //PNG保存
                break;
            case 1:
                jpegOutput(); //JPEG保存
                break;
            case 2:
                gifOutput(); //GIF保存
                break;
            }

            //▼復帰
            var desc = new ActionDescriptor();
            var revert = charIDToTypeID('Rvrt');
            app.executeAction(revert, desc, DialogModes.NO);
        }
        //▼保存しないで閉じる
        activeDocument.close(SaveOptions.DONOTSAVECHANGES);
    }

    alert("処理が終わりました");
}

/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
関数定義
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
//▼PNG保存関数
function pngOutput() {
    var pngOption = new PNGSaveOptions();
    //pngOption.embedColorProfile=false;//効かない
    pngOption.compression = 9; //圧縮：最小/低速
    pngOption.interlaced = false; //インターレース：なし
    activeDocument.saveAs(fileObj, pngOption, true, Extension.LOWERCASE); //保存内容、保存オプション、複製保存
}

//▼JPEG保存関数
function jpegOutput() {
    var jpegOption = new JPEGSaveOptions();
    jpegOption.embedColorProfile = false; //カラープロファイルを含めない
    jpegOption.formatOptions = FormatOptions.PROGRESSIVE; //形式オプション：プログレッシブ
    jpegOption.scans = 3; //スキャン：3
    jpegOption.quality = 12; //画質：最高（低圧縮率）
    activeDocument.saveAs(fileObj, jpegOption, true, Extension.LOWERCASE); //保存内容、保存オプション、複製保存
}

//▼GIF保存関数
function gifOutput() {
    var gifOption = new GIFSaveOptions();
    gifOption.palette = Palette.LOCALSELECTIVE; //パレット：ローカル(特定)
    gifOption.colors = 256; //表示色：256
    gifOption.forced = ForcedColors.BLACKWHITE; //強制：白黒
    gifOption.transparency = true; //透明
    gifOption.dither = Dither.NONE; //オプション：ディザなし
    activeDocument.saveAs(fileObj, gifOption, true, Extension.LOWERCASE); //保存内容、保存オプション、複製保存
}

//▼小数点をカンマに変換する関数
function dot2Com(textResult) {
    if (textResult.length > 1) {
        var textResult = textResult.split(".");
        var textResult = textResult[0] + "," + textResult[1]; //小数ならば小数点をカンマに変換
    } else {
        var textResult = textResult; //整数ならばそのまま
    }
    return textResult;
}
```

ファイルは1枚づつ、開いて、リサイズして、保存して、閉じるとした方が仮想記憶ディスクのアキを圧迫することなく処理できるでしょうが、今回は視覚的に楽しめるだろうと、一度全て開いてから処理するようにしました。

一応、複数の組み合わせで検証してみました。
Photoshopの画像解像度の数値はpxだと小数点が入力できないので1pxの誤差が生じます。
![image](https://qiita-image-store.s3.amazonaws.com/0/122812/2dac51de-7f6d-cc36-e9a9-784012f34780.png)

Windows 10、Photoshop CC 2017 でのみ検証してあります。
Macの場合、階層を選ぶ箇所を書き換えないといけないですね。。。