DTPab

DTPにまつわるあれこれ

オブジェクトの色をランダムに塗る ― 応用編

前回、選択したオブジェクトをランダムに塗り分けるスクリプトを作りました。

uske-s.hatenablog.com

これだと何の制限もなくただランダムに塗り分けるだけなので、実用性に欠けます。なので今回は、このスクリプトにいろいろ機能を追加していきます。
もし「お、これ良さそう!」というアイディアがあったら真似してみてください。

おさらい

前回のスクリプトのコードの全体像を改めて載せておきます。

(function () {
    if (app.documents.length === 0 || app.selection.length === 0) {
        return;
    }

    var sel = app.activeDocument.selection;
    for (var i=0; i<sel.length; i++) {
        var myCMYK = getRandomCMYKColor();
        app.activeDocument.selection[i].fillColor = myCMYK;
    }
})();

function getRandomCMYKColor () {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * 100);
    cmyk.magenta = Math.round(Math.random() * 100);
    cmyk.yellow = Math.round(Math.random() * 100);
    cmyk.black = 0;
    return cmyk;
}

色成分値の範囲を指定してみる

C、M、Yの三色の最大値と最小値を指定して塗り分けられるようにしてみます。
前回と同じようにMath.random()メソッドの取りうる範囲xを考えてみます。

0 < x <= 1

例えばMath.random()が30〜70の範囲で値を返すようにするためにはどのような計算をすればいいでしょうか。
範囲が30〜70とすると、最小値から最大値までの幅は 70 - 30 = 40 となります。両辺に40を乗算してみます。

0 < x <= 40

これで40までの値の範囲が得られます(Math.random() * 40)。さらに両辺に30を足してみると、

30 < x <= 70

となります(Math.random() * 40 + 30)。これを一般化して計算式にすると、

Math.random() * (最大値 - 最小値) + 最小値

となるわけです。実際にスクリプトに組み込んでみましょう。

getRandomCMYKColor()関数に2つの引数(最大値と最小値)を渡せるようにして、関数の中でその2つを使って計算するように書き換えます。

function getRandomCMYKColor (min, max) {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * (max - min)) + min;
    cmyk.magenta = Math.round(Math.random() * (max - min)) + min;
    cmyk.yellow = Math.round(Math.random() * (max - min)) + min;
    cmyk.black = 0;
    return cmyk;
}

呼び出すときはgetRandomCMYKColor (30, 70);のように使えばOKです。これで実際に動かしてみましょう。

f:id:uske_S:20201113213150g:plain
CMYを30〜70の範囲で塗り分け

できあがりはこちらです。

(function () {
    if (app.documents.length === 0 || app.selection.length === 0) {
        return;
    }

    var sel = app.activeDocument.selection;
    for (var i=0; i<sel.length; i++) {
        var myCMYK = getRandomCMYKColor(30, 70);
        app.activeDocument.selection[i].fillColor = myCMYK;
    }
})();

function getRandomCMYKColor (min, max) {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * (max - min)) + min;
    cmyk.magenta = Math.round(Math.random() * (max - min)) + min;
    cmyk.yellow = Math.round(Math.random() * (max - min)) + min;
    cmyk.black = 0;
    return cmyk;
}

CMYKそれぞれで範囲を指定する

先ほどのスクリプトの応用で、CMYKそれぞれで値に幅をもたせて塗り分けられるようにしてみます。
各成分値ごとに最小と最大を配列に含めて、4つの引数を渡せるように関数を書き換えます。

function getRandomCMYKColor (C, M, Y, K) {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * (C[1] - C[0])) + C[0];
    cmyk.magenta = Math.round(Math.random() * (M[1] - M[0])) + M[0];
    cmyk.yellow = Math.round(Math.random() * (Y[1] - Y[0])) + Y[0];
    cmyk.black = Math.round(Math.random() * (K[1] - K[0])) + K[0];
    return cmyk;
}

各配列の[0]に最小値、[1]に最大値を入れて引数に渡す想定です。呼び出すときは2要素の配列を4つ、getRandomCMYKColor([20, 50], [0, 20], [0, 0], [0, 10]);というように指定します。これも実行してみましょう。

f:id:uske_S:20201111014044g:plain
C20〜50、M0〜20、Y0、K0〜10で塗り分け

できあがりはこちらです。

(function () {
    if (app.documents.length === 0 || app.selection.length === 0) {
        return;
    }

    var sel = app.activeDocument.selection;
    for (var i=0; i<sel.length; i++) {
        var myCMYK = getRandomCMYKColor([20, 50], [0, 20], [0, 0], [0, 10]);
        app.activeDocument.selection[i].fillColor = myCMYK;
    }
})();

function getRandomCMYKColor (C, M, Y, K) {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * (C[1] - C[0])) + C[0];
    cmyk.magenta = Math.round(Math.random() * (M[1] - M[0])) + M[0];
    cmyk.yellow = Math.round(Math.random() * (Y[1] - Y[0])) + Y[0];
    cmyk.black = Math.round(Math.random() * (K[1] - K[0])) + K[0];
    return cmyk;
}

スクリプトを修正せずに結果を変えられるようにしたい

中の計算式を都度変えながらスクリプトを使うのはけっこう面倒だし、何か間違えて編集して動かなくなってしまったら大変です。スクリプトはいじらず、設定を変えるにはどうしたらいいでしょうか。

  • ダイアログを出してユーザーに入力してもらう
  • 外部ファイルを読み込む

僕はこのどちらかを採用することが多いです。今回はCMYKの最大値・最小値、合計8つの値を設定しなければいけないので、ダイアログで処理するには大変です。後者のやり方を採用してみましょう。

