javascriptimagecanvashtml5-canvasimage-rendering

Why are some images blurry and others crisp on the same canvas?


In a canvas project I am working on, I have a tile based gird, along with a panel of options. I have gotten everything to work, and all the images are crisp, screen resizing is accounted for, there is no anti-aliasing, etc. Everything (graphics wise) is perfect, the problem is that sometimes I need to change the number of rows and columns, and this changes the overall dimensions of the canvas. Due to some other problems, I use a buffer canvas inside of the editor class, which gets rendered onto the main canvas.

Here is my editor code (only the graphics):

export class Editor{
    constructor(spritesheet, toolImages){
        this.spritesheet = spritesheet;
        this.toolImages = toolImages;

        this.rows = 25;
        this.cols = 25;
        this.size = 16;

        this.extra = this.size *3;//extra on the bottom

        this.w = this.cols * this.size;
        this.h = this.rows * this.size + this.extra;


        this.buffer = document.createElement('canvas').getContext('2d', { alpha:false, desynchronized:true });
        this.buffer.imageSmoothingEnabled = false;
        this.buffer.canvas.width = this.cols * this.size;
        this.buffer.canvas.height = this.rows * this.size + this.extra;
        this.buffer.canvas.style.imageRendering =  'pixelated'
        this.buffer.imageSmoothingEnabled = false;

        this.map = [];//2d map object, not important
        
    }
    addRow(){
        this.rows++;
        this.h = this.rows * this.size + this.extra;
        this.map[this.rows-1] = [];
        for(let i = 0; i < this.rows; i++){
            this.map[this.rows-1][i] = 'blank';
        }
        this.buffer.canvas.height = this.rows * this.size + this.extra;
        console.log('adding: ' + this.rows);
    };
    delRow(){
        this.rows--;
        this.h = this.rows * this.size + this.extra;
        this.buffer.canvas.height = this.rows * this.size + this.extra;

        delete this.map[this.rows];
        console.log('deleting: ' + this.rows);
    };

    drawToBuffer(cutx, cuty, pasteX, pasteY, mult = 1){
        this.buffer.drawImage(this.spritesheet, cutx, cuty, this.size, this.size, pasteX, pasteY, this.size * mult, this.size * mult);
        
    }
    prepare(){
        this.buffer.clearRect(0,0,this.buffer.canvas.width,this.buffer.canvas.height);
        this.buffer.fillStyle = '#00131B';
        this.buffer.fillRect(0,0,this.buffer.canvas.width,this.buffer.canvas.height);
        this.buffer.fillStyle = '#000000';
        this.buffer.fillRect(0, this.buffer.canvas.height - this.extra + 1, this.buffer.canvas.width, this.size-2);

        let amount = Math.floor(this.buffer.canvas.width/6);

        let Y = this.size * (this.rows +1);
        let S;
        this.buffer.fillStyle = '#555555';
        switch(this.editorShow){
        //there are more tabs, but they all look like this one:

            case 'walls':
                S = (this.w - this.size*2)/4;
                this.drawToBuffer(0, 25*this.size,  0, Y, 2);
                this.drawToBuffer(0, 27*this.size,  S, Y, 2);
                this.drawToBuffer(0, 28*this.size,S*2, Y, 2);
                this.drawToBuffer(0, 29*this.size,S*3, Y, 2);
                this.drawToBuffer(0, 30*this.size,S*4, Y, 2);
            break;
        }
//toolbar images
        this.buffer.drawImage(this.toolImages[0], 3, this.buffer.canvas.height - this.extra);
        this.buffer.drawImage(this.toolImages[1], amount+3, this.buffer.canvas.height - this.extra);
        this.buffer.drawImage(this.toolImages[2], amount*2+3, this.buffer.canvas.height - this.extra);
        this.buffer.drawImage(this.toolImages[3], amount*3+3, this.buffer.canvas.height - this.extra);
        this.buffer.drawImage(this.toolImages[4], amount*4+3, this.buffer.canvas.height - this.extra);
        this.buffer.drawImage(this.toolImages[5], amount*5+3, this.buffer.canvas.height - this.extra);

    }
    render(context){
        context.drawImage(this.buffer.canvas,0,0);
    }

}

And here is the main code (that is running in the browser):

const context = document.querySelector('canvas').getContext('2d');


window.setInterval(()=>{
   context.clearRect(0,0,context.canvas.width,context.canvas.height);
   context.canvas.style.imageRendering = 'pixelated';
   context.canvas.width = currentEditor.w;
   context.canvas.height = currentEditor.h;
   currentEditor.prepare();
   currentEditor.render(context);
   resizeLevel(currentEditor.w, currentEditor.h);
}, 1000/60);

//the resize function
function resizeLevel(w,h) {

    // Get the height and width of the window
    var height = document.documentElement.clientHeight;
    var width  = document.documentElement.clientWidth;

    let width_height_ratio = w / h;
    // This makes sure the DISPLAY canvas is resized in a way that maintains the MAP's width / height ratio.
    if (width / height < width_height_ratio) height = Math.floor(width  / width_height_ratio);
    else                                         width  = Math.floor(height * width_height_ratio);

    // This sets the CSS of the DISPLAY canvas to resize it to the scaled height and width.
    context.canvas.style.height = height + 'px';
    context.canvas.style.width  = width  + 'px';
    //this centers the canvas
    context.canvas.style.marginTop = (innerHeight/2 - height/2) + 'px';
    context.canvas.style.marginLeft = (innerWidth/2 - width/2) + 'px';
   
}

All of the images are crisp to begin with, but whenever I add or subtract rows or columns, all of the images that are scaled by 2 (this.drawToBuffer(w, x , y, z, 2)) become blurry, even if I add a row and delete a row.

Before: Crisp image

After: Blurry image

I want the images to be like the first one, crisp and pixelated, and no matter what I try, the images that are scaled up will not look crisp, I have made sure that they are all on integers, set the image rendering of the buffer to pixelated, and turned off imageSmothingEnabled.

Here is the full site: http://labyrinth-sweeper.herokuapp.com/
And here is the full code: https://github.com/Hello-World25/Amaze-V2

How can I get these scaled images to look crisp?


Solution

  • Whenever canvas is resized, it's context gets reset, which means that imageSmoothingEnabled goes back to default value true. Make sure to set it back to false after resize and before next drawImage call.