How prevent blurry drawing on canvas when using putImageData? - javascript

I want to use putImageData to paint on canvas. But for some reason the painted pixels are blurry and I don't know why. Here is a minimal example:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style>
html, body, canvas { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body oncontextmenu="return false;">
<canvas id='canvas'></canvas>
<script type='text/javascript'>
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
let uarr = new Uint8ClampedArray(4);
uarr[0] = 0;
uarr[1] = 0;
uarr[2] = 0;
uarr[3] = 255;
let imgData = new ImageData(uarr , 1, 1);
ctx.putImageData(imgData, 20, 20);
</script>
</body>
</html>
This should paint one black pixel at the 20,20 coordinates, but for some reason it look blurry.
How can I force to draw sharp edges?

You need to set the canvas width and height attributes, as your CSS is stretching a default size canvas to the size of the screen, like stretching a too small image. After that ctx.imageSmoothingEnabled should work fine.
var canvas = document.getElementById('canvas');
// This will stretch correctly
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
var uarr = new Uint8ClampedArray(4);
uarr[0] = 0;
uarr[1] = 0;
uarr[2] = 0;
uarr[3] = 255;
var imgData = new ImageData(uarr , 1, 1);
ctx.putImageData(imgData, 20, 20);
html, body, canvas { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }
<canvas id='canvas'></canvas>
As #Kaido mentions in the comments below, imageSmoothingEnabled does not really work that way. Check his fiddle for a little bit more of an in-depth look at what it can be used for.

Related

Html5 canvas animation leaves a trail

I'm trying to make a little game to play around with canvas. I've decided to go for Flappy Bird as there are plenty of examples online. My goal was to make the game "responsive" for all devices. If you open it in a mobile or whichever screen, you should be able to play it normally.
For this purpose I drew a background image within the canvas that will adapt the height to the screen's height and repeat it through the x-axis.
The issue I'm having is that, when I animate the bird, this one leaves a trail behind it.
As like so:
An easy fix would be clearing the canvas as explained in this post:
Canvas image leaves weird trail when moving left
But it isn't possible because of the way I'm painting my background (I paint it once and repaint it if it resizes).
Question
How do I avoid this image trail with a static background? Is it maybe possible to have like a false static background that doesn't need to be repainted?
I obviously want to do it in the canvas, without needing to have an image in the html.
This is the code with the main functionality so that you can take a look:
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
cvs.style.height = "100vh";
cvs.style.width = "100vw";
var bird = new Image();
var bg = new Image();
bird.src = "https://lh3.googleusercontent.com/prntPuix7QxlhKXx6IBtTxv7PrdEJtmzFJ4mopScNS1_klze86BVNy3PqHbQn2UQ4JyJdix3XQ=w128-h128-e365";
bg.src = "https://opengameart.org/sites/default/files/bg_5.png";
let gravity = 1;
birdPositionY = 10;
birdPositionX = 50;
const draw = () => {
ctx.drawImage(bird, birdPositionX, birdPositionY);
birdPositionY += gravity;
birdPositionX += 3;
requestAnimationFrame(draw);
};
const resizeCanvas = () => {
const {
width,
height
} = cvs.getBoundingClientRect();
cvs.height = height;
cvs.width = width;
let x = 0,
y = 0;
while (x < cvs.width && y < cvs.height) {
ctx.drawImage(bg, x, y, 360, cvs.height);
x += bg.width;
}
};
bg.onload = () => {
resizeCanvas();
};
window.addEventListener("resize", resizeCanvas, false);
draw();
<!DOCTYPE html>
<html>
<head>
<title>Flappy Bird</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<style>
html {
overflow: hidden;
}
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="index.js"></script>
</body>
</html>
Hope it did make sense!
Thanks in advance!
Draw your background on a canvas that you will keep off-screen, then draw this canvas every frame on your visible one before you draw your moving parts.
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
// our backround canvas that we won't append in the doc ("off-screen")
const background = cvs.cloneNode();
const bg_ctx = background.getContext('2d');
cvs.style.height = "100vh";
cvs.style.width = "100vw";
var bird = new Image();
var bg = new Image();
bird.src = "https://lh3.googleusercontent.com/prntPuix7QxlhKXx6IBtTxv7PrdEJtmzFJ4mopScNS1_klze86BVNy3PqHbQn2UQ4JyJdix3XQ=w128-h128-e365";
bg.src = "https://opengameart.org/sites/default/files/bg_5.png";
let gravity = 1;
birdPositionY = 10;
birdPositionX = 50;
const draw = () => {
// first draw the background canvas
ctx.drawImage(background, 0,0);
// then your persona
ctx.drawImage(bird, birdPositionX, birdPositionY);
birdPositionY += gravity;
birdPositionX += 3;
requestAnimationFrame(draw);
};
const resizeCanvas = () => {
const {
width,
height
} = cvs.getBoundingClientRect();
// resize both canvases
cvs.height = background.height = height;
cvs.width = background.width = width;
let x = 0,
y = 0;
while (x < cvs.width && y < cvs.height) {
// draw on the off-screen canvas
bg_ctx.drawImage(bg, x, y, 360, cvs.height);
x += bg.width;
}
};
bg.onload = () => {
resizeCanvas();
draw();
};
window.addEventListener("resize", resizeCanvas, false);
<!DOCTYPE html>
<html>
<head>
<title>Flappy Bird</title>
<meta name="viewport" content="width=device-width, user-scalable=no" />
<style>
html {
overflow: hidden;
}
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="index.js"></script>
</body>
</html>
I think you could just call resizeCanvas in your draw loop to redraw the background.
EDIT: Apparently that does not work. Another option is just drawing it in the draw loop like this:
const draw = () => {
ctx.clearRect(0, 0, cvs.width, cvs.height)
let x = 0, y = 0;
while (x < cvs.width && y < cvs.height) {
ctx.drawImage(bg, x, y, 360, cvs.height);
x += bg.width;
}
ctx.drawImage(bird, birdPositionX, birdPositionY);
birdPositionY += gravity;
birdPositionX += 3;
requestAnimationFrame(draw);
};
Edit: That did not work either. The other option would be to make two canvases and overlap them with one drawing the background and one drawing the bird.

why can't I draw a square on the canvas?

Hey guy's I'm making a snake game in JS and right now all I'm trying to do is draw a snake in the center of the canvas. I've set the canvas dimensions to the board dimensions so everything scales right but nothing shows up. Any help would be appreciated:)
//declare global variables
const canvas = document.querySelector('#canvas');
//set canvas context
const ctx = canvas.getContext('2d');
//set canvas dimensions to board dimensions
canvas.width = 768;
canvas.height = 512;
//put canvas dimensions into variables
const cvsW = canvas.width;
const cvsH = canvas.height;
//create snake unit
const unit = 16;
//create snake and set starting position
let snake = [{
x : cvsW/2,
y : cvsH/2
}]
ctx.fillStyle = 'limegreen';
ctx.fillRect(snake.x, snake.y, unit, unit);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Snake</title>
<style>
body {
background-color: #333;
}
#canvas {
background-color: #4d4d4d;
display: block;
margin: auto;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<canvas id="canvas" width="768" height="512"></canvas>
<script src="script.js"></script>
</body>
</html>
This is happening because your snake is an array of objects. You either need to turn this into a single object for your code to work or use an index to select the object inside.
ctx.fillRect(snake[0].x-unit/2, snake[0].y-unit/2, unit, unit);
Also, note that in order to correctly centre your snake you need to subtract the unit/2 from both x and y coordinates.
You can also remove the setting of the canvas dimensions within your code, as this is set when your define the height and width attributes on your canvas element.
See working example below:
//declare global variables
const canvas = document.querySelector('#canvas');
//set canvas context
const ctx = canvas.getContext('2d');
//put canvas dimensions into variables
const cvsW = canvas.width;
const cvsH = canvas.height;
//create snake unit
const unit = 16;
//create snake and set starting position
let snake = [{
x: cvsW / 2,
y: cvsH / 2
}];
ctx.fillStyle = 'lime';
ctx.fillRect(snake[0].x - unit / 2, snake[0].y - unit / 2, unit, unit);
body {
background-color: #333;
}
#canvas {
background-color: #4d4d4d;
display: block;
margin: auto;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
<canvas id="canvas" width="768" height="512"></canvas>

