I. Introduction to image-rendering

There is an interesting feature in CSS called image-rendering, which uses algorithms to better display scaled images.

Suppose we have a screenshot of a small TWO-DIMENSIONAL code (bottom left, only the schematic diagram is not scannable), and the image will be blurred after magnifying it by 10 times (bottom right) :

When rendering the enlarged image with the feature of image-rendering: pixelated, CSS will use the algorithm to render it pixelated and make the image contour have sharper edges:

This feature is ideal for use on monochromatic, contorted images that need to be enlarged, creating a pseudo-vector sensation (reducing zoom distortion).

When used in pixelated photos, which are rich in color and detail, they create a Mosaic appearance:

This is a long way from the mosaics that the title of this article hopes to achieve – currently images need to be enlarged to look good, but we want to be able to overlay images with mosaics of equal size while retaining the original size.

However, the image-rendering feature does not work on elements that are not scaled:

MDN – This property has no effect on non-scaled images.

Two, the realization of the size of the mosaics such as stepping pits

The principle of equal-size Mosaic is equivalent to blurring a photo and then sharpening the algorithm to get a variety of small squares.

Pixelated helps us do the “sharpen” step, we have to figure out how to do the “blur”.

First, blur with filters doesn’t work because image-rendering is strongly related to the zoom factor, so think about how you can use the zoom power of the image.

Suffice it to say that images on the WEB are a lot like smart objects in Photoshop — you can resize them any way you want (such as by magnifying them many times to make them blurry), but when you finally change the image back to its original size, it will return to its original shape (without any distortion).

How to retain the “blurred” information after image enlargement is a priority problem to be solved.

Smart friends have already thought of using Canvas for processing. After all, canvas can easily acquire and draw images, and the Image information drawn is pure data rather than Image objects. Therefore, the image data that has been enlarged and then reduced (to full size) will be distorted (which is exactly what we want to happen).

But there are some potholes:

  • External image throughimage-rendering: pixelatedThe information displayed after the algorithm is processed,canvasIt’s not accessible because it’s on the display layer.canvasYou still get unsharpened, blurry native art content;
  • canvasIf it doesn’t scale, herecanvasaddimage-rendering: pixelatedIt doesn’t make any sense.

This means that you can’t zoom in and sharpen an image outside of the canvas and then write to the canvas to zoom out (and iterate) to get the full-size sharpened image.

3. Interesting Canvas stretching

To solve the above problems, let’s first look at an interesting feature of Canvas.

If we define width and height in the Canvas tag:

<canvas width="100" height="50" ></canvas>
Copy the code

At the same time, it defines another width and height for the canvas in the style:

canvas {
  width: 200px;
  height: 200px;
}
Copy the code

So what size will the canvas display be?

The answer is CSS size, but the canvas content size is defined by the width and height within the canvas label. This means that even though we are looking at a 200px by 200px canvas, its content is actually stretched (twice as wide and four times as high).

Note: the canvas is on the left and the original is on the right

This is also a feature of Canvas as a replaceable element — CSS cannot modify its content. Imagine that CSS can dynamically change the size of canvas content, which means that part of canvas content will be clipped out, or some more white space, which is obviously not desirable. Therefore, it is a reasonable browser behavior for canvas to scale to the size specified in the style on the premise of keeping the content intact.

Using this feature of Canvas, we can implement equal-size Mosaic as follows:

  • Create a canvas, style its width and height, and setimage-rendering: pixelatedFeatures;
  • Calculate the best display size of the picture (to be similarbackground-size: containIn the form of display);
  • Sets the canvas width height (not style) to style width height1/N;
  • Draw the image, draw the image width and height for the best display size1/N.

In this way, we have actually drawn an image whose size is only 1/N of the optimal size, which is then changed back to the optimal size visually by n-fold magnification of canvas. Because the image is drawn on canvas, it will remain blurred after being enlarged back to the optimal size, thus meeting the matching requirements of image-rendering.

Note: The “best size” referred to here refers to the best size corresponding to “ensure full display of image” in Step 2, not the image’s native size.

Four, code implementation

We follow the above steps to write the corresponding code, of course, we want to be flexible, for example, the above N can be customized by the user. The code for this chapter is also available on Github.

4.1 HTML part

It mainly selects the control of the picture, the canvas, the for the convenience of the canvas to obtain the image, the text box for the user to define the zoom multiple, and the execute button:

  <input id="file" type="file" accept="image/*" />
  <canvas id="canvas"></canvas>
  <img id="img-raw" />
  <label for="compress-times">Compression multiple:</label>
  <input id="compress-times" type="number" value="12">
  <button>pixelated</button>
Copy the code

4.2 the CSS part

We need to style the canvas and configure the image-rendering: pixelated rendering feature. In addition, the tag is just an intermediary for passing the user’s selected image to the canvas and can be hidden directly:

    canvas {
      display: block;
      border: gray solid 1px;
      width: 600px;
      height: 600px;
      image-rendering: pixelated;
    }

    img {
      display: none;
    }
Copy the code

