canvas像素操作简介
Canvas提供像素操作是为了允许开发者对图像进行精细和灵活的控制,支持各种复杂的图像处理和操作
- Canvas的像素操作允许我们利用JavaScript代码直接访问并修改HTML5 Canvas元素内的像素数据。
- 在Canvas中,每个像素都有一个对应的坐标位置,可以通过像素的坐标来获取或修改像素的颜色值。通过Canvas的像素操作,可以实现对图像进行像素级别的处理、修改和绘制。
颜色模型
颜色模型,是用来表示颜色的数学模型。一般的颜色模型,可以按照如下分类:
- 面向硬件设备的颜色模型:RGB、CMYK、YCrCb;
- 面向视觉感知的颜色模型:HSL、HSV(B)、HSI、Lab;
浏览器支持 RGB 和 HSL 两种颜色模型:
-
-
RGB 模型
- RGB 模型的颜色由红(Red)、绿(Green)、蓝(Blue)三个颜色通道的不同亮度和组合来表示。每个通道的取值范围是 0-255,通过调整这三个通道的亮度可以得到不同的颜色。
- RGB 模型 是一种加色混色模型,在叠加混合的过程中,亮度等于色彩亮度的综合,混合的越多亮度就越高,三色通道中每个颜色有 256 阶的亮度,为 0 时最暗,255 时最亮。
-
像素和分辨率
-
-
-
- 位图与像素点
-
-
-
-
- 位图放大后,会看到一个个小格子,每个格子为 1*1 的像素点。
- 一张图有多少个像素点,与图片的分辨率有关,即常说的图片宽高尺寸(图像的自然尺寸 natureWidth、natureHeight),比如一张图的宽是 1920、高是 1080,则它拥有 1920 * 1080 = 2073600 个像素点。
- 1920 * 1080就是图像的分辨率。
-
-
-
-
- 像素的颜色模型
-
-
-
-
- 像素作为位图的最基本单位,会通过颜色模型来描述,最常见的即是 RGB,加上透明通道是 RGBA。
- RGBA 共四个通道分量,每个分量使用 1 个字节来表示。
- 每个字节有 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:图像高度
- array:
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)
})
}
像素操作的方法
-
-
CanvasRenderingContext2D.getImageData()返回一个ImageData对象,用来描述 canvas 区域隐含的像素数据,这个区域通过矩形表示,起始点为*(sx, sy)、宽为sw、高为sh。*
-
-
-
CanvasRenderingContext2D.putImageData()是 Canvas 2D API 将数据从已有的ImageData对象绘制到位图的方法。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。此方法不受画布转换矩阵的影响。
-
-
createImageData-创建新的ImageData对象:
-
CanvasRenderingContext2D.createImageData()是 Canvas 2D API 创建一个新的、空白的、指定大小的 ImageData 对象。所有的像素在新对象中都是透明的。
-
-
修改像素数据:
- 可以通过修改ImageData对象的data属性来修改像素数据。ImageData对象的data属性是一个Uint8ClampedArray类型的数组,每四个元素表示一个像素的RGBA值。可以通过修改数组中的相应元素来改变像素的颜色。
- 可以使用context.createImageData(width, height)方法创建一个新的ImageData对象。其中,width和height表示图像的宽度和高度。可以通过修改新创建的ImageData对象的data属性来修改像素数据。
像素操作应用
图像滤镜
对图像数据应用简单的数学运算,实现一些滤镜效果.
通过控制每个像素4个数据的值,即可达到简单滤镜的效果。但是复杂的滤镜比如边缘检测,就需要用到卷积运算来实现。
-
- 灰度滤镜:计算出图片每个像素点的灰度值
- 黑白滤镜:图片中没有灰色,只有黑与白,灰度值大于255/2,则显示为黑色,否则为白色
- 反色滤镜:图片每个像素点的颜色变为原来颜色的255-原
- 模糊滤镜:计算一个像素点及其周围像素点的平均值
- 马赛克滤镜:和模糊滤镜类似,只是马赛克滤镜是计算每一块的平均值,在这里,需要计算图片的宽与高来确定方块的边长
图像二值化
用户通过抠图操作以后,得到图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 线程负担了,主线程就会很流畅,使主线程更加专注于页面渲染和交互,不会被阻塞或拖慢。