跳到主要内容

putImageData

2024年06月19日
柏拉文
越努力,越幸运

一、认识


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 通道的奇偶加密规则实现像素合并与分离思路如下:

  • imageData1imageData2 中有像素点: 将 R 偶数的通道 +1 变为奇数

  • imageData1imageData2 中没有像素点: 将 R 奇数的通道 +1 变为偶数

基于 R 通道的像素分离: 基于合并规则, imageData1R 通道像素点在 imageData2 中为奇数, 因此:

  • imageData2R 通道像素点为奇数: 说明在 imageData1 中有像素点, 将 R 通道填充为 255

  • imageData2R 通道像素点为偶数: 说明在 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);