Rounded Corners With Processing.js

How to create rounded corners for your website using the Processing.js API

This box got SO rounded!!!
I used the Processing.js API to create this rounded box and it's shaddow. Thanks to Query UI... it's Draggable... so drag it around and check out the subtle transparency!
Why Use Processing.JS?

I wanted to create a rounded corners function in Processing.js that I could use in a multitude of web projects. Processing.js is really great for this kind of purpose because you can build classes of graphic objects very easily and manipulate matrices (layers) to create complex interactive scenes. These can later be extended to provide further functionality to your design, without having to re-build your code from the ground up. I guess that is true of any Object Oriented language but some of Processing's greatest power is locked within it's ability to Push and Pop Matrices... that is.... to store "layers filled with shapes" ready for group manipulation.

Rounded Corner Applications

There are so many uses for rounded corners, and so little time... so here are the first four that came to mind:

  1. Rounded DIV corners for web pages.
  2. Rounded boxes for games.
  3. Rounded form buttons.
  4. Rounded link icons like my animated RSS icon at the foot of this page.
How To Make Rounded Corners in Processing.JS

Making a rounded box is actually pretty simple. As you can probably guess: you need 4 straight lines for the edges, and 4 curved lines for the corners. Using Processing.js you can combine all these lines together to create one shape that is automatically filled and stroked with very little effort. Hover over the box to see the 4 straight lines and the 4 rounded corners.

Hover for line/curve break-down.

OK: let's dig into the code: The first thing to do is create our function(). We're going to call it something obvious, 'roundedCorners()' and add some variable names through which we can pass the properties of the box.

void roundedCorners(left, top, width, height, roundness){
}

First we will call "void" because we do not want any values returned from our function. Then because we are looking at this from a web perspective, it makes semantic sense to use CSS-type property names. Such as: left, top, width, height etc. This works well when drawing shapes, as a regular rect() would also be drawn using a top-left co-ordinate system in Processing anyway.

Now we have defined our properties, we can get to work on the math inside the function: consider a completely two dimensional square box. How many vertexes does it have? Four. One for each corner. A round box will obviously need more, but how many?

To know the answer to this question we need to understand a little more about the command set available in the Processing API.

A Perfect Circle

You probably had those abstract debates when you were a kid about the possibility of creating perfect circle: can there ever be a perfect circle? Mathematically it's possible, but realistically... we never see them. We see approximations of circles. We see 32 vertexes set out at the points a circle would cross, but even these values are usually only correct to a 32 bit floating point.

So when ever we want to draw a curve from one point to another, we must first apply several trigonomic functions to determine how to get there. Fortunately... that is beyond the scope of this article, because the Canvas object and Processing.JS handle all of this for us. Thank God!

However: for the canvas to understand how we want out curve to look, we need to give it enough information to apply the math. This is why when you draw any curve, you need to specify an anchor point. We can do this with the vertex() command. Once we have set out anchor point, we can begin building our curve using either the curveVertex ( x , y ) command or... the bezierVertex ( x1, y1, x2, y2, x3, y3 ) command.

Even though the curveVertex() command looks simpler... it's not. You would be required to draw a minimum of 3 curveVertex() points after your anchor anyway, so you may as well just use one line of code with bezierVertex().

Use the <textarea> example above to update the Processing.JS code. Comment a few things out and back in again, change some values and try deleting the anchor vertex point or endShape() / beginShape() to see what happens. This should give you a much better understanding of the Processing.js API's requirements for drawing curves.

Back to Our Function()

Now we understand that we need four vertexes to draw a curve, we can begin to build our final rounded box shape. We will start with a straight line because it's slightly easier. But even so, our code needs to take our left, top and roundness values into consideration before we put math to pixel.

Rounded Corner Metrics

As you can see, we do not want to start drawing a straight line from the very top-left of our box. We need to offset our starting point by the value of our roundness variable. So we draw our first vertex like so...

vertex ( left + roundness, top );

If you were paying attention, you will notice that our fist point is where our red dot has been placed. The next step is to draw a second vertex, which serves as: a) the end of a two vertex line and b) the anchor point for our first bezierCurve(). This second vertex is represented by the blue dot and our code now looks like this.

vertex ( left + roundness, top );
vertex ( left + width - roundness, top);

Now we must draw a curve from the top of the box, round to the right side of the box using the roundness offset. So we need to draw from: ( left + width - roundness, top ) to: ( left + width, top + roundness ). But wait... we have only specified two points, a bezierCurve() must have at least 3 points. We need to draw a curve from Point A to Point C, through Point B. Because we want to create a very smooth curve, we are going to use the very top right of the box for our 2nd bezierCurve vertex. Like so:

vertex ( left + roundness, top );
vertex ( left + width - roundness, top);
bezierVertex ( left + width - rounded, top, left + width, top, left + width, top + rounded );

So then: we have set our anchor vertex() at the blue point and also begin our bezierCurve() at the blue point... then we curve through the yellow point and finished our bezier on the orange point. We can now step our way around the rest of the box using the same method... and the final code will look something like this:

// Rounded Corners with Processing.js
// By F1LT3R - http://groups.google.com/group/processingjs

void setup() {
  size(200,200);
  strokeWeight(2);
}

void draw(){
  background(100);
  roundedCorners(50, 50, 100, 100, 10);
}

void roundedCorners(int left, int top, int width, int height, int roundness)
{
beginShape();               
  vertex(left + roundness, top);
  vertex(left + width - roundness, top);
  bezierVertex(left + width - roundness, top,
               left + width, top,
               left + width, top + roundness);
                          
  vertex(left + width, top + roundness);
  vertex(left + width, top + height - roundness);
  bezierVertex(left + width, top + height - roundness,
               left + width, top + height,
               left + width - roundness, top + height);
        
  vertex(left + width - roundness, top + height);
  vertex(left + roundness, top + height);        
  bezierVertex(left + roundness, top + height,
               left, top + height,
               left, top + height - roundness);
        
  vertex(left, top + height - roundness);
  vertex(left, top + roundness);
  bezierVertex(left, top + roundness,
               left, top,
               left + roundness, top);        
endShape();
}

As usual: no copyright here. Use the code as you wish. I would love to hear from you if you do.

Rotational Simplification

It is possible to simplify this code by drawing one step, rotating the matrix by 90 degrees and drawing the step again. This can be repeated four times to complete the box, but there are trade-offs doing the math for this using a top-left co-ordinate system. The easiest way to achieve the rotational method is to draw & spin the object from the center. This is where Processing really comes into it's own, but I think I will save that for another article.