Wednesday, 11 August 2021

How to properly Down-scale HTML Canvas for good looking images?

Introduction

I'm trying to deal with blurry visuals on my canvas animation. The blurriness is especially prevalent on mobile-devices, retina and high-dpi (dots-per-inch) screens.

I'm looking for a way to ensure the pixels that are drawn using the canvas look their best on low-dpi screens and high-dpi screens. As a solution to this problem I red multiple articles about canvas-down-scaling and followed this tutorial:

https://www.kirupa.com/canvas/canvas_high_dpi_retina.htm

Integrating down-scaling in the project

The project in which I want to implement down-scaling can be found below and consists of a few important features:

  • There is a (big) main canvas. (Performance optimization)
  • There are multiple (pre-rendered) smaller canvasses that are used to draw and load a image into. (Performance optimization)
  • The canvas is animated. (In the code snippet, there is no visible animation but the animation function is intergrated.)

Question

I tried to implement this method on both the mainCanvas and the smaller pre-renderd canvasses. As shown in the result of the snippet it is not implemented correctly, the output is still blurry. What did I do wrong and how am I able to fix this issue?

Explanation of the code snippet

The variable devicePixelRatio is set to 2 to simulate a high-dpi phone screen, low-dpi screens have a devicePixelRatio of 1. Multiple pre-rendered canvasses generated is the function spawn is the snippet there are 5 different canvasses, but on the production environment there are 10's.

If there are any pieces of information missing or questions about this post, please let me know. Thanks a lot!

Edit: Added a reference canvas so the blur is more visible, the blur is slightly there but still visible

Code Snippet

var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d' );
var circles = [];

//Simulate Retina screen = 2, Normal screen = 1
let devicePixelRatio = 2


function mainCanvasPixelRatio() {
    // get current size of the canvas
    let rect = canvas.getBoundingClientRect();

    // increase the actual size of our canvas
    canvas.width = rect.width * devicePixelRatio;
    canvas.height = rect.height * devicePixelRatio;

    // ensure all drawing operations are scaled
    c.scale(devicePixelRatio, devicePixelRatio);

    // scale everything down using CSS
    canvas.style.width = rect.width + 'px';
    canvas.style.height = rect.height + 'px';
}

// Initial Spawn
function spawn() {
    for (let i = 0; i < 2; i++) {

        //Set Radius
        let radius = parseInt(i*30);

        //Give position
        let x = Math.round((canvas.width/devicePixelRatio) / 2);
        let y = Math.round((canvas.height /devicePixelRatio) / 2);

        //Begin Prerender canvas
        let PreRenderCanvas = document.createElement('canvas');
        const tmp = PreRenderCanvas.getContext("2d");

        //Set PreRenderCanvas width and height
        let PreRenderCanvasWidth = ((radius*2)*1.5)+1;
        let PreRenderCanvasHeight =  ((radius*2)*1.5)+1;


        //Increase the actual size of PreRenderCanvas
        PreRenderCanvas.width = PreRenderCanvasWidth * devicePixelRatio;
        PreRenderCanvas.height = PreRenderCanvasHeight * devicePixelRatio;

        //Scale PreRenderCanvas down using CSS
        PreRenderCanvas.style.width = PreRenderCanvasWidth + 'px';
        PreRenderCanvas.style.height = PreRenderCanvasHeight + 'px';

        //Ensure PreRenderCanvas drawing operations are scaled
        tmp.scale(devicePixelRatio, devicePixelRatio);

        //Init image
        const image=  new Image();

        //Get center of PreRenderCanvas
        let m_canvasCenterX = (PreRenderCanvas.width/devicePixelRatio) * .5;
        let m_canvasCenterY =  (PreRenderCanvas.height/devicePixelRatio) * .5;

        //Draw red circle on PreRenderCanvas
        tmp.strokeStyle = "red";
        tmp.beginPath();
        tmp.arc((m_canvasCenterX), (m_canvasCenterY), ((PreRenderCanvas.width/devicePixelRatio)/3) , 0, 2 * Math.PI);
        tmp.lineWidth = 2;
        tmp.stroke();
        tmp.restore();
        tmp.closePath()

        //Set Image
        image .src=  "https://play-lh.googleusercontent.com/IeNJWoKYx1waOhfWF6TiuSiWBLfqLb18lmZYXSgsH1fvb8v1IYiZr5aYWe0Gxu-pVZX3"

        //Get padding
        let paddingX = (PreRenderCanvas.width/devicePixelRatio)/5;
        let paddingY = (PreRenderCanvas.height/devicePixelRatio)/5;

        //Load image
        image.onload = function () {
            tmp.beginPath()
            tmp.drawImage(image, paddingX,paddingY, (PreRenderCanvas.width/devicePixelRatio)-(paddingX*2),(PreRenderCanvas.height/devicePixelRatio)-(paddingY*2));
            tmp.closePath()
        }

        let circle = new Circle(x, y, c ,PreRenderCanvas);

        circles.push(circle)
    }
}



// Circle parameters
function Circle(x, y, c ,m_canvas) {
    this.x = x;
    this.y = y;
    this.c = c;
    this.m_canvas = m_canvas;
}

//Draw circle on canvas
Circle.prototype = {
    //Draw circle on canvas
    draw: function () {
        this.c.drawImage( this.m_canvas, (this.x - (this.m_canvas.width)/2), (this.y - this.m_canvas.height/2));
    }
};

// Animate
function animate() {
    //Clear canvas each time
    c.clearRect(0, 0, (canvas.width /devicePixelRatio), (canvas.height /devicePixelRatio));

    //Draw in reverse for info overlap
    circles.slice().reverse().forEach(function( circle ) {
        circle.draw();
    });

    requestAnimationFrame(animate);
}

mainCanvasPixelRatio()
spawn()
animate()
#mainCanvas {
     background:blue;
}

#ReferenceCanvas {
    background:blue;
}
Down-Sampling/Blurry:
<canvas id="mainCanvas"></canvas>

<br>

Reference Not-Blurry:
<canvas id="ReferenceCanvas"></canvas>

<script>
    window.onload = function() {
        var myCanvas = document.getElementById('ReferenceCanvas');
        var ctx = myCanvas.getContext('2d');
        var img = new Image;
        img.onload = function(){
            ctx.drawImage(img, 95,20, 110,110);
        };
        img.src = "https://play-lh.googleusercontent.com/IeNJWoKYx1waOhfWF6TiuSiWBLfqLb18lmZYXSgsH1fvb8v1IYiZr5aYWe0Gxu-pVZX3";
    };
</script>


from How to properly Down-scale HTML Canvas for good looking images?

No comments:

Post a Comment