|
~ Java for Web Page Production ~ Session 6 - Introduction to 2D Graphics |
Hope you enjoyed your break!
The last session probably made your brain ache somewhat. Don't worry too much about this, as we will be revisiting many of the topics, and making use of many of the principles in forthcoming examples - the expression anonymous inner classes will just roll off your tongue by the time you have reached the end of the course!
This session, we will be looking at one of the more attractive parts of Java - painting different shapes on the screen in various interesting ways.
By the end of this session, you should have a good grasp on how to paint shapes and text on the screen using different patterns, lines and colours.
You should also have a good grasp on how colours work in Java, and some of terminology used when dealing with colours.
We will round of this session with a sample applet to show randomly moving and changing shapes on the screen.
In Session 5, we covered:
Scoring - Techniques for implementing scoring within an applet (e.g. for games)
AWT and Swing - An overview of what this is in Java
Swing Controls - Commonly used Swing controls, such as Labels, Text Boxes, Password Boxes, Checkboxes, Frames and Panels.
Interfaces - An overview of what interfaces are and how they are used
Event Handling - Event handling using interfaces - e.g. for mouse clicks etc.
Anonymous Inner Classes - Writing event handlers within the body of your code
Adaptors - How to avoid implementing a method for an entire interface
Borders - How to put different types of borders around controls
In session 6, we will be looking at:-
Introduction
Session 5 Summary
Session 6 Overview
Introduction to the 2D API
The JApplet's Paint method and the Graphics2D Class
The co-ordinate system used by the 2D API
Drawing different shapes - the rectangle, line, ellipse, and text.
Changing line and fill attributes, including colours, gradient fills and dashed lines
About colour - the Color object
Standard Colours
The RGB palette
Kaleidoscope example - animating our shapes using a Timer and a 2D array.
Summary & Further Resources.
In the spirit of computer terminology, the 2D API is an unintelligible term for a fairly straightforward concept.
2D means Two-Dimensional. This basically means flat.
A one-dimensional object can go left and right - e.g. along a line.
A two-dimensional object can go left and right plus up and down - e.g. within the bounds of a flat surface (e.g. a computer screen)
A three-dimensional object can go left and right plus up and down plus forwards and backwards (e.g. the world)
Further dimensions, we'll leave to the realms of theoretical Physics!
So you can see that 2D refers to drawing things in a flat, two-dimensional plane - e.g. on the computer screen, or on a printed piece of paper. There is a Java 3D API which simulates what three-dimensional objects look like when viewed on a two-dimensional surface, but that's fairly advanced stuff - we could easily have a course devoted to this on its own!
The API bit refers to Application Programming Interface. It's a bit of jargon that's used often in programming. It usually means a library of classes that can be used to perform specific tasks.
Put the two together, and you get a number of classes that can be used to draw things on the screen (or to the printer). Indeed, it is used quite extensively by Swing components to draw themselves independently of the computer they are being used on. We will not be looking at printing on this course, but once you have learned the techniques for drawing on the screen, stretching this to a printer is fairly straightforward - look up the details in the Java 2D tutorial on java.sun.com for further details.
Paint Method and the Graphics2D class
When creating your Java applet, you extend the JApplet class, and all of its associated methods.
One of the methods is called init which determines what should happen when the applet is first initialised (i.e. loaded into the web page). We have been using this extensively in all of our examples.
Another useful method, particularly when using the Graphics2D class (which represents the canvas on which various shapes, pictures, controls, etc. are painted) is the paint method. This method is called whenever the surface of the applet (i.e its canvas) needs to be repainted - e.g. when the applet is first started, if it reappears having been hidden behind another window, etc.
Thus, its a good idea to put any instructions to do with painting on the applet inside this paint method.
Furthermore, the paint method passes (as a parameter) a copy of the Graphics2D object that refers to the canvas of the JApplet class from which your applet is descended.
This parameter is passed as a Graphics type object, of which Graphics2D is a descendant. Thus, you can cast the object from a Graphics type object to a Graphics2D type object, and use all of the methods associated with the Graphics2D class in order to alter the JApplet's canvas. See the section in last session on Polymorphism for more details.
The example below shows a simple example, which, when initialised, sets the foreground and background colours for the applet's canvas.
When asked to paint the canvas, the paint() method is called. The applet will find the Graphics2D object and call it g2, set the paint colour to light-grey, and then draw a 'raised' looking rectangle around the entire applet, starting in the top-left hand corner of the applet (co-ordinate position 0,0) with the width being the width of the canvas - g2. getSize().width - minus 1, and the height being the height of the canvas minus 1 - g2.getSize().height - 1.
A second rectangle is then painted five pixels (dots) further in both from the left and the top, the width being correspondingly less to make the second rectangle stop fice pixels (dots) both from the right and the bottom - thus, the width has to be ten pixels less than the first rectangle in both height and width (to account for the offset from both edges). The second rectangle is a 'lowered' looking rectangle.
Putting the two rectangles together makes the two together look like a raised border.
| import java.awt.*; import javax.swing.*; public class GraphicsExample extends JApplet { public void init() { setBackground(Color.lightGray); setForeground(Color.black); } public void paint (Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setPaint(Color.lightGray); g2.draw3DRect(0, 0, getSize().width-1, getSize().height-1, true); g2.draw3DRect(5, 5, getSize().width-11, getSize().height-11, false); } } |
Exercise - try the example in a new project
Up until now, the examples have been provided for you to try out for yourself. When you come to create your own applets, you may wish to base your applets on existing applets, or template applets that you have created for yourself.
The following exercise takes you through amending a 'blank' template applet to do just this.
As an exercise, try typing out this listing:-
Launch QuickCup
Open the project called blank.qjp
Create a new project (File menu, and then New Project) and give the project a filename. This saves the project under a new name, retaining the details of the previous project.
Give the project a Title (replace the NAME HERE text) and description (replace the Brief Description and the line below if you wish).
In the Files In Project section, click on the file path ending blank.htm. This places the file path into the edit box below the listbox.
In this edit box, amend the filename from blank.htm to GraphicsExample.htm keeping the rest of the path the same.
Click on the + button (position to the left of the edit box) to add a new file with this name. Click on Yes to confirm you wish to create a new blank file with this name.
Next to the Main HTML File: heading, click on the large button to make this new HTML file the default one for this project. This means that this HTML file will be used when launching the applet.
In the edit box, amend the filename from GraphicsExample.htm to GraphicsExample.java keeping the rest of the path the same.
Click on the + button (position to the left of the edit box) to add a new file with this name. Click on Yes to confirm you wish to create a new blank file with this name.
Next to the Main Java File: heading, click on the large button to make this new Java Source Code file the default one for this project. This means that this java file will be used when launching the applet. If you were creating an application, this would be the file with the main() method in. If creating an applet, it would be the file with class that extends the JApplet class.
Click on the blank.htm tab. Click so that the cursor is right at the beginning of the document, before the <HTML> tag. Hold down the Shift and Ctrl keys together, and press the End button. This highlights all text in the blank.htm document. Hold down the Ctrl key and press C to copy the highlighted text.
Click on the GraphicsExample.htm tab. Hold down the Ctrl key and press V to paste the template HTML text into the blank HTML file.
Click on the blank.java tab. Click so that the cursor is right at the beginning of the document, before the <HTML> tag. Hold down the Shift and Ctrl keys together, and press the End button. This highlights all text in the blank.java document. Hold down the Ctrl key and press C to copy the highlighted text.
Click on the GraphicsExample.java tab. Hold down the Ctrl key and press V to paste the template Java text into the blank Java source file.
Click on the main Settings tab, and next to the Other Java Files listbox, click on the file path ending with blank.htm. Click on the - button to the left of the text box to remove the file from the project (don't worry, it's still on the disk, it's just not visible from the project).
Similarly, click on the file path ending with blank.java and click on the - button to the left of the text box to remove the file from the project.
Now save the project and files within the
project by clicking on the
button.
You have now created a template project on which to base your new project.
Click on the the GraphicsExample.htm tab and scroll to the top of the page.
Replace the words Applet Title that appear within the <TITLE></TITLE> tags on line 4 with the word Graphics Example 1 - Fill Applet and draw borders - this is the text that is displayed on the browser's title bar when the page is loaded.
Replace the XXX on line 16 with the name of the class - i.e. GraphicsExample
Also replace the XXX on line 28 with the name of the class - i.e. GraphicsExample - this is a link to the source code on the web page.
Click on the GraphicsExample.java tab. On line 2, replace the APPLET NAME text with Graphics Example 1 and the Applet Description text with Fill Applet and draw borders
On line 19, change DD to today's date and Mon to today's three-letter month (e.g. if it is the 12th of November today, you would replace DD with 12 and Mon with Nov
On line 31, replace XXX with GraphicsExample
On line 34, also replace XXX with GraphicsExample
Place the cursor at the start of line 37 (just before the closing curly brace of the init() method) and press the Enter button to insert a blank line.
Cursor back up to the blank line, and press the space bar four times to indent the line to show that the code we are going to type is within the init() method.
Type out the two lines of the init() method as shown in the box above.
Similarly, fill in the lines of the paint() method as shown in the box above.
Finally, in the getAppletInfo() method, replace XXX with GraphicsExample and the Applet Description text with the words Fill Applet and draw border
Save the project.
Click on the compile (
)button
or choose the Compile menu and the Project Class File option, or just
press F9.
Check the console (black) window to see if you have made any typing mistakes. Correct these as necessary. Remember to save the project after you have made any corrections, otherwise the old version of the project (complete with errors) will be compiled.
Finally, run the Applet with the
AppletViewer (
)
button to try out the applet. The results should look something like this:-

It doesn't do much, but we'll add bits to it later.
Click on the cross in the right-hand corner of the applet window to close the applet.
Try swapping around the true and false of the two rectangles in the paint() method to see the difference in look. Compile and run with Appletviewer again.
Try dragging the applet around the screen, perhaps obscuring the applet beyond the boundaries of the screen, and dragging it back again. The picture is repainted as and when it is necessary to do so.
Try resizing the window. Notice that the border changes size with the window. This is because it is repainted whilst the window is being resized, and the 3D rectangle is not an absolute value, but derived from the (continually changing) width and height of the window.
Finally, try opening a second window (e.g.
another internet explorer window) over the top of the java window, so that part
of the java window is still visible as in the picture below:-

Now click on the title bar of the java window, but do not release the mouse button. You have activated the java window, but the repaint will not occur until you release the mouse button - notice that part of the window, because it was obscured behind another window, needs to be repainted. Once you release the mouse button, you can see the repainting happen. This is the paint method being called automatically as it is required.
We will be adding to this example to demonstrate the use of other graphical methods in the following sections.
If you run short of time, open the project named eg1.qjp and work from this.
Co-ordinates
In order to draw shapes on the screen, you will need to understand how to tell Java where to position these shapes on the screen.
If you look very closely at your computer screen(without blinding yourself), you can see that the picture is actually made up of thousands of tiny little rectangles, each of which lights up a different colour / brightness to form the picture on the screen. This is the same concept as how a television picture is composed.
These rectangles are arranged in rows and columns, rather like the grid pattern formed by a piece of graph paper. The squares are known as pixels in computer terminology, a rather loose abbreviation of picture elements.
To locate a position on the screen, you count the number of pixels across from the left edge of an applet, and the number of pixels down from the top edge. The left most pixel is given the number 0, and the top-most pixel the number 0 too.
Thus, the co-ordinate of a pixel on the fifth
column across from the left, and the tenth row down from the top would be (4,9).
Why not (5,10)? Well, remember that the top and left-most columns are zero, not
one. If you count from 0 to 4 on your fingers, that's five fingers - the fifth
column. Similarly for the tenth row down.
Most shapes in the Java 2D API can be described by drawing a box around the shape - for example, you can draw a square around a circle, and the length of the box represents the diameter of the circle.
Thus,
most of the methods that describe how to draw a shape on the screen are
described by giving a set of co-ordinates to describe the top-left hand corner
of the bounding box, and then a width and height (in pixels) for the box.
Take the elliptical shape on the left. It can be described be the blue dotted bounding box, which stretches from co-ordinate (0,0) to co-ordinate (4,9). The width is 5 pixels, and the height is 10 pixels. We have fully described the bounding box inside which the ellipse shape can be drawn.
Useful Java2D libraries to import
Finally, note that most of the shapes based on this sort of bounding box geometry can be found in the java.awt.geom library. Thus, to draw shapes, you need to use the following import declaration:-
import java.awt.geom.*;
As an aside, you will probably be working with lines quite extensively (i.e. to draw the outside of a shape, you draw it with a line), and thus their styles. The BasicStroke class which helps define the style of a line can be imported from the java.awt library as follows:-
import java.awt.*;
That's the theory bit out of the way. It makes sense that the simplest shape to draw must be a rectangle, as its used to give the outside boundaries for other shapes.
Most items that get drawn via the Graphics2D class (in this case for the applet's canvas, passed via the paint() method), make use of the draw method. You simply pass the object to be drawn, and it gets drawn.
Of course, you have to create the object first. To draw a rectangle, you create a new Rectangle2D object. Unfortunately, the Rectangle2D class doesn't do anything in its own right (it is what's known as an abstract class - a bit like an interface, but more limited). You must use a descendant of this class to determine how that coordinates are stored - either as a float or a double. As Java uses doubles as default, it makes sense to use this. However, if you were storing lots of rectangles, you may want to save a bit of memory, and use floats, which usually take up half the space of doubles. To us mere mortals, though, they are both types of variables that store decimal (non-whole or fractional) numbers.
For example:-
g2.draw(new Rectangle2D.Double(15.0,15.0,223.0,70.0));
will create a new Rectangle2D.Double object, (i.e. a rectangle that stores its coordinates as doubles, but really, who cares?) It will create the rectangle starting at 15 pixels across from the left and 15 pixels down, and make the rectangle 223 pixels wide and 70 pixels tall. This new object is then passed to the applet's canvas via its draw method, and so gets painted on the applet, with the top-left hand corner (0,0) being the top-left hand corner of the applet.
Try typing the following lines of code (shown in bold) into the applet, which should now look something like this:-
| import java.awt.*; import javax.swing.*;| import java.awt.geom.*; public class GraphicsExample extends JApplet { public void init() { setBackground(Color.lightGray); setForeground(Color.black); } public void paint (Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setPaint(Color.lightGray); g2.draw3DRect(0, 0, getSize().width-1, getSize().height-1, true); g2.draw3DRect(5, 5, getSize().width-11, getSize().height-11, false); g2.setStroke(new BasicStroke(2.0f)); |
The BasicStroke class refers to an object that defines how a line should
be drawn. The simplest way of creating a BasicStroke object is to pass a
float value to its constructor to give the width of the line. If you try to pass
2.0 to the constructor, you will get a compile error, as the default type
for a decimal number is a double and not a float (doubles are more accurate than
floats). You could cast the double to a float by typing (float)2.0 or use
the shorthand notation 2.0f which means 2.0 as a float.
So the lines above, set the lines on the canvas to 2 pixels thick, and the colour to blue. Then a new rectangle is created, of fixed size. Then the colour is set to green, and the rectangle that follows will be painted in this colour, starting at co-ordinates (10,10) and being a third the width of the window, and the whole height of the window minus 20 pixels (to account for the top and bottom borders).
Try resizing the window. Notice that the blue (first) rectangle stays the same size, whereas the green (second) rectangle resizes according to the window size - this is because as the window is being resized, the rectangles are being repainted. Each time the window size changes, the paint() method is being called again.
Filling a rectangle
Having drawn the outside of the rectangle, why not fill in the middle of it with some colour? It's very easy to do this. Just pass the same Rectangle2D object into the g2 object using a new method called fill instead of draw. Amend the source code as described below (changes in bold):-
| import java.awt.*; import javax.swing.*; import java.awt.geom.*; public class GraphicsExample extends JApplet { public void init() { setBackground(Color.lightGray); setForeground(Color.black); } public void paint (Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setPaint(Color.lightGray); g2.draw3DRect(0, 0, getSize().width-1, getSize().height-1, true); g2.draw3DRect(5, 5, getSize().width-11, getSize().height-11, false); g2.setStroke(new BasicStroke(2.0f)); |
We have created a couple of temporary local variables (they exist only within the paint method, as they are declared within this block of code - scope again!) The variables are called resizingRectangle for the rectangle that resizes when we resize the window. The fixed rectangle is called fixedRectangle.
We have created the temporary variables so that the same shape object can be passed to both the draw and fill methods without having to recreate the object unnecessarily each time.
Notice, however, that the fixed rectangle appear to have a thinner border. Why is this? It is because the outline of the rectangle is painted first, and the interior of the rectangle painted afterwards, which overwrites the inside part of the border, making it look thinner. Try swapping the draw and fill methods for fixedRectangle - does the border get thicker? This is because the border gets painted on top of the interior.
When painting shapes, the order that you paint the objects can determine which objects can become partially or wholly obscured by the objects that are painted afterwards.
Rounded rectangles
It
is also possible to specify a rectangle with rounded corner. To specify how
rounded the corners should be, you need to add a few extra parameters to
specify the width and height of the bounding box around each of the four corners
that defines the arc that forms the rounded corner.
In the example given on the left, the part of the circle that touches the outside of the rectangle would be the arc used to form the rounded edge of the rectangle, forming an arc 1 pixel wide and high at each corner.
The command to specify this is as follows:-
new RoundRectangle2D.Double(pixelsfromleft,pixelsfromtop, width, height, cornerwidth, cornerheight);
Try adding the following lines to the end of the paint() method, but before its closing curly brace } :-
| g2.setPaint(Color.white); RoundRectangle2D roundedRect = new RoundRectangle2D.Double(250.0,15.0,223.0,70.0,20.0,15.0); g2.fill(roundedRect); g2.setPaint(Color.black); g2.draw(roundedRect); |
This creates a rounded rectangle of the same width and height as the previous rectangle, but place further to the right, and with rounded corners 20 pixels wide and 15 pixels high. The rectangle has a black border (g2.draw()) and white interior (g2.fill()).
Drawing a rectangle was easy enough. Surely
drawing a simple line will be a breeze. Indeed!
A line is determined by two sets of co-ordinates, each of which determine and end to the line. In this example, (2,1) is one co-ordinate, and (7,1) is another.
We can see the bounding box for the rectangle starts at (1,1) and is 2 pixels wide and six pixels deep. Interesting, but not terribly useful in this context.
To create a new line, create a new Line2D.Double object, and pass it the co-ordinates of the two line-end points - e.g. the above line could be drawn as follows (assuming we have a Graphics2D object called g2):-
g2.draw(new Line2D.Double(2,1,1,7));
It doesn't matter which way around you put the co-ordinate pairs, as the line will still be the same:-
g2.draw(new Line2D.Double(1,7,2,1));
is directly equivalent.
Try putting the following lines at the end of the paint() method. It draws a thick yellow cross over the whole of the applet which stretches if the window is resized:-
| g2.setStroke(new BasicStroke(5.0f)); g2.setPaint(Color.yellow); g2.draw(new Line2D.Double(0,0,getSize().width-1,getSize().height-1)); g2.draw(new Line2D.Double(0,getSize().height-1,getSize().width-1,0)); |
Note how this is achieved. One line starts at (0,0) and ends with (applet width-1,applet height-1) giving a line going from the top-left to the bottom-right of the applet. The second line starts at (0,applet height-1) and ends with (applet width-1,0) giving a line going from the bottom-left to the top-right of the applet.
When the user resizes the applet window, the line resizes with it - this is because whilst resizing is occurring, the applet is being continually redrawn by continually re-calling the paint() method.
An
ellipse is a circle that can be defined by a bounding box, which touches the
ellipse at four sides (top, bottom, left and right). A circle is a specific
type of ellipse, where both the width and the height of that bounding box
are the same.
In the example on the left, there is an ellipse which has a bounding box that is 5 pixels wide and 10 pixels deep. It could be created using the following command (assuming we have a Graphics2D object called g2):-
g2.draw(new Ellipse2D.Double(0,0,5,10));
Let's try drawing an ellipse three-quarters of the size of the applet, in the middle. In order not to obscure anything on top of it, we should put the source code after the 3D rectangle is painted, but before anything else is painted. Type out the bold part of the paint() method as follows:-
| public void paint (Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setPaint(Color.lightGray); g2.draw3DRect(0, 0, getSize().width-1, getSize().height-1, true); g2.draw3DRect(5, 5, getSize().width-11, getSize().height-11, false); g2.setStroke(new BasicStroke(2.0f)); Ellipse2D resizingEllipse = new Ellipse2D.Double( getWidth()/8, getHeight()/8, getWidth()*(3.0/4.0), getHeight()*(3.0/4.0) ); g2.setPaint(new Color(227,156,21)); // amber colour g2.fill(resizingEllipse); g2.setPaint(new Color(179,21,193)); // dark-violet colour g2.draw(resizingEllipse); g2.setPaint(Color.gray); Rectangle2D resizingRectangle = new Rectangle2D.Double( ........ |
Thus, we are creating an Ellipse2D descendant object (of class Ellipse2D.Double) with the bounding box of the ellipse starting at the width/height of the applet divided by 8 and the width / height of the ellipse the width/height of the applet times 3 and divided by 4 (i.e. three-quarters the size of the applet). This should place it in the centre of the applet (i.e. give a border of one-eight the size of the applet on each side.
Notice that we are creating brand new colours for the draw and fill colours - these are numbers based on something called the RGB palette to form specific colour tones - this will be explained in a later section.
Try copying the block of code (highlight, Ctrl-X to cut, move cursor to new insertion point, Ctrl-V to paste) to just before the cross (two lines) is painted. The ellipse is painted over shapes that are placed previously in the paint() method. This demonstrates the importance of getting the sequence of painting correct according to the effect you wish to achieve.
We have previously used the JLabel component to show text on the screen. This is not, however, the only way to draw text - it is also possible to draw the 'shape' of the text directly onto a canvas.
We do this by creating a TextLayout object. When creating an object from this Class, we typically pass the following parameters to the constructor method:-
Text (as a String) to be put on the canvas
Font for the text. This is another object, which when created, could have these parameters passed to it:-
Name (e.g. "Helvetica" or "Times New Roman")
Style (e.g. Font.BOLD+Font.ITALIC or Font.PLAIN)
Size (in points - e.g. 10pt or 12pt)
Font render context. This is another object, which when created, has these parameters passed to it:-
Optional Affine transformation object (for scaling, moving, rotating and shearing or slanting) - otherwise, set to null.
Whether it is antialiased (to give smoother but rather smudgy looking edges)
Whether it uses fractional metrics (to give greater accuracy)
A sample TextLayout declaration might be:-
TextLayout
myText = new TextLayout( "Text to be drawn",
new Font("Helvetica", Font.BOLD, 46),
new FontRenderContext(null,false,false) );
To draw the string, use either the fill() or draw() method to draw the text as solid or outline.
The outline needs to be retrieved from the TextLayout first in order to draw it - e.g.
g2.fill(myText.getOutline(null));
Note that you will also need to add the following libraries to your import list:-
import java.awt.font.*;
Finally, this gives no description of where to place the text on the canvas. To do this, we need to perform a function on the text shape known as a translation - this simply means moving it from the canvas' origin (i.e. the top-left hand corner). This leads us into a rather involved part of geometric mathematics known as transformations which you may (but probably won't) remember from school.
We won't be going into transformations in too much depth, except to say that you have to set up a new object of the class AffineTransform to portray this translation, or movement from the origin to another location on the canvas.
The translate() method sets the movement amount (i.e. the number of pixels to the right and the number of pixels down). In this case, it gets the width of the applet, subtracts that width of the text (myText.getBounds().getWidth()) and divides the result by two in order to centre the text horizontally in the applet. A similar process is done to centre the text vertically.
The outline is extracted from the TextLayout object myText using the getOutline method, and translate it from the origin using the myTransform translation.
Finally, the text is filled in cyan and outlined in magenta. Very gaudy.
Try typing in the following lines of code at the end of the paint() method (but before the closing curly brace):-
| TextLayout myText = new TextLayout( "Very Gaudy Text", new Font("Helvetica", Font.BOLD, 46), new FontRenderContext(null,false,false) ); AffineTransform myTransform = new AffineTransform(); myTransform.translate( (float)(getSize().width-myText.getBounds().getWidth())/2.0, (float)(myText.getBounds().getHeight()+getSize().height)/2.0); g2.setPaint(Color.cyan); g2.fill(myText.getOutline(myTransform)); g2.setStroke(new BasicStroke(2.0f)); g2.setPaint(Color.magenta); g2.draw(myText.getOutline(myTransform)); |
Notice that the text stays centred, even if you move or resize the window.
Transformations: Shearing, rotating and scaling
For our purposes, you're right, this is rather complex. However, when you come to do more interesting transformations, such as shearing and rotating, or even combinations, the purpose becomes more evident.
Try inserting the following line before the three myTransform.translate lines, and change the parts shown in bold:-
| TextLayout myText = new TextLayout( "Very Gaudy Text", new Font("Helvetica", Font.BOLD, 46), new FontRenderContext(null,false,false) ); AffineTransform myTransform = new AffineTransform(); myTransform.rotate(Math.toRadians(5)); myTransform.translate( (float)(getSize().width-myText.getBounds().getWidth())/2.0, (float)(getSize().height-myText.getBounds().getHeight())/2.0); g2.setPaint(Color.cyan); g2.fill(myText.getOutline(myTransform)); g2.setStroke(new BasicStroke(2.0f)); g2.setPaint(Color.magenta); g2.draw(myText.getOutline(myTransform)); |
Mathematicians like to measure angles in radians (there are 2 x pi radians to a full 360 degrees of a circle). Human beings think in degrees, though. E.g. most people know that 90 degrees is a right-angle - e.g. North is 90 degrees from East or West. So an angle half as acute would be a 45 degree angle.
To convert from degrees to radians, use the Math.toRadians(5) method. In this case, the 5 in brackets means 5 degrees. You could replace it with whatever angle you like. If you wanted upside-down text, you would use 180 (a half-circle).
The method myTransform.rotate() simply takes the transformation object that you created, and moves the co-ordinates that are stored inside it so that they are rotated the given number of radians.
Note that this changes the boundary for the text object quite significantly, which is why the translation formula has also changed for the vertical direction.
Try resizing the window. the text should stay in the middle.
So what if you wanted to shear the text (i.e. make it lean over) as well? You would shear the text - i.e. make it lean over. Try adding the following lines to the end of the paint() method:-
| myTransform.shear(-0.8,0); g2.setPaint(Color.lightGray); g2.fill(myText.getOutline(myTransform)); g2.setPaint(Color.gray); g2.draw(myText.getOutline(myTransform)); |
This will shear the text to the right, which looks like this:-
Try changing the -0.8 to 0.8 - the shearing is now to the left. Notice that we are changing the first of two values - this refers to the x axis or the width value:-

How about shearing down and up - try changing the shear line to:-
myTransform.shear(0,-0.1);

And shearing down:-
myTransform.shear(0,0.1);

What's more, you're not restricted to applying these transformations to text objects - they will work on any shape - use the Graphics2D setTransform() method before drawing a shape, where you pass the transformation to the canvas itself. Careful with this, as the transformation will be performed with respect to the origin (ie. the top left hand corner of the canvas). Thus, rotating may not have the effect you anticipated. Try the following change to the fixed rounded rectangle code:-
| g2.setPaint(Color.white); AffineTransform linkedTransform = new AffineTransform(); linkedTransform.rotate(Math.toRadians(700-getSize().width)); g2.setTransform(linkedTransform); RoundRectangle2D roundedRect = new RoundRectangle2D.Double(250.0,15.0,223.0,70.0,20.0,15.0); g2.fill(roundedRect); g2.setPaint(Color.black); g2.draw(roundedRect); linkedTransform.setToIdentity(); g2.setTransform(linkedTransform); |
The setToIdentity method will set the transformation back to its original state - i.e. nothing. This prevents the transformation being applied to any more shapes.
As you adjust the width of the applet (carefully), so this adjusts the rotation of the rounded rectangle, but the rotation is about the top-left hand corner as a centre. What if you wanted to rotate about the centre of the square itself. Try this alteration to the source code:-
| g2.setPaint(Color.white); // fill resizable rounded rectangle first AffineTransform linkedTransform = new AffineTransform(); linkedTransform.translate(getSize().width/2,getSize().height/2); linkedTransform.rotate(Math.toRadians(700-getSize().width)); g2.setTransform(linkedTransform); RoundRectangle2D roundedRect = new RoundRectangle2D.Double(-111.5,-35.0,223.0,70.0,20.0,15.0); g2.fill(roundedRect); g2.setPaint(Color.black); // then paint border on top g2.draw(roundedRect); linkedTransform.setToIdentity(); g2.setTransform(linkedTransform); |
The translate method will cause the origin to move from the top-left hand corner to the exact centre of the canvas - so any rotation, or indeed any co-ordinates, will be specified relative to this new position. Thus the RoundRectangle2D.Double object created has it top-left co-ordinates adjusted so that they are half the width (i.e. 223 divided by 2 is 111.5) to the left of the new origin and half the height (i.e. 70 divided by 2 is 35) above the new origin. Thus, the co-ordinates are actually negative.
The setToIdentity method will reset both the rotation and change of origin back to normal.
The final transformation is the scale which scales a shape up and down. The scale factor multiplies the current width or height. For example, 1.0 leaves the shape unchanged, 0.5 reduces it to half its previous size, and 2 increases it to twice its previous size. The scaling works in both the x (horizontal) and y (vertical) direction. For example, replace (or comment out by prefixing with //) the line (from the previous example):-
linkedTransform.rotate(Math.toRadians(700-getSize().width));
with this line:-
linkedTransform.scale(getSize().width/700.0, getSize().height/100.0);
What happens now when you resize the window both vertically and horizontally? The rectangle is scaled in proportion to the size of the window (we know the width starts off as 700 and the height as 100, as these values are passed in from the HTML page).
Changing Line and Fill Styles / Colours etc.
We can do quite a lot to our shapes now. How about changing the pattern that is used to fill the shapes?
We have already created solid patterns using the following command:-
g2.setPaint(Color.lightGray);
This ensures that any following shapes are coloured light grey. Other possible solid colours are:-
black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow
Remember to prefix the colour with the class name Color and a dot. If you wish to use other colours, you will need to use the RGB palette, as described in the next section on colours.
There are other types of paint classes other than Color.
The GradientPaint class fills a shape that changes between two different colours.
The change is represented by specifying the co-ordinates where the shape should be the first colour, and then the co-ordinates where the shape should be the second colour. The colour will blend gradually between these points. outside these areas, the colour will be solid if the acyclic (i.e. no cycling) option is selected, or the pattern will repeat if the cyclic option is set.
Open
QuickCup project eg3.qjp, and run it with AppletViewer (
button or Ctrl+R).
The screen should look something like this:-
The first is achieved by the following piece of code:-
GradientPaint bluetowhiteHoriz = new GradientPaint(20,20,Color.blue,120,20,Color.white);g2.setPaint(bluetowhiteHoriz); g2.fill(new RoundRectangle2D.Double(20,20,100,50,5,5)); |
This creates a new GradientPaint object, and sets the starting point to be the top-left hand corner of the rectangle to be painted (20,20). At this point, the shape will be blue. Then end point, where the shape becomes completely black, is (120,20) - at the top-right hand corner of the rectangle to be painted. Note that if you put a line between these two points, it would be horizontal. This is why the colour change occurs from left to right.
The next fill is achieved by the following piece of code:-
GradientPaint bluetowhiteVert = new GradientPaint(150,20,Color.blue,150,70,Color.white);g2.setPaint(bluetowhiteVert); g2.fill(new RoundRectangle2D.Double(150,20,100,50,5,5)); |
In this example, the blue starts again at the top-left hand corner of the rectangle to be painted (150,20). However, this time, the white starts at (150,70) which is the bottom-right hand corner of the rectangle. If you put a line between these two points, it would be vertical. This is why the colour change occurs from top to bottom
The next fill is achieved by the following piece of code:-
GradientPaint bluetowhiteCycle2 = new GradientPaint(300,20,Color.blue,350,20,Color.white,true); g2.setPaint(bluetowhiteCycle2); g2.fill(new RoundRectangle2D.Double(300,20,100,50,5,5)); |
In this example, the blue starts again at the top-left hand corner of the rectangle to be painted (300,20) However, this time, the white starts at (350,20) which is half-way along the top of the rectangle. If you put a line between these two points, it would be a horizontal line, half the width of the top side. This time, the cyclic parameter for the GradientPaint constructor is set to true instead of being omitted. This means that the colour will go from blue at the left hand side to white in the middle, and cycle colours back again to the same width as the original cycle - thus back to blue again by the time the fill reaches the right hand side of the rectangle.
The next fill is achieved by the following piece of code:-
GradientPaint whitetoblueCycle4 = new GradientPaint(450,20,Color.white,475,20,Color.blue,true); g2.setPaint(whitetoblueCycle4); g2.fill(new RoundRectangle2D.Double(450,20,100,50,5,5)); |
This time, the cycle starts at (450,20) for blue, and ends at (475,20) for white. This is 25 pixels wide, and is horizontal again. Note that the width of the rectangle is 100 pixels, so the colour cycle happens every 25 pixels - from white to blue 25% along the width of the rectangle, back to white half-way along the width of the rectangle, back to blue 75% along the width of the rectangle, back to white again on the right-hand side.
Drawing Lines (Strokes)
You can also customize how lines and borders (strokes) appear around objects. We have been using this to set the thickness of outlines and lines drawn using the Graphics2D draw() method.
Open QuickCup
project eg4.qjp, and run it with AppletViewer (
button or Ctrl+R).
You should see a screen that looks something like this:-

The first line is generated by the following code :-
BasicStroke stroke5 = new BasicStroke(5.0f);g2.setStroke(stroke5); g2.setPaint(Color.red); g2.draw(new Line2D.Double(20,20,120,70)); |
This generates a new stroke style using an object of the BasicStroke class that is five pixels wide. The paint colour is set to red, and a line is drawn.
The next example is generated by the following code :-
GradientPaint bluetowhiteVert = new GradientPaint(150,20,Color.blue,150,70,Color.white);g2.setPaint(bluetowhiteVert); g2.draw(new Line2D.Double(150,20,250,70)); |
The above line does the same, except it paints the line using a gradient paint moving vertically from blue to white.
The next example is generated by the following code :-
330,50,Color.green,true); g2.setPaint(redtogreenCycle); g2.setStroke(new BasicStroke(2.0f)); TextLayout myText = new TextLayout("Sample", new Font("Helvetica", Font.BOLD, 34), new FontRenderContext(null,false,false) ); AffineTransform myTransform = new AffineTransform(); myTransform.translate(300.0f,50.0f); g2.draw(myText.getOutline(myTransform)); |
The above example combines a cycling red to green gradient paint, a stroke width of 2 pixels, and a new piece of text, the outline drawn using this gradient / stroke.
The next example is generated by the following code :-
g2.setPaint(Color.red);g2.setStroke(new BasicStroke(2.0f)); g2.draw(new Rectangle2D.Double(450,20,100,50)); float dash10[] = {10.0f,2.0f,5.0f,2.0f}; g2.setStroke(new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, dash10, 0)); g2.draw(new Rectangle2D.Double(455,25,90,40)); |
The final example sets the paint colour to a solid red again. The outside line is drawn with a stroke width of 2 pixels.
The inside line is drawn with a different stroke - ignore BasicStroke.CAP_BUTT and BasicStroke.JOIN_BEVEL and the 0 (which is the bevel size). The interesting but it the dash10 array of float values, which defines how a dashed line should be displayed. The 0 that follows is the offset at which to start the dashing. So, if you didn't like how the dash goes around the corners, you might put a different value than 0 to get the line to start further along the boundary.
The dash10[] array is an array of floats hence the numberf notation. This can be any number of values. The first number gives the length of the solid line in pixels, and the second number gives the length of the missing (broken) part of the line in pixels. So, in this case the pattern is 10 pixels of line, with a gap of 2 pixels, followed by a shorter line of 5 pixels in length, with another gap of 2 pixels, and then back to the beginning again.
We have used the Color object quite extensively.
As described earlier, you can make use of one of the static Color object instances to get hold of more common colours, such as:-
black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white, yellow
Remember to prefix the colour with the class name Color and a dot (e.g. Color.cyan).
However, if you wish to have more precise control of the colour, you will need to refer to what is known as the RGB value. This is an abbreviation of Red Green Blue.
Each pixel (dot) on your computer monitor is really made up of three smaller dots that are each coloured red, green, and blue. By increasing or decreasing the relative brightness of each of these dots, the combination blends to make another colour (a bit like mixing different coloured spotlights).
So virtually any colour can be represented by specifying a colour in terms of red, green and blue, and giving a brightness value to each of these colours to make up a specific colour.
So, if you take the three values (0,0,0), this refers to this first number (red) having a brightness of 0 (i.e. off), the second number (green) having a brightness of 0 (i.e. off again), and the third number (blue) having a brightness of 0 (i.e. off once again). Unsurprisingly, this combination of three numbers gives the colour BLACK. Or, for purists, it represents a lack of colour.
The maximum brightness for each of these values is 255. Thus (255,255,255) unsurprisingly represents WHITE.
To create a colour object yourself, use the following form:-
new Color(redvalue,greenvalue,bluevalue);
Thus, you can represent black as either Color.black or new Color(0,0,0) and white as either Color.white or new Color(255,255,255)
You can adjust the mixes between the colour values to give different shades.
Usefully, most paint programs, when you choose a colour from a palette, show the Red Green and Blue values of the colour. So you can use your paint program to pick a colour visibly, and then take the RGB values and pop them into your Java program to create a new Color object of that colour!
Unfortunately, there was not enough time to create this example for this session. I will try to create it after the end of the course as a further example. Apologies if you were awaiting this with eager anticipation!
| TechRepublic - sign up for their Java Tips email newsletter - it gives excellent Java tips straight to your mailbox twice weekly. | http://www.techrepublic.com |