BMS, Movie, Illustrations, Programming

[JavaScript] 「#MakeGirlsMoe でもっと遊べるプログラム」をフォークした

#MakeGirlsMoe でもっと遊べるプログラムを書いた(実質百合画像生成) がとても良かったので機能を追加しました。問題がありましたら消します。

追加した機能は、

  • 生成した画像とノイズを自動ダウンロードするように(チェックボックスで有効化)
  • インポートした画像の補間が出来るように
  • 生成数、補間時の分割数を指定できるように
  • 画像の生成が終了したらすぐに次の画像を生成するように修正
  • その他微修正

です。

使い方は変わっていないので、元の記事を読んで下さい。百合画像を生成したい場合は、補間時の分割数に3を入力してください。

ちなみに、ノイズ画像をインポートする場合は、「Importをクリック」→「Generateをクリック」→「表示中の画像を複製!をクリック」の手順を踏んでください。

適当に2枚選んで補間するとこんな感じになります。(動きます)

それから、同じノイズでも他のパラメータを変えると別の画像になります。

従って、パラメータにRandomを指定して補間すると以下のようになってしまうので注意してください。

以下が実際のコードです。ダブルクリックでコピーできます。ご自由にフォークしてください。

なお、現状では補間された画像のノイズ画像を生成・保存することができません。気が向いたら修正します。

// original source: by tosaka2
//   http://tosaka2.hatenablog.com/entry/2017/08/15/174439

//this.gan.run(n, this.state.options.noise ... の行にブレークポイント
getState = () => this.state;

getOption = () => getState().options;
getNoise = () => getState().gan.noise;

// NoiseをFixedに
fixNoise = () => getOption().noise = 1;
// NoiseをRandomに
randomNoise = () => getOption().noise = 0;

// Noiseを出力
printNoise = () => "[" + getNoise().join(',') + "]";
// Noiseを設定
setNoise = a => { cn = getNoise(); for (var i = 0; i < a.length; i++) cn[i]=a[i]; };
// ↑ここで表示されているノイズ画像も更新したい(厳しい)

// Generate
generate = () => document.querySelector(".btn").click();
// NoiseをRandomにしてGenerate
generateByRandom = () => { randomNoise(); generate(); };
// 引数で指定したNoiseでGenerate
generateBy = a => { fixNoise(); setNoise(a); generate(); };

// ベクトルの操作
add = (a, b) => a.map((x, i) => x + b[i]);
sub = (a, b) => a.map((x, i) => x - b[i]);
times = (a, t) => a.map((x, i) => x * t);
interpolate = (a, b, p) => a.map((x, i) => x * (1-p) + b[i] * p);

// 選択されている2枚の画像の、{noise: ノイズ値, img: 画像(DOM), noise_src: ノイズのDataURL}
_tmps = [];

// 生成済みの画像を下に表示。ついでにダウンロード。
var addImg = function(src, noise_src, saveImageRequired = true, saveNoise = true) {
    var results = document.body.querySelector(".imgs");

    if (!results) {
        results = document.createElement("div");
        results.className = "row imgs";
        document.body.querySelector(".App").appendChild(results);
    }

    var img = document.createElement("img");
    img.src = src;

    // Array.fromで配列を複製して副作用を防ぐ
    // (恐らく同一の実体がWebGAN内で使われている)
    var noise = Array.from(getNoise());

    var img2 = document.createElement("img");
    img2.src = noise_src;
    img2.style.width = "10px";

    img.onclick = ((noise_, img_, noise_src_) => (() => {

        //console.log("[" + noise_.join(',') + "]");
        //console.log(img_);

        _tmps.map(x => x.img.style.opacity = 1.0);
        _tmps.push({noise: noise_, img: img_, noise_src: noise_src_});
        _tmps = _tmps.slice(-2);
        _tmps.map(x => x.img.style.opacity = 0.5);
    }))(noise, img, noise_src);

    results.appendChild(img);
    if(saveNoise) {
        results.appendChild(img2);
    }

    var autoDL = document.getElementById("checkbox_autoDownload").checked;

    if(saveImageRequired && autoDL) {
        downloadPngImageBySrc(img.src,   'makegirlsmoe_img');
        if(saveNoise) {
            // 補間時に書き出されるノイズ画像は正しくない
            downloadPngImageBySrc(noise_src, 'makegirlsmoe_noise');
        }
    }

    return img;
}

// chromeで同じファイル名のファイルを103個以上ダウンロードすると
// ファイルの保存ダイアログが出てしまう問題対策
var img_index = 1;

// src で指定されたPNG画像をダウンロードする
var downloadPngImageBySrc = function(src, prefix) {
    var link = document.createElement('a');
    link.href = src;
    link.download = prefix + '_' + img_index + '.png';
    document.body.appendChild(link);
    link.click();

    img_index++;
}

