Unpredictable behaviour of HTML canvas - javascript

I observe weird behaviour of HTML canvas both in chrome and in firefox. For some reason there is one-pixel wide palid line when I fill rect over the smaller rect of different color after clipping.
Here is JS fiddle snippet: https://jsfiddle.net/srkgbxw1/7/
var my_canvas = document.getElementById('canvas'),
context = my_canvas.getContext("2d");
context.translate(0.5, 0.5)
context.fillStyle = "orange";
context.fillRect(10,10,100,100);
context.beginPath();
context.rect(20,20,90,90);
context.clip();
context.fillStyle = "white";
context.fillRect(0,0,110,110);
context.fillStyle = "orange";
context.fillRect(0,0,190,190);
This is the result:
Please help me figure out what the reason for the pallid line after third fillRect with orange?
UPD: original question's changed after I was pointed out I had made a stupid mistake in its first edition
UPD 2 I got the answer, this happens due to color interpolation at edges of clipping region because context was translated for half a pixel, which was done to get thin lines (recommended technique). To avoid interpolation, clipping region should be adjusted for half a pixel as well, then the pallid line disappears.

If you look into the logs:
TypeError: context.setStrokeStyle is not a function
Therefore, when you comment it, everything after it is executed while when you uncomment it, it fails and everything after this line is not executed.
EDIT: For the second question issue:
The issue is equivalent to this, why the blue square does not fully overlap the red square:
context.translate(0.5, 0.5);
context.fillStyle = "red";
context.fillRect(10,10,100,100);
context.fillStyle = "blue";
context.fillRect(10,10,100,100);
Because of translate, you are now drawing in float pixels, everything moved by half a pixel.
I suggest you read this for more information about how it is handled:
fillRect() not overlapping exactly when float numbers are used
Removing context.translate(0.5, 0.5); removes the "palid line".

Related

Make canvas fill include stroke in shapes

Ok, so i am making a 3D rendering engine in pure javascript, as a challenge of course - to test my linear algebra skills. I am not using webgl, so please do not say "use webgl".
Anyways, the software will take in triangles, a camera and local transformations, and render the data onto the screen (i even made it interactive)
There are only 6 lines of rendering code, however, which are:
// some shading and math calculations then this:
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.lineTo(x0, y0);
context.closePath();
context.fill();
context.stroke();
And while that works, it drops to 10fps with 4k+ faces on my Chromebook. (60fps on a regular computer)
Anyways, that outputs this:
But to make it faster, and because canvas state changes are slow, i removed the stroke, making the rendering code:
// some shading and math calculations then this:
context.fillStyle = color;
//context.strokeStyle = color;
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.lineTo(x0, y0);
context.closePath();
context.fill();
//context.stroke();
which runs twice as fast, but the resulting thing that gets rendered to the screen is this: (different model)
which has ugly lines everywhere at the edges of the triangles (which get removed when I re-add the stroke)
However, the fps doubles and performance gains are great...
So i believe the lines are caused because the canvas fill doesnt include the area where it would have stroked (the outline, as you may say).
I have tried to fix it with math, and although it works there are some edge cases where it doesn't
So my question is as follows:
Is there a way to make the context fill include the stroke area without stroking, because it is very expensive?
Using both stroke and fill will force the rasterization twice which explains the approximate double time.
The reason why you get glitches between the triangles is because of rounding errors and anti-aliasing. There is not a straight-forward solution to this; the stroke will cover the glitches of course, but to do it without the stroke will require you to offset and expand at least every other triangle.
However, you could use a small trick to cover up the gap and that is to redraw the entire image (as bitmap) on top offset just a single pixel (you might get away with 0.5 pixel but then anti-aliasing is needed). This adds to the time, but far less than rasterization or recalculation of the paths.
Say that the result on the left is what you have (simulated here) with a clear gap. Redrawing it on top as shown in the right will cover the gap without too much distortion.
Simply use:
ctx.drawImage(sourceCanvas, 1, 1);
Tip: when only calling fill() you don't need closePath() as it is called implicit, saving one op. Microscopic gain perhaps but still (with more complex geometry it even might have an influence :) ).
Note: drawing to itself will cause an internal allocation of a temporary bitmap copy. However, you will only need to do one extra drawImage() operation. The option is to use off-canvas render but draw twice to a main displayed canvas. Either way...
var ctx = c.getContext("2d");
ctx.fillStyle = "#777";
tri(10,10, 72,17, 40.2, 100);
// simulates gap
ctx.fillStyle = "#222";
tri(72.5,17.5, 40.7,100.5, 90,25);
// fill entire image back again, drawn twice here for demo
ctx.drawImage(c, 100, 0);
ctx.drawImage(c, 0, 0, 100, 150, 101, 1, 100, 150);
ctx.fillText("Raster", 5, 8);
ctx.fillText("Offset self", 105, 8);
function tri(x0,y0,x1,y1,x2,y2) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.fill();
}
<canvas id=c></canvas>