html5 canvas mirroring objects

I wrote the following code in my text editor:
<!DOCTYPE HTML><html>
<head>
<script>
window.onload = function()
{
var canvas = document.getElementById("canvasArea");
var context = canvas.getContext("2d");
var ball = new Image();
var smallImage = "https://i.warosu.org/data/sci/img/0076/83/1448614341262.png";
var ballXPos = 75;
var ballYPos = 15;
var ballWidth = 90;
var ballHeight = 90;
var reflectAdj = 3.5;
var reflectAlpha = .4;
var reflect Y = (2*ballYPos) + (2*(ballHeight-reflectAdj));
var gradLV = context.createLinearGradient(0, 0, 0, canvas.height);
ball.onload = function()
{
gradLV.addColorStop( 0, "lightskyblue");
gradLV.addColorStop(.3, "orange");
gradLV.addColorStop(1, "blue");
context.fillStyle = gradLV;
context.fillRect(0, 0, canvs.width, canvas.height);
context.translate(0, reflectY);
context.scale(1,-1);
context.globalAlpha = reflectAlpha;
context.drawImage(ball, ballXpos, ballYpos, ballWidth, ballHeight);
}
}
</script>
</head>
<body>
<div style = "width:400px; height:210px; margin:0 auto; padding:5px;">
<canvas id = "canvasArea" width = "400" height = "210"
style = "border:2px solid black">
Your browser doesn't currently support HTML5 Canvas.
</canvas>
</div>
</body>
</html>
In this code, the canvas is supposed to show a mirrored object, but the canvas is completely blank. Can someone please tell me what I did wrong/ PLease and thank you.
Your code was full of a number of small errors, most of which were typos. You were also failing to specify the image URL as the src of the new Image() constructor.
The code below fixes these issues and provides visual output on the canvas. I'm not sure exactly what mirror effect you're going for, but hopefully this sets you on the right path.
window.onload = function() {
var canvas = document.getElementById("canvasArea");
var context = canvas.getContext("2d");
var ball = new Image();
ball.src = "https://i.warosu.org/data/sci/img/0076/83/1448614341262.png";
var ballXPos = 75;
var ballYPos = 15;
var ballWidth = 90;
var ballHeight = 90;
var reflectAdj = 3.5;
var reflectAlpha = .4;
var reflectY = (2*ballYPos) + (2*(ballHeight-reflectAdj));
var gradLV = context.createLinearGradient(0, 0, 0, canvas.height);
ball.onload = function() {
gradLV.addColorStop( 0, "lightskyblue");
gradLV.addColorStop(.3, "orange");
gradLV.addColorStop(1, "blue");
context.fillStyle = gradLV;
context.fillRect(0, 0, canvas.width, canvas.height);
context.translate(0, reflectY);
context.scale(1,-1);
context.globalAlpha = reflectAlpha;
context.drawImage(ball, ballXPos, ballYPos, ballWidth, ballHeight);
}
}
<div style = "width:400px; height:210px; margin:0 auto; padding:5px;">
<canvas id = "canvasArea" width = "400" height = "210" style = "border:2px solid black">
Your browser doesn't currently support HTML5 Canvas.
</canvas>
</div>

