I noticed a weird outline on my canvas lines. Below is an example:
http://jsfiddle.net/0Lzd562x/6/
The blue stroked rectangle is drawn after the red one but it looks like they mix together or there is a glow on the lines. I've tried setting the lineWidth to a larger value and it fixes the issue, but I want slim lines. Also tried using ctx.lineTo() to draw the rectangles but with same results. I want the latter (blue) rectangle to be on top overriding the right side of the red one.
What's happening is that you're drawing lines along the splits between pixels, not in the middle of the pixels themselves. This causes the line to be rendered at half opacity on each of the pixels. Because of the opacity, the colors from both squares are being added to 2 pixels, resulting in a thick 2px purple line.
The reason for the splitting of the line is antialiasing. You can find a fantastic talk about it by Steve Wittens
You can't turn antialiasing off, but you can draw the lines in the middle of the pixels by adding .5 to the starting x/ys of the rectangles, as long as the sizes are integers
Here's the resulting fiddle: http://jsfiddle.net/0Lzd562x/11/
Related
I would like to create 'n' number of circles along a mouse created vector line when the mouse is clicked and dragged.
This would result in a row of circles which would be a certain distance apart, depending on the number of circles in the line. The first and last circles would be at the point where the mouse was first clicked and then released.
Example: If the line is 1000px long, and 3 circles are required there would be a circle at 0px 500px and 1000px. If the line is 1000px long and 5 circles are required, there would be a point at 0px, 250px, 500px, 750px and 1000px.
I had an attempt at paper.js with the following results, but this is not quite what I need. In this example, the circles follow a free path and are spaced by a set number of pixels.
Click here to see
As an extra, it would be nice to see the path of the mouse line before the mouse is released.
As #NicholasKyriakides wrote in his comment, this can be best implemented using a Path with getPointAt(offset).
I have implemented this in your code example and made it more generic, so you can use any path, not only a straight line.
I have also replaced your circle items with instances of SymbolItem, which is more efficient, because they share a lot of data.
Here is the Sketch
I am using clearRect on a HTML5 canvas to redraw a rectangle. When using floating point coordinates the clearRect leaves a border from my rectangle on the canvas.
The following code demonstrates the problem with the rectangle using integer coordinates being fully cleared while the one using floating point leaves a border.
<html>
<head>
</head>
<body>
<script type="text/javascript" >
var canvas = document.createElement("canvas");
canvas.width = 100;
canvas.height = 100;
canvas.style.border = "1px solid";
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.fillRect(20.1,20.1,30,30);
ctx.clearRect(20.1,20.1,30,30);
ctx.fillRect(50,50,30,30);
ctx.clearRect(50,50,30,30);
</script>
</body>
</html>
The resulting canvas looks like this:
I can fix this by clearing a larger region, but that increases the risk of clearing and having to redraw adjacent shapes. This is for example suggested here: I can't completely clear the transformed rectangle in <canvas>
I can fix it by using integer coordinates, but that is not an option in this application.
Are there other ways to make clearRect actually clear all of the drawn rectangle without clearing a larger region or using integer coordinates?
All points in canvas are in fact centered in their middle coordinates (0.5, 0.5).
If you want to draw a black line one pixel thick, you'll have to draw it with centered coordinates.
If you draw it on an integer boundary, you'll in fact draw a two pixel thick lines both with lower opacity, leading to a thicker line drawn in dark gray instead of black :
Here's a picture showing this, zoomed 3 times :
More generally, any coordinates off the 0.5 boundary will be drawn with an opacity proportional to its distance to mid point.
Here's a set of horizontal line segments starting on an integer boundary, then shifted 1/10th of a pixel every 20 pixels :
zoomed 4 times :
We can see that we really have a 1 pixel line only when centered.
For your issue, there's no way you 'partially' clear a pixel : pixel is the ultimate unit here, so since colors have already been mixed, you can only either clear whole pixel, or just attenuate its intensity (which is the result you see).
I can think of two solutions :
rather than clearing, redraw everything except what you don't want any more. For this you have to handle some kind of scene graph, meaning : you need to have a collection of all the objects that needs drawing (held within an array for instance), and at draw time, you erase everything, redraw everything except the rectangle.
handle a bigger canvas behind the scene, that will have a higher resolution than the user canvas. This is were you draw, with better quality, and after drawing you copy it to the low-resolution user canvas.
Draw on 0.5 boundaries with integer sizes (width/height of your rect for instance). This main canvas might be 4 or 8 times bigger. The maximum size of the canvas is limited, so watch out for this if you target all browsers, they do not all allow the same max size (below 6400X6400 should be fine, but not sure about it). You can handle multiples backstage canvas to go beyond that limit, with a little bit of extra work.
(Rq for solution 2 : be sure to disable image smoothing before copying to avoid artifacts).
(( the fiddle for the drawings is here : jsbin.com/xucuxoxo/1/ ))
Edit : it is a good practice to translate the context from (0.5;0.5) right after you created it. Then you will always draw integer coordinates. This way, you ensure that all, say, 1 pixel thick line will actually be drawn one pixel thick. Test rounding with floor or ceil, and choose the one you prefer.
Html canvas always applies anti-aliasing to "cure the jaggies".
Anti-aliasing visually smooths lines by adding semi-transparent pixels along the line so the eye is fooled into seeing a less-jagged line.
When you draw your rectangles, these semi-transparent pixels are automatically being applied outside the 30,30 area of your rectangles.
This means your 30x30 rectangle is actually slightly larger than 30x30.
When you do context.clearRect the browser does not clear those extra semi-transparent pixels.
That's why the uncleared pixels appear "ghostly" -- they are semi-transparent.
Unfortunately, there is no way currently to turn off anti-aliasing for html canvas primitive drawing (lines, etc).
You have discovered the 2 fastest solutions:
round pixel drawing coordinates to integers
clear an area slightly larger than the original drawing
You can draw without anti-aliasing by drawing pixels manually using getImageData/putImageData. This manual method works but is costly to performance. The decreased performance defeats the purpose of clearing just the drawn area.
Bottom line: You've already discovered the best solutions canvas currently has to offer :-(
I am trying to smoothen the transition from a diagonal line to a horizontal line by using arcs of circles that I calculate with a little calculus. The problem I am running into is that it seems as though I can't get the arcs to be positioned correctly.
For example: jsfiddle.net/5Wa9e/2
It might just be a problem with my calculations but from "inspection" it seems as though I have the right circles. I want the red points on the right to always be at the bottom of the circle at the point where the tangent is horizontal.
I am using: A#{radius},#{radius} 0 0 0 #{curveEndX},#{curveEndY}" to define the arc. Am I missing something?
--edit--
It was just my math. Turns out the arcs I see are just fallback mechanisms for when the SVG renderer can't find a circle matching my constraints.
--edit--
end result: jsfiddle.net/5Wa9e/2
I'm using svg/d3 for creating a chart made of 'rect' elements.
What is the best way for adding a partical border/stroke for each rectangle (only on top of the rectangle)?
Thanks
I don't think SVG supports stroking only portions of a rectangle or path - stroke isn't like a CSS border. You're left with a few other options, all of which take some extra work:
Stroke the entire rect and apply a clipPath to remove the other three edges - probably works best if you make the rectangles bigger than necessary.
Apply a linear gradient fill to each rect, using the gradient definition to show a "border" at the top of the shape.
Add a separate line element to act as the border for each rect.
Use the stroke-dasharray property (docs) to set a dash definition where the "dash" only covers the top of the rect. This might be tricky to get right, but I suspect it wouldn't be too hard, as the stroke probably begins at the top left of the shape.
I'm using canvas of HTML5 to create a "preview" image which mainly consists of some rectangles and simple lines. Works fine so far, but there's one problem I cannot fix somehow. Presume the following situation:
context.fillStyle = "rgba(0,0,0,0.75)";
context.fillRect(100.64646,100,50.94967,20);
context.fillRect(100.64646+50.94967,100,100,20);
So I'm drawing 2 rectangles with some opacity. The x-starting coordinate plus the x-length of the first rect is equal to the x-starting coordinate of the second rect, so in theory they should collide without any margin between. Sadly, the result is different:
(see http://files.clemensfreitag.de/thin_spacing.jpg)
There's a very tiny spacing between the boxes, and the background color is visible. But:
This problem doesn't occur if the coordinates and lengths are integer values.
Is there any way to get it done by using float values? Converting them to integers before drawing might be acceptable in my application, but I'm just wondering why this should not work with floats.
Best,
Clemens
What you're seeing is the result of overlaying two opaque colors. When the first rectangle ends at 151.59613, the rectangle is automatically antialiased, filling in the rightmost column with rgba(0,0,0,0.4470975). When the second rectangle starts at the same x coordinate, it is also antialiased, filling in the leftmost column (the same as the first rectangle's rightmost) with rgba(0,0,0,0.3029025). The two values do add up to rgba(0,0,0,0.75), but that's not how they are blended. Instead, the second color (rgba(0,0,0,.3029025)) is drawn on top of the first, resulting in rgba(0,0,0,0.4470975+(1-0.4470975)*0.3029025) = rgba(0,0,0,0.61457305). So there isn't actually a gap between the two rectangles, but rather a 1px column that is a slightly lighter shade of grey.
Similarly, if you were using solid colors then the second rectangle's antialiased column would overwrite the first's, resulting in an even lighter shade of grey in the "gap".
The issue does not show up with integer values because no antialiasing is required - each rectangle ends at the edge of a pixel.
It looks like none of the globalCompositeOperation settings fix this, and turning off antialiasing would sometimes result in a 1px gap, so I think your simplest solution is to force integer values (alternatively, you could clear that column then fill it in with the desired color).
This problem is related to the way objects are drawn on a float based grid (especially vertical and horizontal lines and thus rects).
See there for an explanation and a schema : http://canop.org/blog/?p=220
Depending on the size of your objects, you need to use integer or mid-integer coordinates and sizes for your shapes, the goal being to fill complete pixels in both dimensions.
For example :
use a mid-integer for a thin line (one pixel width)
use an integer coordinate for a 2 pixels wide line
(and extend the logic for rects)