How to resize and rotate image simulataneously using canvas - html

I have to resize and rotate a image before upload, resize is to make the picture smaller, and rotate is to correct the image captured by iPhone.
Here is the code i am using, for resize, i am using a smaller canvas to redraw the image, for rotation, i am using canvas to do it.
the problem is that, my image only show a portion of the source image. how to show the full image?
This is the source image
This is what i got with the code: you can see the rotation was correct, but the resize is not, it clips the source image and left only part of it.
this is what i want.
const img = new Image();
img.src = 'https://i.stack.imgur.com/rzJQD.jpg';
img.onload = e => resize_and_rotate(img, 6);
function resize_and_rotate(img, orientation) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
// limit the image to at most 600px width or 900px height.
let ratio = img.height / img.width;
if (img.width > 600) {
canvas.width = 600;
canvas.height = canvas.width * ratio;
} else if (img.height > 900) {
canvas.height = 900;
canvas.width = canvas.height / ratio;
}
let width = canvas.width;
let height = canvas.height;
/*
For iPhone, landscape mode(with home key point to right) is the correct mode, it orientation is 1
for portrait mode(home key point to bottom), the image will rotate right by 90 degree.
*/
if (orientation === 6) { // rotate 90 degree.
// swap canvas width and height.
canvas.width = height;
canvas.height = width;
// move to the center of the canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
// rotate the canvas to the specified degrees
ctx.rotate(0.5 * Math.PI);
// since the context is rotated, the image will be rotated also
ctx.drawImage(img, -img.width / 2, -img.height / 2);
} else if (orientation === 3) { // rotate 180 degree.
// 180° rotate left
ctx.translate(canvas.width, canvas.height);
ctx.rotate(Math.PI);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
} else if (orientation === 8) { // rotate 90 degree, counter-clockwise.
canvas.width = height;
canvas.height = width;
// move to the center of the canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
// rotate the canvas to the specified degrees
ctx.rotate(-0.5 * Math.PI);
// since the context is rotated, the image will be rotated also
ctx.drawImage(img, -img.width / 2, -img.height / 2);
} else {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
// return base64 data.
// let base64 = canvas.toDataURL("image/jpeg");
// return base64;
// for SO
document.body.append(canvas);
}
canvas{max-height: 100vh; max-width: 100vw}
If i remove the following piece of code, the result was right, but it does not resize my image. seems like something wrong with the canvas size? please help.
// limit the image to at most 600px width or 900px height.
let ratio = img.height / img.width;
if (img.width > 600) {
canvas.width = 600;
canvas.height = canvas.width * ratio;
} else if (img.height > 900) {
canvas.height = 900;
canvas.width = canvas.height / ratio;
}

