canvas像素操作简介

355 阅读9分钟

canvas像素操作简介

Canvas提供像素操作是为了允许开发者对图像进行精细和灵活的控制,支持各种复杂的图像处理和操作

  1. Canvas的像素操作允许我们利用JavaScript代码直接访问并修改HTML5 Canvas元素内的像素数据
  2. 在Canvas中,每个像素都有一个对应的坐标位置,可以通过像素的坐标来获取或修改像素的颜色值。通过Canvas的像素操作,可以实现对图像进行像素级别的处理、修改和绘制。

颜色模型

颜色模型,是用来表示颜色的数学模型。一般的颜色模型,可以按照如下分类:

  • 面向硬件设备的颜色模型:RGB、CMYK、YCrCb;
  • 面向视觉感知的颜色模型:HSL、HSV(B)、HSI、Lab;

浏览器支持 RGB 和 HSL 两种颜色模型:

    1. RGB 模型

      1. RGB 模型的颜色由红(Red)、绿(Green)、蓝(Blue)三个颜色通道的不同亮度和组合来表示。每个通道的取值范围是 0-255,通过调整这三个通道的亮度可以得到不同的颜色。
      2. RGB 模型 是一种加色混色模型,在叠加混合的过程中,亮度等于色彩亮度的综合,混合的越多亮度就越高,三色通道中每个颜色有 256 阶的亮度,为 0 时最暗,255 时最亮。
    2. 像素和分辨率

      • 位图与像素点
        1. 位图放大后,会看到一个个小格子,每个格子为 1*1 的像素点。
        2. 一张图有多少个像素点,与图片的分辨率有关,即常说的图片宽高尺寸(图像的自然尺寸 natureWidth、natureHeight),比如一张图的宽是 1920、高是 1080,则它拥有 1920 * 1080 = 2073600 个像素点。
        3. 1920 * 1080就是图像的分辨率。
      • 像素的颜色模型
        1. 像素作为位图的最基本单位,会通过颜色模型来描述,最常见的即是 RGB,加上透明通道是 RGBA。
        2. RGBA 共四个通道分量,每个分量使用 1 个字节来表示。
        3. 每个字节有 8 个比特位,二进制取值在 00000000-11111111,即有 0-255 共 256 种取值。

Canvas中的图像数据对象

canvas 、ImageData、ImageBitmap。DOM标签 <canvas> 对应的对象和类型,用于加载图像资源和操作图像数据。

ImageData

ImageData 对象表示 canvas 元素指定区域的像素数据。前面提过,图片像素数据实际上就是一个个的颜色值,ImageData 使用 RGBA 颜色模型来表示,所以 ImageData 对象的像素数据,长度为width * height * 4

new ImageData(array, width, height);
new ImageData(width, height);
    • array:Uint8ClampedArray类型数组的实例,存储的是像素点的颜色值数据,数组元素按每个像素点的 RGBA 4通道的数值依次排列,该数组的长度必须为 width * height * 4,否则报错。如果不给定该数组参数,则会根据宽高创建一个全透明的图像。
    • width:图像宽度
    • height:图像高度

Uint8ClampedArray是8位无符号整型固定数组,属于 11 个类型化数组(TypeArray)中的一个,元素值固定在 0-255 区间。这个特点对于存储像素点的颜色值正好,RGBA 四个通道,每个通道的取值都是 0 - 255 间的数字。如[255, 0, 0, 255]表示红色、无透明。

ImageData 在 Canvas中应用
ImageData 图像数据,是基于浏览器的 Canvas 环境,应用也都是在 Canvas 操作中,常见的如创建方法 createImageData()、读取方法 getImageData()、更新方法 putImageData()

Canvas 操作 ImageData 的方法

    • createImageData():创建一个全新的空的ImageData对象,与 ImageData() 构造函数作用一样,返回像素点信息数据。
context.createImageData(width, height)
context.createImageData(imagedata)
    • getImageData():返回canvas画布中部分或全部的像素点信息数据。context.getImageData(sx, sy, sWidth, sHeight);
