为什么 canvas 弄乱了我图片的颜色?

Why is canvas messing with my image's colors?

我正在开发一款具有绘画功能的应用程序。用户可以在最初仅由纯黑色和纯白色像素组成的图像上作画。稍后,当用户画完之后,我需要根据每个像素的颜色对该图像做一些处理。

然而,我意识到当我处理图像时,像素不再是纯粹的 black/white,而是中间有很多灰色,即使用户没有画任何东西。我写了一些代码来检查它,发现图像上有超过 250 种不同的颜色,而我期望只有两种(黑色和白色)。我怀疑 canvas 以某种方式弄乱了我的颜色,但我不明白为什么。

我在 GitHub 上主持了一个 demo,展示了这个问题。

图片

This is the image. It is visibly made of only black and white pixels, but if you want to check by yourself you can use this website. It's source code 在 GitHub 上可用,我用它作为我自己的颜色计数实现的参考。

我的代码

这是我加载图像并计算独特颜色的代码。您可以获得完整的源代码 here.

class AppComponent {
  /* ... */

  // Rendering the image
  ngAfterViewInit() {
    this.context = this.canvas.nativeElement.getContext('2d');

    const image = new Image();
    image.src = 'assets/image.png';

    image.onload = () => {
      if (!this.context) return;

      this.context.globalCompositeOperation = 'source-over';
      this.context.drawImage(image, 0, 0, this.width, this.height);
    };
  }

  // Counting unique colors
  calculate() {
    const imageData = this.context?.getImageData(0, 0, this.width, this.height);
    const data = imageData?.data || [];

    const uniqueColors = new Set();

    for (let i = 0; i < data?.length; i += 4) {
      const [red, green, blue, alpha] = data.slice(i, i + 4);
      const color = `rgba(${red}, ${green}, ${blue}, ${alpha})`;
      uniqueColors.add(color);
    }

    this.uniqueColors = String(uniqueColors.size);
  }

这是来自其他站点的实现:

function countPixels(data) {   
    const colorCounts = {};
    for(let index = 0; index < data.length; index += 4) {
        const rgba = `rgba(${data[index]}, ${data[index + 1]}, ${data[index + 2]}, ${(data[index + 3] / 255)})`;

        if (rgba in colorCounts) {
            colorCounts[rgba] += 1;
        } else {
            colorCounts[rgba] = 1;
        }
    }    
    return colorCounts;
}

如您所见,除了实现相似之外,它们输出的结果也大不相同——我的网站说我有 256 种独特的颜色,而另一个说只有两种。我也尝试只复制和粘贴实现,但我得到了相同的 256。这就是为什么我认为问题出在我的 canvas,但我无法弄清楚发生了什么。

您正在缩放图像,由于您没有说明要使用哪种插值算法,因此使用了默认的平滑算法。

这将使所有位于固定边界上的像素现在应该跨越多个像素与它们的白色邻居“混合”并产生灰色阴影。

有一个 imageSmoothingEnabled 属性 告诉浏览器使用最近邻算法,这将改善这种情况,但即使那样你也可能不会得到完美的结果:

const canvas = document.querySelector("canvas");
const width = canvas.width = innerWidth;
const height = canvas.height = innerHeight;
const ctx = canvas.getContext("2d");
const img = new Image();
img.crossOrigin = "anonymous";
img.src = "https://raw.githubusercontent.com/ajsaraujo/unique-color-count-mre/master/src/assets/image.png";
img.decode().then(() => {
  ctx.imageSmoothingEnabled = false;
  ctx.drawImage(img, 0, 0, width, height);
  const data = ctx.getImageData(0, 0, width, height).data;
  const pixels = new Set(new Uint32Array(data.buffer));
  console.log(pixels.size);
});
<canvas></canvas>

所以最好不要缩放图像,或者以计算机友好的方式缩放(使用 2 的倍数)。