HTML5 Canvas lines not having consistent width - html

I'm drawing a rectangle on HTML5 canvas using the below code:
canvasId = $('#objectData').find('#miImages '+imageId+' .imageContainer canvas').attr("id");
canvas = document.getElementById(canvasId);
context = canvas.getContext("2d");
context.beginPath();
context.rect(coordinateArray[0],coordinateArray[1],coordinateArray[2],coordinateArray[3]);
context.lineWidth = 2;
context.strokeStyle = "DarkBlue";
context.stroke();
The problem is that the two vertical lines are a lot thicker than the two horizontal lines, what is causing this?
EDIT: I have height set at 89 and width set at 802. When I remove the height the rectangle no longer distorts.

I was sizing my canvas with CSS instead of JavaScript which was scaling the canvas rather than actually resizeing it. I fixed this using the below code.
function applyCanvasSize(){
$("#objectData img").on('load', function(){
var theHeight = $(this).height();
var canvasId = $(this).next('canvas').attr('id');
var canvas = document.getElementById(canvasId);
if (canvas.getContext) {
canvas.width = 802;
canvas.height = theHeight;
}
});
}

Kinda late but had the same problem.
Somewhere in my code, I was changing the size of the canvas using style.width and style.height. Directly use width and height instead. Without "px" in the end (thus only accepts pixels ?). You wont get the distortion anymore.

Related

HTML5 - scaled image is blurry when drawn on canvas in chrome & opera

When i draw scaled image on canvas using the drawImage() function it looks slightly blurry in Chrome & Opera, but if i draw the full size image first and then the scaled one it looks crisp. What causes the blurriness and how can i fix it?
Here is the original image:
Here is the result in Chrome & Opera:
const img = new Image();
const crisptCanvas = document.getElementById('crisp-canvas');
const crispContext = crisptCanvas.getContext('2d');
const blurryCanvas = document.getElementById('blurry-canvas');
const blurryContext = blurryCanvas.getContext('2d');
const sx = 0, sy = 0, sWidth = 1980, sHeight = 1251;
const dx = 0, dy = 0;
const scaleFactor = 0.4762626262626263;
// Draw an image on canvas
function scaleImage(scale, context)
{
const dWidth = (sWidth*scale);
const dHeight = (sHeight*scale);
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
};
// When image is loaded draw it on both canvases
img.onload = function(){
// First draw the source image in full scale and then using the -scaleFactor
setTimeout(()=> {
scaleImage(1, crispContext);
scaleImage(scaleFactor, crispContext);
}, 0);
// Draw the image using the -scaleFactor
scaleImage(scaleFactor, blurryContext);
}
img.src = "https://i.stack.imgur.com/eWDSw.png"
<canvas width="944" height="596" id="crisp-canvas" ></canvas>
<canvas width="944" height="596" id="blurry-canvas" ></canvas>
Well after one day trying everything i can think of, i was not able to find a solution when i draw the scaled image on canvas.
Here are some of the things i have tried:
1) I have tried using the scale() method, but results were the same.
2) I have tried setting the imageSmoothingEnabled property, that work well with pixelated art games, but for high resolution images the quality was awful.
3) I have tried using the window.requestAnimationFrame() method than on the first request draw the full scale image, on hidden canvas and after that draw the scaled image on my main canvas. That work well, but when i change the tab(focus on another tab), after a few minutes the images on my main canvas became blurry again. Then i added a method to check when the user focus on my tab, using the Page Visibility API, and again redraw the full scale image on the hidden canvas, but that did not work. Only the last drawn image on the hidden canvas was crisp and all images drawn before the last one were blurry. So the only solution was to create hidden canvases for each image and that is not practical, so i had to try another approach.
So here is the solution came up with:
1) Set width and height canvas properties to my original image size 1980x1251
2) I scaled the canvas, using its style width & height properties
Have in mind that using this method everything drawn on the canvas like shapes, text, lines... will also be scaled. So for example if you draw a rectangle (10x10) pixels. It will have width and height each equal to 10px, only when the canvas width & height style properties match the canvas width & height properties.
canvas.style.width = canvas.width + 'px';
canvas.style.height = canvas.height + 'px';
const img = new Image();
const crispCanvas = document.getElementById('crisp-canvas');
const crispContext = crispCanvas.getContext('2d');
const sx = 0, sy = 0, sWidth = 1980, sHeight = 1251;
const dx = 0, dy = 0;
const scaleFactor = 0.4762626262626263;
// Set crisp canvas width & height properties to match the source image
crispCanvas.width = sWidth;
crispCanvas.height = sHeight;
// Scale the source canvas using width & height style properties
function scaleCanvas(scale, canvas) {
const dWidth = (sWidth*scale);
const dHeight = (sHeight*scale);
canvas.style.width = dWidth + 'px';
canvas.style.height = dHeight + 'px';
}
// When image is loaded, scale the crisp canvas and draw the full size image
img.onload = function(){
scaleCanvas(scaleFactor, crispCanvas);
crispContext.drawImage(img, sx, sy);
}
img.src = "https://i.stack.imgur.com/eWDSw.png";
<canvas id="crisp-canvas" ></canvas>

