banner
AndrewTsui

AndrewTsui

JavaScript 中操作 Canvas 添加噪点

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

{
  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 上。

第二部分:

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);
  }
});

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

第三部分:

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 相关的方法进行重定义和代理处理。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。