The method of adding dark watermark to the page at the front end

The method of adding dark watermark to the page at the front end

The last article mentioned the method of adding a clear watermark on the page, but the clear watermark is easier to remove, and for some unprocessed pictures, there is no watermark when the user saves them directly. At this time, the problem of information leakage still exists. In order to solve this problem, we need to use dark watermark.

Realization idea

We know that the picture is composed of multiple pixels. Through the getImageData method of canvas, we can get the pixel data of the specified rectangle on the canvas

The getImageData() method returns an ImageData object that copies the pixel data of the specified rectangle on the canvas.

For each pixel in the ImageData object, there are four aspects of information, namely RGBA value:

  • R - red (0-255)
  • G - green (0-255)
  • B - Blue (0-255)
  • A - alpha channel (0-255; 0 is transparent and 255 is fully visible)

It is worth noting that small changes in RGB component values are indistinguishable to the naked eye and do not affect the recognition of pictures. This is the cornerstone of adding dark watermark to the picture

color/alpha exists as an array and is stored in the data attribute of the ImageData object.

The following code can obtain the color/alpha information of the first pixel in the returned ImageData object:

red=imgData.data[0];
green=imgData.data[1];
blue=imgData.data[2];
alpha=imgData.data[3];

Interested students can print to see the effect

function createBackgroundImage(content, proportion, tiltAngle) {
   const can = document.createElement('canvas')
   can.width = document.body.clientWidth / proportion
   can.height = document.body.clientHeight / proportion
   const context = can.getContext('2d')
   context.rotate(-25 * Math.PI / 180);
   context.font = "800 30px Microsoft JhengHei";
   context.fillStyle = "#000";
   context.textAlign = 'center';
   context.textBaseline = 'Middle';
   context.fillText(content, 100, 100)
   console.log(context.getImageData(0, 0, can.width, can.height))
   return can.toDataURL("image/png")
}
const div = document.getElementById('content')
    console.log('div', div)
    div.style.backgroundImage = `url(${createBackgroundImage('Boyo', 6, 10)})`

As shown below, the ImageData result of the corresponding image is given, which has the following properties

  1. data: Uint8ClampedArray(52752) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]
  2. colorSpace: "srgb"
  3. height: 42
  4. width: 314
  5. [[Prototype]]: ImageData

Uint8clapedarray is the corresponding pixel point. Every four represent one pixel point, and then they are arranged from left to right and from top to bottom.

Here is a simple encryption: put the encrypted pattern into the original image. If the corresponding overlapping pixels in the original image have content, the lowest bit is 1, otherwise it is 0

function mergeData(rawImageSrc, watermarkImageSrc) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.onload = function () {
      const myCanvas = document.createElement("canvas");
      myCanvas.width = img.width;
      myCanvas.height = img.height;
      const ctx = myCanvas.getContext("2d")
      const bit = 0
      const offset = 3
      const oImageData = getImageData(rawImageSrc)
      const oData = oImageData.data
      const newData = getImageData(watermarkImageSrc).data
      for (let i = 0; i < oData.length; i++) {
        if (i % 4 === bit) {
          // Modify target channel only
          if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {
            // For pixels without information, change the odd pixels of the target channel to even pixels
            if (oData[i] === 255) {
              oData[i]--
            } else {
              oData[i]++
            }
          } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {
            // Pixels with information
            oData[i]++
          }
        }
      }
        ctx.putImageData(oImageData, 0, 0)
        resolve(myCanvas.toDataURL("image/png"))
     }
       img.src = rawImageSrc
    })
  function getImageData(image) {
    const img = new Image()
    img.src = image
    const myCanvas = document.createElement("canvas");
    myCanvas.width = img.width;
    myCanvas.height = img.height;
    const myContext = myCanvas.getContext("2d")
    myContext.drawImage(img, 0, 0);
    return myContext.getImageData(0, 0, myCanvas.width, myCanvas.height)
  }

Corresponding decryption method

function decrypt(watermarkImage) {
 return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function () {
​
          const myCanvas = document.createElement("canvas");
          myCanvas.width = img.width;
          myCanvas.height = img.height;
          const ctx = myCanvas.getContext("2d")
​
​
          const imageData = getImageData(watermarkImage)
          var data = imageData.data;
          for (var i = 0; i < data.length; i++) {
            if (i % 4 == 0) {
              // Red component
              if (data[i] % 2 == 0) {
                data[i] = 0;
              } else {
                data[i] = 255;
              }
            } else if (i % 4 == 3) {
              // alpha channel is not processed
              continue;
            } else {
              // Turning off other components will not affect the answer, even more beautiful o(^ ▽ ^) O
              data[i] = 0;
            }
          }
​
          ctx.putImageData(imageData, 0, 0)
          resolve(myCanvas.toDataURL("image/png"))
        }
​
        img.src = watermarkImage
      })
}

Core sample code

    const image1 = createBackgroundImage('Boyo', 3, 10)
    const image2 = createBackgroundImage('learn', 3, 10)
    mergeData(image1, image2).then(res => {
      console.log('res', res)
      decrypt(image2).then(res => {
        console.log('finalImage', res)
      })
    })

Show the above res and finallImage and compare them. Daxian, one is two words: boyue, and the other is only one word: learn

Admittedly, the above method is not universal. After all, the encryption and decryption methods are written as fixed, but the idea is unified, that is, the pixels are modified on the basis of the original image.

About more

See the reference articles for details

1,https://cloud.tencent.com/developer/article/1841652

2,https://www.cnblogs.com/deeproom/p/14212568.html

3,https://blog.csdn.net/qq_44197554/article/details/122648423

Added by cigardude on Mon, 14 Feb 2022 02:41:02 +0200