RE:【ネタバレあり】おうちdeサイゼ!Javascriptで間違い探し (15分!ライブラリ不要!)

久しぶりシステム担当の記事です…

最近、社内でPhotoshopを使ってサイゼリヤの間違い探しを解いたのですが、
前回の話「【ネタバレあり】おうちdeサイゼ!大人の間違い探しの答え」に続き、15分くらいあれば簡単なJavascriptを使って、探せるじゃないかとチャレンジしてみました。

しかもライブラリを使わず、簡単なJavaScriptのみのプログラムで実装したいと思います!

まずは、Photoshopで探すのと同じ要領で、違う画像を2つのLayer分けて重ね、差分を見つけます。

[待ちきれない方はこちらです]

早速紹介します。

今回はhtml canvasを使います。

1. html canvasを初期化する

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

2. 画像を読み込みと表示

    var image = new Image();
    image.src = 'a.jpg';
    image.onload = function () {
        context.drawImage(image, 0, 0);
    }

ここまでは一般的な画像表示方法ですが、画像データを繰り返し使いたい場合、
pass by referenceが必要になるので、変数をobjectに変えます。

※ポイントは画像のwidth、heightとデータ(grey)を覚えるobjectに変えます。
※特にgreyはカラーデータをモノクロデータに変換した後に使います。

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

    var imageGreyA = {width: 0, height: 0, grey: ""}; //width,height,greyを覚える
    var imageGreyB = {width: 0, height: 0, grey: ""};  //width,height,greyを覚える

    loadImage(context, imageGreyA, 'a.jpg', 0, 0);
    loadImage(context, imageGreyB, 'b.jpg', 600, 0);

    function loadImage(context, greyData, filename, x, y) {
        var image = new Image();
        image.src = filename;
        image.onload = function () {
            greyData.width = image.width;    //widthを取得
            greyData.height = image.height;     //heightを取得

            context.drawImage(image, x, y);
            var imageData = context.getImageData(x, y, greyData.width, greyData.height).data;
            greyData.grey = new Uint8ClampedArray(greyData.width * greyData.height);      //画像データを取得
        }
    }

3. 色をモノクロに

    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

    var imageGreyA = {width: 0, height: 0, grey: ""};
    var imageGreyB = {width: 0, height: 0, grey: ""};

    loadImage(context, imageGreyA, 'a.jpg', 0, 0);
    loadImage(context, imageGreyB, 'b.jpg', 600, 0);

    function loadImage(context, greyData, filename, x, y) {
        var image = new Image();
        image.src = filename;
        image.onload = function () {
            greyData.width = image.width;
            greyData.height = image.height;

            context.drawImage(image, x, y);
            var imageData = context.getImageData(x, y, greyData.width, greyData.height).data;
            greyData.grey = new Uint8ClampedArray(greyData.width * greyData.height);      //pixelをarrayに変換
            for (var i = 0; i < imageData.length; i += 4) {
                greyData.grey[i / 4] = 0.30 * imageData[i + 0] + 0.59 * imageData[i + 1] + 0.11 * imageData[i + 2];  //pixelを変換
            }
        }
    }

4. 2つ画像を比べる関数を作る

    function diffData() {
        if (imageGreyA.grey !== "" && imageGreyB.grey !== "") {

            var diff = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);   //pixelをarrayに変換
            var diff2 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);  //pixelをarrayに変換

            for (var i = 0; i < diff.length; i++) {
                diff[i] = Math.abs(imageGreyA.grey[i] - imageGreyB.grey[i]);            //画像Aと画像Bの差
                diff2[i] = Math.abs(imageGreyB.grey[i] - imageGreyA.grey[i]);      //繰り返す画像Bと画像Aの差

                diff[i] = diff[i] & diff2[i];                        //差の結果を合体する
            }
        }
    }

5. 結果を表示する

            var resultData = context.getImageData(0, 600, imageGreyA.width, imageGreyA.height);
            var diffLength = diff.length * 4;
            var rgba = new Uint8ClampedArray(diffLength);    //ここからarrayをRGBAに戻す
            for (var j = 0; j < diffLength; j += 4) {
                rgba[j] = diff[j / 4];
                rgba[j + 1] = diff[j / 4];
                rgba[j + 2] = diff[j / 4];
                rgba[j + 3] = 255;               //aは関係ないので255にする
            }
            resultData.data.set(rgba);             //画像データをimageDataに設定
            context.putImageData(resultData, 0, 600);     //contextに表示

6. 結果を良くするためには調整が必要です。

