Simple Image Resizing in the Browser

You know you can resize images right in the browser without the need to use any tool or library

Use canvas

Canvas API allows us to draw 2D graphics as we want in the browser. This means that we can completely redraw an image with the desired size. In other words, resize it  Let’s take a look at the method used to redraw an image on the canvas drawImage . drawImagecan have up to 9 parameters:

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

Inside:

  • sxsy: is the top left position of the original image we want to redraw.
  • sWidthsHeight: is the size of the image we want to redraw from the original image.
  • dxdy: is the position on the canvas that we want to redraw the original image.
  • dWidthdHeight: is the size on the canvas that we want to redraw the original image.

So just set dWidthand dHeightset the size we want to resize and we will resize the image. Try it out. For example, I have this picture.

mashu.png

The original image size is 1207 x 1800 . Now I will resize it to 301 x 450 . Our code is like this:

const img = new Image();

img.onload = function() {
    const canvas
    const canvas = newCanvas(width, height);

    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 301, 450);
    document.body.appendChild(canvas);
};

img.src = url;

This is the result.

mashu-rsz.png

Hmm, it doesn’t look so good

The image is quite lacking in sharpness compared to the original image. This is because this API is not designed to resize images with good algorithms such as image editing tools or server-side libraries for performance reasons. So resizing with too much difference size will make the image look like this. To limit this, we will resize it little by little to avoid resizing too much. Resizing multiple times may cause a loss of quality, but it will avoid such aliasing. I modified the code a bit like this, each time I will resize to half, gradually to the size I want.

function resize(img, width, height, quality = null) {
    const canvas = newCanvas(width, height);

    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);

    return canvas;
}

const img = new Image();

img.onload = function() {
    let canvas = img;

    while (canvas.width * .5 > 301) {
        canvas = resize(canvas, canvas.width / 2, canvas.height / 2);
    }

    canvas = resize(canvas, 301, 450);
};

img.src = url;

Here is the result:

mashu-multirsz.png

It looks much better .

There is also an experimental feature currently available on Chrome that can customize resize quality as the imageSmoothingQuality option of 2D canvas context. I tried it and got the result like this:

mashu-hqrsz.png

It’s not much different from the above, I think the top is even more beautiful

**Use ****createImageBitmap**

Still the Canvas API but this time we have another method, still only support on chrome (Firefox has support but crop instead of resize

). createImageBitmap allows creating ImageBitmapto save image data from the original image (image or canvas). During the creation process also supports resize feature. And especially it returns one Promiseso we don’t have to worry it will cause browser lag when rendering. Resizing an image still doesn’t see any difference, but you’ll see the difference in the paragraph after we resize multiple images at once. Probably thanks to that so it allows the use of better resize algorithms.

Now let’s try it out, the code is like this:

const img = new Image();

img.onload = function() {
    createImageBitmap(img, {
        resizeWidth: 301,
        resizeHeight: 450,
        resizeQuality: 'high',
    }).then((data) => {
        const canvas = newCanvas(width, height);
            canvas.getContext('2d').drawImage(data, 0, 0);
            document.body.appendChild(canvas);
        };
    });
}

img.src = url;

Here is the result:

mashu-cib.png

This is a picture I tried to resize with a good photo editing tool to see the quality is not much different.

mashu-pdn

Here is the code of all the above way for you to compare. You open the full example to compare for ease. Results may vary depending on the browser you use.

Link codepen: https://codepen.io/thphuong/pen/qBOeMRb

Resize GIF

If you try the above way with GIF images will not work. For example, I resize this image

yutathrowheart.gif

Will only be the first frame.

yutathrow-first-frame.png

You probably already know that gif images are made up of frames. The day before, I also wrote a gif making article here . So if we can split the gif into frames, then resize each frame, then merge them back to the same as the resized gif.

I will use gif-frames to split gif into frame and gif.js to combine frames into gif .

No more explanation of how the library is used to save you time , code separating gif files into frames like this.

async function getFrames(url) {
  const frames = await gifFrames({
    url,
    frames: "all",
    outputType: "canvas"
  });

  return frames.map(frame => ({
    image: frame.getImage(),
    delay: frame.frameInfo.delay
  }));
}

And this one to merge frames into gif

function renderGif(frames) {
  const gif = new GIF({
    workers: 2,
    quality: 1,
    transparent: 'rgba(0, 0, 0, 0)',
  });

  return new Promise((resolve, reject) => {
    gif.on("finished", resolve);

    try {
      gif.render();
    } catch (e) {
      reject(e);
    }
  });
}

Function resize as before, then join and run, I get the result

yuta-throw-rsz-1.gif

Hmm, it’s got a black border again. As you can see, the original gif has a transparent background. With png, to have a transparent background we will give the transparent pixels alpha channel value = 0 (a in rgba). At the edge of the border to make the transition from colored to transparent will be smooth, not jagged, there will be a lot of px with reduced transparency, like this.

png-edge.png

As you can see, it is resize canvas, because it renders the png image, it is convenient to render the pixels in the border instead of staying the same as the original image. Gifs do not have an alpha channel like png (RGB only), each pixel can only have color or no color, no blurry see-through type like png. The border is almost the same as png.

gif-edge.png

When we merge the frames, because there is no alpha channel, the transparent pixels that are colored are rgba(0, 0, 0, 0) converted to rgb(0, 0, 0)(black). As you can see from the code above, we need an option transparent: 'rgba(0, 0, 0, 0)' to know which pixels should be converted to transparent. In addition, pixels close to the edge of color are rgb(0, 0, 0)and are anear 0, so they remain black. That’s why we have less black border like the picture above.

So now we have 2 options. Fill the background with no more transparent borders as I did in the previous exercise : p. Or we fix it by fixing pixels with alpha channel to be not transparent as required by gif.

To correct the data of artwork in the canvas, we use it canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data. The image data will be an array of 8-bit integers (0-255). Each 4 numbers will be 1 pixel, representing 4 values in rgba. So now I loop in the pile of array pixels and see that any color with a value of less than 255 will set it to 255. In addition, the pixels that have alpha channel too small (<127), I also set = 0 always to prevent jaggies. Code a little bit more, see the function fixEdgeSmoothingin the demo.

The results are much better. Compared to resize with a good library like Imagemagick, I feel no less

yuta-throw-rsz.gif

Code and demo here, guys.

Codesandbox: https://codesandbox.io/s/gif-resize-zn6bnl

Demo: https://zn6bn.csb.app

If you noticed, I used it createImageBitmapwhen possible. When running in a browser that does not support createImageBitmap Firefox, pressing the resize button will result in a time lag when the browser resizes is too time-consuming.

#image #javascript #programming

Simple Image Resizing in the Browser
22.50 GEEK