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.
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?
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.