为啥画布会弄乱我的图像颜色?

Posted

技术标签:

【中文标题】为啥画布会弄乱我的图像颜色?【英文标题】:Why is canvas messing with my image's colors?为什么画布会弄乱我的图像颜色? 【发布时间】:2022-01-09 08:39:48 【问题描述】:

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

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

我在 GitHub 上托管了 demo,展示了问题。

图片

This 是图像。它显然仅由黑白像素组成,但如果您想自己检查,可以使用this website。 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。这就是为什么我认为问题出在我的画布上,但我不知道发生了什么。

【问题讨论】:

从图像编辑的背景说起,这是将 2 种颜色位图的图像转换为 RGB 调色板的典型做法。除非您在加载图像时将白色强制转换为白色并将黑色强制转换为黑色,否则您将获得随机变化,因为转换会查看相邻像素以确定它“应该是”的值。即使是 Gimp 和 Photoshop 也不能 100% 匹配白色到白色和黑色到黑色进行这样的转换。 这很有意义......但它适用于其他网站!我的意思是,它遍历所有像素,只找到两种颜色——黑色和白色。 一个网站可能正在考虑调色板并查看与之相关的颜色,因此它只能看到您设置的 2 种颜色。看到超过 2 种颜色的网站可能会像您一样将其转换为 RGB 图像,这就是其他颜色“神奇地”出现的时候。因为我不知道你在用像素做什么,所以我真的不建议切换到 SVG 而不是 PNG,但你可能值得去研究它,看看它是否仍然可以满足你的需求. SVG 会给你你想要的颜色清晰度,但 IDK 是关于像素的。 【参考方案1】:

您正在缩放图像,并且由于您没有告诉使用哪种插值算法,因此正在使用默认的平滑算法。

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

有一个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);
);
&lt;canvas&gt;&lt;/canvas&gt;

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

【讨论】:

以上是关于为啥画布会弄乱我的图像颜色?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 fread 会弄乱我的字节顺序?

为啥 PHP 会弄乱我的 CSS?

为啥浏览器的后退按钮会弄乱我的 Vue 组件?

为啥 matplotlib.PatchCollection 会弄乱补丁的颜色?

为啥 Python 多处理队列会弄乱字典?

iOS 以编程方式截屏会弄乱图像