JavaScript 中操作 Canvas 添加噪点

最近在研究爬虫中验证码及 Canvas 相关的东西，简单记录一下 code snippets

Copy
{
const getImageData = CanvasRenderingContext2D.prototype.getImageData;
//
const noisify = function (canvas, context) {
if (context) {
const shift = {
'r': Math.floor(Math.random() * 10) - 5,
'g': Math.floor(Math.random() * 10) - 5,
'b': Math.floor(Math.random() * 10) - 5,
'a': Math.floor(Math.random() * 10) - 5
};
//
const width = canvas.width;
const height = canvas.height;
//
if (width && height) {
const imageData = getImageData.apply(context, [0, 0, width, height]);
//
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const n = ((i * (width * 4)) + (j * 4));
imageData.data[n + 0] = imageData.data[n + 0] + shift.r;
imageData.data[n + 1] = imageData.data[n + 1] + shift.g;
imageData.data[n + 2] = imageData.data[n + 2] + shift.b;
imageData.data[n + 3] = imageData.data[n + 3] + shift.a;
}
}
//
window.top.postMessage("canvas-defender-alert", '*');
context.putImageData(imageData, 0, 0);
}
}
};
//
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
//
return Reflect.apply(target, self, args);
}
});
//
CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
//
return Reflect.apply(target, self, args);
}
});
}

{
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');
//
window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();
//
if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}
//
if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);
}

这段 JavaScript 代码是用于对 Canvas 元素进行处理的。下面是对代码的解释：

第一部分：
const getImageData = CanvasRenderingContext2D.prototype.getImageData;

这行代码创建了一个名为 getImageData 的变量，并将 CanvasRenderingContext2D.prototype.getImageData 的引用赋值给它。这意味着在后续的代码中，可以通过 getImageData 变量来调用 CanvasRenderingContext2D 原型上的 getImageData 方法。

然后是一个名为 noisify 的函数，该函数用于给 Canvas 添加噪点效果。接下来的代码是对 noisify 函数的定义和实现。

在 noisify 函数内部，存在以下逻辑：

首先，通过传入的参数 canvas 和 context 进行判断。

然后，生成一个包含随机数的 shift 对象，用于控制噪点的颜色偏移量。

接着，获取传入的 Canvas 的宽度和高度。

如果宽度和高度都存在，则使用 getImageData 方法获取 Canvas 上的像素数据，并存储在 imageData 变量中。

然后，使用嵌套的循环遍历每个像素，并根据 shift 对象中的随机数对像素的颜色值进行偏移。

在处理完所有像素后，发送一条消息给顶层窗口以触发 "canvas-defender-alert" 事件。

最后，使用 context.putImageData(imageData, 0, 0) 将经过处理的像素数据重新绘制到 Canvas 上。

第二部分：

Copy
HTMLCanvasElement.prototype.toBlob = new Proxy(HTMLCanvasElement.prototype.toBlob, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});

HTMLCanvasElement.prototype.toDataURL = new Proxy(HTMLCanvasElement.prototype.toDataURL, {
apply(target, self, args) {
noisify(self, self.getContext("2d"));
return Reflect.apply(target, self, args);
}
});

CanvasRenderingContext2D.prototype.getImageData = new Proxy(CanvasRenderingContext2D.prototype.getImageData, {
apply(target, self, args) {
noisify(self.canvas, self);
return Reflect.apply(target, self, args);
}
});

这部分代码重新定义了三个方法：toBlob、toDataURL 和 getImageData，并使用 Proxy 进行了代理处理。通过对这些方法进行代理，可以在调用它们时自动调用 noisify 函数给 Canvas 添加噪点效果。

第三部分：

Copy
const mkey = "canvas-defender-sandboxed-frame";
document.documentElement.setAttribute(mkey, '');

window.addEventListener("message", function (e) {
if (e.data && e.data === mkey) {
e.preventDefault();
e.stopPropagation();

if (e.source) {
if (e.source.CanvasRenderingContext2D) {
e.source.CanvasRenderingContext2D.prototype.getImageData = CanvasRenderingContext2D.prototype.getImageData;
}

if (e.source.HTMLCanvasElement) {
e.source.HTMLCanvasElement.prototype.toBlob = HTMLCanvasElement.prototype.toBlob;
e.source.HTMLCanvasElement.prototype.toDataURL = HTMLCanvasElement.prototype.toDataURL;
}
}
}
}, false);

这部分代码定义了一个用于监听消息的事件监听器。当收到特定消息 mkey 时，会执行一系列操作。具体而言：

首先，将自定义属性 mkey 添加给页面的根元素（document.documentElement）。

然后，注册一个 "message" 事件的监听器，并通过条件判断来处理接收到的消息。

如果消息是等于 mkey 的，则阻止默认行为和事件传播。

接着，通过 e.source 来访问消息来源的窗口对象，并检查其是否具有 CanvasRenderingContext2D 和 HTMLCanvasElement 这两个原型上的方法。

如果是，将相应的方法重新定义为之前代理过的方法，从而保证对这些方法的调用会经过 noisify 处理。

总而言之，该 JavaScript 代码实现了给 Canvas 元素添加噪点效果的功能，并在特定条件下对 Canvas 相关的方法进行重定义和代理处理。