canvas drawImage upscale and then crop - html

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
};

Related

How to resize and rotate image simulataneously using canvas

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}

Are HTML canvas clip paths inclusive or exclusive?

I've been working on a Typescript based touch screen client for our CQC home automation platform, and ran across something odd. There are lots of places where various graphical elements are layered over images. When it's time to update some area of the screen, I set a clip area and update.
But I always ended up with a line around everything, which was the color of the underlying color fill behind the image. I of course blamed myself. But, in the end, instead of committing suicide, I did a little test program.
It seems to indicate that drawImage() does NOT include the clip path boundary, while a color fill does. So blitting over the part of the images that underlies the area I'm updating doesn't completely fill the target area, leaving a line around the area.
After that simple program demonstrated the problem, I went back and for image updates I inflated the clip area by one, but left it alone for everything else, and now it's all working. I tested this in Chrome and Edge, just to make sure it wasn't some bug, and they both act exactly the same.
Strangely, I've never see any statement in the docs about whether clip paths are intended to be exclusive or inclusive of the boundary, but surely it shouldn't be one way for one type of primitive and another way for others, right?
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x + w, y);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x, y + h);
ctx.lineTo(x, y);
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "test.jpg";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
window.addEventListener("load", init, false);
I think they behave same.
Clip region are not inclusive of the border, but they can use anti aliasing.
Chrome was not using this techinque and was giving jagged lines on clipping. ( probably they changed recently ).
The thin black border is the side effect of a compositing operation.
The clip region is across a pixel. so the fillRect will draw black everywhere, but the border will be 50% black and 50% transparent, compositing with the first image draw.
The second draw image get clpped, at the border with 50% opacity to simulate the half pixel. at this point at the clip border you have:
image 100%
black fill 50%
image 50%
This will make a small dark border appear.
function drawRect(ctx, x, y, w, h) {
ctx.moveTo(x, y);
ctx.lineTo(x, y + h);
ctx.lineTo(x + w, y + h);
ctx.lineTo(x + w, y);
ctx.closePath();
}
function init()
{
var canvas = document.getElementById("output");
canvas.style.width = 480;
canvas.style.height = 480;
canvas.width = 480;
canvas.height = 480;
var drawCtx = canvas.getContext("2d");
drawCtx.translate(0.5, 0.5);
var img = new Image();
img.src = "http://fabricjs.com/assets/printio.png";
img.onload = function() {
// DRaw the image
drawCtx.drawImage(img, 0, 0);
// SEt a clip path
drawCtx.beginPath();
drawRect(drawCtx, 10, 10, 200, 200);
drawCtx.clip();
// Fill the whole area, which fills the clip area
drawCtx.fillStyle = "black";
drawCtx.fillRect(0, 0, 480, 480);
// Draw the image again, which should fill the area
drawCtx.drawImage(img, 0, 0);
// But it ends up with a black line around it
}
}
init();
<canvas id="output" />

How to maintain aspect ratio in case of rendering images using texture2D in WebGL to canvas?