Why does thin dashed line appear solid if length is even?

I've been playing with dashed lines on the HTML5 canvas, and I have run into a roadblock. If I set a LineDash of [2,2,2,2,2] the line looks nearly solid if it's an even number of pixels long. It looks more clearly dashed if it's an odd number of pixels long. I get the same results on my Mac in Firefox, Chrome, and Safari.
Here is simple code showing the effect:
context = document.getElementById("canvas").getContext('2d');
context.lineWidth = 1;
context.strokeStyle = '#990000';
context.setLineDash([2,2,2,2,2]);
// Line ends on an even y-coordinate
context.beginPath();
context.moveTo(2,0);
context.lineTo(2,94);
context.closePath();
context.stroke();
// Line ends on an odd y-coordinate
context.strokeStyle = '#000099';
context.beginPath();
context.moveTo(102,0);
context.lineTo(102,95);
context.closePath();
context.stroke();
Also, neither LineWidth (thickness) nor orientation fix the problem. The problem persists if I change LineWidth from 1 to any other value. And it persists whether the line is horizontal or vertical.
I put a bunch of examples in this codepen: https://codepen.io/anon/pen/ENLwdb
Why should the length matter at all? Shouldn't the dash pattern appear the same regardless of length?
Is my only recourse to alter line lengths if I detect they are even?
It is because the code is using closePath() on a line which makes it add an extra line on top, from the end point back to the starting point. Depending on the length and pattern the returning line may or may not fill the remaining gaps.
Remove the closePath() (which is only needed to close paths for stroked polygon shapes) and it will work.
Modified CodePen

Colors of old circles on canvas recolor, why?