外部ファイルとの連携―Fileオブジェクト

スクリプトから、例えば同階層にあるテキストファイルの内容を読み込んで、スクリプトにその内容を反映させます。この記事では詳細は割愛しますが、下記の関数を追記します。

function importJsonFile() {
    var filePath = decodeURIComponent(File($.fileName).parent) + "/setting.json";
    var tgtFile = File(filePath);
    var res, flag;
    try {
        tgtFile.open("r");
        res = tgtFile.read();
        flag = true;
    } catch (e) {
        alert(e);
    } finally {
        tgtFile.close();
    }
    if (!flag) { return false; }
    return eval("(" + res + ")");
}

スクリプトと同じ階層にある setting.json というファイルを読み込む関数です。
肝心の setting.json は下記のように記述してください。listプロパティには、ダイアログで表示させる名前を配列として定義します。それと対応させるように、プロパティ名を"5""6""7"として中身(色成分値)の設定を書き込みます。プロパティ名が"5"から始まる連番になっているのは後述します。

{
    "list": ["ブルー系配色", "グリーン系配色", "オレンジ系配色"],
    "5": {
        "cyan": [20, 70],
        "magenta": [0, 30],
        "yellow": [0, 10],
        "black": [0, 10]
    },
    "6": {
        "cyan": [20, 70],
        "magenta": [0, 10],
        "yellow": [30, 90],
        "black": [0, 10]
    },
    "7": {
        "cyan": [0, 20],
        "magenta": [20, 60],
        "yellow": [30, 80],
        "black": [0, 20]
    }
}

このサンプルでは3パターンの設定を記述しています。この設定をスクリプト実行時に使い分けたいので、スクリプトにダイアログを表示させます。
ダイアログを表示させるにあたって、この記事のモジュールを使います。

uske-s.hatenablog.com

この記事にあるように、ボタンのインデックス+5が関数の戻り値になります。なので、先ほどの setting.json はプロパティが"5"から始まっているわけですね。この記事だと変数に代入する形で関数を定義していますが、関数の宣言文にしてスクリプトの末尾に追記しましょう。

最後に、元のスクリプトでドキュメントを開いているかをチェックする最初のif文の下に、下記3行を挿入します。

var mySetting = importJsonFile();
var setting = multiBtnDlg("ランダム配色", "設定を選んでください", mySetting.list, 0);
if (setting < 5) {return;}

追記した関数を呼び出すだけです。

これを実行した結果がこちら。

f:id:uske_S:20201114023820g:plain
3つの配色パターンを登録した

できあがりはこちら。

(function () {
    if (app.documents.length === 0 || app.selection.length === 0) {
        return;
    }

    var mySetting = importJsonFile();
    var setting = multiBtnDlg("ランダム配色", "設定を選んでください", mySetting.list, 0);
    if (setting < 5) {return;}
    var sel = app.activeDocument.selection;
    for (var i = 0; i < sel.length; i++) {
        var myCMYK = getRandomCMYKColor(mySetting[setting].cyan, mySetting[setting].magenta, mySetting[setting].yellow, mySetting[setting].black);
        app.activeDocument.selection[i].fillColor = myCMYK;
    }
})();

function getRandomCMYKColor(C, M, Y, K) {
    var cmyk = new CMYKColor();
    cmyk.cyan = Math.round(Math.random() * (C[1] - C[0])) + C[0];
    cmyk.magenta = Math.round(Math.random() * (M[1] - M[0])) + M[0];
    cmyk.yellow = Math.round(Math.random() * (Y[1] - Y[0])) + Y[0];
    cmyk.black = Math.round(Math.random() * (K[1] - K[0])) + K[0];
    return cmyk;
}

function importJsonFile() {
    var filePath = decodeURIComponent(File($.fileName).parent) + "/setting.json";
    var tgtFile = File(filePath);
    var res, flag;
    try {
        tgtFile.open("r");
        res = tgtFile.read();
        flag = true;
    } catch (e) {
        alert(e);
    } finally {
        tgtFile.close();
    }
    if (!flag) { return false; }
    return eval("(" + res + ")");
}

function multiBtnDlg(title, text, list, focus) {
    var cDlg = new Window("dialog", title);
    cDlg.add("statictext", undefined, text, { multiline: true });
    var btnGrp = cDlg.add("group");
    var btnList = [];
    for (var i = 0; i < list.length; i++) {
        if (i === focus) {
            btnList.push(btnGrp.add("button", undefined, list[i], { name: "ok" }));
        } else {
            btnList.push(btnGrp.add("button", undefined, list[i]));
        }
        eval(
            "btnList[i].onClick = function() {\n" +
            "    cDlg.close(" + (i + 5) + ");\n" +
            "}");
    }
    return cDlg.show();
}

setting.json の中身をいじくってみて、結果の違いを試してみてください。
設定を増やしたい場合は、listプロパティにダイアログに表示させる名前を追加し、ほかと同じ要領で"8"のプロパティを作ってください。

まとめ

こうして作ったスクリプトは自分だけが使うならこれで完成でもいいですが、実際に人に使ってもらうとすると「安全に動作する」「予想外のエラーが出てスクリプトが止まることがないようにする」といった最後の作り込みが必要になります。いろいろな例外処理を仕込んでいくのです。
特に突っ込んだ解説をしなかった3つ目のサンプルスクリプトは、応用次第では様々なスクリプトに転用できると思います。特にimportJsonFile()関数などはそのままコピペしてもらってもいいでしょう。
この記事がデザインワークを手助けしたり、スクリプトの勉強の力添えができたら本望です。