Talk about several ways to realize watermark

ES2049
中文

Encounter problems

In our daily work, we often encounter a lot of sensitive data. In order to prevent data leakage, we have to do some "packaging" on the data. The purpose is to allow those "criminals" who deliberately leak data to give up their illegal actions under severe "pressure of public opinion" and make them "attempt to commit a crime", achieving the effect of being defeated without a fight. As we work in the security department, the concept of data security has been deeply rooted in our bones. We must pay attention to whether there is a risk of leakage in every text and every picture. How to prevent data leakage is a question we have been thinking about. For example, the watermark of a picture is a problem that is often involved in our work. Because the content of the work itself is the development of the audit platform, some risky pictures will often appear on the audit platform. Considering that the security awareness of the auditors is uneven, so in order to prevent insecure things from happening, the work of adding a watermark to the image must be done of.

analyse problem

First of all, considering the business scenario, the problem at this stage is only to worry about data leakage during the review process. For the time being, we only consider an explicit watermark, which means adding some text or other data that can distinguish your personal identity on the image. In this way, individuals can be traced based on the leaked data. Of course, the warning function of taking precautions and preventing problems is the most important.

Solve the problem

Method to realize

There are many ways to implement watermarking. According to the division of labor, the functions can be divided into front-end watermarks and back-end watermarks. The advantages of front-end watermarks can be summarized in three points. Server pressure. Second, the speed is fast, no matter which front-end implementation method, the performance is better than the back-end. Third, the implementation is simple. The biggest advantage of the back-end implementation of watermarking can also be summarized in three points, that is, security, security, and security. Zhihu, Weibo is a watermarking solution implemented at the back end. However, considering comprehensive considerations, we still adopt the front-end implementation of watermarking. The following will also briefly introduce how nodejs implements back-end image watermarking.

node implementation

Three npm packages are provided. This part is not the focus of our article, only a simple demo is provided.
1,gm https://github.com/aheckmann/gm 6.4k star

const fs = require('fs');
const gm = require('gm');


gm('/path/to/my/img.jpg')
.drawText(30, 20, "GMagick!")
.write("/path/to/drawing.png", function (err) {
  if (!err) console.log('done');
});

Need to install GraphicsMagick or ImageMagick ;
2,node-imageshttps://github.com/zhangyuanwei/node-images

var images = require("images");

images("input.jpg")                     //Load image from file 
                                        //加载图像文件
    .size(400)                          //Geometric scaling the image to 400 pixels width
                                        //等比缩放图像到400像素宽
    .draw(images("logo.png"), 10, 10)   //Drawn logo at coordinates (10,10)
                                        //在(10,10)处绘制Logo
    .save("output.jpg", {               //Save the image to a file, with the quality of 50
        quality : 50                    //保存图片到文件,图片质量为50
    });

No need to install other tools, lightweight, zhangyuanwei Chinese development, Chinese document;
3,jimphttps://github.com/oliver-moran/jimp
Can be used with gifwrap realize gif watermark;

Front end implementation

1, the background image realizes full-screen watermark
You can go to the Ali internal and external personal information page to view the effect, the principle:
image.png
Advantages: The pictures are generated on the back-end and are safe;
Disadvantages: need to initiate an http request to obtain image information;
Effect display: Because it is an internal system, it is inconvenient to show the effect.

2, dom realizes full image watermark and image watermark
Get the width and height of the picture in the onload event of the picture, generate the watermark area according to the picture size, and block it on the upper layer of the picture. The dom content is the watermark copy or other information. The implementation method is relatively simple.

const wrap = document.querySelector('#ReactApp');
const { clientWidth, clientHeight } = wrap;
const waterHeight = 120;
const waterWidth = 180;
// 计算个数
const [columns, rows] = [~~(clientWidth / waterWidth), ~~(clientHeight / waterHeight)]
for (let i = 0; i < columns; i++) {
    for (let j = 0; j <= rows; j++) {
        const waterDom = document.createElement('div');
        // 动态设置偏移值
        waterDom.setAttribute('style', `
            width: ${waterWidth}px; 
            height: ${waterHeight}px; 
            left: ${waterWidth + (i - 1) * waterWidth + 10}px;
            top: ${waterHeight + (j - 1) * waterHeight + 10}px;
            color: #000;
            position: absolute`
        );
        waterDom.innerText = '测试水印';
        wrap.appendChild(waterDom);
    }
}