HTML, CSS, JS, Canvas rectangle not deleting

I would like the rectangle to move on the canvas and not copy every time.
It draws it but then the rectangle stays there.
I am a beginner with the canvas so if it is an epic fail then be prepared.
The codepen is at LINK.
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var boxWidth = 50;
var boxHeight = 50;
var bX = WIDTH / 2 - boxWidth / 2;
var bY = HEIGHT / 2 - boxHeight / 2;
function render() {
ctx.fillStyle = "white";
ctx.rect(bX, bY, boxWidth, boxHeight);
ctx.fill();
}
function control() {
bX++;
}
function main() {
control();
render();
}
main();
var run = setInterval(main, 10)
canvas {
width: 400px;
height: 400px;
background-color: black;
}
div {
text-align: center;
}
<div>
<canvas width="400px" height="400px" background-color="black" id="canvas"></canvas>
</div>
Repaint your canvas before drawing the rectangle each time - think about it. Its all done in layers.
The rectangle is "staying there" because you aren't replacing the rectangle your drew last time.
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var boxWidth = 50;
var boxHeight = 50;
var bX = WIDTH / 2 - boxWidth / 2;
var bY = HEIGHT / 2 - boxHeight / 2;
function render() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); //use clear rect instead
ctx.fillStyle = "white";
ctx.fillRect(bX, bY, boxWidth, boxHeight); //use fillRect instead of fill()
}
function control() {
bX++;
}
(function main() {
control();
render();
requestAnimationFrame(()=>main());
})()
canvas {
width: 400px;
height: 400px;
background-color: black;
}
div {
text-align: center;
}
<html>
<head></head>
<body>
<div>
<canvas width="400px" height="400px" background-color="black" id="canvas"></canvas>
</div>
</body>
</html>
Also have a look at the requestAnimationFrame() method as opposed to setInterval - it syncs up with the window's javascript timer and is less likely to cause problems.