4.3 JS part

    let imgBlobUrl;
    const file = document.getElementById('file');
    const img = document.getElementById('img-raw');
    const compressTimes = document.getElementById('compress-times');
    const defaultCompressTimes = compressTimes.value | 0;
    const canvas = document.getElementById('canvas');
    const button = document.querySelector('button');

    const boundingRect = canvas.getBoundingClientRect();
    const ctx = canvas.getContext('2d');
    const canvas_w = boundingRect.width;
    const canvas_h = boundingRect.height;

    // set background-size: contain to contain
    function matchImgSizeToCanvas(imgElem = img) {
      let w = imgElem.width;
      let h = imgElem.height;
      if (w > canvas_w || h > canvas_h) {
        let radio = Math.max(h / canvas_h, w / canvas_w);
        radio = Number(radio.toFixed(2));
        imgElem.width = parseInt(w / radio);
        imgElem.height = parseInt(h / radio); }}// Draw an image of 1/N size and set the canvas width and height property to 1/N of the style width and height to achieve n-fold magnification of the canvas content
    function run() {
      let ct = parseInt(compressTimes.value) || defaultCompressTimes;
      canvas.width = parseInt(canvas_w / ct);
      canvas.height = parseInt(canvas_h / ct);
      ctx.drawImage(img, 0.0.parseInt(img.width / ct), parseInt(img.height / ct));
    }

    function cleanCanvas() {
      ctx.clearRect(0.0, canvas_w, canvas_h);
    }

    function reset() {
      img.removeAttribute('width');
      img.removeAttribute('height');
      cleanCanvas();
      matchImgSizeToCanvas(img);
      run();
    }

    file.addEventListener('change'.function (e) {
      window.URL.revokeObjectURL(imgBlobUrl);
      const picFile = this.files[0];
      imgBlobUrl = window.URL.createObjectURL(picFile);
      img.onload = function init() {
        reset();
      }
      img.src = imgBlobUrl;
    }, false);

    button.addEventListener('click', reset, false);
Copy the code

Execution effect:

After selecting the file/clicking the button, you can get the corresponding pixel style art photo according to the compression multiple.

Mosaic plug-in package

In the example above we learned how to use the Canvas feature to design mosaics of equal size. Now we will try to encapsulate this feature as a simple plugin that allows one-click Mosaicing of the list of images on the page.

The implementation of the plugin is also very simple — when the user clicks the button, insert a canvas of the same size as the image container (the size is set by the style), draw an image covering the canvas, and reduce the width and height of the canvas to enlarge the canvas content:

5.1 Plug-in Scripts

/ * *@file mosaic.js **/

class Mosaic {
    constructor(url, container, options = {}) {
        if (typeof container === 'string') {
            container = document.querySelector(container);
        }

        if(! url || ! container? .style) {console.error('Incorrect parameters');
        }

        this.url = url;
        this.options = options;
        this.container = container;

        this.init();
    }
    init() {
        const img = new Image();
        const canvas = document.createElement('canvas');
        canvas.style.position = 'absolute';
        canvas.style.zIndex = 999;
        canvas.style.imageRendering = 'pixelated';
        this.img = img;
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        const containerBoundingRect = this.container.getBoundingClientRect();
        const container_w = containerBoundingRect.width;
        const container_h = containerBoundingRect.height;

        // Initialize canvas size to container size with style
        canvas.style.width = container_w + 'px';
        canvas.style.height = container_h + 'px';

        img.onload = () = > {
            this.run(container_w, container_h);
        }

        img.src = this.url;
    }
    run(w, h) {
        // Zoom out multiple, which can be passed in as an argument. Default is 12
        const compressTimes = parseInt(this.options.compressTimes) || 12;
        let compress_w = parseInt(w / compressTimes);
        let compress_h = parseInt(h / compressTimes);
        // Change the canvas size property to 1/ shrink multiple
        this.canvas.width = compress_w;
        this.canvas.height = compress_h;
        // Draw the image to cover the reduced canvas
        this.ctx.drawImage(this.img, 0.0, compress_w, compress_h);
        this.container.prepend(this.canvas);
        this.img = null;
    }
    remove() {
        this.container.removeChild(this.canvas);
        this.canvas = null; }}export default Mosaic;
Copy the code

5.2 Plug-in usage page

/** @file plugin-demo.html **/
<head>
  <style>
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    li {
      float: left;
      line-height: 0;
      margin: 0 20px 20px 0;
    }

    li>img {
      max-height: 180px;
    }

    div {
      display: block;
      clear: both;
    }
  </style>
</head>

<body>
  <ul>
    <li><img src="./assert/0.png" /></li>
    <li><img src="./assert/1.png" /></li>
    <li><img src="./assert/2.png" /></li>
    <li><img src="./assert/3.png" /></li>
  </ul>
  <div>
    <button id="generate">Lay out the Mosaic</button>
    <button id="remove">Remove mosaics</button>
  </div>


  <script type="module">
    import Mosaic from './mosaic.js';

    let liElems = document.querySelectorAll('li');
    let mosaicList = [];

    document.querySelector('#generate').onclick = () = > {
      remove();
      for (let i = 0; i < liElems.length; i++) {
        let liElem = liElems[i];
        let url = liElem.querySelector('img').src;
        let mosaic = newMosaic(url, liElem); mosaicList.push(mosaic); }}function remove() {
      mosaicList.forEach((mosaic) = > {
        mosaic.remove();
      });
      mosaicList.length = 0;
    }

    document.querySelector('#remove').onclick = remove;

  </script>
</body>
Copy the code

Execution effect:

Click the “Overlay” or “Remove” buttons to easily stylize/remove the pixels of each image on the list.

Vi. Compatibility

The compatibility of image-rendering can be found on Caniuse. The current coverage is as follows:

Internet Explorer, UC, and Android 4.4.4 browsers are most affected. You need to consider whether to use the CSS feature in your product.


That’s all for this article, and the code is available on Github.

Hope can make you gain, mutual encouragement ~