Advantages: simple and easy to implement;
Disadvantages: too large or too many images will affect performance;
Show results:
image.png
3, canvas implementation method (first version implementation plan)
Method 1: Directly operate
Don't talk nonsense, just go to the code

useEffect(() => {
      // gif 图不支持
    if (src && src.includes('.gif')) {
      setShowImg(true);
    }
    image.onload = function () {
      try {
        // 太小的图不加载水印
        if (image.width < 10) {
          setIsDataError(true);
          props.setIsDataError && props.setIsDataError(true);
          return;
        }
        const canvas = canvasRef.current;
        canvas.width = image.width;
        canvas.height = image.height;
        // 设置水印
        const font = `${Math.min(Math.max(Math.floor(innerCanvas.width / 14), 14), 48)}px` || fontSize;
        innerContext.font = `${font} ${fontFamily}`;
        innerContext.textBaseline = 'hanging';
        innerContext.rotate(rotate * Math.PI / 180);
        innerContext.lineWidth = lineWidth;
        innerContext.strokeStyle = strokeStyle;
        innerContext.strokeText(text, 0, innerCanvas.height / 4 * 3);
        innerContext.fillStyle = fillStyle;
        innerContext.fillText(text, 0, innerCanvas.height / 4 * 3);
        const context = canvas.getContext('2d');
        context.drawImage(this, 0, 0);
        context.rect(0, 0, image.width || 200, image.height || 200);
           // 设置水印浮层
        const pattern = context.createPattern(innerCanvas, 'repeat');
        context.fillStyle = pattern;
        context.fill();
      } catch (err) {
        console.info(err);
        setShowImg(true);
      }
    };
    image.onerror = function () {
      setShowImg(true);
    };
  }, [src]);

Advantages: pure front-end implementation, the pictures copied by right-click are also watermarked;
Disadvantages: gif is not supported, pictures must support cross-domain;
Effect display: given below.
Method 2: Canvas generates a watermark url and assigns it to the css background property

export const getBase64Background = (props) => {
  const { nick, empId } = GlobalConfig.userInfo;
  const {
    rotate = -20,
    height = 75,
    width = 85,
    text = `${nick}-${empId}`,
    fontSize = '14px',
    lineWidth = 2,
    fontFamily = 'microsoft yahei',
    strokeStyle = 'rgba(255, 255, 255, .15)',
    fillStyle = 'rgba(0, 0, 0, 0.15)',
    position = { x: 30, y: 30 },
  } = props;
  const image = new Image();
  image.crossOrigin = 'Anonymous';
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  canvas.width = width;
  canvas.height = height;
  context.font = `${fontSize} ${fontFamily}`;
  context.lineWidth = lineWidth;
  context.rotate(rotate * Math.PI / 180);
  context.strokeStyle = strokeStyle;
  context.fillStyle = fillStyle;
  context.textAlign = 'center';
  context.textBaseline = 'hanging';
  context.strokeText(text, position.x, position.y);
  context.fillText(text, position.x, position.y);
  return canvas.toDataURL('image/png');
};

// 使用方式 
<img src="https://xxx.xxx.jpg" />
<div className="warter-mark-area" style={{ backgroundImage: `url(${getBase64Background({})})` }} />

Advantages: pure front-end implementation, support cross-domain, support git image watermark;
Disadvantages: the generated base64 url is relatively large;
Effect display: given below.
In fact, based on the implementation of these two canvases, you can easily come up with a third way, which is to cover the non-picture canvas in the first method on the upper layer of the picture, so that the shortcomings of the two schemes can be perfectly avoided. But stay for a moment to think about the combination of the two schemes, or use canvas to draw, is there a simpler and more understandable way. Yes, use svg instead.
4, SVG method (the scheme in use)
Give a react version of the watermark component.

