non vorrei lavorare

ブログ名の通りです。javascript three.js mruby rust OCaml golang julialang blender

URLのハッシュを画像化してみた

おはようございます。先週末は、家族で幕張ビーチ花火フェスタ(第36回千葉市民花火大会)に行ってきました。長男は始まると同時に怖がり、寝てしまい。次男は大きな音や周囲が明るくなるような派手な花火が打ち上がると怖がっていましたが、その合間はしっかりと観察してました。

2014hanabi_2

背景

以前より、Emscriptenで吐いたコードを複数PNGに分割することで、jsdo.itで悪事を働いていた。

今回はバイナリー直でPNG

今回はtmlib.jsの公式エディタのrunstantのハッシュが長さもあり、比較できそうだったので、これでやってます。runstantではzip圧縮時にBase64化してるので、これをPNGで画像化する際はバイナリで扱うことでオーバーヘッドを避けられればとの目論見で、MDNのTypedArraryをつかったBase64とバイナリの変換処理を使わせてもらってます。

(function() {

    /*\
    |*|
    |*|  Base64 / binary data / UTF-8 strings utilities
    |*|
    |*|  https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
    |*|
    \*/

    function b64ToUint6(nChr) {
        return nChr > 64 && nChr < 91 ?
            nChr - 65 : nChr > 96 && nChr < 123 ?
            nChr - 71 : nChr > 47 && nChr < 58 ?
            nChr + 4 : nChr === 43 ?
            62 : nChr === 47 ?
            63 :
            0;
    }

    function base64DecToArr(sBase64, nBlocksSize) {
        //console.log("sBase64 = ",sBase64);
        var
            sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
            nInLen = sB64Enc.length,
            nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
            taBytes = new Uint8Array(nOutLen);

        for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
            nMod4 = nInIdx & 3;
            nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
            if (nMod4 === 3 || nInLen - nInIdx === 1) {
                for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
                    taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
                    //console.log(nOutIdx);
                }
                nUint24 = 0;

            }
        }
        //console.log(taBytes);
        return taBytes;
    }

    /* Base64 string to array encoding */

    function uint6ToB64(nUint6) {

        return nUint6 < 26 ?
            nUint6 + 65 : nUint6 < 52 ?
            nUint6 + 71 : nUint6 < 62 ?
            nUint6 - 4 : nUint6 === 62 ?
            43 : nUint6 === 63 ?
            47 :
            65;

    }

    function base64EncArr(aBytes) {

        var nMod3 = 2,
            sB64Enc = "";

        for (var nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
            nMod3 = nIdx % 3;
            if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) {
                sB64Enc += "\r\n";
            }
            nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
            if (nMod3 === 2 || aBytes.length - nIdx === 1) {
                sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
                nUint24 = 0;
            }
        }

        return sB64Enc.substr(0, sB64Enc.length - 2 + nMod3) + (nMod3 === 2 ? '' : nMod3 === 1 ? '=' : '==');

    }

    function png2ary(png, callback) {
        var img = new Image();
        img.onload = function() {
            c = document.createElement('canvas');
            w = c.width = img.width;
            h = c.height = img.height;
            x = c.getContext('2d');
            i = 0;
            //b = [];
            x.drawImage(img, 0, 0);
            g = x.getImageData(0, 0, w, h).data;
            var b = new Uint8Array(g.length / 4);
            var idx = 0;
            for (l = g.length; i < l && g[i + 3] === 255; i += 4) {
                b[idx++] = g[i];
            }
            callback(b);
        };
        img.src = png;

    }

    function ary2png(ary) {
            //console.log(buf);
            var s, p, l;
            s = p = 0;
            // 画像のサイズを算出する。
            for (l = ary.length; s * s < l; s++) {}
            console.log("s = ", s);
            var c = document.getElementById("cs");
            c.width = c.height = s;
            x = c.getContext("2d");
            G = x.createImageData(s, s);
            g = G.data;
            var minb, maxb;
            minb = maxb = ary[p];
            for (l = s * s; p < l /*&& (v = ary[p])*/ ; p++) {
                v = ary[p];
                if (v < minb) minb = v;
                if (v > maxb) maxb = v;
                o = p << 2;
                //console.log(p,o);
                g[o] = g[o + 1] = g[o + 2] = v;
                g[o + 3] = 255;
            }
            console.log("minb = ", minb);
            console.log("maxb = ", maxb);
            x.putImageData(G, 0, 0);
            r = c.toDataURL();
            console.log(r);
            return r;
        }
        // end from http://jsdo.it/cx20/yfCo

    function hash3png(hash) {
        // zip圧縮されたバイナリ配列を取得する。
        //console.log("hash",hash);
        var buf = decodeURI(hash);
        //console.log(buf);
        // base64でデコードする。
        var ary = base64DecToArr(buf);
        var png = ary2png(ary);


        //test
        png2ary(png, function(ary2) {
            var base64 = base64EncArr(ary2);
            var jsz = new window.parent.JSZip();
            var unzip = jsz.load(base64, {
                base64: !0
            });
            console.log(unzip.file("data").asText());
        });

    }
    window.addEventListener("load", function() {
        var btn = document.getElementById("btn");
        btn.addEventListener("click", function() {

            var hash = document.getElementById("hash").value;
            hash3png(hash);
        }, false);

    }, false)
})();

まとめ

これを実装したrunstatnt上のコードはハッシュより、PNGのDataURLの方が短かったが、デフォルトのrunstantのコードはハッシュの方が短かったりと、結果はまちまちっぽい。

夏休みの自由研究をするお友達へ

JPEGで画像化すると不可逆圧縮なので、何も考えないで使うと、うまく元に戻らないよ。

Link