DEV Community

Cover image for Resize Images in JS FAST! (Using browser multi-threading)
Vipertech.ch
Vipertech.ch

Posted on

Resize Images in JS FAST! (Using browser multi-threading)

Today, we will dive into offscreen canvas with a bit of canvas knowledge in mind that you must have so please check other resources if needed, and check out how we play between blob, image_data (canvas), Bitmap Image, and data-url (base64), we can render JS IMAGE FILTERS (https://dev.to/vipertechofficial/22-javascript-canvas-filters-just-like-instagram-smarthack-55b) off the main thread using it, and it works fine! Also just check the code, it is not so long and well commented, please note the project I built (https://pixa.pics/) uses https://www.npmjs.com/package/workerpool to make all that stuff propagate onto workers...

1) We create a Bitmap Image which will be transferred onto our canvas made for bitmap images
2) We convert the raster image into a file format we choose when we create a blob file (a file-like object)
3) We convert that file-like object into a dataurl which is base64 encoded image (data:image/png;base64,...and some base64)
THIS IS MAGIC

// CONST:
// imgd_data (you get img_data using a canvas, please get familiar with canvas)
// no_transparent (Boolean to know which file format to use)
// pxl_width (Simply width of the image)
// pxl_height (Simply height of the image)
// resize_width (Which size of width will it get at the end?)

try { // Can work in Web Worker so you can run it on a CPU thread different from the main one which doesn't freeze the UI

    var imgd = null;
    // imgd = new ImageData(pxl_width, pxl_height);
    // imgd.data = imgd_data;

    var canvas = new OffscreenCanvas(pxl_width, pxl_height);
    var ctx = canvas.getContext('2d');
    imgd = ctx.getImageData(0, 0, pxl_width, pxl_height);


    var resize_ratio = resize_width / pxl_width;
    var resizeWidth = parseInt(pxl_width * resize_ratio);
    var resizeHeight = parseInt(pxl_height * resize_ratio);

    var canvas2 = new OffscreenCanvas(resizeWidth, resizeHeight);
    var ctx2 = canvas2.getContext("bitmaprenderer"); // "bitmaprenderer" context is available in web worker which isn't true for "2d" context

    // Of course with a bitmaprenderer context we may need to create a bitmap image, this is how we do :
    // 1) We create a Bitmap Image which will be transferred onto our canvas made for bitmap images
    // 2) We convert the raster image into a file format we choose when we create a blob file (a file-like object)
    // 3) We convert that file-like object into a dataurl which is base64 encoded image (data:image/png;base64,...and some base64)
    // THIS IS MAGIC
    return createImageBitmap(imgd, {
        premultiplyAlpha: 'none',
        colorSpaceConversion: 'none',
        resizeWidth: resizeWidth,
        resizeHeight: resizeHeight,
        resizeQuality: "pixelated", // One of pixelated, low (default), medium, or high
    }).then((btmp_i) => {

        // And at once, we resized it before transfering it now onto the canvas context responsible for exporting it
        ctx2.transferFromImageBitmap(btmp_i);

        var blob_params = no_transparent ? {type: "image/jpeg", quality: 0.3}: {type: "image/png"}; // We can also export the image in webp format but just wrap it into a try catch because it isn't supported everywhere

        // We call the canvas of our second context (canvas2) and ask the browser to create a blob (which is a file-like object of immutable, raw data)
        return ctx2.canvas.convertToBlob(blob_params).then((blob) => {

            // With it, we read the blob file as a data url you know (data:image/png;base64,...and some base64)
            function blob_to_base64(blob) { 
              return new Promise((resolve, _) => { 
                var reader = new FileReader();
                reader.onloadend = () => resolve(reader.result);
                reader.readAsDataURL(blob);
              })
            }

            return blob_to_base64(blob).then((data_url) => { // This we get a promise so we can call ".then()" to this function

                 return data_url;
            });
        });
    });

}catch(e) { // Hopefully if OffscreenCanvas or such isn't supported we can use the classical method but it won't work in Web Worker my friend

    var canvas = document.createElement("canvas");
    canvas.width = pxl_width;
    canvas.height = pxl_height;
    var ctx = canvas.getContext('2d');

    var imgd = null;
    // imgd = new ImageData(pxl_width, pxl_height);
    // imgd.data = imgd_data;
    // ctx.putImageData(image_data, 0, 0);

    var resize_ratio = resize_width / pxl_width;
    var resizeWidth = parseInt(pxl_width * resize_ratio);
    var resizeHeight = parseInt(pxl_height * resize_ratio);

    // THIS IS NOT MAGIC, we just draw an image on our simple secondary canvas (but again it doesn't work off the main thread since it can't work in Web Worker)
    var canvas2 = document.createElement("canvas");
    canvas2.width = resizeWidth;
    canvas2.height = resizeHeight;
    var ctx2 = canvas2.getContext("2d");
    ctx2.drawImage(canvas, 0, 0, resizeWidth, resizeHeight);

    if(no_transparent){

        return canvas2.toDataURL("image/jpeg", 0.3); 
    }else {

        return canvas2.toDataURL("image/png");
    }

    // We can also export the image in webp format but just wrap it into a try catch because it isn't supported everywhere
}

// USEFULL TIPS: Use https://www.npmjs.com/package/workerpool combined with THIS ENSOULEMENT:
new Function(`return async function(param1, param2){}`)()(param1, param2)
Enter fullscreen mode Exit fullscreen mode

Discussion (0)