context.getImageData(sx, sy, sWidth, sHeight);
    • putImageData():将指定的 ImageData 对象像素点数据绘制到位图上。context.putImageData(imagedata, dx, dy [, dirtyX, [ dirtyY, [ dirtyWidth, [dirtyHeight]]]]);
context.putImageData(imagedata, dx, dy [, dirtyX, [ dirtyY, [ dirtyWidth, [dirtyHeight]]]]);

使用 ImageData 的栗子

var imageData=contexta.getImageData(0,0,canvasa.width,canvasb.height)
	//对图片的像素进行操作  imageData.data  data的属性中存储的就是图像的像素
	var pixelData=imageData.data
	for(var i=0;i<canvasb.width*canvasb.height;i++){
		//首先要获取这个像素的rgb的值
		var r=pixelData[4*i+0]  //r
		var g=pixelData[4*i+1]  //g
		var b=pixelData[4*i+2]  //b		
		//灰度的计算方法  灰色滤镜
        //x = 0.299r + 0.587g + 0.114b 这个公式可以用来计算rgb的灰度值	
		var grey=r*0.3+g*0.59+b*0.11
		//把灰度值赋值给rgb
		pixelData[4*i+0]=grey
		pixelData[4*i+1]=grey
		pixelData[4*i+2]=grey
				
	}	
	contextb.putImageData(imageData,0,0,0,0,canvasb.width,canvasb.height)

ImageBitmap

ImageBitmap 表示一个位图图像,可绘制到 canvas 中,并且具有低延迟的特性。

  • 与 ImageData 一样的是,他们都是在浏览器环境下可以访问的全局对象。
  • 与 ImageData 不一样的是,ImageBitmap 没有构造函数,可以直接引用对象(无意义),但无法通过构造函数创建,而需要借助 createImageBitmap() 进行创建。

createImageBitmap() 接受不同的图像资源,返回一个成功结果为ImageBitmap的 Promise 异步对象。

createImageBitmap(image[, options])
createImageBitmap(image, sx, sy, sw, sh[, options])

createImageBitmap 可以直接读取多种图像数据源,比如 ImageData、File、以及多种HTML元素对象等等,这个函数更加灵活的处理图像数据。在 canvas 中使用 ImageBitmap 主要使用 drawImage 函数加载位图对象:

<input id="input-file" type="file" accept="image/*" multiple />
document.getElementById('input-file').onchange = (e) => {
  const file = e.target.files[0]
  createImageBitmap(file).then(imageBitmap => {
    const canvas = document.createElement('canvas')
    canvas.width = imageBitmap.width
    canvas.height = imageBitmap.height
    const ctx = canvas.getContext('2d')
    ctx.drawImage(imageBitmap, 0, 0)
    document.body.append(canvas)
  })
}

像素操作的方法

  1. getImageData-获取像素数据

    1. CanvasRenderingContext2D.getImageData() 返回一个ImageData对象,用来描述 canvas 区域隐含的像素数据,这个区域通过矩形表示,起始点为*(sx, sy)、宽为sw、高为sh。*
  2. putImageData-绘制像素数据

    1. CanvasRenderingContext2D.putImageData() 是 Canvas 2D API 将数据从已有的ImageData对象绘制到位图的方法。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。此方法不受画布转换矩阵的影响。
  3. createImageData-创建新的ImageData对象

    1. CanvasRenderingContext2D.createImageData() 是 Canvas 2D API 创建一个新的、空白的、指定大小的 ImageData 对象。所有的像素在新对象中都是透明的。
  4. 修改像素数据:

    1. 可以通过修改ImageData对象的data属性来修改像素数据。ImageData对象的data属性是一个Uint8ClampedArray类型的数组,每四个元素表示一个像素的RGBA值。可以通过修改数组中的相应元素来改变像素的颜色。
    2. 可以使用context.createImageData(width, height)方法创建一个新的ImageData对象。其中,width和height表示图像的宽度和高度。可以通过修改新创建的ImageData对象的data属性来修改像素数据。

像素操作应用

图像滤镜

对图像数据应用简单的数学运算,实现一些滤镜效果.

