みさご解体新書

モザイク

実行例

ソースコード

TypeScript

0.jpg / app.ts

解説/アルゴリズム

3*3 のような一定範囲のピクセルを対象に、RGB ごとに平均化した値を設定し直す。

上記だと、 (171 + 21 + 66 + 84 + 180 + 3 + 205 + 78 + 168) / 9 ≒ 108 を 3x3 のピクセルに再代入する。

範囲計算の際にインデックスがはみ出る場合はその部分の計算は行わない。

上記だと、 (200 + 47 + 55 + 251 + 53 + 172) / 6 ≒ 129 を 2x3 のピクセルに再代入する。

コード例(GLSL)

attribute vec2 aPosition;
uniform vec2 uResolution;
varying vec2 vPosition;

void main() {
  vec2 clipSpace = aPosition / uResolution * 2.0 - 1.0;
  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
  vPosition = aPosition;
}
precision mediump float;
const int range = 10;
uniform sampler2D uImage;
uniform vec2 uTextureSize;
varying vec2 vPosition;

void main() {
  vec2 cur = floor(vPosition / float(range)) * float(range);
  vec4 color = vec4(0.0);
  for (int y = 0; y < range; y++) {
    for (int x = 0; x < range; x++) {
      vec2 pos = (cur + vec2(x, y)) / uTextureSize;
      color += texture2D(uImage, pos);
    }
  }
  gl_FragColor = vec4(color.rgb / float(range * range), 1.0);
}

コード例(p5.js)

import * as p5 from "p5";

new p5((p: p5) => {
  let image: p5.Image;

  // range * rangeおきに平均化する
  const range = 10;

  p.preload = () => {
    image = p.loadImage("./0.jpg");
  };

  p.setup = () => {
    p.createCanvas(p.windowWidth, p.windowHeight);
    image.loadPixels();

    // 画像のピクセルを縦横rangeおきに走査
    for (let y = 0; y < image.height; y += range) {
      for (let x = 0; x < image.width; x += range) {
        // RGBそれぞれの合計
        let tr = 0;
        let tg = 0;
        let tb = 0;

        // 対象となるピクセル数
        let count = 0;

        // range * rangeの範囲を走査
        for (let yy = 0; yy < range; yy++) {
          for (let xx = 0; xx < range; xx++) {
            const ty = y + yy;
            const tx = x + xx;
            if (ty < image.height && tx < image.width) {
              const color = getPixel(tx, ty);
              const r = p.red(color);
              const g = p.green(color);
              const b = p.blue(color);
              tr += r;
              tg += g;
              tb += b;
              count++;
            }
          }
        }

        // RGBごとに平均を取る
        tr /= count;
        tg /= count;
        tb /= count;

        // 平均の色でrange * rangeのピクセルを塗りつぶす
        for (let yy = 0; yy < range; yy++) {
          for (let xx = 0; xx < range; xx++) {
            const ty = y + yy;
            const tx = x + xx;
            if (ty < image.height && tx < image.width) {
              setPixel(tx, ty, [tr, tg, tb]);
            }
          }
        }
      }
    }

    image.updatePixels();
    p.image(image, 0, 0);
  };

  function getPixel(x: number, y: number) {
    const i = (y * image.width + x) * 4;
    return [
      image.pixels[i],
      image.pixels[i + 1],
      image.pixels[i + 2],
      image.pixels[i + 3],
    ];
  }

  function setPixel(x: number, y: number, color: number[]) {
    const i = (y * image.width + x) * 4;
    image.pixels[i + 0] = color[0];
    image.pixels[i + 1] = color[1];
    image.pixels[i + 2] = color[2];
  }
});