Canvas drawimage() not displaying image

I am trying to make a full screen, responsive image on Canvas. I believe I have the sizing figured out, but it isn't drawing the image and is instead leaving the canvas blank.
This is the HTML and JavaScript I am currently using:
<canvas id="MainCanvas"></canvas>
<script>
var canvas = document.getElementById("MainCanvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var wRatio = canvas.width / imageObj.width;
var hRatio = canvas.height / imageObj.height;
var ratio = Math.min (wRatio, hRatio);
var imageObj = new Image();
imageObj.onload= function() {
context.drawImage(imageObj, 0, 0, canvas.width, canvas.height,imageObj.width*ratio, imageObj.height*ratio);
};
imageObj.src="Local File.jpg";
</script>
CSS:
canvas{
border: 5pt solid black;
position: relative;
height: 100%;
width: 100%;
}
Any help is greatly appreciated.
First off, good job on the minimal example. When I ran it in jsFiddle, here's what I got in my javascript console.
Uncaught TypeError: Cannot read property 'width' of undefined
Looking at your code, i noticed that it was trying to reference the width and height of the image before it had loaded. I put that code inside your onLoad function and that error was corrected. Next I got this error:
Uncaught TypeError: Failed to execute 'drawImage' on 'CanvasRenderingContext2D': Valid arities are: [3, 5, 9], but 7 arguments provided.
Seeing that, I looked up CanvasRenderingContext2D on MDN. That showed me what it required, and I added a 0,0 for the dx, dy values. This is completely wrong of course, but it at least gets the image to render (jsfiddle link if the below example doesn't work correctly):
var canvas = document.getElementById("MainCanvas");
console.log(canvas);
var context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var imageObj = new Image();
imageObj.onload= function() {
var wRatio = canvas.width / imageObj.width;
var hRatio = canvas.height / imageObj.height;
var ratio = Math.min (wRatio, hRatio);
context.drawImage(imageObj, 0, 0, canvas.width, canvas.height,0,0,imageObj.width*ratio, imageObj.height*ratio);
};
imageObj.src="//placehold.it/500";
html, body {
height: 100%;
}
canvas{
border: 5pt solid black;
position: relative;
height: 100%;
width: 100%;
box-sizing: border-box;
display:block;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.css" rel="stylesheet"/>
<canvas id="MainCanvas"></canvas>

Categories