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 相關的方法進行重定義和代理處理。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。