You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让HTML Canvas自定义滤镜返回可用于putImageData的ImageData对象?

问题分析

你遇到的错误原因很直接:ctx.putImageData() 方法要求第一个参数必须是**ImageData对象**,但你的 filter 方法返回的是 Uint8ClampedArray 类型的像素数据数组(也就是 input.data.slice() 生成的数组),这不符合API的参数类型要求,所以抛出了类型错误。

而你修改为直接修改原 inputData 的方式能正常运行,是因为此时你传入 putImageData 的是原本的 ImageData 对象(data 只是它的一个属性),修改它的 data 数组后,这个对象本身依然是合法的 ImageData 类型。

解决方案

让你的 filter 方法直接返回一个新的 ImageData 对象即可——ImageData 构造函数支持传入像素数组、宽度和高度来创建实例。只需要修改 filter 方法的返回语句:

// 替换原来的 return outputData;
return new ImageData(outputData, width, height);

这样调用 filter 方法时,得到的就是合法的 ImageData 对象,直接传给 putImageData 就不会报错了。

修改后的完整代码
//////////////////////////////
// Distortion Filter        //
// Originally by JoelBesada // 
//////////////////////////////
class FilterDistortion {
  constructor() {
    this.name = 'Distortion';
    this.defaultValues = {
      size: 4,
      density: 0.5,
      mix: 0.5,
    };
    this.valueRanges = {
      size: { min: 1, max: 10 },
      density: { min: 0.0, max: 1.0 },
      mix: { min: 0.0, max: 1.0 },
    };
  }

  filter(input, values = this.defaultValues) {
    const { width, height } = input;

    const inputData = input.data;
    const outputData = inputData.slice();

    let size = (values.size === undefined)
      ? this.defaultValues.size
      : parseInt(values.size, 10);
    if (size < 1) size = 1;

    const density = (values.density === undefined)
      ? this.defaultValues.density
      : Number(values.density);

    const mix = (values.mix === undefined)
      ? this.defaultValues.mix
      : Number(values.mix);

    const radius = size + 1;
    const numShapes = parseInt(((((2 * density) / 30) * width) * height) / 2, 10);

    for (let i = 0; i < numShapes; i++) {
      const sx = (Math.random() * (2 ** 32) & 0x7fffffff) % width;
      const sy = (Math.random() * (2 ** 32) & 0x7fffffff) % height;

      const rgb2 = [
        inputData[(((sy * width) + sx) * 4) + 0],
        inputData[(((sy * width) + sx) * 4) + 1],
        inputData[(((sy * width) + sx) * 4) + 2],
        inputData[(((sy * width) + sx) * 4) + 3],
      ];

      for (let x = sx - radius; x < sx + radius + 1; x++) {
        for (let y = sy - radius; y < sy + radius + 1; y++) {
          if (x >= 0 && x < width && y >= 0 && y < height) {
            const rgb1 = [
              outputData[(((y * width) + x) * 4) + 0],
              outputData[(((y * width) + x) * 4) + 1],
              outputData[(((y * width) + x) * 4) + 2],
              outputData[(((y * width) + x) * 4) + 3],
            ];
            const mixedRGB = this.mixColors(mix, rgb1, rgb2);

            for (let k = 0; k < 3; k++) {
              outputData[(((y * width) + x) * 4) + k] = mixedRGB[k];
            }
          }
        }
      }
    }

    // 关键修改:返回新的ImageData对象
    return new ImageData(outputData, width, height);
  }

  linearInterpolate(t, a, b) {
    return a + (t * (b - a));
  }

  mixColors(t, rgb1, rgb2) {
    const r = this.linearInterpolate(t, rgb1[0], rgb2[0]);
    const g = this.linearInterpolate(t, rgb1[1], rgb2[1]);
    const b = this.linearInterpolate(t, rgb1[2], rgb2[2]);
    const a = this.linearInterpolate(t, rgb1[3], rgb2[3]);

    return [r, g, b, a];
  }
}

/////////////////
// Driver Code //
/////////////////
var img = new Image(); img.onload = draw; img.src = "//i.imgur.com/Kzz84cr.png";
function draw() {
  c.width = this.width; c.height = this.height;
  
  var ctx = c.getContext("2d");
  // main loop
  ctx.drawImage(this, 0, 0);                     // draw video frame
  var data = ctx.getImageData(0, 0, c.width, c.height);
  // 现在可以直接传入filter返回的结果
  ctx.putImageData(new FilterDistortion().filter(data), 0, 0);
  ctx.globalCompositeOperation = "source-over";  // "reset"
  // rinse, repeat
}
<canvas id=c></canvas>

内容的提问来源于stack exchange,提问作者Rami Chasygov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:25:06