putImageData
一、认识
putImageData()
是 Canvas 2D API
将数据从已有的 ImageData
对象绘制到位图的方法。如果提供了一个绘制过的矩形,则只绘制该矩形的像素。此方法不受画布转换矩阵的影响。
二、语法
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.putImageData(imagedata, dx, dy);
ctx.putImageData(imagedata, dx, dy, dirtyX, dirtyY, dirtyWidth, dirtyHeight);
-
imageData
:ImageData
,包含像素值的数组对象。 -
dx
: 源图像数据在目标画布中的位置偏移量(x 轴方向的偏移量)。 -
dy
: 源图像数据在目标画布中的位置偏移量(y 轴方向的偏移量)。 -
dirtyX
可选: 在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(x 坐标)。 -
dirtyY
可选: 在源图像数据中,矩形区域左上角的位置。默认是整个图像数据的左上角(y 坐标)。 -
dirtyWidth
可选: 在源图像数据中,矩形区域的宽度。默认是图像数据的宽度。 -
dirtyHeight
可选: 在源图像数据中,矩形区域的高度。默认是图像数据的高度。
三、场景
3.1 写入像素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.putImageData(myImageData, 0, 0);
3.2 像素灰度
灰度公式: 用红绿和蓝的平均值或者 x = 0.299r + 0.587g + 0.114b
-
R
通道data[i]
:data[i] = (data[i] + data[i+1] + data[i+2]) / 3
或者0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2]
-
G
通道data[i+1]
:data[i+1] = (data[i] + data[i+1] + data[i+2]) / 3
或者0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2]
-
B
通道data[i+2]
:data[i+2] = (data[i] + data[i+1] + data[i+2]) / 3
或者0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2]
灰度实现:
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
function gray(params) {
const { ctx, imageData } = params || {};
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const gray = 0.299 * r + 0.578 * g + 0.114 * b;
data[i] = gray;
data[i + 1] = gray;
data[i + 2] = gray;
}
ctx.putImageData(imageData, 0, 0);
}
img.onload = function () {
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
ctx.drawImage(this, 0, 0);
const originImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
gray({ ctx, imageData: originImageData });
};
img.src = "../../images/41cd22ab-9fee-45d4-92a0-722d9ee7fd29-1.png";
document.body.appendChild(canvas);
3.3 像素反相
反相公式: 每个通道减掉颜色的最大色值 255
-
R
通道data[i]
:data[i] = 255 - data[i]
-
G
通道data[i+1]
:data[i+1] = 255 - data[i+1]
-
B
通道data[i+2]
:data[i+2] = 255 - data[i+2]
反相实现
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
function invert(params) {
const { ctx, imageData } = params || {};
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
ctx.putImageData(imageData, 0, 0);
}
img.onload = function () {
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
ctx.drawImage(this, 0, 0);
const originImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
invert({ ctx, imageData: originImageData });
};
img.src = "../../images/41cd22ab-9fee-45d4-92a0-722d9ee7fd29-1.png";
document.body.appendChild(canvas);
3.4 基于 R 通道的奇偶加密规则实现像素合并与分离
通过 R
通道的奇偶加密规则实现像素合并与分离思路如下:
-
imageData1
在imageData2
中有像素点: 将R
偶数的通道+1
变为奇数 -
imageData1
在imageData2
中没有像素点: 将R
奇数的通道+1
变为偶数
基于 R
通道的像素分离: 基于合并规则, imageData1
的 R
通道像素点在 imageData2
中为奇数, 因此:
-
imageData2
的R
通道像素点为奇数: 说明在imageData1
中有像素点, 将R
通道填充为255
-
imageData2
的R
通道像素点为偶数: 说明在imageData1
中没有像素点, 将R
通道关闭,更改为0
-
imageData2
中的G
通道和B
通道为了达到分离效果,可以将其关闭,更改为0
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
function mergeImageData(params) {
const { ctx, imageData1, imageData2 } = params;
const data1 = imageData1.data;
const data2 = imageData2.data;
for (let i = 0; i < data2.length; i += 4) {
if (data1[i + 3] === 0 && data2[i] % 2 === 1) {
if (data2[i] === 255) {
data2[i]--;
} else {
data2[i]++;
}
} else if (data1[i + 3] !== 0 && data2[i] % 2 === 0) {
if (data2[i] === 255) {
data2[i]--;
} else {
data2[i]++;
}
}
}
ctx.putImageData(imageData2, 0, 0);
}
function decompose(params) {
const { ctx, imageData } = params;
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
if (data[i] % 2 == 0) {
data[i] = 0;
} else {
data[i] = 255;
}
data[i + 1] = 0; // 关闭其他分量, 不处理的话也可以, 会显示原图
data[i + 2] = 0; // 关闭其他分量, 不处理的话也可以, 会显示原图
}
ctx.putImageData(imageData, 0, 0);
}
img.onload = function () {
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
const text = "Hello World";
ctx.font = "30px Microsoft Yahei";
ctx.fillText(text, 60, 130);
const textImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
ctx.drawImage(this, 0, 0);
const imgImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
mergeImageData({
ctx,
imageData1: textImageData,
imageData2: imgImageData,
});
// 此时的 imgImageData 数据就是合并后的数据, 可以将此时的 Canvas 插入到 Body 或者 导出图片查看
decompose({ ctx, imageData: imgImageData });
// 此时的 imgImageData 数据就是分离后的数据, 可以将此时的 Canvas 插入到 Body 或者 导出图片查看
};
img.src = "../../images/41cd22ab-9fee-45d4-92a0-722d9ee7fd29-1.png";
document.body.appendChild(canvas);