Your problem lies in drawImage.
You are not using enough arguments and your are not using the correct values there.
After you have done your transformations (translate(center); rotate()), you correctly try to inverse the translation so that the image be painted from the top-left corner as it should. However, you are using the original size of your image as x,y parameters instead of the destination ones.
Also, by using the 3 params version, you let destinationWidth and destinationHeight be the original size of your image, while you need the width and height of your canvas:
ctx.drawImage(img, -width / 2, -height / 2, width, height);
const img = new Image();
img.src = 'https://i.stack.imgur.com/rzJQD.jpg';
img.onload = e => resize_and_rotate(img, 6);
function resize_and_rotate(img, orientation) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
// limit the image to at most 600px width or 900px height.
let ratio = img.height / img.width;
if (img.width > 600) {
canvas.width = 600;
canvas.height = canvas.width * ratio;
} else if (img.height > 900) {
canvas.height = 900;
canvas.width = canvas.height / ratio;
}
let width = canvas.width;
let height = canvas.height;
/*
For iPhone, landscape mode(with home key point to right) is the correct mode, it orientation is 1
for portrait mode(home key point to bottom), the image will rotate right by 90 degree.
*/
if (orientation === 6) { // rotate 90 degree.
// swap canvas width and height.
canvas.width = height;
canvas.height = width;
// move to the center of the canvas
ctx.translate(canvas.width / 2, canvas.height / 2);
// rotate the canvas to the specified degrees
ctx.rotate(0.5 * Math.PI);
// since the context is rotated, the image will be rotated also
ctx.drawImage(img, -width / 2, -height / 2, width, height);
} else if (orientation === 3) { // rotate 180 degree.
// 180° rotate left
ctx.translate(canvas.width, canvas.height);
ctx.rotate(Math.PI);
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
} else if (orientation === 8) { // rotate 90 degree, counter-clockwise.
canvas.width = height;
canvas.height = width;
// move to the center of the canvas
ctx.translate(height / 2, width / 2);
// rotate the canvas to the specified degrees
ctx.rotate(-0.5 * Math.PI);
// since the context is rotated, the image will be rotated also
ctx.drawImage(img, -width / 2, -height / 2, width, height);
} else {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, width, height);
}
// return base64 data.
// let base64 = canvas.toDataURL("image/jpeg");
// return base64;
// for SO
document.body.append(canvas);
}
canvas{max-height: 100vh; max-width: 100vw}

Related

Animating drawing arcTo lines on canvas

