Ladan's Personal Blog on ML and Software Development

JUCE 2D Graphics: Paths and Basic Geometry

Experiments with the JUCE 2D graphics API

A while ago I had to learn a bit of JUCE 2D graphics programming for some experiments we were doing at TONZ. I thought it was super interesting at the time and kind of fun, so I’ve spent a little bit more outside of work playing with the graphics subsystem to learn to do more things.

I’ll walk through how to make a Venn Diagram using JUCE GUI geometry and graphics primitives.

Using a clipping region

Let’s start from the beginning.

The Graphics Context

Graphics are updated in UI components whenever the paint() method gets called, which every UI Component can overload with custom code. It is defined in the base Component class and inherited through the whole GUI API.

The paint() method gets called when the object is drawn, and it is passed a reference to a Graphics context object. This object controls how drawing will be performed, and provides information about where and how, at the time the paint() method is called.

Properties like color, opacity, bounds, clipping region, etc, are stored in Graphics context object and can be read or set within the paint() method while it is drawing.

Geometry and Paths

The simplest way to draw in JUCE is to use methods available to draw basic geometrical shapes: rectangles, ellipses, triangles. There are a number of ways to do this, but I am interested in using Paths, which represents a sequence of connected lines and curves that can abstractly represent a shape that gets drawn or transformed. You can simply ask the Graphics context object to draw a supported shape using points, but I think that’s less interesting than working with Path objects because these can do more things.

Creating two overlapping circles

	/* Define two paths */
	
	juce::Path path1, path2;

	/* Create two paths representing circles that overlap */
	path1.addEllipse(getWidth() - 450, 50, 200, 200);
	path2.addEllipse(getWidth() - 300, 50, 200, 200);

Now let’s draw them using the strokePath() method in the Graphics context object. We should set a different color for each using the setColour() method. This needs to be done separately, before each call to strokePath(). The Graphics context object is like a robot painter, one that needs to dip their brush into the right color as they carry out their intended work in order.

	g.setColour(juce::Colours::blue); /* Blue */
	g.strokePath(path1, juce::PathStrokeType(1.5f)); /* Draw first circle */
	g.setColour(juce::Colours::red) /* Red */
	g.strokePath(path2, juce::PathStrokeType(1.5f)); /* Draw second circle */

Let’s see them

Two overlapping circles

Making a Venn Diagram

The next challenge is to try to depict the classic Venn Diagram. In this exercise, I wanted to have each circle be filled with a distinct color, with the shared region a third color.

There are many ways to do this but I wanted to use a clever trick with clipping region and paths. I’m going to introduce a method I love using called reduceClipRegion(), which tells the Graphics context where it is allowed to draw.

Idea:

  1. Create two Paths that are overlapping circles
  2. Fill each of them with their own colour
  3. Apply a clipping region of path 1 in the Graphics context object
  4. Fill path 2 again, so it will only have the region shared with path 1 filled, due to the clipping region reduction in step 3
	/* Define two paths */
	
	juce::Path path1, path2;

	/* Step 1 */
	path1.addEllipse(getWidth() - 450, 50, 200, 200);
	path2.addEllipse(getWidth() - 300, 50, 200, 200);

	/* Step 2 */
	g.setColour(juce::Colours::blue);   /* Blue */
	g.fillPath(path1);                  /* Draw and fill the first circle */
	g.setColour(juce::Colours::red);    /* Red */
	g.fillPath(path2);                  /* Draw and fill the second circle */
	g.setColour(juce::Colours::purple); /* Red + Blue = Purple */

	/* Step 3 */
	g.reduceClipRegion(path1);          /* Reduce the clip region to the area of path 1 */

	/* Step 4 */
	g.fillPath(path2);                  /* Fill circle #2, only within the clipping region (path 1) */

The Venn Diagram

Here is what it looks like after step 4.

Using a clipping region

That’s it for now. In the next post I’m going to talk about another reason using Path objects are so cool: the ability to manipulate them using Affine transform operations. Affine tranformations are represented compactly using a matrix, so I found myself in familiar territory and was able to use some linear algebra know-how from ML to do some interesting things.