canvas操纵像素
你如果认为 canvas 只是画图工具,那接下来的操作会颠覆你的认知。 canvas 提供 api 可以获取画布上任何一个像素,并可以自由的操作他们。
获取像素
直接访问像素的功能由 canvas 上下文中的 ImageData 对象提供,它提供了以下一组方法,都会返回 ImageData 对象。
- getImageData() 接受 x 轴坐标、y 轴坐标、宽度、高度四个参数,获取画布上这个矩形区域的像素数据;
- createImageData() 可凭空创建指定宽高的矩形区域,初始是黑色,也可以输入一个 ImageData 对象用于创建一个同样大小的区域,但注意不会复制像素数据。
context.getImageData(x, y, width, height);
context.createImageData(width, height);
context.createImageData(anothorImageData);
获取到的 ImageData 对象中 data 属性是一个一维数组,乍看乱糟糟的,但细看你会发现其实这就是 RGBA 的颜色数据,也就是数组中每个四位就是一个像素的颜色数据,这里注意一下透明度 A 也是 0~255,不是 CSS 里简化过的 0~1。
举个例子
现在假定在一个纯红色区域取一块2*2
的矩形,我们得到的像素数据是:
let pixels = [255, 0, 0, 255, 255, 0, 0, 255,
255, 0, 0, 255, 255, 0, 0, 255]
他们与图像的对应关系是从左到右,从上到下,大概就像上面代码格式化这样,如图所示:
根据 4 对 1 的对应关系,我们很容易就能写出遍历的办法,offset 就相当于指针,每次移动 4 位,代码如下:
for (let offset = 0, len = pixels.length; offset < len; offset += 4) {
r = pixels[offset];
g = pixels[offset + 1];
b = pixels[offset + 2];
a = pixels[offset + 3];
}
当需要访问特定坐标的像素时,可以使用如下公式,其中 xpos 是像素点在该区域的 x 坐标;ypos 是像素点在该区域的 y 坐标,imagedata.width 是指区域横向有多少像素。
let offset = (xpos + ypos * imagedata.width) * 4;
let r = pixels[offset];
let g = pixels[offset + 1];
let b = pixels[offset + 2];
let a = pixels[offset + 3];
绘制像素
可以将修改过的 ImageData 对象重新用上下文的 putImageData() 方法绘制到指定区域,该方法接受三个参数:ImageData 对象、x 轴坐标、y 轴坐标。绘制指定的位置绘制 ImageData 对象的内容。直接看下面例子的核心代码。
先绘制铺满画布的色块,点击按钮触发 change 事件处理器可改变颜色,过程见注释。
完整例子:演示反色变化
【PS】对 js 了解不深的朋友可能会有疑问,遍历过程操作的是 pixels,imageData 怎么会改变呢?
这是因为 js 中对象都是地址传递的特点,也就是 pixels = imageData.data 操作只是将 pixels 变量的指向到 imageData.data 所指向的内存空间,所以操作 pixels 就是操作 imageData.data。
window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 绘制色块,每个色块宽10像素,高等于画布高,铺满画布
for (let i = 0; i < canvas.width; i += 10) {
context.fillStyle = (i % 20 === 0) ? '#f00' : ((i % 30 === 0) ? '#0f0' : '#00f');
context.fillRect(i, 0, 10, canvas.height);
}
};
function change() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 获取整个画布的ImageData对象
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// 取出颜色数据
const pixels = imageData.data;
// 遍历颜色数据求每个颜色的反色
for (let offset = 0, len = pixels.length; offset < len; offset += 4) {
pixels[offset] = 255 - pixels[offset];
pixels[offset + 1] = 255 - pixels[offset + 1];
pixels[offset + 2] = 255 - pixels[offset + 2];
// 这里没有操作透明度
}
// 将ImageData重新绘制到画布上
context.putImageData(imageData, 0, 0);
}
更多有趣的例子
canvas 强大的像素操作可以给我们带来更多的可能,也许你会想开始做一个网页版的美图工具了吧(笑)。
这里还有一些有趣的 demo 可以玩玩:
综合案例
有关颜色的番外部分到这里就基本完结了,最后有个综合题,会应用这些技术。
将系列第二篇中的鼠标画图工具改造成鼠标喷漆工具,这里建议自己动手实践一下。
下面例子基本思路就是取得画布像素数据,每当鼠标点下并移动(执行 onMouseMove)就随机改变鼠标周围一定范围的像素点的颜色。
完整案例:鼠标喷漆工具
window.onload = function () {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
// 获得整个画布区域的ImageData对象
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// 取出像素数据
const pixels = imageData.data;
// 设定笔刷大小
const brush_size = 25;
// 设定笔刷密度
const brush_density = 80;
// 笔刷的颜色变量
let brush_color;
function onMouseMove() {
// 根据设定的笔刷密度生成随机像素点
for (let i = 0; i < brush_density; i++) {
// 随机像素点角度相对于鼠标的角度
const angle = Math.random() * Math.PI * 2;
// 根据设定的笔刷大小,随机像素点以鼠标为圆心的半径
const radius = Math.random() * brush_size;
// 计算出像素点的x轴相对坐标
const xpos = (mouse.x + Math.cos(angle) * radius) | 0;
// 计算出像素点的y轴相对坐标
const ypos = (mouse.y + Math.sin(angle) * radius) | 0;
// 算出该像素点在pixels中的偏移量
const offset = (xpos + ypos * imageData.width) * 4;
// 对这个像素点的颜色数据进行操作,将颜色分解成三基色
pixels[offset] = brush_color >> 16 & 0xff;
pixels[offset + 1] = brush_color >> 8 & 0xff;
pixels[offset + 2] = brush_color & 0xff;
pixels[offset + 3] = 255;
}
// 重新绘制区域
context.putImageData(imageData, 0, 0);
}
canvas.addEventListener('mousedown', () => {
// 随机一个颜色
brush_color = utils.parseColor(Math.random() * 0xffffff, true);
canvas.addEventListener('mousemove', onMouseMove, false);
}, false);
canvas.addEventListener('mouseup', () => {
canvas.removeEventListener('mousemove', onMouseMove, false);
}, false);
};
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。