Have a problem

In our daily work, we often encounter a lot of sensitive data. In order to prevent data leakage, we need to do some “packaging” on the data. The purpose is to let those who have the intention to leak data “lawbreakers” under serious “public pressure” and give up illegal acts, so that “attempted crime”, to achieve the effect of subjugation without a fight. And in the security department of our work, the concept of data security has long been deep in the bone marrow, each text, each picture, we should pay attention to whether there is a risk of leakage, how to prevent data leakage, is the problem we have been thinking about. For example, image watermarking is often involved in our work. Because the content of my work is the development of the audit platform, there are often some risky pictures in the audit platform, considering the security awareness of the audit staff is uneven, so in order to prevent unsafe things, the work of adding watermarks to pictures is necessary.

To analyze problems

First of all, considering the business scenario, the problem at the present stage is only to worry about data leakage in the audit process. We only consider explicit watermarking for the time being, that is, adding some words or other data on the picture that can distinguish your personal identity. In this way, the leaked data can be traced back to individuals. Of course, the warning function is the most important.

To solve the problem

implementation

There are many ways to realize watermarking, which can be divided into front-end watermarking and back-end watermarking according to the division of personnel to realize functions. The advantages of front-end watermarking can be summarized as three points. First, it does not occupy server resources and completely depends on the computing power of the client, reducing the pressure on the server. Second, fast speed, no matter what kind of front-end implementation, performance is better than the back end. Third, the implementation is simple. The biggest advantage of back-end watermarking can also be summarized as three points, that is, security, security. Zhihu and Weibo are watermarking schemes implemented by the back end. However, considering comprehensively, we still use the front-end watermarking scheme. Nodejs also provides a brief description of how to implement backend image watermarking.

The node implementation

Three NPM packages are provided. This section is not the focus of our article, but a simple demo is provided.

1, gm github.com/aheckmann/g… 6.4 k 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');
});
Copy the code

You need to install GraphicsMagick or ImageMagick;

2, node-images: github.com/zhangyuanwe…

var images = require("images");

images("input.jpg")                     //Load image from file 
                                        // Load the image file
    .size(400)                          //Geometric scaling the image to 400 pixels width
                                        // Scale the image to 400 pixels wide
    .draw(images("logo.png"), 10.10)   //Drawn logo at coordinates (10,10)
                                        // draw the Logo at (10,10)
    .save("output.jpg", {               //Save the image to a file, with the quality of 50
        quality : 50                    // Save the image to a file with a quality of 50
    });
Copy the code

No need to install other tools, lightweight, developed by Zhangyuanwei, Chinese documentation;

3, Jimp: github.com/oliver-mora… Can be matched with gifwrap to achieve GIF watermarking;

The front-end implementation

1. Background image achieves full-screen watermarking

Can go to Ali inside and outside the personal information page to view the effect, principle:

Advantages: Images are generated back end, security;

Disadvantages: need to initiate HTTP request to obtain picture information;

Effect display: because it is an internal system, it is not convenient to display the effect.

2. Dom realizes full image watermarking and image watermarking

In the onload event of the image to obtain the width and height of the image, according to the size of the image to generate watermark area, block in the upper layer of the image, 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;
// Count the number
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');
		// Dynamically set the offset value
		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 = 'Test watermark'; wrap.appendChild(waterDom); }}Copy the code

Advantages: simple and easy to implement;

Disadvantages: Too large or too many images will affect performance;

Effect display:

3. Canvas Implementation (first version implementation scheme)

Method 1: Operate directly on the picture

Without further ado, let’s get right to the code

useEffect(() = > {
  	// GIF images are not supported
    if (src && src.includes('.gif')) {
      setShowImg(true);
    }
    image.onload = function () {
      try {
        // Too small image does not load watermark
        if (image.width < 10) {
          setIsDataError(true);
          props.setIsDataError && props.setIsDataError(true);
          return;
        }
        const canvas = canvasRef.current;
        canvas.width = image.width;
        canvas.height = image.height;
        // Set the watermark
        const font = `The ${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);
       	// Set the watermark float layer
        const pattern = context.createPattern(innerCanvas, 'repeat');
        context.fillStyle = pattern;
        context.fill();
      } catch (err) {
        console.info(err);
        setShowImg(true); }}; image.onerror =function () {
      setShowImg(true);
    };
  }, [src]);
Copy the code

Advantages: pure front-end implementation, right copy of the picture is also a watermark;

Disadvantages: no GIF support, images must support cross-domain;

Effect demonstration: given below.

Method 2: Canvas generates watermark URL and assigns value to 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');
};

// The usage mode
<img src="https://xxx.xxx.jpg" />
<div className="warter-mark-area" style={{ backgroundImage: `url(${getBase64Background({})}) `}} / >
Copy the code

Advantages: pure front-end implementation, support cross-domain, support Git map watermarking;

Disadvantages: Generated Base64 urls are large;

Effect demonstration: given below.

In fact, according to the implementation methods of the two kinds of canvas, it is easy to come up with the third way, which is to cover the non-picture canvas in the first method on the upper layer of the picture, so as to perfectly avoid the shortcomings of the two schemes. However, stop for a moment and think about the combination of the two schemes, or canvas to draw, is there a simpler way to understand? Yes, use SVG instead.

4. SVG (currently in use)

The React version of the watermark component.

export const WaterMark = (props) = > {
  // Get watermark data
  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;
  // Set the background image and background style
  const boxStyle = {
    backgroundSize: waterMarkStyle,
    backgroundImage: `url("data:image/svg+xml; 100% utf8, < SVG width = \ '\' height = \ '\' 100% XMLNS = \ version = \ '\' 1.1 'http://www.w3.org/2000/svg\' > < text width = \ '100% \' height=\'100%\' x=\'20\' y=\'68\' transform=\'rotate(-20)\' fill=\'rgba(0, 0, 0, 0.2) 14 \ 'the font - size = \' \ 'the 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;
    // Get the image width and height
    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 loading is hidden
    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="Image loading error" helpText="Click on the copy image link" />
        : (
          <>
            <img onLoad={onLoad} referrerPolicy="no-referrer" onError={onError} src={src} alt="Picture display error" />
            <Icon className={styles.img_loading} type="loading" />
          </>
        )
      }
    </div>
  );
};
Copy the code

Advantages: support GIF image watermarking, no cross-domain problems, use repeat attribute, no DOM insertion process, no performance problems;

Faults:…

Dom structure display:

5. Display of effect drawings

There is no big difference between canvas and SVG in the effect display, so the effect picture is shown in a single picture.

QA

Question 1: If you remove the DOM from the watermark, isn’t the image watermarked?

Answer: You can use MutationObserver to listen for water nodes and hide images if the nodes are modified.

Question two: Right mouse button copy picture?

Answer: The right click function is disabled for all images

Question 3: What if I get the picture information from the console’s network?

Answer: There is no good solution for this operation, so we recommend a back-end implementation

conclusion

Front end to achieve watermark scheme is only a temporary solution, business backend implementation and use server resources, is the ideal solution is to provide an independent watermark services, although will be a slight delay in the process of loading, but relative to the data security, millisecond delay is acceptable, this does not affect the business services and can guarantee stability.

In the process of answering questions every day, there will be a lot of business to me communication watermarking cover risk point problem, every time can only use the importance of data security to reply them, of course, the size of the watermark, transparency, and intensity were also in constant tuning, believe there will be a version, can have the effect of the watermark, also can better solve the problem of shade.

Author: ES2049 / Bu Lu

The article can be reproduced at will, but please keep this link to the original text.

You are welcome to join ES2049 Studio. Please send your resume to [email protected].