I am trying to implement an animation of drawing an arcTo line on Canvas. For a straight line for example, the animation would be as follows
c = canvas.getContext("2d");
width = window.innerWidth;
height = window.innerHeight;
complete = false
var percent = 1
function drawEdge(x1, y1, x2, y2, color){
c.beginPath();
c.lineWidth = 10;
c.strokeStyle = color;
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
c.closePath();
}
function getPosition(x1, y1, x2, y2, percentageBetweenPoints){
let xPosition = x1 + (x2 - x1) * (percentageBetweenPoints / 100);
let yPosition = y1 + (y2 - y1) * (percentageBetweenPoints / 100);
const position = {
x: xPosition,
y: yPosition,
}
return position
}
function drawLine(){
if (!complete){
requestAnimationFrame(drawLine);
}
if (percent >= 100){
complete = true;
percent = 100;
} else{
percent = percent + 1;
}
position = getPosition(300,300,1000,300,percent);
c.clearRect(0, 0 , width, height);
drawEdge(300,300,position.x,position.y, "black");
}
drawLine()
This creates an animation of a line being drawn across the screen. However, I am having trouble doing the same thing for arcTo lines. Is there any way to implement this?
You are looking for something like this?
let ctx = canvas.getContext('2d');
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = 'bold 18px Arial';
requestAnimationFrame(draw);
function draw(t) {
t = t % 5e3 / 5e3;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(canvas.width/2, canvas.height/2, 50, 0, t * 2 * Math.PI);
ctx.stroke();
ctx.fillText((t*100).toFixed(0), canvas.width/2, canvas.height/2);
requestAnimationFrame(draw);
}
<canvas id=canvas></canvas>
To Hack or not to Hack?
There are two ways to do this
Calculate the start, end, and length of each line segment, the start, end angle, direction (CW or CCW), and center of each arc segment. Basically repeating all the maths and logic (around 50 lines of code) that makes arcTo such a useful render function.
You can get details on how to approach the full solution from html5 canvas triangle with rounded corners
Use ctx.lineDash with a long dash and a long space. Move the dash over time with ctx.lineDashOffset giving the appearance of a line growing in length (see demo). The dash offset value is reversed, starting at max length and ending when zero.
NOTE there is one problem with this method. You don't know the length of the line, and thus you don`t know how long it will take for the line to be completed. You can make an estimation. To know the length of the line you must do all the calculations (well there abouts)
The Hack
As the second method is the easiest to implement and covers most needs I will demo that method.
Not much to say about it, it animates a path created by ctx.arcTo
Side benefit is it will animated any path rendered using ctx.stroke
requestAnimationFrame(mainLoop);
// Line is defined in unit space.
// Origin is at center of canvas, -1,-1 top left, 1, 1 bottom right
// Unit box is square and will be scaled to fit the canvas size.
// Note I did not use ctx.setTransform to better highlight what is scaled and what is not.
const ctx = canvas.getContext("2d");
var w, h, w2, h2; // canvas size and half size
var linePos; // current dash offset
var scale; // canvas scale
const LINE_WIDTH = 0.05; // in units
const LINE_STYLE = "#000"; // black
const LINE_SPEED = 1; // in units per second
const MAX_LINE_LENGTH = 9; // in units approx
const RADIUS = 0.08; //Arc radius in units
const SHAPE = [[0.4, 0.2], [0.8, 0.2], [0.5, 0.5], [0.95, 0.95], [0.0, 0.5], [-0.95, 0.95], [-0.5, 0.5], [-0.8, 0.2], [-0.2, 0.2], [-0.2, -0.2], [-0.8, -0.2], [-0.5, -0.5], [-0.95, -0.95], [0.0, -0.5], [0.95,-0.95], [0.5, -0.5], [0.8, -0.2], [0.2, -0.2], [0.2, 0.2], [0.6, 0.2], [0.8, 0.2]];
function sizeCanvas() {
w2 = (w = canvas.width = innerWidth) / 2;
h2 = (h = canvas.height = innerHeight) / 2;
scale = Math.min(w2, h2);
resetLine();
}
function addToPath(shape) {
var p1, p2;
for (p2 of shape) {
!p2.length ?
ctx.closePath() :
(p1 ? ctx.arcTo(p1[0] * scale + w2, p1[1] * scale + h2, p2[0] * scale + w2, p2[1] * scale + h2, RADIUS * scale) :
ctx.lineTo(p2[0] * scale + w2, p2[1] * scale + h2)
);
p1 = p2;
}
}
function resetLine() {
ctx.setLineDash([MAX_LINE_LENGTH * scale, MAX_LINE_LENGTH * scale]);
linePos = MAX_LINE_LENGTH * scale;
ctx.lineWidth = LINE_WIDTH * scale;
ctx.lineJoin = ctx.lineCap = "round";
}
function mainLoop() {
if (w !== innerWidth || h !== innerHeight) { sizeCanvas() }
else { ctx.clearRect(0, 0, w, h) }
ctx.beginPath();
addToPath(SHAPE);
ctx.lineDashOffset = (linePos -= LINE_SPEED * scale * (1 / 60));
ctx.stroke();
if (linePos <= 0) { resetLine() }
requestAnimationFrame(mainLoop);
}
body {
padding: 0px,
margin: 0px;
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

canvas drawImage upscale and then crop

Using drawImage, I am trying to do the following with an image that is 1280x720...
Upscale it to 1920x1080
Crop it so that only 600x1080 remains from the centre
I have this so far...
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
img=new Image();
img.onload=function(){
canvas.width=1920;
canvas.height=1080;
ctx.drawImage(img,0,0,img.width,img.height,0,0,1920,1080);
}
img.src="https://dummyimage.com/1280x720/000/fff";
//img.src="https://dummyimage.com/1920x1080/000/fff";
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=100 height=100></canvas>
The upscaling part I have got working but now I am looking at the crop, anyone have an example I can see?
Is there any benefit from cropping first before rescaling?
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, 1920, 1080);
Something like that:
x = (img.width - 600) / 2;
y = (img.height - 1080) / 2;
ctx.drawImage(img, x, y, 600, 1080, 0, 0, 1920, 1080);
but check for the destination area parameters depending on what exactly you want to get.
Clip image to fit canvas
The canvas will clip the image for you.
By default all rendering has a clip region set to the canvas size. Because the clip is performed regardless of the size of the content (all content must be checked against the clip region and is done in hardware (GPU)) rendering the full image is slightly quicker than rendering a portion of the image.
ctx.drawImage(image,x,y); // is the quicker function
ctx.drawImage(image,ix,iy,iw,ih,x,y,w,h); // the slower function
Note; This is not true when the rendered visible destination content is significantly smaller than the image source
Thus to render the image cropped to a smaller canvas you only need to find the center and then render the image at half its size away from that center.
ctx.drawImage(
image, // image to render
(ctx.canvas.width - image.width) / 2, // center sub half image width
(ctx.canvas.height - image.height) / 2 // center sub half image height
);
If you need to up scale first the following will render any size image to fit 1080 height.
const imgW = 1920;
const imgH = 1080;
ctx.drawImage(
image, // image to render
(ctx.canvas.width - imgW) / 2, // center sub half image display width
(ctx.canvas.height - imgH) / 2, // center sub half image display height
imgW, imgH
);
Crop image
If you wish to save memory and crop the image you use a canvas to hold the cropped image.
function cropImageCenter(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const ctx = c.getContext("2d");
ctx.drawImage(image,(w - image.width) / 2, (h - image.height) / 2);
return c;
}
var img = new Image;
img.src = "imageURL1280by720.jpg";
img.onload = () => {
img = cropImageCenter(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};
Or to upscale and crop
function scaleCropToHeight(image,w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
const scale = h / image.height;
const ctx = c.getContext("2d");
ctx.drawImage(
image,
(w - image.width * scale) / 2,
(h - image.height * scale) / 2,
image.width * scale,
image.height * scale
);
return c;
}
var img = new Image;
img.src = "imageURL1920by1080.jpg";
img.onload = () => {
img = scaleCropToHeight(img, 600, 1080);
ctx.drawImage(img,0,0); /// render cropped image on to canvas
};

Canvas image: center wide image and hide overflow

I have a very wide image that exceeds mostly viewing widths and must be rendered using a <canvas> tag. How would I hide the overflow and also center the image?
In other words, I'm looking for the canvas equivalent to background-position: center.
Ideally, this would be done in a way which is responsive - so if the viewing window is resized, the image stays centered.
Here's an example:
window.onload = function() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = document.getElementById("image");
ctx.drawImage(img, 0, 0);
};
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
canvas {
overflow: hidden;
}
.canvasContainer {
width: 100%;
overflow-x: hidden;
}
img {
display: none;
}
<div class="container">
<div class="canvasContainer">
<img id="image" src="http://via.placeholder.com/2400x800?text=center" />
<canvas id="canvas" width="2400" height="800" />
</div>
</div>
Note: There is a text Center present in the placeholder, but is currently not visible
This code should do what you need.
The image width should be set to the canvas.width to avoid the image overflowing the canvas.
The image height is now relative to the image width so the ratio stays the same.
I have included an event listener which will resize your canvas/image to the size of the window.
function init() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = document.getElementById("image");
var canHeight = window.innerWidth / 4;
canvas.width = window.innerWidth;
canvas.height = canHeight;
var width = canvas.width;
ctx.drawImage(img, 0, 0, width, canHeight);
}
init();
window.addEventListener('resize', function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
init();
});
For the canvas you just draw the image where you want it. It will not add scroll bars. The example loads the image and centers it on the canvas. You can click to see the image scaled to fit (see all the image), fill (see full height, or full width whichever fits best to fill the canvas) and click again to see at full resolution centered.
const image = new Image();
image.src = "http://via.placeholder.com/2400x800";
image.onload = showImage;
addEventListener("resize",showImageFit)
function drawText(text){
const ctx = canvas.getContext("2d");
ctx.font = "28px arial";
ctx.textAlign = "center";
ctx.fillText(text,canvas.width / 2, 28);
}
function showImage(){
canvas.width = innerWidth - 8;
canvas.height = innerHeight - 8;
const x = (canvas.width / 2 - image.naturalWidth / 2) | 0;
const y = (canvas.height / 2 - image.naturalHeight / 2) | 0;
canvas.getContext("2d").drawImage(image,x,y);
drawText("Click to scale image to fit");
canvas.onclick = showImageFit;
}
function showImageFit(){
canvas.width = innerWidth - 8;
canvas.height = innerHeight - 8;
const scale = Math.min( canvas.width /image.naturalWidth , canvas.height / image.naturalHeight );
const x = (canvas.width / 2 - (image.naturalWidth / 2) * scale) | 0;
const y = (canvas.height / 2 - (image.naturalHeight / 2) * scale) | 0;
canvas.getContext("2d").drawImage(image,x,y,image.naturalWidth * scale, image.naturalHeight * scale);
drawText("Click to scale image to fill");
canvas.onclick = showImageFill;
}
function showImageFill(){
canvas.width = innerWidth - 8;
canvas.height = innerHeight - 8;
const scale = Math.max( canvas.width /image.naturalWidth , canvas.height / image.naturalHeight );
const x = (canvas.width / 2 - (image.naturalWidth / 2) * scale) | 0;
const y = (canvas.height / 2 - (image.naturalHeight / 2) * scale) | 0;
canvas.getContext("2d").drawImage(image,x,y,image.naturalWidth * scale, image.naturalHeight * scale);
drawText("Click to see image at full resolution and centered");
canvas.onclick = showImage;
}
canvas {
border : 2px solid black;
}
body {
margin : 0px;
}
<canvas id="canvas"></canvas>
Thanks for the feedback - the suggested solutions work with solving the canvas problem.
I found another solution, which was to treat the canvas as I would any other oversized element and use CSS.
+-------------------------------------------+
| page container |
+---------------------------------------------------------+
| |
| canvas |
+---------------------------------------------------------+
| |
+-------------------------------------------+
Solution (and illustration) taken from here:
center oversized image in div
margin-left: 50%;
transform: translateX(-50%);

