Why does lineTo() change interior pixels? - javascript

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.

Related

Canvas - How to draw a growing trail

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

Weird HTML 5 Canvas Antialiasing

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/

Erase rects in Canvas, but they come back

I'm trying to write a small Tetris clone to learn Canvas. I've got as far as having a single block fall. I'm simply having it fall down one column in a 'for' loop, redrawing in the grid square below and then erasing the square above using rectClear. (The clumsy alert() is to see progress).
for (var y = 21; y >= 0; y--) {
drawSquare(3,y,'green');
clearSquare(3,(y+1));
alert(y);
};
Interestingly, if I swap the "clearSquare" and "drawSquare" lines, none get erased at all.
I'm not sure if this is a problem in my logic, or if I'm failing to grasp something in canvas. As you can see I've clumsily put in "context.save()" in case my changes weren't being "committed" or something, but I believe this is kind of irrelevant here.
Here's a JSFiddle
The method rect() will add a rectangle to the current path every time it is called. Clearing the canvas will only clear the pixels, not the path, so next time you fill/stroke it will be included, hence reappear.
To solve simply call this tp the drawSquare method before adding a rectangle:
ctx.beginPath(); // this will start a new path, clearing the old
ctx.rect(...);
Updated fiddle

How do you fill specific countries using d3.geo with canvas?

I created my own pre-projected topojson file, and I am trying to change the fillstyle of a specific country in my canvas. http://bl.ocks.org/anonymous/fea6cfde1184e2f10de8 is my code/example. I tried to catch the specific country code and set a fill for that country (Germany in this case). However, it fills a bunch of other countries in for some reason as well. I suspected it might be a shared borders issue, but some countries that do not share a border get filled as well.
Also interesting is that if I do not render the borders for the other countries as in http://bl.ocks.org/anonymous/3256fe09efaa8d2ebc96 the fill works, but obviously all the borders disappear. Am I simply not understanding how canvas fills work? Is there a different way to highlight a specific country? It has occurred to me that I can use a mask to fill in a specific country, but that seems like an awfully roundabout way to do it.
Note, I am aware that it is way simpler to do using SVG/CSS, but I would really like to get it working in canvas.
As in this example, you need to draw all the borders first, then just fill the region you want:
var isDEU = null;
for (var i = 0; i < geoJson.features.length; i++) {
if (geoJson.features[i].id == "DEU") {
isDEU = geoJson.features[i];
}
context.save();
canvasPath(geoJson.features[i]);
context.stroke();
context.restore();
}
context.fillStyle = "#f00"
context.beginPath();
canvasPath(isDEU);
context.fill();
Example here.

Drawing on the canvas with a "pencil"

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

Categories

Resources