HTML5: inaccurate positioning of rectangles - javascript

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)

Related

HTML Canvas - glowing outline on stroke path?

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/

Create 'n' circles along a mouse created vector line paper.js

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

JavaScript canvas clearRect leaves borders when using floating point coordinates

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 :-(

Rotate a Two.js object in its position

I have a large circle with smaller ones inside made using two.js.
My problem is that these two do not rotate in their own place but in the top left axis.
I want the group of circles (circlesGroup) rotate only inside the large one in a static position. The circlesGroup and the large circle are grouped together as rotatoGroup.
two.bind('update', function(frameCount, timeDelta) {
circlesGroup.rotation = frameCount / 120;
});
two.bind('update', function(frameCount, timeDelta) {
rotatoGroup.rotation = frameCount / 60;
});
The whole code is in CodePen.
All visible shapes when invoked with two.make... ( circles, rectangles, polygons, and lines ) are oriented in the center like this Adobe Illustrator example:
When this shape's translation, rotation, or scale change those changes will be reflected as transformations about the center of the shape.
Two.Groups however do not behave this way. Think of them as display-less rectangles. They're origin, i.e group.translation vector, always begins at (0, 0). In your case you can deal with this by normalizing the translation your defining on all your circles.
Example 1: Predefined in normalized space
In this codepen example we're defining the position of all the circles around -100, 100, effectively half the radius in both positive-and-negative x-and-y directions. Once we've defined the circles within these constraints we can move the whole group with group.translation.set to place it in the center of the screen. Now when the circles rotate they are perceived as rotating around themselves.
Example 2: Normalizing after the fact
In this codepen example we're working with what we already have. A Two.Group that contains all of our shapes ( the bigger circle as well as the array of the smaller circles ). By using the method group.center(); ( line 31 ) we can normalize the children of the group to be around (0, 0). We can then change the translation of the group in order to be in the desired position.
N.B: This example is a bit complicated because it invokes underscore's defer method which forces the centering of the group after all the changes have been registered. I'm in the process of fixing this.

Move rectangles so they don't overlap

This is a half programming, half math question.
I've got some boxes, which are represented as four corner points. They are true rectangles, the intersections of two sets of parallel lines, with every line in each set at a right angle to both lines in the other set (just so we're clear.)
For any set of n boxes, how can I efficiently calculate where to move them (the least distance) so that they do not overlap each other?
I'm working in javascript here. Here's the data:
//an array of indefinite length of boxes
//boxes represented as arrays of four points
//points represented as arrays of two things, an x and a y, measured in
//pixels from the upper left corner
var boxes = [[[504.36100124308336,110.58685958804978],[916.3610012430834,110.58685958804978],[916.3610012430834,149.58685958804978],[504.36100124308336,149.58685958804978]],[[504.4114378910622,312.3334473005064],[554.4114378910622,312.3334473005064],[554.4114378910622,396.3334473005064],[504.4114378910622,396.3334473005064]],[[479.4272869132357,343.82042608058134],[516.4272869132358,343.82042608058134],[516.4272869132358,427.82042608058134],[479.4272869132357,427.82042608058134]],[[345.0558946408693,400.12499171846],[632.0558946408694,400.12499171846],[632.0558946408694,439.12499171846],[345.0558946408693,439.12499171846]],[[164.54073131913765,374.02074227992966],[264.54073131913765,374.02074227992966],[264.54073131913765,428.02074227992966],[164.54073131913765,428.02074227992966]],[[89.76601656567325,257.7956256799442],[176.76601656567325,257.7956256799442],[176.76601656567325,311.7956256799442],[89.76601656567325,311.7956256799442]],[[60.711850703535845,103.10558195262593],[185.71185070353584,103.10558195262593],[185.71185070353584,157.10558195262593],[60.711850703535845,157.10558195262593]],[[169.5240557746245,23.743626531766495],[231.5240557746245,23.743626531766495],[231.5240557746245,92.7436265317665],[169.5240557746245,92.7436265317665]],[[241.6776988694169,24.30106373152889],[278.6776988694169,24.30106373152889],[278.6776988694169,63.30106373152889],[241.6776988694169,63.30106373152889]],[[272.7734457459479,15.53275710947554],[305.7734457459479,15.53275710947554],[305.7734457459479,54.53275710947554],[272.7734457459479,54.53275710947554]],[[304.2905062327675,-3.9599943474960035],[341.2905062327675,-3.9599943474960035],[341.2905062327675,50.04000565250399],[304.2905062327675,50.04000565250399]],[[334.86335590542114,12.526345270766143],[367.86335590542114,12.526345270766143],[367.86335590542114,51.52634527076614],[334.86335590542114,51.52634527076614]],[[504.36100124308336,110.58685958804978],[916.3610012430834,110.58685958804978],[916.3610012430834,149.58685958804978],[504.36100124308336,149.58685958804978]],[[504.4114378910622,312.3334473005064],[554.4114378910622,312.3334473005064],[554.4114378910622,396.3334473005064],[504.4114378910622,396.3334473005064]],[[479.4272869132357,343.82042608058134],[516.4272869132358,343.82042608058134],[516.4272869132358,427.82042608058134],[479.4272869132357,427.82042608058134]],[[345.0558946408693,400.12499171846],[632.0558946408694,400.12499171846],[632.0558946408694,439.12499171846],[345.0558946408693,439.12499171846]],[[164.54073131913765,374.02074227992966],[264.54073131913765,374.02074227992966],[264.54073131913765,428.02074227992966],[164.54073131913765,428.02074227992966]],[[89.76601656567325,257.7956256799442],[176.76601656567325,257.7956256799442],[176.76601656567325,311.7956256799442],[89.76601656567325,311.7956256799442]],[[60.711850703535845,103.10558195262593],[185.71185070353584,103.10558195262593],[185.71185070353584,157.10558195262593],[60.711850703535845,157.10558195262593]],[[169.5240557746245,23.743626531766495],[231.5240557746245,23.743626531766495],[231.5240557746245,92.7436265317665],[169.5240557746245,92.7436265317665]],[[241.6776988694169,24.30106373152889],[278.6776988694169,24.30106373152889],[278.6776988694169,63.30106373152889],[241.6776988694169,63.30106373152889]],[[272.7734457459479,15.53275710947554],[305.7734457459479,15.53275710947554],[305.7734457459479,54.53275710947554],[272.7734457459479,54.53275710947554]],[[304.2905062327675,-3.9599943474960035],[341.2905062327675,-3.9599943474960035],[341.2905062327675,50.04000565250399],[304.2905062327675,50.04000565250399]],[[334.86335590542114,12.526345270766143],[367.86335590542114,12.526345270766143],[367.86335590542114,51.52634527076614],[334.86335590542114,51.52634527076614]]]
This fiddle shows the boxes drawn on a canvas semi-transparently for clarity.
You could use a greedy algorithm. It will be far from optimal, but may be "good enough". Here is a sketch:
1 Sort the rectangles by the x-axis, topmost first. (n log n)
2 for each rectangle r1, top to bottom
//check for intersections with the rectangles below it.
// you only have to check the first few b/c they are sorted
3 for every other rectangle r2 that might intersect with it
4 if r1 and r2 intersect //this part is easy, see #Jose's answer
5 left = the amount needed to resolve the collision by moving r2 left
6 right = the amount needed to resolve the collision by moving r2 right
7 down = the amount needed to resolve the collision by moving r2 down
8 move r2 according to the minimum value of (left, right down)
// (this may create new collisions, they will be resolved in later steps)
9 end if
10 end
11 end
Note step 8 could create a new collision with a prior rectangle, which wouldn't be resolved properly. Hm. You may need to carry around some metadata about previous rectangles to avoid this. Thinking...
Keep in mind the box model, given any two rectangles you have to calculate the two boxes width and height, adding their respective margins, paddings, and borders (add the left/right of them to detect collision on the x axis, and top/bottom to detect collision on the y axis), then you can calculate the distance between element 1 and 2 adding the result to their respective coordinate position, for example ((positionX2+totalWidth2) - (positionX1+totalWidth1)) to calculate collision along the X axis. If it is negative, they are overlapping. Once you know this, if they won't overlap by moving them, you can move them normally, otherwise you have to subtract the amount of space they are overlapping from the value you want to move them.
Since the environment is a 2D plane, this should be pretty straightforward. With a library such as jQuery would be a joke, but even in plain js is just basic addiction and subtraction.
Assuming the boxes are aligned to the x and y axis as in your comment, first I'd change the representation of each rectangle to 4 points: top, right, bottom, left and store them as points on the rectangle. Second, let's simplify the problem to "Given n rectangles, where is the nearest point where rectangle r can move to so that it doesn't overlap any other rectangles"? That simplifies the problem a great deal, but also should provide a decent solution. Thus, we have our function:
function deOverlapTheHonkOuttaTheRectangle(rectangle, otherRectangles){
..
}
Now, each other rectangle will disallow a certain range of motion for the original rectangle. Thus, you calculate all of these disallowed moves. From these, you can calculate the disallow shape that overlaps the origin and each other. For example, lets say rect1 disallows a shift of -3px to 5px right and 4px to 10px up, and rect2 disallows -4px to 1px right and -2px to 5px up. rect1 was not considered until rect2 came along, since that one overlaps the origin and rect1. Starting with rect2, you'd have [[-4, -2],[1,-2],[1,5],[-4,5]]. Figuring in rect1 gives [[-4, -2],[1,-2],[1,4],[5,4],[5,10],[-3,10],[-3,5],[-4,5]] (see image below for clarification). You keep building these up for each overlapping disallowed rectangle. Once you have considered all the rectangles, then you can use a distance formula from the origin to get the smallest distance you can move your rectangle and move it.
Finally, you repeat this process for all remaining rectangles.

Categories