October 31st, 2016

openFrameworks - Brushes & Shapes

In the last post, we went through some high-level C++ basics. Going forward, we'll dig into the openFrameworks ecosystem.

The next chapter on graphics comes after the an important run-down containing information necessary for getting openFrameworks setup and running on your system. There are some great links, and setup guides available. If you're interested in checking those out before we get too much further, please do so:

Ok; moving on. This chapter was written by Mike Hadley with editing help from: Abraham Avnisan, Brannon Dorsey and Christopher Baker. I'm going to take this chapter in chunks because its quite dense and full of great fundamental information.

Brushes and Basic Shapes

Triangles

Before drawing anything, it's important to note how to specify locations on a screen. Computer graphics implements a Cartesian Coordinate System, which means that the upper left corner of a given area is marked as [0, 0] ([x, y]). As you progress across the screen (x) towards the right, values increase. As you progress down the screen (y), values increase.

2d graphics functions are categorized into two groups:

  • Basic Shapes: circle, square, line, triangle,
  • Freeform Shapes: polygon, paths

The code example, you can reference here, walks through some introductory methods to drawing some basic shapes. Here are some important methods:

// sets the entire screen to a single shade of grey 0 (black) - 255 (white)
ofBackground(150);

// sets the drawing color
ofSetColor(255);

// Creates a rectangle at the given coordinates. First two are x,y; second two are width and height
ofDrawRectangle(50, 50, 100, 100);

// Creates a circle centered at 250, 100, with a radius of 50
ofDrawCircle(250, 100, 50);

// Draws an ellipse centered at (400 100), 80 wide x 100 high
ofDrawEllipse(400, 100, 80, 100);

// Three corners: (500, 150), (550, 50), (600, 150)
ofDrawTriangle(500, 150, 550, 50, 600, 150);

// Line from (700, 50) to (700, 150)
ofDrawLine(700, 50, 700, 150);

// If this is added all fills will appear filled
ofFill();

// Removes all fills
ofNoFill();

Circles are rendered with a default resolution of 20. This causes them to appear a little jagged. To resolve this, we can use a method called ofSetCircleResolution. It is applied to setup method in the ofApp.cpp file created by the project generator.

void ofApp::setup(){
    ofSetCircleResolution(100);
}

Brushes

Circles

Brushes are pretty interesting, and allows us to quite literally, paint on the canvas. The first few examples use some basic methods to draw squares on the canvas when the mouse button is pressed. Some methods, and constants to note are:

// expects an int; will return a bool representing the pressed state of the corresponding mouse button
// OF_MOUSE_BUTTON_LEFT = a constant containing the int that represents the left button
ofGetMousePressed(OF_MOUSE_BUTTON_LEFT);

// method for setting the registration point for a rect.
// OF_RECTMODE_CENTER = constant for setting rect registration to center.
ofSetRectMode(OF_RECTMODE_CENTER);

// will get the X coord of the mouse.
ofGetMouseX();

// will get the Y coord of the mouse.
ofGetMouseY();

Frame Rate in openFrameworks is regulated by using ofSetFrameRate(<frame_rate>) in the setup method inside ofApp.cpp. The value you pass to the ofSetFrameRate method acts a speed limit, not a speed minimum. Rendering will not exceed this value.


Cartesian vs Polar Coordinates: As we noted earlier, Cartesian coordinates are representative of a screen and subsequently a square. Cartesian coordinates can be represented using 'x, y'. Polar coordinates are different in that the x value is actually derived from an angle value (polar angle) and the y is radius (polar distance) of a circle.

The following is a loose visual description of how to express Polar vs Cartesian coordinates.

  float distance = ofRandom(35);
  // Formula for converting from Polar to Cartesian coordinates:
  //  x = cos(polar angle) * (polar distance)
  //  y = sin(polar angle) * (polar distance)

  // We need our angle to be in radians if we want to use sin() or cos()
  // so we can make use of an openFrameworks function to convert from degrees
  // to radians
  float angle = ofRandom(ofDegToRad(360.0));
  float xOffset = cos(angle) * distance;
  float yOffset = sin(angle) * distance;

Painting in shades of gray can be tiresome. ofSetColor can accept a series of arguments representing RBG and Alpha channels. Here's an example of the method signature:

  // arguments: r, g, b, a
  // rgba values are anywhere between 0 & 255
  ofSetColor(255, 15, 0, 5);

Color can be set in another way too. There's a handy method called ofColor, that allows you to create color values and manipulate there values.


  // how we use ofColor to create variables that contain color values.
  ofColor myBlue(0, 0, 255, 50);

  myBlue.b = 150;
  myBlue.a = 255;

  // and can set to one of the openFrameworks colors
  ofColor myBlue = ofColor::aqua;

Continuing with our list of handy methods, the article introduces a function of the ofColor class called: getLerped(...). This function is used to achieve linear interpolation between two colors. Here it is in action:


  myFirstColor.getLerped(mySecondColor, 0.3);

Basically this function is called with two arguments: 1) the ofColor (mySecondColor) to which the interpolation is applied, and 2) a float representing how close the new color will be to the original (myFirstColor). It will then return a new ofColor that is between the two specified colors.

I'm starting to find openFrameworks has a class or method for nearly everything. They all have their uses, and in some cases are similar in their functionality.

Let's take a look at ofMap. This class takes 5 arguments and essentially takes 1 range of values and transforms them into a different range of values. This can be extremely useful for doing something like taking the "loudness" of a sound recorded on a microphone and using it to determine the color of a shape drawn on the screen. Here's how it looks:


  float value = 100;
  float inputMin = 25;
  float inputMax = 125;
  float outputMin = 0;
  float outputMax = 25;

  float myVar = ofMap(value, inputMin, inputMax, outputMin, outputMax);

We tell ofMap that our value is in-between inputMin and inputMax and we want it mapped so that a value equal to 25 will be output as 0, and a value that is equal to 125 will be output as 25. We're effectively enforcing a new range for the given value.


Triangle Brush section notes:

rotate: rotate - a function of ofVec2f used to rotate the given vector by n deg around the origin point.

What a cool code example. It was definitely worth walking through and taking the time to understand.


AND, finally, taking a screen shot. By adding the following code to the keyPressed method in the ofApp.cpp file, we can take screen shots of our work. In fact, the images you see in this article have been created using this technique. Here's the code:


void ofApp::keyPressed(int key){
  if (key == 's') {
      // HACK: only needed on windows, when using ofSetAutoBackground(false)
      glReadBuffer(GL_FRONT);

      // We use the timestamp here so that you can save multiple images without
      // overriding previous screenshots (i.e. each file has a unique name)
      ofSaveScreen("savedScreenshot_"+ofGetTimestampString()+".png");
  }
}

2017 : James Walton : Digital Carpentry