I'm trying to understand the combination of HTML5/CSS3 and Javascript more and more.
That's why I thought, make a little project so you learn all about that more.
In short, I like the new iOS7 wallpaper and use it on my website (http://www.betadevelops.com). Then I thought, let's make this more lightweight and draw it with pure Javascript.
I started and managed to get quite far (http://www.betadevelops.com/jOS7.html). But now I face a stupid problem I can't seem to get fixed.
I draw circles on the canvas, and dynamically assign colors to it. But each time a new circle (and so a new color gets chosen) it automatically recolors the old circles...
So let's say, 10 circles:
1: blue circle, draw's it and done
2: yellow circle, draw's it and done, but it also colors the first blue one to yellow
I also wanted to add opacity and blurring. The opacity kinda works in the sense it has opacity on only 2-3 circles from the 20 I draw. I think this is not possible because I use Math.Random the calculate a random opacity.
Considered the blurring, I can add blurring to the whole canvas with follow code
canvas.style.webkitFilter = "blur(3px)";
but that's not what I want. I want the blur on the circle itself and to be more precisely, the outline. I read about it and it's not possible, but you can mimmick the looks with using CSS box-shadow.
So I tried
canvas.style.webkitFilter = "box-shadow(10px 10px 5px #888)";
but this also doesn't work it seems...
So, you website guru's. What am I doing wrong and can you help me out?
You can find the code by clicking on the second link. Uploaded it there.
EDIT:
Nevermind the blur, managed to solve it partially with this code
if (blurred) {
ctx.shadowColor = color;
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
#Stig Runar Vangen has the correct answer.
I would just add that if you don't intend the circles to "run", you could use ctx.closePath after drawing each ctx.arc.
ctx.beginPath();
ctx.arc(centerX, centerY, diameter, 0, 2 * Math.PI, false);
ctx.closePath();
color = color.replace('opacity', Math.random().toString());
ctx.fillStyle = color;
ctx.fill();
The reason why you see that all your circles gets the same color, might be because you join all circles into one draw operation. To separate each circle draw operation, start each circle placement with:
ctx.beginPath();
Each arc should then also be drawn with a call to either ctx.stoke() or ctx.fill() after the definition of each single circle.
This is purely guesswork as I haven't seen your code.

JavaScript canvas, getting flat lines

I'm working on project which have to draw graphs. Everything is quite good, but noticed one problem, the lines are showing strange.. it seems like someone draw my graph with brush holding horizontally.. when line goes down everything is OK, but when line is going horizontal the it becomes much smaller ... I can't find what's the problem could be..
Please help, because I started to get wrong graphs when it needs to draw horizontal line...
Here is the link to my project:
http://www.unolita.lt/images/signalai/Documents/Koreliacine%20funkcija.html
You can clearly see my problem on 1st picture..
Here is it's code:
function drawSignal()
{
var canvas = document.getElementById("canvSignal");
if (canvas.getContext)
{
var ctx = canvas.getContext("2d");
ctx.lineWidth = 3;
function Signalas()
{
<...>
ctx.beginPath();
ctx.moveTo(x, y);
ctx.strokeStyle = "black";
<...>
y=250- Sn[n] ;
ctx.lineTo(x, y);
ctx.stroke(x, y);
<...>
To put all code here was too much problematic..
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.
Possible Solution : If you have to draw a line with an odd numbered width, then you will have to offset the center of your line by 0.5 up or down. That way rendering will happen at boundary of pixel and not in middle, and you will always have a sharp line with no residue at the end edges.
So add 0.5 for odd numbered line width so that your points should be half numbered
ctx.lineTo(x+0.5, y+0.5);
ctx.stroke(x+0.5, y+0.5);
I have modified your code like this in line number 134 and 135 and got a output like this . Hope, this helps
Refer Here :
incorrect display lineWidth=1 at html5 canvas
HTML5 Canvas and Line Width
Line Width in Canvas

clear pixels under a shape in HTML canvas

I am using an HTML canvas and javascript and I need to clear all of the pixels underneath a shape created by closing a path (for example, I am using flot, and I want to make rounded corners, and to do this, I first need to remove the square corners by drawing a curve on top of the corner to remove the desired pixels).
Right now, I am doing this by just filling the shape with the same color as the background, which can imitate what I want to do, but, it is not ideal as it makes it impossible to place the chart on top of non-solid backgrounds without seeing the square corners. I know that there is a clearRect method that would do what I want to do, but with only rectangles, I need to do it with any closed shape. Is it possible, and if so, how would I do it?
brainjam's code was heading in the right direction, but didn't fully solve the problem. Here's the solution:
context.save();
context.globalCompositeOperation = 'copy';
context.fillStyle = 'rgba(0,0,0,0)';
//draw shape to cover up stuff underneath
context.fill();
context.restore();
Here's an example of a function that will clear a circle from a canvas:
var clearCircle = function(x, y, radius)
{
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
I think what you want is a clipping region, defined by the clip() function. The latter takes a bunch of paths. Here's an example.
This is a little different from what you are specifically asking (which is to remove pixels after drawing them), but actually not drawing the pixels in the first place is probably better, if I understand your requirements correctly.
Edit: I now think I understand that what you want to do is clear pixels to transparent black. To do that, after having defined your paths, do something like this:
context.fillStyle = 'rgba(0,0,0,0)';
context.fill();
The first statement sets the fill color to transparent black.
Use globalCompositeOperation = 'destination-out' instead of 'copy', it will erase all pixels of the shape in the canvas.
See all kinds of composition here
very usefull !

Categories

Resources