export const WaterMark = (props) => {
  // 获取水印数据
  const { nick, empId } = GlobalConfig.userInfo;
  const boxRef = React.createRef();
  const [waterMarkStyle, setWaterMarkStyle] = useState('180px 120px');
  const [isError, setIsError] = useState(false);
  const {
    src, text = `${nick}-${empId}`, height: propsHeight, showSrc, img, nick, empId
  } = props;
  // 设置背景图和背景图样式
  const boxStyle = {
    backgroundSize: waterMarkStyle,
    backgroundImage: `url("data:image/svg+xml;utf8,<svg width=\'100%\' height=\'100%\' xmlns=\'http://www.w3.org/2000/svg\' version=\'1.1\'><text width=\'100%\' height=\'100%\' x=\'20\' y=\'68\'  transform=\'rotate(-20)\' fill=\'rgba(0, 0, 0, 0.2)\' font-size=\'14\' stroke=\'rgba(255, 255, 255, .2)\' stroke-width=\'1\'>${text}</text></svg>")`,
  };
  const onLoad = (e) => {
    const dom = e.target;
    const {
      previousSibling, nextSibling, offsetLeft, offsetTop,
    } = dom;
    // 获取图片宽高
    const { width, height } = getComputedStyle(dom);
    if (parseInt(width.replace('px', '')) < 180) {
      setWaterMarkStyle(`${width} ${height.replace('px', '') / 2}px`);
    };
    previousSibling.style.height = height;
    previousSibling.style.width = width;
    previousSibling.style.top = `${offsetTop}px`;
    previousSibling.style.left = `${offsetLeft}px`;
    // 加载 loading 隐藏
    nextSibling.style.display = 'none';
  };
  const onError = (event) => {
    setIsError(true);
  };
  return (
    <div className={styles.water_mark_wrapper} ref={boxRef}>
      <div className={styles.water_mark_box} style={boxStyle} />
      {isError
        ? <ErrorSourceData src={src} showSrc={showSrc} height={propsHeight} text="图片加载错误" helpText="点击复制图片链接" />
        : (
          <>
            <img onLoad={onLoad} referrerPolicy="no-referrer" onError={onError} src={src} alt="图片显示错误" />
            <Icon className={styles.img_loading} type="loading" />
          </>
        )
      }
    </div>
  );
};

Advantages: support gif image watermark, there is no cross-domain problem, use repeat attribute, no inserting dom process, no performance problem;
shortcoming:. . .
dom structure display:
image.png
5. The renderings show
The effects achieved by canvas and svg are not very different in display, so the renderings are all shown in one image.
image.png

QA

Question one:
If the dom of watermark is deleted, isn't the picture without watermark?
Answer:
MutationObserver can be used to monitor the nodes of water. If the node is modified, the picture will be hidden;
Question two:
Copy the picture with the right mouse button?
Answer:
Right-click function is disabled for all pictures
Question three:
What if the picture information is obtained from the network of the console?
Answer:
This operation has not thought of a good solution for the time being, it is recommended to adopt a back-end implementation scheme

Summarize

The front-end watermarking solution is always only a temporary solution, and the back-end implementation of the business consumes server resources. In fact, the most ideal solution is to provide an independent watermarking service. Although there will be a slight delay in the loading process, it is relatively safe for data. In other words, the millisecond delay is still acceptable, which can ensure that the service stability of the business is not affected.
In the daily Q&A process, many business parties will come to me to communicate about the watermark concealing the risk points. Each time they can only respond to them with the importance of data security. Of course, the size, transparency, and intensity of the watermark are also different. In the continuous tuning, I believe there will be a version that can not only play the role of watermarking, but also better solve the occlusion problem.

Author: ES2049/Bu Lu
The article can be reprinted at will, but please keep this link to the original text.
You are very welcome to join ES2049 Studio if you are passionate. Please send your resume to caijun.hcj@alibaba-inc.com .
阅读 5k

ES2049 Studio
阿里巴巴 - CRO 技术部 - 体验技术
3.2k 声望
2.8k 粉丝
0 条评论
你知道吗?

3.2k 声望
2.8k 粉丝
宣传栏