携帯で撮った写真なので、少しpixelがずれてます、それをソースで改善してみました。
違うモニターにも見やすくために白黒を調整しました。

    function diffData() {

        if (imageGreyA.grey !== "" && imageGreyB.grey !== "") {
            //start diff

            //main diff
            var diff = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
            var diff2 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
	
            //+1 pixel diff
            var diff3 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);   //1px差を無してみます
            var diff4 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);

            //-1 pixel diff
            var diff5 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);  //1px差を無してみます
            var diff6 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);

            for (var i = 0; i < diff.length; i++) {
                diff[i] = Math.abs(imageGreyA.grey[i] - imageGreyB.grey[i]);
                diff2[i] = Math.abs(imageGreyB.grey[i] - imageGreyA.grey[i]);

                diff3[i] = Math.abs(imageGreyA.grey[i + 1] - imageGreyB.grey[i]);
                diff4[i] = Math.abs(imageGreyA.grey[i] - imageGreyB.grey[i - 1]);

                diff5[i] = Math.abs(imageGreyB.grey[i + 1] - imageGreyA.grey[i]);
                diff6[i] = Math.abs(imageGreyB.grey[i] - imageGreyA.grey[i - 1]);

                diff[i] = diff[i] & diff2[i] & diff3[i] & diff4[i] & diff5[i] & diff6[i];

                if (diff[i] >= 128) {   //見やすさを調整してみます:黒さ半分以下pixelは黒、白さ半分以上pixelは白にします
                    diff[i] = 255;
                } else {
                    diff[i] = 0;
                }
            }

            //draw result
            var resultData = context.getImageData(0, 600, imageGreyA.width, imageGreyA.height);
            var diffLength = diff.length * 4;
            var rgba = new Uint8ClampedArray(diffLength);
            for (var j = 0; j < diffLength; j += 4) {
                rgba[j] = diff[j / 4];
                rgba[j + 1] = diff[j / 4];
                rgba[j + 2] = diff[j / 4];
                rgba[j + 3] = 255;
            }
            resultData.data.set(rgba);
            context.putImageData(resultData, 0, 600);
        }
    }

15分で作って見ましたが、調整いろいろ含めて30分に掛かりました。
最終結果はこちらになります、悪くはないでしょうね。

[実際サンプルはこちらです]

最終ソースコードはこちらです。

function main() {
    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');

    var imageGreyA = {width: 0, height: 0, grey: ""};
    var imageGreyB = {width: 0, height: 0, grey: ""};
    loadImage(context, imageGreyA, 'a.jpg', 0, 0);
    loadImage(context, imageGreyB, 'b.jpg', 600, 0);

    function loadImage(context, greyData, filename, x, y) {
        var image = new Image();
        image.src = filename;
        image.onload = function () {
            greyData.width = image.width;
            greyData.height = image.height;

            context.drawImage(image, x, y);
            var imageData = context.getImageData(x, y, greyData.width, greyData.height).data;
            greyData.grey = new Uint8ClampedArray(greyData.width * greyData.height);
            for (var i = 0; i < imageData.length; i += 4) {
                greyData.grey[i / 4] = 0.30 * imageData[i + 0] + 0.59 * imageData[i + 1] + 0.11 * imageData[i + 2];
            }
            diffData();
        }
    }

    function diffData() {

        if (imageGreyA.grey !== "" && imageGreyB.grey !== "") {
            //start diff

            //main diff
            var diff = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
            var diff2 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
	
            //+1 pixel diff
            var diff3 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
            var diff4 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);

            //-1 pixel diff
            var diff5 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);
            var diff6 = new Uint8ClampedArray(imageGreyA.width * imageGreyA.height);

            for (var i = 0; i < diff.length; i++) {
                diff[i] = Math.abs(imageGreyA.grey[i] - imageGreyB.grey[i]);
                diff2[i] = Math.abs(imageGreyB.grey[i] - imageGreyA.grey[i]);

                diff3[i] = Math.abs(imageGreyA.grey[i + 1] - imageGreyB.grey[i]);
                diff4[i] = Math.abs(imageGreyA.grey[i] - imageGreyB.grey[i - 1]);

                diff5[i] = Math.abs(imageGreyB.grey[i + 1] - imageGreyA.grey[i]);
                diff6[i] = Math.abs(imageGreyB.grey[i] - imageGreyA.grey[i - 1]);

                diff[i] = diff[i] & diff2[i] & diff3[i] & diff4[i] & diff5[i] & diff6[i];

                if (diff[i] >= 128) {
                    diff[i] = 255;
                } else {
                    diff[i] = 0;
                }
            }

            //draw result
            var resultData = context.getImageData(0, 600, imageGreyA.width, imageGreyA.height);
            var diffLength = diff.length * 4;
            var rgba = new Uint8ClampedArray(diffLength);
            for (var j = 0; j < diffLength; j += 4) {
                rgba[j] = diff[j / 4];
                rgba[j + 1] = diff[j / 4];
                rgba[j + 2] = diff[j / 4];
                rgba[j + 3] = 255;
            }
            resultData.data.set(rgba);
            context.putImageData(resultData, 0, 600);
        }
    }
}