0060: Cairo IV – Filled Arc, Precision Drawing, and Curves

This is a continuation of our Cairo briefs…

Filled Arc

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

This is the callback:

bool onDraw(Scoped!Context context, Widget w)
{
	float x = 320, y = 180, radius = 40;
       float start = 0.7, finish = 2.44;
		
 	// draw the arc
	context.setLineWidth(3);
	context.arc(x, y, radius, start, finish);
	context.fill();

	return(true);
		
} // onDraw()

This is like the other arc() calls, but followed with a fill() command instead of stroke().

But we can also do some interesting stuff by issuing a bunch of arc() calls interspersed with moveTo()’s and such like…

A Cartoon Smile with Arcs

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

The cartoon smile is done like this:

bool onDraw(Scoped!Context context, Widget w)
{
	float xPos1 = 213, yPos1 = 160, xPos2, yPos2;
	float xPos3, yPos3, radius1 = 40, radius2 = 10;
	
	// draw the first arc (the mouth shape)
	context.setLineWidth(3);
	context.arc(xPos1, yPos1, radius1, 0.7, 2.44);
	context.stroke();
	// find the right corner of the mouth shape
	xPos2 = xPos1 + cos(0.7) * radius1;
	yPos2 = yPos1 + sin(0.7) * radius1;
	// draw the second arc
	context.arc(xPos2, yPos2, radius2, 4.2, 0.7);
	context.stroke();
	// find the left corner of the mouth shape
	xPos3 = xPos1 + cos(2.44) * radius1;
	yPos3 = yPos1 + sin(2.44) * radius1;
	// draw the third arc
	context.arc(xPos3, yPos3, radius2, 2.6, 5.6);
	context.stroke();
	
	return(true);
	
} // onDraw()

The X and Y Positions

These are the centers of our three circles around which we’ll draw three arcs. The first—xPos1, yPos1—is for the mouth shape and the other two are for the “smile lines.”

Finding the Corners of the Mouth

xPos2/yPos2 and xPos3/yPos3 are positioned at the two ends of the smile arc—the corners of the mouth—which takes a bit of trig, but nothing complicated.

Finding the end of an arc is pretty straightforward. Looking back at the start angle of the mouth arc and the radius, we calculate the right corner of the mouth like this:

xPos2 = xPos1 + cos(0.7) * radius1;
yPos2 = yPos1 + sin(0.7) * radius1;

I’ll throw in a quick reminder that arcs are drawn in a clockwise direction which is why this calculation yields xPos2/yPos2, the right corner.

Same for the left corner except we use the end position of the arc:

xPos3 = xPos1 + cos(2.44) * radius1;
yPos3 = yPos1 + sin(2.44) * radius1;

Follow each of those pairs of calculations with a call to arc() with a smaller radius and we’re done.

###Where Did Those cos() & sin() Args Come From?

They’re the start and end points of the ‘smile’ arc and they’re in radians. A quick look at our Radians Compass bears this out:

Figure 1: Left: First example, Middle/Right: second example in two steps
Figure 2: Radians Compass with 'smile' start and end points marked.

Drawing a Curve

Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)
Results of this example:
Current example output
Current example output
Current example terminal output
Current example terminal output (click for enlarged view)

In Cairo terms, the difference between an arc and curve is:

  • an arc uses a center ‘anchor’ point as a base and two positions around a circle as start and end points, whereas
  • a curve uses x/y locations to set up two control points and only needs coordinates for the end point of the curve.

So how does the curveTo() function know where to start the curve?

There are three rules governing placement of curveTo()’s starting x/y position:

  1. It’s set beforehand with moveTo(),
  2. if no moveTo() call precedes curveTo(), the first control point doubles as a starting point, or
  3. it uses the end point of a previously-drawn line, arc, or curve.

When you look at the code for our two example files, which are a single curve, and two curves joined together, you’ll note that the curve in the first example is repeated in the second, but the resulting curves are quite different. But, looking back at the rules, it’s easy to see why…

Comparing the two curve-drawing examples…

In the first example, the starting x/y position is passed to curveTo() and because it’s not preceded by a moveTo() call, it’s also where the drawing actually starts.

In the second example, however, the first call to curveTo() is preceded by a moveTo() which means the moveTo() decides where drawing starts. Here’s how the two examples play out:

Figure 1: Left: First example, Middle/Right: second example in two steps
Figure 1: Left: First example, Middle/Right: second example in two steps

Conclusion

Keep these things in mind while drawing curves and you’ll do just fine:

  • remember to use a moveTo() before a curveTo(), or else
  • the first x/y coordinate will be the starting point, and
  • curves use control points to control the severity of the curve.

And that’s about it for curves. Next time, we’ll dig into what’s referred to as Cairo’s Toy Text mechanism. Until then.

Comments? Questions? Observations?

Did we miss a tidbit of information that would make this post even more informative? Let's talk about it in the comments.

You can also subscribe via RSS so you won't miss anything. Thank you very much for dropping by.

© Copyright 2023 Ron Tarrant