0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【入門者向け】Canvas入門講座#15 モザイクしてみよう【JavaScript】

Last updated at Posted at 2020-12-01

#問題15

問題13~15は問題9がベースとなっています。まず問題9を実装してください。

画像読み込み後、mosaic押下時に8x8のモザイク処理を行え。
モザイク処理とは平均の色で塗りつぶすことである。

以下のHTMLを使用すること。(今回の問題はcanvasのサイズが640x320になっています。)

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題15</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {            
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#mosaic-button').click(e => {        
    });
    
});
</script>
</head>
<body>
<canvas id="my-canvas" width="640" height="320"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="mosaic-button" type="button" value="mosaic" />
</body>
</html>

#解答

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題15</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    const MOSAIC_WIDTH = 8,
        MOSAIC_HEIGHT = 8; 

    $('#my-file').on('change', e => {
        const img = new Image();
        $(img).on('load', e => {
            // コンテキストを取得
            const ctx = $('#my-canvas')[0].getContext('2d');

            // canvasを黒色で塗りつぶす
            ctx.fillStyle = '#000000';
            ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);            

            // canvasと画像のアスペクト比を求める
            const canvasWidth = $('#my-canvas').prop('width'),
                canvasHeight = $('#my-canvas').prop('height');
                canvasAspect = canvasWidth / canvasHeight;  // canvasのアスペクト比
                imgAspect = img.width / img.height; // imgのアスペクト比
            
            // canvasと画像のアスペクト比を比較し、貼り付ける領域を決定する
            let dstWidth, dstHeight;
            if(canvasAspect > imgAspect) {// canvasの方が横長
                dstHeight = canvasHeight;
                dstWidth = dstHeight * imgAspect;               
            } else {// canvasの方が縦長
                dstWidth = canvasWidth;
                dstHeight = dstWidth / imgAspect;
            }

            // 画像を貼り付ける
            ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, dstWidth, dstHeight);
        });
        img.src = URL.createObjectURL(e.target.files[0]);
    });

    $('#mosaic-button').click(e => {
        const canvas = $('#my-canvas')[0],
            ctx = canvas.getContext('2d');

        ctx.save();

        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height),
            data = imgData.data;

        for(let y = 0; y < canvas.height; y += MOSAIC_HEIGHT) {
            for(let x = 0; x < canvas.width; x += MOSAIC_WIDTH) {
                
                // モザイクの色を計算する
                let r, g, b;
                r = g = b = 0;
                for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
                    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
                        // 画素のインデックスを求める
                        const p = ((y + j) * canvas.width + (x + i)) * 4;
                        r += data[p];
                        g += data[p + 1];
                        b += data[p + 2];
                    }
                }

                // 各色の平均を計算する
                const pixelCount = MOSAIC_WIDTH * MOSAIC_HEIGHT; // モザイクの画素数            
                r /= pixelCount;
                g /= pixelCount;
                b /= pixelCount;

                // モザイクをかける            
                for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
                    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
                        // 画素のインデックスを求める
                        const p = ((y + j) * canvas.width + (x + i)) * 4;
                        data[p] = r;
                        data[p + 1] = g;
                        data[p + 2] = b;
                    }
                }
            }
        }

        // imageDataをcanvasに貼り付ける
        ctx.putImageData(imgData, 0, 0);

        ctx.restore();
    });
    
});
</script>
</head>
<body>
<canvas id="my-canvas" width="640" height="320"></canvas>
<br>
<input id="my-file" type="file" />
<br>
<input id="mosaic-button" type="button" value="mosaic" />
</body>
</html>

モザイク処理ができました。かっこいいですね。

image.png

#解説
基本的な流れは問題13,14と似ています。
やりたいことは8x8の平均の色で塗りつぶすことです。

グレースケールや2値化では1つのfor文で回していましたが
今回のモザイク処理ではx方向とy方向の2つのfor文でループさせたほうがわかりやすいと思います。
下図のようにYの値を8ずつ変えながらX方向に8ずつ走査します。

image.png

for(let y = 0; y < canvas.height; y += MOSAIC_HEIGHT) {
    for(let x = 0; x < canvas.width; x += MOSAIC_WIDTH) {
        // ここでモザイク処理
    }
}

今回のモザイクサイズは8x8ですので、
現在の画素からさらにx方向とy方向の2つのfor文でループさせて、まずは画素の各成分を全部足し合わせます。
それをモザイクの画素数で割れば平均が出ます。
その平均の色で塗りつぶします。

// モザイクの色を計算する
let r, g, b;
r = g = b = 0;
for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
        // 画素のインデックスを求める
        const p = ((y + j) * canvas.width + (x + i)) * 4;
        r += data[p];
        g += data[p + 1];
        b += data[p + 2];
    }
}

// 各色の平均を計算する
const pixelCount = MOSAIC_WIDTH * MOSAIC_HEIGHT; // モザイクの画素数            
r /= pixelCount;
g /= pixelCount;
b /= pixelCount;

// モザイクをかける            
for(let j = 0; j < MOSAIC_HEIGHT; j += 1) {
    for(let i = 0; i < MOSAIC_WIDTH; i += 1) {
        // 画素のインデックスを求める
        const p = ((y + j) * canvas.width + (x + i)) * 4;
        data[p] = r;
        data[p + 1] = g;
        data[p + 2] = b;
    }
}

#発展
今回canvasのサイズが640x320で幅、長さ共に8で割り切れましたが
割り切れない場合はどのように実装すればよいでしょうか?
よい練習になると思うのでぜひやってみてください。(こちらが参考になるかも)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?