HTML canvas image is fading out after multiple iterations/reusing

I have a portal which is used for collecting orders from users in hand written format.
In my portal, I am using HTML canvas for getting inputs from user.
Once the user write order and submits it, I will read the drawings from the canvas and saves it into my DB.
HTML
<canvas height="750" width="768" id="userNotes"></canvas>
Javascript
var canvas = document.getElementById('userNotes');
var notesDataURL = canvas.toDataURL();
saveImageDataToDataBase (notesDataURL);
Next time when the user comes for a new order, I will draw this image back into the canvas, so that he can make modifications on the same and submit it as fresh order.
Javascript
var canvas = document.getElementById('userNotes');
var context = canvas.getContext('2d');
var img = new Image();
img.src = imageData;
context.drawImage(img, 0, 0);
Problem that I am facing is that after multiple iterations, the image starts fading out.
One observation is that fading is more at the bottom part of the image and less on the top side.
Consider the below sample images,
After 10 iterations image became like this,
Below is a JS FIddle created using sample code, in this after about 25 iterations fading will be visible(issue is visible only in tablet mentioned below).
https://jsfiddle.net/hz8r993v/
Observation:
An observation which I made is the issue is happening only in a specific tablet model, Samsung SM-P550, which is unfortunately the one my application is build for.
I am not able to reproduce this issue while using this application in my laptop, PC or another sm-p650 tablet.
Currently Only happening in all tablets of model SM-P550. Even I am confused with this observation.
I also tried disabling ImageSmoothingEnabled properties, but not helping.
Any leads/clues are appreciated.
JPEG compression quirk & rounding.
Looking at the image you provided suggests to me that you are incorrectly offsetting the image each time you render it. As you have not provided enough code for anyone to make an assessment as to why this is happening leaves us only to guess.
Lossless JPEG!
My first instinct is that you have offset the drawing or scaled it. I create the example below and offset a jpeg url of the rendered text by 0.2 pixels.
The result no blur Not a real surprise Jpegs are designed so that they can be copied. The artifacts introduced by the compression actually remove the blur introduced by the offset
Draw jpeg image 0.2 pixels down save and repeat.
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
}
}
function iterate(){
count += 1;
copy();
paste(0,0.2);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 150></canvas>
Your image clearly shows a vertical blurring so I set about finding when I can blur the image so that it over comes the Jpeg compression. Offsetting by 0.5 or more does not blur the image just scrolls pixel perfect 100 pixels (note I copy past 100 times move image 0.5 pixels down yet resulting image has moved 100 pixels. I have marked row 100)
Jpeg turns 0.5 pixel steps into 1 pixel steps
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
if(count === 100){
ctx.fillRect(0,100,canvas.width,1);
}
}
}
function iterate(){
count += 1;
copy();
paste(0,0.5);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 150></canvas>
At end redline is pixel row 100.
Seams that Jpeg compression is much better at preserving the original src image pixels than I suspected. But that does not help solve the problem.
Device specific
So is it a quirk of the device. I look up the specs and nothing stands out. I begin to suspect that you may have a canvas display scaling issues (the canvas resolution not matching the display size)
I start to set up the snippet to use differing resolutions and by shear chance I run the code at the same resolution as the samsung device mentioned in the question.
Blurring yay... but i did not change the canvas pixel scaling, all I did was change the canvas resolution.
Example of blurring.
Note that offset is 0.01 pixels (100th of a pixel)
var ctx = canvas.getContext("2d");
ctx.font = "64px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("testing",canvas.width / 2,canvas.height / 2);
ctx.font = "18px Arial";
var count = 0;
var imgURL;
function copy(){
imgURL = canvas.toDataURL();
}
function paste(x,y){
var img = new Image;
img.src = imgURL;
img.onload = function(){
ctx.fillStyle = "white";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.drawImage(img,x,y);
ctx.fillStyle = "red";
ctx.fillText("Copy : " + count,canvas.width / 2,count * 20);
}
}
function iterate(){
count += 1;
copy();
paste(0,0.01);
if(count < 100){
setTimeout(iterate,50);
}
}
iterate();
<canvas id="canvas" width = 300 height = 1023></canvas>
Possible fix.
As there is not enough code to give a complete answer the above experiments have given me enough information to make an educated guess.
The problem is specific to the resolution 1024 height. (I have tried a few other (very limited) resolutions and could not get blurring)
The blurring does not occur at that resolution if the image is rendered at the pixel boundaries.
The possible fix.
When you render the image convert the render coordinates to integers using Math.floor(coordinate) this will ensure there is no fractional offset, even very tiny offsets that should not affect the image can be amplified by the jpeg compression when the resolution is at 1024 (and maybe some other resolutions as well).
var oldImage = new Image;
oldImage.src = "old image url";
oldImage.onload = function(){
ctx.drawImage(oldImage,Math.floor(x),Math.floor(y));
}
Hope this helps.
I am probably not experienced enough to tell you what about that tablet version, or what in your HTML/Javascript could be causing this issue. I am, however, good at problem solving, and solving puzzles.
I have two possible guesses:
Is there anything in your code, which could lead to a change in the resolution? The only reason that I think this may be part of the cause, is that because the image is being redrawn on a canvas that is the same size, any change in the image size could cause the image to become slightly pixilated. and repetitive changes in the image size would only exaggerate this change.
That is probably not the sole cause. I think that there is probably an additional cause because the issue is only present on the one specific tablet version. I am not familiar with that tablet version, but is there anything about its OS or interface that could alter the file when it is saved or redisplayed?
As a side note, it would be nice if you could provide a comparison image, just to see the change.
Hope that this at least points you in the right direction.

How to draw on canvas on mousedown?

I am looking to simply add a dot to the canvas.
I have the following code:
var canv = document.getElementById("myCanvas");
var context = canv.getContext("2d");
var radius = 5;
var putPoint = function(e){
context.beginPath();
context.arc(e.clientX, e.clientY, radius, 0, Math.PI*2);
context.fill();
}
canv.addEventListener('mousedown', putPoint);
I was learning how to do this with a video tutorial. However they were setting the canvas
as the full width/height of the browser window, whereas my canvas is on 400px * 400px and is contained within a div. I think this is the problem.
So my question is are the "e.client" parameters not working because of my canvas being only
a small part of the window?
If so, how can I track the mouse co-ordinates on my canvas?
You must adjust e.clientX/e.clientY by the offset of the canvas element.
Otherwise you're miscalculating the position of your mouse and your dot is probably being drawn outside the bounds of your canvas.
Here's your code modified to take the canvas offset into account.
var putPoint = function(e){
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var mouseX=e.clientX-BB.left;
var mouseY=e.clientY-BB.top;
context.beginPath();
context.arc(mouseX,mouseY,radius,0,Math.PI*2);
context.fill();
}

html5 canvas background image blur and resize

I have an idea of making a canvas the background with an image. The reason why I want the canvas element to be the background is because I would like to blur the image when the user is logged in. I think that would be a nice effect. And since I don't refresh the page I like it to animate a blurring effect. I am going to have multiple questions in here because they all rely on each other.
I have 2 ideas of how to make this. the first one will have a simple div tag with the background image set. then, when the user have logged in - a canvas element will be created by the script with the background image placed as in the div, then blur it and let it fade in over the div tag.
Another way would be to build the canvas element from the beginning and then let the image be set from the beginning.
This is how I make a background image.
var canvas = document.getElementById('bg_canvas');
var context = canvas.getContext('2d');
var imageObj = new Image();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0);
};
imageObj.src = '2.jpg';
Okay.. thats the easy part.. I need a way to make the height or width in the drawImage function set to auto. if the width is bigger than the height I want the height to be set to auto. I don't want the image to be stretched over the screen. How should I do that?
Now problem number 2. When the user resizes the screen, I would like to resize the image inside the canvas element as well. blurred or not blurred.
i assume it would be set to something like this:
$(document).ready(function() {
winResize();
$(window).resize(function() {
winResize());
}
});
function winResize() {
var canvas = document.getElementById('bg_canvas');
var context = canvas.getContext('2d');
var imageObj = new Image();
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
imageObj.onload = function() {
context.drawImage(imageObj, 0, 0, canvas.height, canvas.width);
};
imageObj.src = '2.jpg';
}
Now I still need the auto mechanism for the height or width of the image.
I am not going to ask for the blur effect, since it should be able to find online somewhere, but what i would like to know is what you would recommend for me to do, when fading in the blurred version. I don't remember there should be a way to fade inside a canvas element, so perhaps I have to duplicate the already existing canvas and then blur and then fade in.
Load the plain image, use a canvas to create a blurred version of the image, then turn that into a real image again for use in an <img> or css background:
function blurImage(imgURL) {
var img = new Image();
img.onload = function() {
var canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
var context = canvas.getContext("2d");
context.drawImage(img,0,0);
/*
code that actually blurs the image goes here
*/
var dataURI = canvas.toDataUrl("image/png");
setBlurredImage(dataURI);
};
img.src = imgURL;
}
with a set handler:
function setBlurredImage(dataURI) {
whateverElement.style.background = "url(" + dataURI + ")";
}
And now you have a background image that is blurred.

How to resize the drawing of the canvas in HTML5?

I would like to resize the canvas to a smaller dimension than what it is. Here is my code:
var modelPane = document.getElementById('model_pane');
//create canvasclass="modelview"
var canvas_ = document.createElement('canvas');
canvas_.setAttribute('width', 1160);
canvas_.setAttribute('height', 500);
canvas_.setAttribute('id', 'canvas');
canvas_.setAttribute('class', 'modelview');
var ctx_ = canvas_.getContext('2d');
//draw
for (i=0;i<nodes.length;i++)
{
ctx_.strokeRect(nodes[i].x, nodes[i].y, nodes[i].width, nodes[i].height);
}
//scale
ctx_.scale(0.3,0.3);
modelPane.appendChild(canvas_);
Yet this doesn't work. Anyone know why? The canvas doesn't resize to the new dimensions.
You need to call scale first:
var ctx_ = canvas_.getContext('2d');
ctx_.scale(0.3,0.3);
//draw
for (i=0;i<nodes.length;i++)
ctx_.strokeRect(nodes[i].x, nodes[i].y, nodes[i].width, nodes[i].height);
}
Think about it, like you need to scale you canvas down. Then draw on the smaller canvas.
Example