Animate a Fill Circle using Canvas

Basically I want to be able to Fill a Circle using canvas, but it animate to a certain percentage.
I.e only have the circle fill up 80% of the way.
My canvas knowledge isn't amazing, Here is an image i made in photoshop to display what i want.
I want the circle to start empty and then Fill up to say 70% of the circle.
Is this possible with Canvas, if so? can anyone shed some light on how to do it?
Here is a fiddle of what I've managed
http://jsfiddle.net/6Vm67/
var canvas = document.getElementById('Circle');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 80;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = '#13a8a4';
context.fill();
context.lineWidth = 10;
context.strokeStyle = '#ffffff';
context.stroke();
Any help would be massively appreciated
Clipping regions make this very easy. All you have to do is make a circular clipping region and then fill a rectangle of some size to get a "partial circle" worth of fill. Here's an example:
var canvas = document.getElementById('Circle');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 80;
var full = radius*2;
var amount = 0;
var amountToIncrease = 10;
function draw() {
context.save();
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.clip(); // Make a clipping region out of this path
// instead of filling the arc, we fill a variable-sized rectangle
// that is clipped to the arc
context.fillStyle = '#13a8a4';
// We want the rectangle to get progressively taller starting from the bottom
// There are two ways to do this:
// 1. Change the Y value and height every time
// 2. Using a negative height
// I'm lazy, so we're going with 2
context.fillRect(centerX - radius, centerY + radius, radius * 2, -amount);
context.restore(); // reset clipping region
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.lineWidth = 10;
context.strokeStyle = '#000000';
context.stroke();
// Every time, raise amount by some value:
amount += amountToIncrease;
if (amount > full) amount = 0; // restart
}
draw();
// Every second we'll fill more;
setInterval(draw, 1000);
http://jsfiddle.net/simonsarris/pby9r/
This is a little more dynamic, object-oriented version, so you can configure the options as the circle radius, border width, colors, duration and step of animation, you can also animate the circle to a certain percentage. It was quite fun to write this.
<canvas id="Circle" width="300" height="300"></canvas>
<script>
function Animation( opt ) {
var context = opt.canvas.getContext("2d");
var handle = 0;
var current = 0;
var percent = 0;
this.start = function( percentage ) {
percent = percentage;
// start the interval
handle = setInterval( draw, opt.interval );
}
// fill the background color
context.fillStyle = opt.backcolor;
context.fillRect( 0, 0, opt.width, opt.height );
// draw a circle
context.arc( opt.width / 2, opt.height / 2, opt.radius, 0, 2 * Math.PI, false );
context.lineWidth = opt.linewidth;
context.strokeStyle = opt.circlecolor;
context.stroke();
function draw() {
// make a circular clipping region
context.beginPath();
context.arc( opt.width / 2, opt.height / 2, opt.radius-(opt.linewidth/2), 0, 2 * Math.PI, false );
context.clip();
// draw the current rectangle
var height = ((100-current)*opt.radius*2)/100 + (opt.height-(opt.radius*2))/2;
context.fillStyle = opt.fillcolor;
context.fillRect( 0, height, opt.width, opt.radius*2 );
// clear the interval when the animation is over
if ( current < percent ) current+=opt.step;
else clearInterval(handle);
}
}
// create the new object, add options, and start the animation with desired percentage
var canvas = document.getElementById("Circle");
new Animation({
'canvas': canvas,
'width': canvas.width,
'height': canvas.height,
'radius': 100,
'linewidth': 10,
'interval': 20,
'step': 1,
'backcolor': '#666',
'circlecolor': '#fff',
'fillcolor': '#339999'
}).start( 70 );
</script>