通过控制每个像素4个数据的值,即可达到简单滤镜的效果。但是复杂的滤镜比如边缘检测,就需要用到卷积运算来实现。

    1. 灰度滤镜:计算出图片每个像素点的灰度值
    2. 黑白滤镜:图片中没有灰色,只有黑与白,灰度值大于255/2,则显示为黑色,否则为白色
    3. 反色滤镜:图片每个像素点的颜色变为原来颜色的255-原
    4. 模糊滤镜:计算一个像素点及其周围像素点的平均值
    5. 马赛克滤镜:和模糊滤镜类似,只是马赛克滤镜是计算每一块的平均值,在这里,需要计算图片的宽与高来确定方块的边长

图像二值化

用户通过抠图操作以后,得到图2 的结果,那么如何从抠图结果转换到 图3 的 Mask 图呢?其实也很简单,遍历所有图像所有像素,如果某个像素值的 alpha 通道值是0,那么新图中对应的像素点颜色为黑,否则为白。

像素替换

替换图片点击像素为颜色选择器的像素,同理可以实现背景替换

findIndex:点击点像素的像素在imageData一维数组中的索引i,该像素为 i~i+3 对应 rgba

图像混合

图像混合(Blend Mode),简单理解是对两张图片(source 源和destination 目标)的对应像素点的像素值进行加和运算得到新的像素值,加和的计算逻辑有很多,比如像素值直接相加,比如某些条件下取 src 的像素值,某些条件下取 dst 的像素值等等。

以srcOver模式为例子,即 抠图 叠加在 背景图之上。这里默认两张图的大小相同

          // 单个像素进行 alpha混合
            const srcOver = (src, dst) => {
                const a = dst.a + src.a - dst.a * src.a;
                const r = (src.r * src.a + dst.r * dst.a * (1 - src.a)) / a;
                const g = (src.g * src.a + dst.g * dst.a * (1 - src.a)) / a;
                const b = (src.b * src.a + dst.b * dst.a * (1 - src.a)) / a;
                return {
                    r, g, b, a
                };
            };
            // 两个像素块混合
            const composite = (srcImageData, dstImageData) => {
                const srcData = srcImageData.data;
                const dstData = dstImageData.data;
                const resultData = new Uint8ClampedArray(dstData.length);

                for (let i = 0; i < srcData.length; i += 4) {
                    const src = {r: srcData[i], g: srcData[i+1], b: srcData[i+2], a: srcData[i+3] / 255 };
                    const dst = {r: dstData[i], g: dstData[i+1], b: dstData[i+2], a: dstData[i+3] / 255 };
                    const value = srcOver(src, dst);

                    resultData[i] = value.r;
                    resultData[i+1] = value.g;
                    resultData[i+2] = value.b;
                    resultData[i+3] = value.a * 255;
                }

                const resultImageData = new ImageData(resultData, srcImageData.width, srcImageData.height);
                return resultImageData;
            }

局限性

使用 canvas 实现图像处理的功能,要在浏览器实现图像处理,占用内存资源是不可避免的。

纯 JS 实现的图像处理,当图像像素越大,占用的内存显然越大

性能问题:

  • 大尺寸画布:操作大尺寸的Canvas时,像素很多,像素级别的操作会显著降低性能,尤其是在需要频繁更新画布内容的情况下。
  • 频繁更新:如果每帧都需要对大量像素进行操作(例如在游戏或动画中),可能会导致性能瓶颈,出现卡顿和帧率下降的问题。
  • 单线程限制:JavaScript是单线程运行的,在主线程进行大量像素操作可能会阻塞UI渲染,影响用户体验。

策略:

  • 减少画布尺寸:尽量减少需要处理的画布尺寸,或者将大画布拆分为多个较小的区域分别处理。
  • 批量操作:尽量减少对getImageData和putImageData的调用次数,进行批量操作。
  • Web Workers:利用Web Workers将复杂的像素处理操作放到后台线程执行,避免阻塞主线程。
  • 优化像素计算算法、或使用一些别的专门做图像处理的库,OpenCV.js 、Sharp等
// 初始化WebGL上下文
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');

Web Worker

为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程就会很流畅,使主线程更加专注于页面渲染和交互,不会被阻塞或拖慢。