Table of Contents
This tutorial shows how to use a Graphics object to draw text, lines, and geometric shapes.
This is fundamental to performing drawing in JUCE.
Platforms: Windows, Mac OS X, Linux, iOS, Android
Download the demo project for this tutorial here: tutorial_graphics_class.zip
The demo project for this tutorial contains a main application window and a main component. You should be familiar with these from the last tutorials: Tutorial: The application window and Tutorial: The main content component.
You already know that the appearance of the main component (or, as a matter of fact, any other Component!) is determined by the implementation of its
paint() function. The demo project here takes off where the last tutorial (Tutorial: The main content component) concluded. The implementation of the
paint() function initially looks as follows:
If you compile and run the app now, you should see that the window now has a light blue background colour, and the text Hello, World! is drawn on top of it, in the centre of the window.
In the following, we will add some code that draws some more graphics into the
MainComponent object, using the Graphics class. This is a very powerful class, and we will be using it a lot in future tutorials to implement the custom visual appearance of different JUCE components.
Let's have another look at the paint function. Remember that the
paint() function is a callback called by the operating system when it is time to render your Component on screen — you should never call this function yourself.
Notice that as the argument to this callback, a reference to a Graphics instance is passed in. This Graphics object is provided by the underlying framework. It is the graphics context that you can use to render any graphical elements: text, lines, shapes, colours, gradients and much more. We will explore some of these in this tutorial.
- The Graphics class is only useful in the
paint()callback. Normally, you should never use it anywhere else.
Let's first continue with text. The line
sets the font size to 20 points for the subsequent line (which draws the text Hello, World! using that font). But what if we want to not only change the size of the font, but also use another typeface and bold or italic letters? And how do we change the position of the text?
Because we are using this font for our main component, we choose the descriptive variable name
The first argument of the Font constructor determines the typeface, the second is the font size, and the third is the font style. Here, we chose italic for the style. The font styles are actually flags that can be used as a bitmask (see Tutorial: The application window), so you can combine them for example like this:
If you compile and run the app again, you should see that the font has changed.
- Using a typeface for a font that is not actually installed on the computer is a very common reason of fonts not working properly in a JUCE app.
although your code will usually be better readable and maintainable with separating the statements and using a named variable. (With modern compilers, introducing such an additional variable will have no impact on performance.)
Now we change the position of the text. In the process, we will learn how positioning is handled in JUCE.
The easy way would be to simply change the alignment of the text with respect to the whole component, for example by changing the Justification::centred value to another one of the possible values, for example the Justification::topLeft value. (You can also check out the other possible Justification::Flags values.) However, another very powerful approach is to explicitly define the size and position. There is another version of the Graphics::drawText() function using this approach. Change the line starting with the
g.drawText() call to the following:
This tells the Graphics object to render the text into an area that is 200 pixels wide, 40 pixels high, and located 20 pixels to the left and 40 pixels to the bottom from the top-left corner of the main component.
The app should now look like this:
- Remember: coordinates in JUCE are always measured from the top-left corner of the current component, which is the point (0, 0). They can be given as
floatnumbers. When used to specify the position of a graphical element or child component, it will be positioned such that its top left corner will appear at the given position. A more in-depth explanation can be found in Tutorial: The Component class, sizes, bounds, and positions.
Finally, the last argument of the Graphics::drawText() function is a
bool flag which determines whether an ellipsis (...) should be shown if the text does not fit within the given width, or whether the text should be simply chopped off.
- Change the width of the text field from 200 to smaller values and note how the ellipsis flag works.
- The Graphics::drawText() function is good for rendering single-line text. For multi-line text, it offers other functions such as Graphics::drawMultiLineText() and Graphics::drawFittedText().
In this section, we continue with drawing some geometrical shapes using the Graphics class.
Add the following lines to the bottom of the
This will draw a green horizontal line 5 pixels wide across the window, starting from (10, 300) and ending at (590, 300). Note that every time you want to draw a geometric shape in another colour than the one used last time, you have to call the Graphics::setColour() function before you draw.
You can of course also draw diagonal lines by specifying other coordinates. In fact, JUCE also supports subpixel coordinates (you can use
float values for the positions). If the position falls between physical screen pixels, JUCE will apply anti-aliasing for the drawing.
- Explore other types of lines. Can you figure out how to draw dashed lines, or arrows? Hint: have a look at the Graphics class documentation.
This will render a brown rectangle, 200 pixels wide, 170 pixels high, positioned with its top-left corner at the position (300, 120).
An optional fifth argument lets you specify the line thickness:
If you want a filled rectangle, use the function Graphics::fillRect() instead:
Instead of giving the position, width, and height separately, there is a more convenient class to represent a rectangle: the Rectangle class. There is also a version of the Graphics::drawRect() function that takes such a Rectangle instance to specify the position of the rectangle:
This very convenient Rectangle class will be explored in a future tutorial.
- Find out how to draw a rounded rectangle. Next, also try to draw a filled rounded rectangle.
You don't have to fill the rectangle with a solid colour. You can also use a colour gradient or one of several other patterns. Let's imagine that the brown rectangle represents a house. We can add a brick-like texture by filling it with a checkered pattern. Use the following code to draw the rectangle:
If you compile and run the application now, it should look as follows:
Let's see how the Graphics class draws circles and ellipses. Have a look at the functions Graphics::drawEllipse() and Graphics::fillEllipse(). They work just like the Graphics::drawRect() and Graphics::fillRect() functions.
Let's add a sun to our little landscape. The following code will draw a circle 60 pixels across in the upper-right region of the window:
Note that the position given (530, 10) does not place the centre of the circle at that position. Instead, as with all other graphical elements, the object will be placed such that the top-left corner of its enclosing rectangle will be located at the given position.
You can also explicitly use the bounds of the component to calculate the position, for example:
- Write a wrapper function around the Graphics::drawEllipse() function for drawing circles more conveniently. The function should take the coordinates of a point and a radius, and then draw a circle with the centre at this point and the given radius.
Finally, let's add a roof to our house. This will be a red triangle.
You will find out that there is no function called
drawPolygon() in the Graphics class. For this, we have to take a more generic approach.
Check out the Path class. It essentially handles any sets of connected points. In this case, we need a triangle that comprises three points. For example, we could use the three points (300, 110), (500, 110), (400, 70) so that the roof triangle sits on top of the house rectangle.
That's how our red roof looks like in JUCE code:
Here is how the finished demo app should look if you compile and run it now:
- The Path class is capable of many other things and will be explored in more depth in a future tutorial.
- Rendering and formatting text.
- Drawing lines.
- Drawing geometric shapes such as rectangles, circles, and polygons.
The Graphics class is capable of more graphical rendering functionality than discussed in this tutorial. Notably, you can use it to draw images (from image files) on the screen. There is also much more you can do using the Path class. Some other features of the Graphics class include colour gradients, transparency layers, and transforms. Some of these will be covered in future tutorials.
Generated on Fri Jan 12 2018 09:51:15 for JUCE by 1.8.13