前端给页面添加暗水印的办法

举报
伯约同学 发表于 2022/02/13 22:31:49 2022/02/13
【摘要】 前端给页面添加暗水印的办法上一篇文章讲到了在页面上添加明水印的方法,但是明水印比较好清除,而且对于一些没做处理的图片,当用户直接保存的时候,是没有水印的,这时候信息泄露问题依然存在。为了解决这样的问题,我们需要用到暗水印。实现思路我们知道图片是由多个像素点组成的,通过canvas的getImageData方法,我们可以得到画布指定矩形的像素数据getImageData() 方法返回 Imag...

前端给页面添加暗水印的办法

上一篇文章讲到了在页面上添加明水印的方法,但是明水印比较好清除,而且对于一些没做处理的图片,当用户直接保存的时候,是没有水印的,这时候信息泄露问题依然存在。为了解决这样的问题,我们需要用到暗水印。

实现思路

我们知道图片是由多个像素点组成的,通过canvas的getImageData方法,我们可以得到画布指定矩形的像素数据

getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。

对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:

  • R - 红色 (0-255)

  • G - 绿色 (0-255)

  • B - 蓝色 (0-255)

  • A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)

值得注意的是:RGB 分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。这是我们在图片上添加暗水印的基石

color/alpha 以数组形式存在,并存储于 ImageData 对象的data属性中。

以下代码可获得被返回的 ImageData 对象中第一个像素的 color/alpha 信息:

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

感兴趣的同学可以打印看一下效果

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('伯约同学', 6, 10)})`

如下所示,给出了对应图片的ImageData结果,它有以下几个属性

  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

Uint8ClampedArray则是对应的像素点,每四个表征一个像素点,然后从左往右,从上往下的顺序进行排列。

下面进行一个简单的加密:把加密图案放到原图中,如果原图中对应的重叠像素点有内容,则最低位为1,否则为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) {
          // 只修改目标通道
          if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {
            // 没有信息的像素,将目标通道的奇数像素改为偶数
            if (oData[i] === 255) {
              oData[i]--
            } else {
              oData[i]++
            }
          } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {
            // 有信息的像素
            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)
  }

对应的解密方法

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) {
              // 红色分量
              if (data[i] % 2 == 0) {
                data[i] = 0;
              } else {
                data[i] = 255;
              }
            } else if (i % 4 == 3) {
              // alpha通道不做处理
              continue;
            } else {
              // 关闭其他分量,不关闭也不影响答案,甚至更美观 o(^▽^)o
              data[i] = 0;
            }
          }
​
          ctx.putImageData(imageData, 0, 0)
          resolve(myCanvas.toDataURL("image/png"))
        }
​
        img.src = watermarkImage
      })
}

核心示例代码

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

展示上面的res和finallImage并进行对比大仙,一张是两个字:伯约 另一张只有一个字:学

诚然,上面这个方法并不普适,毕竟加密解密方法都写成固定的了,不过思路是统一的,那就是都在原图的基础上修改像素点。

关于更多内容

具体可以看一下参考文章

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。