I have a image of width * height = 1442 * 1303,
I am able to read them and render to canvas successfully via webgl's texture2D.
In client side ,I am having a arraybuffer that gets the image data which is of size = width*height*4.
So, How to maintain aspect ratio of the image when my canvas width and height is window.innerWidth*0.90 and window.innerHeight*0.90.
Also, I have to directly render using arraybuffer via WEBGL 2dTexture so, I can't use any 2d canvs API such as drawImage. Please suggest something.
There's literally a million answers to this question.
First there's the size of your image, then the size you decide to draw it, and the size of the canvas, followed by the size the canvas is displayed. There's the positions of the vertices your using as well which could be anything.
See this article on WebGL which points out that WebGL uses clip space coordinates (-1 to +1) and this article points out that the size a canvas is displayed is separate from its resolution.
Let's assume you want to draw the image as large as possible and fit it to the canvas.
So first let's look up the size the canvas is being displayed
var canvasDisplayWidth = gl.canvas.clientWidth;
var canvasDisplayHeight = gl.canvas.clientHeight;
Let's assume we want to draw the image as large as possible so
first try fitting the width to the canvas
var imageDisplayWidth = canvasDisplayWidth;
var imageDisplayHeight = img.height * imageDisplayWidth / img.width;
Now let's check if it fit? If not let's use the height
if (imageDrawHeight > canvasDisplayHeight) {
imageDisplayHeight = canvasDisplayHeight;
imageDisplayWidth = img.width * imageDisplayHeight / img.height;
}
Now we need to convert imageDisplayWidth and imageDisplayHeight to the size of pixels in the canvas. Note: If the canvas is being displayed the same size
as the its resolution you can skip this step as the display size and the draw size will be the same.
// make our image take into account the pixel aspect
var canvasPixelsAcrossPerDisplayPixel = gl.canvas.width / canvasDisplayWidth;
var canvasPixelsDownPerDisplayPixel = gl.canvas.height / canvasDisplayHeight;
var imageDrawWidth = imageDisplayWidth * canvasPixelsAcrossPerDisplayPixel;
var imageDrawHeight = imageDisplayHeight * canvasPixelsDownPerDisplayPixel;
Now we need to convert that to clip space
var clipWidth = imageDrawWidth / canvas.width;
var clipHeight = imageDrawHeight / canvas.height;
Now, given a unit quad we can just scale it to fit that size.
var m = m4.identity();
// convert our square unit quad match the size we want
m4.scale(m, [clipWidth, clipHeight, 1], m);
// move our unit square from 0,0 (the center) to the bottom, top corner
m4.translate(m, [-1, 1, 0], m);
// scale our unit sqaure to cover the clip space
m4.scale(m, [2, -2, 1], m);
Now can draw with that matrix and our unit quad
var m4 = twgl.m4;
var gl = twgl.getWebGLContext(document.getElementById("c"));
var programInfo = twgl.createProgramInfo(gl, ["vs", "fs"]);
var arrays = {
position: {
numComponents: 2,
data: [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
],
},
};
var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
// Lets make a texture using a 2d canvas
// There's a circle in the middle. If our
// code is correct it will be a circle when
// drawn (not an oval or ellipse)
var ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 100;
ctx.canvas.height = 75;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = "blue";
ctx.fillRect(10, 10, ctx.canvas.width - 20, ctx.canvas.height - 20);
ctx.strokeStyle = "yellow";
ctx.lineWidth = 20;
ctx.beginPath();
ctx.arc(
ctx.canvas.width / 2, ctx.canvas.height / 2,
Math.min(ctx.canvas.width, ctx.canvas.height) / 2 - 20,
0, Math.PI * 2, false);
ctx.stroke();
var img = ctx.canvas;
var tex = twgl.createTexture(gl, {
src: img,
});
var canvasDisplayWidth = gl.canvas.clientWidth;
var canvasDisplayHeight = gl.canvas.clientHeight;
// Let's assume we want to draw the image as large as possible so
// first try fitting the width to the canvas
var imageDisplayWidth = canvasDisplayWidth;
var imageDisplayHeight = img.height * imageDisplayWidth / img.width;
// Now let's check if it fit? If not let's use the height
if (imageDisplayHeight > canvasDisplayHeight) {
imageDisplayHeight = canvasDisplayHeight;
imageDisplayWidth = img.width * imageDisplayHeight / img.height;
}
// Now we need to convert `imageDisplayWidth` and `imageDisplayHeight` to the size of pixels
// in the canvas. Note: If the canvas is being displayed the same size
// as the its resolution you can skip this step
var canvasPixelsAcrossPerDisplayPixel = gl.canvas.width / canvasDisplayWidth;
var canvasPixelsDownPerDisplayPixel = gl.canvas.height / canvasDisplayHeight;
var imageDrawWidth = imageDisplayWidth * canvasPixelsAcrossPerDisplayPixel;
var imageDrawHeight = imageDisplayHeight * canvasPixelsDownPerDisplayPixel;
// Now we need to convert that to clip space
var clipWidth = imageDrawWidth / gl.canvas.width;
var clipHeight = imageDrawHeight / gl.canvas.height;
// Now, given a unit quad we can just scale it to fit that size.
var m = m4.identity();
// convert our square unit quad to something to match the image's aspect
m4.scale(m, [clipWidth, clipHeight, 1], m);
// move our unit square from 0,0 (the center) to the bottom, left corner
m4.translate(m, [-1, 1, 0], m);
// scale our unit square to cover the clip space
m4.scale(m, [2, -2, 1], m);
var uniforms = {
texture: tex,
matrix: m,
};
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, uniforms);
twgl.drawBufferInfo(gl, gl.TRIANGLES, bufferInfo);
<script id="vs" type="notjs">
attribute vec4 position;
uniform mat4 matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = matrix * position;
// using position since we know it's a unit quad
v_texcoord = position.xy;
}
</script>
<script id="fs" type="notjs">
precision mediump float;
uniform sampler2D texture;
varying vec2 v_texcoord;
void main() {
gl_FragColor = texture2D(texture, v_texcoord);
}
</script>
<script src="https://twgljs.org/dist/twgl-full.min.js"></script>
<canvas id="c" width="50" height="100" style="width: 300px; height: 150px; border: 1px solid black;"></canvas>

how to display part of the canvas after scaling in html5

How to display part of canvas after scaling in html5
For ex:
var c =document.getElementById("mycanvas");
var canvas = c.getContext("2d");
canvas.scale(4,4);
canvas.drawImage(img,0,0);
canvas.drawImage(img,200,200);
img is some image.
Here i have scaled it some value, now it displays the top-left region of the canvas(with only the top-left image) but what if i want it to display bottom-right region(only the bottom-right image) or according to the coordinates i give to it. How can i do that?
Can someone plz help me on this? I will be very grateful.....
If you are scaling you must remember that the coordinates you use to position will also be scaled up, so if you are scaling by a factor of 4 than your coordinates will be 200 * 4 and not 200. To scale the image alone you can use the call drawImage(img,x,y,width,height) and use...
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
var width = img.width * scale;
var height = img.height * scale;
ctx.drawImage(img, 0, 0, width, height);
ctx.drawImage(img, 200, 200, width, height);
Or you will need to divide your coordinates by the scale factor...
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 200 / scale, 200 / scale);
I've put together a fiddle showing the latter approach using clipping to ensure that the image stays in its quadrant http://jsfiddle.net/ujtd2/
Edit using the state stack you can prevent having to do the conversion yourself.
var c = document.getElementById("mycanvas");
var ctx = c.getContext("2d");
var scale = 4;
// add a new item to the context state stack
ctx.save();
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
// discard the previous state be restoring the last state
// back to normal scale
ctx.restore();
// Set translation
ctx.translate(200, 200);
// Repeat for second image
ctx.save();
ctx.scale(scale, scale);
ctx.drawImage(img, 0, 0);
I follow now. To zoom in and show the part of the scene from a specific coordinate use translate.
ctx.scale(4, 4);
ctx.translate(-200, -200);
ctx.drawImage(img, 0, 0);
ctx.drawImage(img, 200, 200);
This zooms in by 4 and then moves the visible portion down and right by 200 pixels, by translating the drawing coordinates up and left by 200 pixels.
You can use drawImage the following way :
drawImage(
image,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destinationX,
destinationY,
destinationWidth,
destinationHeight
);
You determine the region of the source you want and then the place you want to put it on your canvas.
You can find some info here : MDN Draw image documentation

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 :)