Very large HTML5 canvas circle imprecise - graphics

I'm developing an application where users draw euclidean constructions on the HTML5 canvas. As such I can't really limit the size of certain shapes. When exaploring very large circles being drawn on the screen I noticed that very large circles don't have a constant radius.
To be more specific, a circle defined by two points, a center point and one specifing the radius doesn't pass throught the radius point anymore!
Progressivly larger circles. These are all supposed to pass through point E.
The error doesn't occure on multiples of 45 degrees = PI/4. Between these multiples the error is biggest (PI/8 for example)
Here is a jsfiddle containing the first example above:
http://jsfiddle.net/D28J2/2/
My questions: Why does this occure? and Is there some way to (efficently) work around this?

The way I worked around this issue completely was roll my own implementation of a circle draw approximation with bezier curves. An article detailing the implementation can be found here http://www.tinaja.com/glib/ellipse4.pdf.
function magic_circle(ctx, x, y, r){
m = 0.551784
ctx.save()
ctx.translate(x, y)
ctx.scale(r, r)
ctx.beginPath()
ctx.moveTo(1, 0)
ctx.bezierCurveTo(1, -m, m, -1, 0, -1)
ctx.bezierCurveTo(-m, -1, -1, -m, -1, 0)
ctx.bezierCurveTo(-1, m, -m, 1, 0, 1)
ctx.bezierCurveTo( m, 1, 1, m, 1, 0)
ctx.closePath()
ctx.restore()
}
With just these four segements I was able to approximate a circle much better then the build in google chrome canvas implementation.

This is probably a floating point cutoff error. Possibly because sine and cosine aren't giving perfectly accurate values. You can get around it (in Chrome at least) by rotating the canvas instead of the arc.
ctx.save(); // Save the canvas so we can rotate back.
ctx.translate(x, y); // Translate to the origin point.
ctx.rotate(alpha); // Rotate the proper angle.
ctx.arc(0, 0, 3, 0, Math.PI*2); // Draw the small circle at the origin.
ctx.fill();
ctx.arc(r, 0, r, 0, Math.PI*2); // Create a big with the origin 1 radius away.
ctx.restore(); // Restore the canvas to the original orientation
// before drawing. Otherwise the circle looks bad.
ctx.strokeStyle = "black";
ctx.stroke(); // Draw!
I am a big fan of manipulating the canvas instead of shapes. It gives you a more logical area to work with. See http://jsfiddle.net/D28J2/10/

Just throwing this out there but could it be an issue without specifying enough digits of PI? Whenever I do things like that I tend to go slightly overboard and use about 10 digits of PI.

In Google chrome I can repeat the issue but in IE 9 and IE 10 all is fine.
So my guess is that the implementation is wrong in Chrome. It might be a rounding error or they used an interpolation method to avoid sin and cos which isn't very acurate.
Look here as well: HTML5 canvas arcs not rendering correctly in Google Chrome
The only work around I can imagine is drawing the circle by your own code or using a (jQuery?) plug-in that does this for you.

Related

Find original (x,y) new coordinates in fabric canvas

I am building a Warehouse map in 2D with fabricjs where I am displaying their racking systems as a series of rectangles.
As a first "layer/group", i add all bays as groups containing a rectangle and text, both positioned at the same (x,y). They also have both an angle set to fit their orientation in the space.
As a second "layer/group", i add groups containing a circle and text, representing the bay's number of issues. The (x,y) also fits the bays. This way, all my issues are always on top of the bays and the center of the group fits the rotated corner of the bay.
On the first paint, all is well aligned. Once they're shown on the page, the user can create new issues, so I am trying to position the issue group fitting the original (x,y), but since it can all be panned and zoomed, I am having a hard time positioning it where it should be.
I've been looking at their explanations about transforms, but I can't figure who the boss should be and thinking that having nested groups may also be why I am all mixed up.
By doing:
const gMatrix = matchingBay.group.calcTransformMatrix(false);
const targetToCanvas = fabric.util.transformPoint(matchingBay.aCoords.tl, gMatrix);
I am on the bay, but on the "group" corner, which is not what I am looking for. (x,y) will be one of the corners of the rectangle in the group, that may have been rotated.
By specifying the original (x,y) in this code will get me way off the actual painting zone.
So, my question is, how do I get the transformed (x,y) so I can add my issue group at those coordinates?
[EDIT]
As the suggestion of spring, it made me realize I can use the rotated rectangle's transforms to find its coordinates, so I tried:
const rect = matchingBay.getObjects()[BAY_RECTANGLE_INX];
const gMatrix = rect.calcTransformMatrix();
const targetToCanvas = fabric.util.transformPoint(rect.aCoords.bl, gMatrix);
Bottom left corner is where I wish to add the new Circle. After rotation, the bottom left is now the bottom right. But I am still off. As shown, the red circle should be on the corner. It looks like the rotation has not been applied.
qrDecompose gives me something that seems right:
{angle: -90, scaleX: 1, scaleY: -1, skewX: 0, skewY: 0, translateX: 6099.626027314769, translateY: 4785.016008065199 }
I realized that I was not thinking it the right way. Since I have the rectangle already in hands, I just had to get its own transformation and resolve the corner by my own, the following fixed my issue:
{
[...]
const rect = matchingBay.getObjects()[BAY_RECTANGLE_INX];
const gMatrix = rect.calcTransformMatrix();
const decomposed = fabric.util.qrDecompose(gMatrix);
const trans = fabric.util.transformPoint(new fabric.Point((decomposed.scaleX * -rect.width) / 2, (decomposed.scaleY * rect.height) / 2), gMatrix);
const top = trans.y;
const left = trans.x;
[...]
}
Since the matrix is bringing the center point of the rectangle, I can get the corner by substracting its width and height and then transforming the coordinates to find out where the matrix puts it.