Canvas drawings, like lines, are blurry

I have a <div style="border:1px solid border;" /> and canvas, which is drawn using:
context.lineWidth = 1;
context.strokeStyle = "gray";
The drawing looks quite blurry (lineWidth less than one creates even worse picture), and nothing near to the div's border. Is it possible to get the same quality of drawing as HTML using canvas?
var ctx = document.getElementById("canvas").getContext("2d");
ctx.lineWidth = 1;
ctx.moveTo(2, 2);
ctx.lineTo(98, 2);
ctx.lineTo(98, 98);
ctx.lineTo(2, 98);
ctx.lineTo(2, 2);
ctx.stroke();
div {
border: 1px solid black;
width: 100px;
height: 100px;
}
canvas, div {background-color: #F5F5F5;}
canvas {border: 1px solid white;display: block;}
<table>
<tr><td>Line on canvas:</td><td>1px border:</td></tr>
<tr><td><canvas id="canvas" width="100" height="100"/></td><td><div> </div></td></tr>
</table>
I found that setting the canvas size in CSS caused my images to be displayed in a blurry manner.
Try this:
<canvas id="preview" width="640" height="260"></canvas>
as per my post: HTML Blurry Canvas Images
When drawing lines in canvas, you actually need to straddle the pixels. It was a bizarre choice in the API in my opinion, but easy to work with:
Instead of this:
context.moveTo(10, 0);
context.lineTo(10, 30);
Do this:
context.moveTo(10.5, 0);
context.lineTo(10.5, 30);
Dive into HTML5's canvas chapter talks about this nicely
Even easier fix is to just use this:
context = canvas.context2d;
context.translate(0.5, 0.5);
From here on out your coordinates should be adjusted by that 0.5 pixel.
I use a retina display and I found a solution that worked for me here.
Small recap :
First you need to set the size of your canvas twice as large as you want it, for example :
canvas = document.getElementById('myCanvas');
canvas.width = 200;
canvas.height = 200;
Then using CSS you set it to the desired size :
canvas.style.width = "100px";
canvas.style.height = "100px";
And finally you scale the drawing context by 2 :
const dpi = window.devicePixelRatio;
canvas.getContext('2d').scale(dpi, dpi);
The Mozilla website has example code for how to apply the correct resolution in a canvas:
https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Set display size (css pixels).
var size = 200;
canvas.style.width = size + "px";
canvas.style.height = size + "px";
// Set actual size in memory (scaled to account for extra pixel density).
var scale = window.devicePixelRatio; // Change to 1 on retina screens to see blurry canvas.
canvas.width = size * scale;
canvas.height = size * scale;
// Normalize coordinate system to use css pixels.
ctx.scale(scale, scale);
ctx.fillStyle = "#bada55";
ctx.fillRect(10, 10, 300, 300);
ctx.fillStyle = "#ffffff";
ctx.font = '18px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var x = size / 2;
var y = size / 2;
var textString = "I love MDN";
ctx.fillText(textString, x, y);
<canvas id="canvas"></canvas>
Lines are blurred because the canvas virtual size is zoomed to its HTML element actual size. To overcome this issue you need to adjust canvas virtual size before drawing:
function Draw () {
var e, surface;
e = document.getElementById ("surface");
/* Begin size adjusting. */
e.width = e.offsetWidth;
e.height = e.offsetHeight;
/* End size adjusting. */
surface = e.getContext ("2d");
surface.strokeRect (10, 10, 20, 20);
}
window.onload = Draw ()
<!DOCTYPE html>
<html>
<head>
<title>Canvas size adjusting demo</title>
</head>
<body>
<canvas id="surface"></canvas>
</body>
</html>
HTML:
Ok, I've figured this out once and for all. You need to do two things:
place any lines on 0.5 px. Refer to this, which provides a great explanation:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#A_lineWidth_example
There are essentially two heights and two widths associated with the canvas. There is the canvas height and width and then there is the css style height and width of the element. These need to be in sync.
To do this, you need to calculate the css height and width as:
var myCanvasEl = document.getElementById('myCanvas');
var ctx = myCanvasEl.getContext('2d');
myCanvasEl.style.height = myCanvasEl.height / window.devicePixelRatio + "px";
myCanvasEl.style.width = myCanvasEl.width / window.devicePixelRatio + "px";
where myCanvasEl.style.height and myCanvasEl.style.widthis the css styling height and width of the element, while myCanvasEl.height and myCanvasEl.width is the height and width of the canvas.
OLD ANSWER (superseded by above):
This is the best solution I've found in 2020. Notice I've multiplied the devicePixelRatio by 2:
var size = 100;
var scale = window.devicePixelRatio*2;
context.width = size * scale;
cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
context.height = size * scale;
context.scale(scale, scale);
Something else that nobody talked about here when images are scaled (which was my issue) is imageSmoothingEnabled.
The imageSmoothingEnabled property of the CanvasRenderingContext2D interface, part of the Canvas API, determines whether scaled images are smoothed (true, default) or not (false). On getting the imageSmoothingEnabled property, the last value it was set to is returned.
This property is useful for games and other apps that use pixel art. When enlarging images, the default resizing algorithm will blur the pixels. Set this property to false to retain the pixels' sharpness.
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled
To disable it, simply set the properity to false:
ctx.imageSmoothingEnabled = false;
canvas.width=canvas.clientWidth
canvas.height=canvas.clientHeight
To avoid this issue in animation I would like to share a small demo.
Basically I am checking increment values each time & jumping in a set of 1px by removing float values.
HTML:
<canvas id="canvas" width="600" height="600"></canvas>
CSS:
html, body{
height: 100%;
}
body{
font-family: monaco, Consolas,"Lucida Console", monospace;
background: #000;
}
canvas{
position: fixed;
top: 0;
left: 0;
transform: translateZ(0);
}
JS:
canvas = document.getElementById('canvas');
ctx = canvas.getContext('2d');
ctx.translate(0.5, 0.5);
var i = 0;
var iInc = 0.005;
var range = 0.5;
raf = window.requestAnimationFrame(draw);
function draw() {
var animInc = EasingFunctions.easeInQuad(i) * 250;
ctx.clearRect(0, 0, 600, 600);
ctx.save();
ctx.beginPath();
ctx.strokeStyle = '#fff';
var rectInc = 10 + animInc;
// Avoid Half Pixel
rectIncFloat = rectInc % 1; // Getting decimal value.
rectInc = rectInc - rectIncFloat; // Removing decimal.
// console.log(rectInc);
ctx.rect(rectInc, rectInc, 130, 60);
ctx.stroke();
ctx.closePath();
ctx.font = "14px arial";
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.fillText("MAIN BUTTON", 65.5 + rectInc, 35.5 + rectInc);
i += iInc;
if (i >= 1) {
iInc = -iInc;
}
if (i <= 0) {
iInc = Math.abs(iInc);
}
raf = window.requestAnimationFrame(draw);
}
// Easing
EasingFunctions = {
// no easing, no acceleration
linear: function(t) {
return t
},
// accelerating from zero velocity
easeInQuad: function(t) {
return t * t
},
// decelerating to zero velocity
easeOutQuad: function(t) {
return t * (2 - t)
},
// acceleration until halfway, then deceleration
easeInOutQuad: function(t) {
return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
},
// accelerating from zero velocity
easeInCubic: function(t) {
return t * t * t
},
// decelerating to zero velocity
easeOutCubic: function(t) {
return (--t) * t * t + 1
},
// acceleration until halfway, then deceleration
easeInOutCubic: function(t) {
return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
},
// accelerating from zero velocity
easeInQuart: function(t) {
return t * t * t * t
},
// decelerating to zero velocity
easeOutQuart: function(t) {
return 1 - (--t) * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuart: function(t) {
return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
},
// accelerating from zero velocity
easeInQuint: function(t) {
return t * t * t * t * t
},
// decelerating to zero velocity
easeOutQuint: function(t) {
return 1 + (--t) * t * t * t * t
},
// acceleration until halfway, then deceleration
easeInOutQuint: function(t) {
return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
}
}
A related issue could be that you're setting the <canvas>'s height and width from CSS or other sources. I'm guessing it scales the canvas and associated drawings. Setting the <canvas> size using the height and width property (either from the HTML tag or a JS script) resolved the error for me.
Here is my solution: set width and height for canvas
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
Also set in css, so it will not overflow from its parent
canvas {
width: 100%
height: 100%
}
Although LittleJoe's solution worked perfect on desktop it didn't work on mobile because on iphone 11 pro for example the dpi is 3 so I had to set width/height based on dpi. At the end it worked:
let width = 100, height = 100;
const dpi = window.devicePixelRatio;
canvas = document.getElementById('myCanvas');
canvas.width = width * dpi;
canvas.height = height * dpi;
canvas.style.width = width + "px";
canvas.style.height = width + "px";
canvas.getContext('2d').scale(dpi, dpi);
in order to get rid of the blurryness you need to set the size of the canvas in two manners:
first withcanvas.width = yourwidthhere;
and canvas.height = yourheighthere;
second by setting the css attribute either by js or a stylesheet
HTML:
<canvas class="canvas_hangman"></canvas>
JS:
function setUpCanvas() {
canvas = document.getElementsByClassName("canvas_hangman")[0];
ctx = canvas.getContext('2d');
ctx.translate(0.5, 0.5);
// Set display size (vw/vh).
var sizeWidth = 80 * window.innerWidth / 100,
sizeHeight = 100 * window.innerHeight / 100 || 766;
// console.log(sizeWidth, sizeHeight);
// Setting the canvas height and width to be responsive
canvas.width = sizeWidth;
canvas.height = sizeHeight;
canvas.style.width = sizeWidth;
canvas.style.height = sizeHeight;
}
window.onload = setUpCanvas();
This perfectly sets up your HTML canvas to draw on, and in a responsive manner too :)