像素操作 1030+
到目前为止,我们尚未深入了解 canvas 画布真实像素的原理,事实上,你可以直接通过 ImageData 对象操纵像素数据,直接读取或将数据数组写入该对象中。
ImageData 对象
在快应用中 ImageData 对象是一个普通对象,其中存储着 canvas 对象真实的像素数据,它包含以下几个属性
- width 使用像素描述 ImageData 的实际宽度
- height 使用像素描述 ImageData 的实际高度
- data Uint8ClampedArray 类型,描述了一个一维数组,包含以 RGBA 顺序的数据,数据使用 0 至 255(包含)的整数表示data 属性返回一个 Uint8ClampedArray,它可以被使用作为查看初始像素数据。每个像素用 4 个 1 bytes 值(按照红,绿,蓝和透明值的顺序; 这就是 "RGBA" 格式) 来代表。每个颜色值部份用 0 至 255 来代表。每个部份被分配到一个在数组内连续的索引,左上角像素的红色部份在数组的索引 0 位置。像素从左到右被处理,然后往下,遍历整个数组。
Uint8ClampedArray 包含 高度 × 宽度 × 4 bytes 数据,索引值从 0 到(高度 × 宽度 × 4) - 1
例如,要读取图片中位于第 50 行,第 200 列的像素的蓝色部份,你会写以下代码:
const blueComponent = imageData.data[50 * (imageData.width * 4) + 200 * 4 + 2]
你可能用会使用 Uint8ClampedArray.length 属性来读取像素数组的大小(以 bytes 为单位):
const numBytes = imageData.data.length
创建一个 ImageData 对象
去创建一个新的,空白的 ImageData 对象,你应该会使用 createImageData() 方法。有 2 个版本的 createImageData() 方法
const myImageData = ctx.createImageData(width, height)
上面代码创建了一个新的具体特定尺寸的 ImageData 对象。所有像素被预设为透明黑。
你也可以创建一个被 anotherImageData 对象指定的相同像素的 ImageData 对象。这个新的对象像素全部被预设为透明黑。这个并非复制了图片数据。
const myImageData = ctx.createImageData(anotherImageData)
得到场景像素数据
为了获得一个包含画布场景像素数据的 ImageData 对像,你可以用 getImageData() 方法:
const myImageData = ctx.getImageData(left, top, width, height)
这个方法会返回一个 ImageData 对象,它代表了画布区域的对象数据,此画布的四个角落分别表示为(left, top),(left + width, top),(left, top + height),以及(left + width, top + height)四个点。这些坐标点被设定为画布坐标空间元素。
在场景中写入像素数据
你可以用 putImageData() 方法去对场景进行像素数据的写入。
ctx.putImageData(myImageData, dx, dy)
dx 和 dy 参数表示你希望在场景内左上角绘制的像素数据所得到的设备坐标。
例如,为了在场景内左上角绘制 myImageData 代表的图片,你可以写如下的代码:
ctx.putImageData(myImageData, 0, 0)
举例
在这个例子里,我们接着对刚才的快应用 logo 进行置灰色,我们使用 getImageData 获取 ImageData 对象,遍历所有像素以改变他们的数值。然后我们将被修改的像素数组通过 putImageData() 放回到画布中去。 grayscale 函数仅仅是用以计算红绿和蓝的平均值。你也可以用加权平均,例如 x = 0.299r + 0.587g + 0.114b 这个公式
setGray() {
const canvas = this.$element('new-canvas')
const ctx = canvas.getContext('2d')
const canvasW = 380
const canvasH = 380
// 得到场景像素数据
const imageData = ctx.getImageData(0, 0, 380, 380)
const data = imageData.data
for (let i = 0; i < data.length; i += 4) {
const avg = (data[i] + data[i + 1] + data[i + 2]) / 3
data[i] = avg; // red
data[i + 1] = avg; // green
data[i + 2] = avg; // blue
}
// 在场景中写入像素数据
ctx.putImageData(imageData, 0, 0)
}
运行效果如下