使用 HTML Canvas 即时填色

 (关注微信公众号“NetX行者”浏览更多精彩文章)

问题

使用 HTML Canvas 构建网站或应用程序时,通常需要支持填充。也就是说,当用户选择一种颜色并单击一个像素时,用用户选择的颜色填充与所单击像素的颜色相匹配的所有周围像素。

为此,您可以编写一个相当简单的算法,一次一个地遍历像素,将它们与单击的像素进行比较,然后改变或不改变它们的颜色。如果您在执行此操作时重绘画布,以便为用户提供视觉反馈,它可能看起来像这样。

这可行,但速度慢且丑陋。可以大大加快速度,所以它基本上是即时的,看起来像这样

为实现这一点,我们预处理源图像并使用输出立即将彩色蒙版应用到 HTML Canvas。

相关网站

演示:https://shaneosullivan.github.io/example-canvas-fill 

代码:https://github.com/shaneosullivan/example-canvas-fill

https://excalidraw.com/: Excalidraw

案例

Kidz Fun Art:Kidz Fun Art 基于网络的应用程序,该应用程序针对在平板电脑上的使用进行了优化(https://www.kidzfun.art/)

解决方案

从具有多个封闭区域的图像开始,每个封闭区域在这些区域内具有统一的颜色。在此示例中,我们将使用具有四个封闭区域的图像,编号为 1 到 4。

现在创建一个 web worker,它是在浏览器线程的单独线程上运行的 JavaScript,因此在处理大量数据时不会锁定用户界面。

let worker = new Worker("./src/worker.js");

worker.js文件包含执行填充算法的代码。在浏览器 UI 代码中,通过将图像绘制到 Canvas 元素并调用该getImageData函数,将图像像素发送给工作人员。请注意,您将 ImageBuffer 对象发送给工作人员,而不是 ImageData 本身

const canvas = document.getElementById('mycanvas');const context = canvas.getContext('2d');

const dimensions = { height: canvas.height, width: canvas.width };

const img = new Image();
img.onload = () => {
  context.drawImage(img, 0, 0);
  
  const imageData = 
    canvas.getImageData(0, 0, dimensions.width, dimensions.height);

  worker.postMessage({
      action: "process",
      dimensions,
      buffer: imageData.data.buffer,
    }, 
    [imageData.data.buffer]
  );
};

工作脚本然后异步检查图像中的每个像素。它首先将每个像素的 alpha(透明度)值设置为零,这将像素标记为未处理。当它找到一个具有零 alpha 值的像素时,它会从该像素执行 FILL 操作,其中每个周围的像素都被赋予一个增量 alpha 值。也就是说,第一次执行填充时,所有周围像素的 alpha 版本都指定为 1,第二次指定的 alpha 值为 2,依此类推。

每次 FILL 完成时,工作人员都会存储 FILL 使用的区域的独立图像(存储为数字数组)。当它检查了源图像中的所有像素后,它会将它计算出的所有单个图像“掩码”以及所有 alpha 值设置在 1 到 255 之间的数字的单个图像发送回 UI 线程。这意味着使用这种方法,我们最多可以支持 255 个不同的区域进行即时填充,这应该没问题,因为如果给定像素没有经过预处理,我们可以回退到慢速填充。

您在上面完全处理的图像中看到源图像中的所有像素都分配了一个 alpha 值。数值对应于掩码之一,如下所示。

对于这张图片,它会生成四个蒙版,如上图所示。红色区域是具有非零 alpha 值的像素,白色区域是具有 alpha 值零的像素。

当用户单击 HTML Canvas 节点的像素时,UI 代码会检查从 worker 返回的图像中的 alpha 值。如果值为 2,它会选择它收到的掩码数组中的第二项。

现在是时候通过属性使用一些 HTML Canvas 魔法了globalCompositeOperation。此属性允许使用 Canvas 执行各种有趣和有趣的操作,但出于我们的目的,我们对其值感兴趣source-in。这使得调用fillRect()Canvas 上下文只会填充非透明像素,而其他像素保持不变。

const pixelMaskContext = pixelMaskCanvasNode.getContext('2d');
const pixelMaskImageData = new ImageData(
  pixelMaskInfo.width,
  pixelMaskInfo.height
);

pixelMaskImageData.data.set(
  new Uint8ClampedArray(pixelMaskInfo.pixels)
);

pixelMaskContext.putImageData(pixelMaskImageData, 0, 0);

// Here's the canvas magic that makes it just draw the non
// transparent pixels onto our main canvas
pixelMaskContext.globalCompositeOperation = "source-in";
pixelMaskContext.fillStyle = colour;

pixelMaskContext.fillRect(
  0, 0, pixelMaskInfo.width, pixelMaskInfo.height
);

现在您已经用一种颜色填充了蒙版,在本例中为紫色,然后您只需将其绘制到画布上用户可见的蒙版左上角位置,即可完成!

context.drawImage(
  pixelMaskCanvasNode,
  pixelMaskInfo.x,
  pixelMaskInfo.y
);

完成后应该如下图所示

需要注意的是,如果您通过打开文件在本地计算机上尝试此代码index.html,它将不起作用,因为浏览器安全性不会让 Worker 注册。您需要运行本地主机服务器并从那里运行它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值