// 下に表示してある画像をクリア
var clearImgs = function() {
    var imgs = document.body.querySelector(".imgs");
    for (var x of imgs.children) {
        x.onclick = null;
        x.src = "";
    }
    imgs.parentNode.removeChild(imgs);
}

// 待機
var sleep = function(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

// 中断フラグ
var _isAborted = false;

// aとbに指定したNoiseでn枚画像補間し,下に表示.
var generateInterpolations = async function(n) {
    if(_tmps.length < 2) {
        alert("Please select 2 images");
        return;
    }
    var a = _tmps[0].noise;
    var b = _tmps[1].noise;

    // 水平線の挿入
    if(document.body.querySelector(".imgs")) {
      document.body.querySelector(".imgs").appendChild(document.createElement("hr"));
    }

    var img = document.body.querySelector(".result-canvas").firstChild;
    var src = "";

    // デフォルト引数が使われたかどうか。
    // trueならデフォルト引数なので、最初と最後の画像の生成は省略する。
    // と言いたいところだが、noise以外のパラメータ(髪色など)が違うことがあるんじゃ
    for (var i = 0; i < n; i++) {
        if (_isAborted) {
            _isAborted = false;
            break;
        }
        generateBy(interpolate(a, b, i / (n - 1)));
        await waitForCompletion();
        addImg(img.src, document.body.querySelector(".noise-canvas").firstChild.src, true, false);
    }
}

// RandomなNoiseでn枚画像生成し,下に表示.
var generateRandomImages = async function (n) {
    // 水平線の挿入
    if(document.body.querySelector(".imgs")) {
      document.body.querySelector(".imgs").appendChild(document.createElement("hr"));
    }

    var result = document.body.querySelector(".result-canvas").firstChild;
    var src = "";

    // n を -1 にすると永遠に続けるという設定
    for (var i = 0; n < 0 || i < n; i++) {
        if (_isAborted) {
            _isAborted = false;
            break;
        }
        generateByRandom();
        await waitForCompletion();
        addImg(result.src, document.body.querySelector(".noise-canvas").firstChild.src);
    }
}

// 画像が生成完了するまでウェイトする
var waitForCompletion = async function () {
    do {
        await sleep(200);
    }while (Array.from(document.getElementsByTagName('button'))
        .filter(x => x.innerHTML.indexOf('Genera') >= 0)[0]
        .getAttribute('disabled') != null);
}

_bs = [];
// ボタン追加処理
var addButton = function(text, func) {
    var _b = document.body.querySelector(".btn-primary").cloneNode();
    _b.textContent = text;
    _b.onclick = func;
    document.body.querySelector(".options-container").lastChild.appendChild(_b);
    _bs.push(_b);
}

// テキストボックス追加処理
var addTextBoxWithLabel = function(id, label, text) {
    var mydiv = document.createElement('div');
    mydiv.className = "form-group col-xs-6 col-sm-4 option";
        var h5 = document.createElement('h5');
        h5.innerText = label;
        mydiv.appendChild(h5);

        var myinput = document.createElement('input');
        myinput.id = id;
        myinput.className = "form-control";
        myinput.value = text;
        mydiv.appendChild(myinput);

    document.body.querySelector(".options-container").lastChild.appendChild(mydiv);
}

// チェックボックス追加処理
var addCheckBox = function(id, label, checked) {
    var mydiv = document.createElement('div');
    mydiv.className = "form-group col-xs-6 col-sm-4 option";
        var myinput = document.createElement('input');
        myinput.id = id;
        myinput.type = "checkbox";
        if(checked) { myinput.checked = "checked"; }
        mydiv.appendChild(myinput);

        var myspan = document.createElement('span');
        myspan.innerText = label;
        mydiv.appendChild(myspan);

    document.body.querySelector(".options-container").lastChild.appendChild(mydiv);
}

addButton("連続生成!", () => generateRandomImages(
    (document.getElementById("textbox_genNum").value - 0) || -1));

addButton("補間!", () => generateInterpolations(
    (document.getElementById("textbox_interpolateNum").value - 0) || 5));

addButton("中断!", () => { _isAborted = true; });

addButton("表示中の画像を複製!", () => { addImg(
    document.body.querySelector(".result-canvas").firstChild.src,
    document.body.querySelector(".noise-canvas").firstChild.src,
    false); });

// addButton("百合", () => generateInterpolations(3));

addButton("クリア", () => clearImgs());

addTextBoxWithLabel("textbox_genNum", "生成数(-1でエンドレス)", "-1");
addTextBoxWithLabel("textbox_interpolateNum", "補間時の分割数", "5");

addCheckBox("checkbox_autoDownload", "自動ダウンロード", true);