How to avoid fade-out on scaled CCSprite without cancelling anti-aliasing?

I have 3 sprites. Left edge, right edge, and repeating center which has 1 pixel width but is scaled up. The problem is that the scaled sprite fades out the farther away it is from the center:
I've tried using CCTexture's setAliasTexParameters but the result doesn't look good:
How do I get the antialiased looks in the first picture but without the fade out problem?
You could try this on the sprite:
// These parameters set the texture properties:
// minifying filter - linear interpolation,
// magnification filter - linear interpolation,
// texture repeat in S direction,
// texture repeat in T direction (*)
ccTexParams params = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
[sprite.texture setTexParams:&params];
// This explicitly sets the contentSize of the sprite to (10, 200),
// but also sets the "window to the texture" to this rectangle:
[sprite setTextureRect:CGRectMake(0, 0, 10, 200)];
You have to tweak these settings, but hope you get it.
You don't have to scale the sprite.
(*) For S and T check this: Difference between U V and S T texture coordinates

transition a circle into a line by unrolling it with SVG and d3

For a project we are trying to make a circle into a line (and back again) while it is rotating along a linear path, much like a tire rotates and translates when rolling on a road, or a curled fore finger is extended and recurled into the palm.
In this Fiddle, I have a static SVG (the top circle) that rotates along the linear black path (which is above the circle, to mimic a finger extending) that is defined in the HTML.
I also use d3 to generate a "circle" that is made up of connected points (and can unfurl if you click on/in the circle thanks to #ChrisJamesC here ), and is translated and rotated
in the function moveAlongLine when you click on the purple Line:
function moveAlongLine() {
circle.data([lineData])
.attr("transform", "translate(78.5,0) rotate(-90, 257.08 70) ")
.duration(1000)
circle.on("click", transitionToCircle)
}
The first problem is that the .duration(1000) is not recognized and throws a Uncaught TypeError: Object [object Array] has no method 'duration' in the console, so there is a difference between the static definition of dur in SVG and dynamically setting it in JS/D3, but this is minor.
The other is should the transform attributes be abstracted from one another like in the static circle? in the static circle, the translate is one animation, and the rotation is another, they just have the same star and duration, so they animate together. How would you apply both in d3?
The challenge that I can not get, is how to let it unroll upwards(and also re-roll back), with the static point being the top center of the circle also being the same as the leftmost point on the line.
these seem better:
I should try to get the unfurl animation to occur while also rotating? This seems like it would need to be stepwise/sequential based...
Or Consider an octogon (defined as a path), and if it were to rotate 7 of the sides, then 6, then 5.... Do this for a rather large number of points on a polyhedron? (the circle only needs to be around 50 or so pixels, so 100 points would be more than enough) This is the middle example in the fiddle. Maybe doing this programmatically?
Or This makes me think of a different way: (in the case of the octogon), I could have 8 line paths (with no Z, just an additional closing point), and transition between them? Like this fiddle
Or anything todo with keyframes? I have made an animation in Synfig, but am unsure ho get it to SVG. The synfig file is at http://specialorange.org/filedrop/unroll.sifz if you can convert to SVG, but the xsl file here doesn't correctly convert it for me using xsltproc.
this seems really complicated but potential:
Define a path (likely a bézier curve with the same number of reference points) that the points follow, and have the reference points dynamically translate as well... see this for an concept example
this seems complicated and clunky:
Make a real circle roll by, with a growing mask in front of it, all while a line grows in length
A couple of notes:
The number of points in the d3 circle can be adjusted in the JS, it is currently set low so that you can see a bit of a point in the rendering to verify the rotation has occurred (much like the gradient is in the top circle).
this is to help students learn what is conserved between a number line and a circle, specifically to help learn fractions. For concept application, take a look at compthink.cs.vt.edu:3000 to see our prototype, and this will help with switching representations, to help you get a better idea...
I ended up using the same function that generates the circle as in the question, and did a bit of thinking, and it seemed like I wanted an animation that looked like a finger unrolling like this fiddle. This lead me to the math and idea needed to make it happen in this fiddle.
The answer is an array of arrays, with each nested array being a line in the different state, and then animate by interpolating between the points.
var circleStates = [];
for (i=0; i<totalPoints; i++){
//circle portion
var circleState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*i + radius * Math.sin(2 * j * Math.PI / (numberOfPoints - 1));
var y = margintop + radius - radius * Math.cos(2 * j * Math.PI / (numberOfPoints - 1));
return { x: x, y: y};
})
circleState.splice(numberOfPoints-i);
//line portion
var lineState = $.map(Array(numberOfPoints), function (d, j) {
var x = marginleft + radius + lineDivision*j;
var y = margintop;
return { x: x, y: y};
})
lineState.splice(i);
//together
var individualState = lineState.concat(circleState);
circleStates.push(individualState);
}
and the animation(s)
function all() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
function reverse() {
for(i=0; i<numberOfPoints; i++){
circle.data([circleStates[numberOfPoints-1-i]])
.transition()
.delay(dur*i)
.duration(dur)
.ease("linear")
.attr('d', pathFunction)
}
}
(Note: This should be in comments but not enough spacing)
Circle Animation
Try the radial wipe from SO. Need to tweak it so angle starts at 180 and ends back at same place (line#4-6,19) and move along the X-axis (line#11) on each interation. Change the <path... attribute to suit your taste.
Line Animation Grow a line from single point to the length (perimeter) of the circle.
Sync both animation so that it appears good on all browsers (major headache!).

d3.js : getting the bars width or X position right?

I have a weird issue in my bar graph realized using d3.js: the 1 px padding between each rectangle appears irregular. I gather either or both the width or x position are the culprit but i don't understand what i'm doing wrong: the width is a fraction of the svg area and the X position is obtained via a D3 scale.
I've put a demo here: http://jsfiddle.net/pixeline/j679N/4/
The code ( a scale) controling the x position:
var xScale = d3.time.scale().domain([minDate, maxDate]).rangeRound([padding, w - padding]);
The code controlling the width:
var barWidth = Math.floor((w/dataset.length))-barPadding;
Thank you for your insight.
It's irregular because you are rounding your output range (rangeRound). In some cases, the distance between two bars is 3 pixels and sometimes only 2. This is because the actual x position is a fractional value and ends up being rounded one way in some cases and the other way on other cases.
You can mitigate the effect but changing rangeRound to range, but that won't eliminate it entirely as you'll still get fractional pixel values for positions. The best thing to do is probably to simply increase the padding so that the differences aren't as obvious.

Line width in raphaeljs

Is it real to make line with 1px weight in SVG or raphaeljs?
The follow code
var p = Paper.path("M1 1 L50 1");
p.attr("stroke", "#D7D7D7");
p.attr("stroke-width", "1");
p.attr("opacity", 0.5);
draw line which looks like 2px or 3px. Any alternative?
When SVG lines lie at their apparently correct coordinates they actually lie inbetween pixels, so when you state M1 1 L50 1 it paints half a pixel on the top and the other half in the bottom of the pixel, making it look like a thick, semitransparent line.
To solve this problem you need to either paint at half pixels, or translate your elements half a pixel, ie. element.translate(0.5, 0.5)
You can see the blurry and sharp lines here:
http://jsfiddle.net/k8AKy/
You should also use the Paper.renderfix() function since you do not know which browser your users will be using.
From the documentation
Fixes the issue of Firefox and IE9 regarding subpixel rendering. If
paper is dependant on other elements after reflow it could shift half
pixel which cause for lines to lost their crispness. This method fixes
the issue.
This links take you point what's going wrong with integer coordinates and why +0.5 was fixed edge blurring (with nice pictures!!):
http://diveintohtml5.info/canvas.html#pixel-madness
http://kilianvalkhof.com/2010/design/the-problem-with-svg-and-canvas/
Compare:
with +0.5:
You can avoid +0.5 by:
SVG_Area.setAttribute("viewBox", "0.5 0.5 " + width + " " + height);
or by wrapper:
function fiXY(x) { return parseInt(x) + 0.5; }
var rect = document.createElementNS(SVGobj.svgNS, "rect");
rect.setAttribute("x", fiXY(x));
rect.setAttribute("y", fiXY(y));
or by:
SVG_Area.setAttribute("shape-rendering", "crispEdges");
which effect on all shapes in you SVG image....

Resources