在javascript中将内核矩阵卷积以用于html5canvas中的图像过滤器

时间:2018-07-11 22:25:47

标签: javascript html html5-canvas convolution

我一直在尝试使用javascript学习过滤器,一直在关注

https://www.html5rocks.com/en/tutorials/canvas/imagefilters/ 本教程。

我遇到了一些我不了解的代码,有什么机构可以帮助我理解这些代码。

 Filters.convolute = function(pixels, weights, opaque) {
  var side = Math.round(Math.sqrt(weights.length));
  var halfSide = Math.floor(side/2);
  var src = pixels.data;
  var sw = pixels.width;
  var sh = pixels.height;
  // pad output by the convolution matrix
  var w = sw;
  var h = sh;
  var output = Filters.createImageData(w, h);
  var dst = output.data;
  // go through the destination image pixels
  var alphaFac = opaque ? 1 : 0;
  for (var y=0; y<h; y++) {
    for (var x=0; x<w; x++) {
      var sy = y;
      var sx = x;
      var dstOff = (y*w+x)*4;
      // calculate the weighed sum of the source image pixels that
      // fall under the convolution matrix
      var r=0, g=0, b=0, a=0;
      for (var cy=0; cy<side; cy++) {
        for (var cx=0; cx<side; cx++) {
          var scy = sy + cy - halfSide;
          var scx = sx + cx - halfSide;
          if (scy >= 0 && scy < sh && scx >= 0 && scx < sw) {
            var srcOff = (scy*sw+scx)*4;
            var wt = weights[cy*side+cx];
            r += src[srcOff] * wt;
            g += src[srcOff+1] * wt;
            b += src[srcOff+2] * wt;
            a += src[srcOff+3] * wt;
          }
        }
      }
      dst[dstOff] = r;
      dst[dstOff+1] = g;
      dst[dstOff+2] = b;
      dst[dstOff+3] = a + alphaFac*(255-a);
    }
  }
  return output;
};

side和halfSide是什么,为什么使用4 for nested loop。我被困在这里就像很多天。

1 个答案:

答案 0 :(得分:0)

我确实和您一样,我正在尝试使用Javascript-TypeScript实现卷积过滤器。

之所以为4,是因为我们有r,g,b,a

where r = red
where g = green
where b = blue
where a = alpha

此图像数据位于类型为Uint8ClampedArray的数组中

您可以通过以下方式获得此信息:

const width = canvas.width;
const height = canvas.height;

const imageData = ctx.getImageData(0, 0, width, height);

,然后获取真实图像数据: const pixel = imageData.data;

像素数据是Uint8ClampedArray的一种,可以这样表示:

  

[r, g, b, a, r, g, b, a, r, g, b, a ]

,数组中的每4个元素都会获得像素索引,内核中心每1.5次获得像素索引,但这取决于内核大小3x3或9x9

const image = imageData.data

唯一对我有用的代码是这个。

init() {

    const img = new Image();
    const img2 = new Image();
    img.src = '../../../assets/graffiti.jpg';
    img2.src = '../../../assets/graffiti.jpg';


    const canvas: HTMLCanvasElement = this.canvas1.nativeElement;
    const canvas2: HTMLCanvasElement = this.canvas2.nativeElement;

    const ctx: CanvasRenderingContext2D = canvas.getContext('2d');
    const ctx2: CanvasRenderingContext2D = canvas2.getContext('2d');

    this.onImgLoad(img, ctx, canvas.width, canvas.height);
    this.input(img2, ctx2, canvas2.width, canvas2.height);

  }

  input(img, ctx: CanvasRenderingContext2D, width, height) {
    img.onload = () => {
      ctx.drawImage(img, 0, 0);
    };
  }

  onImgLoad(img, ctx: CanvasRenderingContext2D, width, height) {

    img.onload = () => {

      ctx.drawImage(img, 0, 0);

      const kernelArr = new Kernel([
        [0, 1, 0],
        [0, 1, 0],
        [0, 1, 0],
      ]);

      const kernel = [
        0, 1, 0,
        0, 1, 0,
        0, 1, 0
      ];

      console.log(kernel);

      const newImg = new Filter2D(ctx, width, height);
      // const imgData = newImg.inverse(width, height); // applys inverse filter
      const imgData = newImg.applyKernel(kernel);

      ctx.putImageData(imgData, 0, 0);
    };

  }


class Filter2D {

  width: number;
  height: number;
  ctx: CanvasRenderingContext2D;
  imgData: ImageData;

  constructor(ctx: CanvasRenderingContext2D, width: number, height: number) {

    this.width = width;
    this.height = height;
    this.ctx = ctx;
    this.imgData = ctx.getImageData(0, 0, width, height);
    console.log(this.imgData);
  }

  grey(width: number, height: number): ImageData {
    return this.imgData;
  }


  inverse(width: number, height: number): ImageData {

    console.log('Width: ', width);
    console.log('Height: ', height);

    const pixels = this.imgData.data;

    for (let i = 0; i < pixels.length; i += 4) {
      pixels[i] = 255 - pixels[i]; // red
      pixels[i + 1] = 255 - pixels[i + 1]; // green
      pixels[i + 2] = 255 - pixels[i + 2]; // blue
    }

    return this.imgData;

  }

  applyKernel(kernel: any[]): ImageData {

    const k1: number[] = [
      1, 0, -1,
      2, 0, -2,
      1, 0, -1
    ];

    const k2: number[] = [
      -1, -1, -1,
      -1, 8, -1,
      -1, -1, -1
    ];

    kernel = k2;


    const dim = Math.sqrt(kernel.length);
    const pad = Math.floor(dim / 2);

    const pixels: Uint8ClampedArray = this.imgData.data;
    const width: number = this.imgData.width;
    const height: number = this.imgData.height;

    console.log(this.imgData);

    console.log('applyKernelMethod start');
    console.log('Width: ', width);
    console.log('Height: ', height);
    console.log('kernel: ', kernel);


    console.log('dim: ', dim); // 3
    console.log('pad: ', pad); // 1
    console.log('dim % 2: ', dim % 2); // 1

    console.log('pixels: ', pixels);

    if (dim % 2 !== 1) {
      console.log('Invalid kernel dimension');
    }



    let pix, i, r, g, b;
    const w = width;
    const h = height;
    const cw = w + pad * 2; // add padding
    const ch = h + pad * 2;

    for (let row = 0; row < height; row++) {
      for (let col = 0; col < width; col++) {

        r = 0;
        g = 0;
        b = 0;


        for (let kx = -pad; kx <= pad; kx++) {
          for (let ky = -pad; ky <= pad; ky++) {

            i = (ky + pad) * dim + (kx + pad); // kernel index
            pix = 4 * ((row + ky) * cw + (col + kx)); // image index
            r += pixels[pix++] * kernel[i];
            g += pixels[pix++] * kernel[i];
            b += pixels[pix  ] * kernel[i];
          }
        }

        pix = 4 * ((row - pad) * w + (col - pad)); // destination index
        pixels[pix++] = r;
        pixels[pix++] = g;
        pixels[pix++] = b;
        pixels[pix  ] = 255; // we want opaque image

      }
    }


    console.log(pixels);

    return this.imgData;

  }

}