I've been playing with canvas element and discovered that when I attempt to draw NxN uniform solid-colored cells next to each other, in some width/height configurations, there are blurry white-ish lines between them.
For instance, this canvas is supposed to look black but contains some sort of grid which I conjecture to be a result of faulty antialiasing in the browser.
Suffice to say, this bug appears only in some configurations but I would like to get rid of it for good. Is there any way to circumvent this? Have you ever had problems with antialiasing in canvas?
I have made this fiddle which demonstrates the issue and allows you to play with the dimensions of the canvas and number of cells. It also contains the code I use to draw the cells, so that you can inspect it and tell me if I'm doing anything wrong.
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
for (var i = 0; i < numberOfCells; ++i) {
for (var j = 0; j < numberOfCells; ++j) {
ctx.fillStyle = '#000';
ctx.fillRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
}
}
Thanks in advance,
Petr.
jsFiddle : https://jsfiddle.net/ngxjnywz/2/
snippet of javascript
var cellWidth = Math.ceil(canvasWidth / numberOfCells);
var cellHeight = Math.ceil(canvasHeight / numberOfCells);
Depending on the width, height and your numberOfCells you are sometimes getting a... lets say 4.2 which is 4, however this would be displayed wrong and will allow a 1 pixel blank line to appear. So all you need to do is use the Math.ceil function and this will cause your cellWidth and cellHeight to always be the higher number and you won't get blank lines anymore
The best solution is to add a 0.5 pixel wide stroke around all the fills, using the same style as the fill and offsetting all drawing so that you render at the center of pixels rather than the top left.
If you add scaling or translation you will have to adjust the coordinates so that you still give the centers for your drawing coordinates.
In the end you can only reduce the artifacts but for many situations you will not be able to completely remove them.
This answer shows you how to remove the artifacts for an untransformed canvas.
How to fill the gaps
After reading through and trying several approaches, I've decided to come up with my own. I've created another (virtual) canvas which had integer dimensions corresponding to the number of cells in the grid.
After drawing all the cells in there, I call context.drawImage() on the main canvas and pass the virtual canvas as an argument along with offset and scale parameters to make it fit rest of my drawing. Assuming that the browser would scale the virtual canvas's image as a whole (and not as individual cells), I was hoping to get rid of the unwanted separator lines.
In spite of my efforts, the lines are still there. Any suggestions?
Here's the fiddle demonstrating my technique: https://jsfiddle.net/ngxjnywz/5/
Related
I want to draw a growing trail behind an moving object. I know this look quite easy :) But there are some constraints :
the trail must have some homogeneous transparency
and i can not use caching methods because of performance issues
I have tested 2 ways :
One with lineTo() and incrementing stroke width, but the alpha transparency is not homogenic...
https://jsfiddle.net/zOgs/9ntajsa1/
One with lineTo() and circles to fill the blank, transparency is OK but there is a strange behavior when drawing from left to right, negative space appears...
https://jsfiddle.net/zOgs/psa3x9y2/
I also try to use compositeOperation with something like this, but it's messing with my background...
trail.alpha = 0.5;
trail.compositeOperation = 'xor';
for(var i=nb; i>=0; i--) {
trail.graphics.drawCircle(points[i].x,points[i].y,size/2).closePath();
}
I can't find a valid solution to this problem and i am beginning to despair :(
There is probably a better way to do this, but here is an easy way: Use an off-screen canvas to draw the trails, then display that canvas as a bitmap child of the main stage.
Here is a fiddle based on your first one:
https://jsfiddle.net/lannymcnie/9ntajsa1/1/
// Canvas to draw to:
var offCanvas = document.getElementById("canvas2");
var offStage = new createjs.Stage(offCanvas);
// Add the offStage to the main stage.
var bmp = new createjs.Bitmap(offCanvas);
stage.addChild(bmp);
bmp.alpha = 0.1;
// Still get events from main stage
stage.addEventListener('stagemousemove',onMouseMove);
I'm working on a 2d canvas-based app using EaselJS where the user can move indefinitely on the xy-plane by dragging the background. Think google maps, except the background is a repeating tile.
The code for the movement is very simple and looks something like this:
// container - the createjs.Container being panned
// background - a createjs.Shape child of container, on which the
// background is drawn
background.onPress = function(evt) {
var x = evt.stageX, y = evt.stageY;
evt.onMouseMove = function(evt) {
// the canvas is positioned in the center of the window, so the apparent
// movement works by changing the registration point of the container in
// the opposite direction.
container.regX -= evt.stageX - x;
container.regY -= evt.stageY - y;
x = evt.stageX;
y = evt.stageY;
stage.update();
};
evt.onMouseUp = function(evt) {
// Here the background would be redrawn based on the new container coords.
// However the issue occurs even before the mouse is released.
background.redraw();
stage.update();
};
};
All works as expected until reaching 32678px (2^15) on either axis. What occurs is different in different browsers, but the point where it first happens is the same.
In Firefox, it will suddenly shift a large chunk of pixels (~100) rather than 1. It will then happen again at 65538 (2^16+2), perhaps more after that, but I haven't witnessed it. After the trouble points, the drag will continue smoothly, as expected, but remaining shifted.
In Chrome, the effect is more dramatic. The drawing breaks and results in repeated ~100px wide "stripes" of the background across the page at 32768, and does not correct itself on redraw.
Oddly, the numbers in EaselJS don't reflect the issue. The only change in the container's transform matrix is the tx or ty incrementing by 1. No other matrices change. It seems as though EaselJS is getting all the numbers right.
Can anyone shed any light this issue?
Edit:
I worked around this problem by redrawing parts of the container using a calculated regX/regY, rather than attempting to translate very large regX/regY coords on the canvas.
This question may be related
What is the maximum size for an HTML canvas?
From what I gather browsers use short int to store canvas sizes with 32,767 being the maximum possible value.
Links possibly related to your issue,
Mozilla - HTML5 Canvas slow/out of memory
https://stackoverflow.com/a/12535564/149636
This situation is difficult to explain, so let me illustrate with a picture:
Those pixels inside the first shape created are lightened. The screen is cleared with black, the red and green boxes are drawn, then the path is drawn. The only fix that I've found so far was setting the line width of the boxes to 2 pixels, for the reasons outlined here.
Here's the code being used to draw the squares:
sctx.save();
sctx.strokeStyle = this.color;
sctx.lineWidth = this.width;
sctx.beginPath();
sctx.moveTo(this.points[0].x, this.points[0].y);
for (var i = 1; i < this.points.length; i++)
{
sctx.lineTo(this.points[i].x, this.points[i].y);
}
sctx.closePath();
sctx.stroke();
sctx.restore();
And the lines:
sctx.save();
sctx.strokeStyle = 'orange';
sctx.lineWidth = 5;
console.log(sctx);
sctx.beginPath();
sctx.moveTo(this.points[0].x, this.points[0].y);
for (var i = 1; i < this.points.length; i++)
{
sctx.lineTo(this.points[i].x, this.points[i].y);
}
sctx.closePath();
sctx.stroke();
sctx.restore();
And a picture of the same situation where the boxes are drawn at 2px width:
Is lineTo() perhaps messing with the alpha values? Any help is greatly appreciated.
EDIT: To clarify, the same thing occurs when sctx.closePath(); is omitted from the path being drawn.
It would seem as though this is a currently undocumented rendering bug that for some reason appears on all platforms. There is very little info out there about it, and it will hopefully be attended to before HTML5 is the official standard.
As a workaround, don't use lineTo()'s, use multiple sets of single lines.
Let's say you have: http://jsfiddle.net/g3Kvw/
As you can see it seems to be 2px wide. But if you change that to use fillRect() instead of lineTo(), you have http://jsfiddle.net/g3Kvw/1/ which is looking good.
Problem: you will not be able to draw something that is not a rectangle with this method.
But, if you sum 0.5 to EVERY int coordinate (x,y): http://jsfiddle.net/g3Kvw/3/ you will get the regular line.
I think this is some kind of antialiasing calculation bug on FF & webkit... But I didn't find it in the bug tracker, and this "weird" solution to convert the number to float, in order to get a solid line is quite confusing. Because using integers should be enough.
I'm trying to draw a grid of white lines on a black background.
The bottom 3 horizontal lines seem faded until I redraw them, and I can't figure out why this is happening. Has anyone seen this before and/or know what I'm doing wrong?
This is due to the fact lines are drawn over all pixels they're over (on canvas positionning is in float). When you want to draw precise vertical or horizontal lines in javascript on a canvas, you'd better have them in half ints.
See illustration : The first horizontal line was drawn with a y position of 1. This line is fuzzy and wide. The second horizontal line was drawn with a y position of 4.5. It is thin and precise.
For example in your code, I had good results by changing your horizontal lines loop to this :
// Horizontal lines
for (var i = 1; i < objCanvas.height / intGridWidth; i++)
{
objContext.strokeStyle = "white";
var y = Math.floor(i*intGridWidth)+0.5
objContext.moveTo(0, y);
objContext.lineTo(objCanvas.width, y);
objContext.stroke();
}
Here's a fiddle demonstrating it with very thin and clean lines :
http://jsfiddle.net/dystroy/7NJ6w/
The fact that a line is drawn over all pixels it is over means the only way to draw an horizontal line with a width of exactly one pixel is to target the middle. I usually have this kind of util functions in my canvas based applications :
function drawThinHorizontalLine(c, x1, x2, y) {
c.lineWidth = 1;
var adaptedY = Math.floor(y)+0.5;
c.beginPath();
c.moveTo(x1, adaptedY);
c.lineTo(x2, adaptedY);
c.stroke();
}
Of course you'd better do it for vertical lines too to have a good looking page.
It doesn't look faded for me. Maybe it's something to do with your OS or PC, which is not able to render the drawing properly. I'm using Chrome 20 on Win 7. Test it out.
You have to define objContext.lineWidth like this:
objContext.lineWidth = 2;
I'm not sure why last line gets faded though.
See http://jsfiddle.net/jSCCY/
I made a script that draws a series of lines on a canvas that makes it look like a sketch. There are two issues with the script. One, why is the y value twice as much as it should be? And two, why is the line several pixels wide and faded out?
I've tried it in both Google Chrome and Firefox and I get the same incorrect results. I realize that I can divide the y value by two to fix the first problem but that part of my question is why do I need to do that. I shouldn't have to.
I think you have two issues:
You need to be more careful in how you calculate the offset of where to draw. I have some code below that demonstrates how to handle this properly.
You aren't setting the width and height on the <canvas> element itself, which means it will scale your lines in funny ways depending how what you've set in your css.
An Example
I built a simple collaborative drawing app using <canvas> and socket.io that lets you draw to the screen like a pencil. You can check it out here:
http://xjamundx.no.de/
The source is also on github if that might help:
https://github.com/xjamundx/CollabPaintJS/ (main repo)
https://github.com/xjamundx/CollabPaintJS/blob/master/public/collabpaint.js (canvas drawing code)
In particular I do something like this to figure out where to draw things:
x = e.clientX + window.scrollX
y = e.clientY + window.scrollY
x -= $game.offsetLeft
y -= $game.offsetTop
Give a width and a height to your canvas; always !
http://jsfiddle.net/mz6hK/7/
fixed