Loading

网页水印实现

说明文档

1 水印位置

  • 从sight-info.html点击公告信息中WaterMark访问,或者直接打开/html/watermark.html。调用的js代码均在/js/watermark.js中。

  • 网页截图:

2 可见水印实现

  • 思路:

    1. 图片路径转成canvas
    2. canvas添加水印
    3. canvas转成img
  • 步骤说明和关键代码说明:

    async function run_visible() {
        // 1.图片路径转成canvas
        const tempCanvas = await imgToCanvas(imgUrl);
        // 2.canvas添加水印
        const canvas = addWatermark(tempCanvas, "这是一个水印");
        // 3.canvas转成img
        const url = convasToImg(canvas);
        // 查看效果
        document.getElementById("watermark_visible").src = url;
    }
    

    其中imgToCanvas方法:

    // 创建canvas DOM元素,并设置其宽高和图片一样
    const canvas = document.createElement("canvas");
    canvas.width = img.width;
    canvas.height = img.height;
    // 坐标(0,0) 表示从此处开始绘制,相当于偏移。
    canvas.getContext("2d").drawImage(img, 0, 0);
    

    其中addWaterMark在tempCanvas基础上添加水印文字,并设定样式:

    const angle = -30; 
    const ctx = canvas.getContext("2d");
    ctx.fillStyle = "blue";
    ctx.textBaseline = "middle";
    ctx.font = `100px serif`;
    ctx.globalAlpha = 0.6;
    
    ctx.rotate(Math.PI / 180 * angle);
    ctx.fillText(text, -160, 500);
    

    其中convasToImg方法:

    // canvas.toDataURL 返回的是一串Base64编码的URL,指定格式 PNG
    image.src = canvas.toDataURL("image/png");
    

3 不可见数字水印实现

  • 思路:

    • 图片都是有一个个像素点构成的,每个像素点都是由 RGB 三种元素构成。
    • RGB 分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。
    • 因此,当我们把其中的一个分量修改,即使是设计师也很难分辨察觉。
  • 步骤说明和关键代码说明:

    先获取要加入到图片中的文字的像素信息,这里用 canvas 在画布上打印文字,获取像素信息。

    var ctx = document.getElementById('canvas_invi').getContext('2d');
    ctx.font = `40px serif`;
    ctx.fillStyle = "blue";
    ctx.textBaseline = "middle";
    ctx.fillText(text, 10, 130);
    
    var textData;
    textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
    

    然后提取加密信息在待加密的图片上进行处理。也就是,接受要隐藏的数据以及隐藏的颜色通道,然后对原图进行操作,修改图片该通道分量的最低位,如果有文字信息,则最低位置为 1,否则为 0:

    var mergeData = function(ctx, newData, color, originalData) {
        var oData = originalData.data;
        var bit, offset; // offset的作用是找到alpha通道值
    
        switch (color) {
            case 'R':
                bit = 0;
                offset = 3;
                break;
            case 'G':
                bit = 1;
                offset = 2;
                break;
            case 'B':
                bit = 2;
                offset = 1;
                break;
        }
    
        for (var i = 0; i < oData.length; i++) {
            if (i % 4 == bit) {
                // 只处理目标通道
                if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {
                    // 没有信息的像素,该通道最低位置0,但不要越界
                    if (oData[i] === 255) {
                        oData[i]--;
                    } else {
                        oData[i]++;
                    }
                } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {
                    //有信息的像素,该通道最低位置1
                    oData[i]++;
                }
            }
        }
        ctx.putImageData(originalData, 0, 0);
    }
    

    最后在 img.onload 调用 processData(ctx, originalData):

    img.onload = function() {
        // 获取指定区域的canvas像素信息
        ctx.drawImage(img, 0, 0, 256, 256);
        originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
        mergeData(ctx, textData, 'R', originalData)
    };
    
posted @ 2020-12-17 12:05  iterationjia  阅读(94)  评论(0编辑  收藏  举报