Given by Tom Scavo at NPAC/ECS Java Academy on February to April 98. Foils prepared 13 July 98
Outside Index
Summary of Material
Advanced Fonts |
Advanced Geometry |
Animation |
Advanced GUIs |
Note: This tutorial assumes you've completed Parts I and II |
Outside Index Summary of Material
Tom Scavo |
<trscavo@npac.syr.edu> |
NPAC @ SU |
111 College Place |
Syracuse, NY 13244-4100 |
Syracuse University |
College of Engineering and Computer Science |
Northeast Parallel Architectures Center |
present |
Advanced Fonts |
Advanced Geometry |
Animation |
Advanced GUIs |
Note: This tutorial assumes you've completed Parts I and II |
A higher percentage of the code that follows is italicized, that is, much of it depends on original material (instead of core Java) |
In the interest of space, some of the variables used in the code fragments are not declared...the type of the variable should be clear from the name |
Recall the procedure used to center a string (or strings) horizontally and vertically |
We want to capture this functionality in a Java class and thus make it easily accessible |
Our class will be called DrawableString, which is a subclass of the Font class (for reasons that will become clear as we develop the class) |
Here is an outline of DrawableString: class DrawableString extends Font { String string; Color color; DrawableString( String string ); void centerDraw( Component c ); } |
Not listed are accessor and mutator methods for the string and color variables, and other font manipulation methods |
As a subclass of Font, the DrawableString class inherits:
|
For convenience, DrawableString provides:
|
Our test applet is very simple: String s; DrawableString ds; public void init() { ... ds = new DrawableString( s ); ... } public void paint( Graphics g ) { ds.centerDraw( this ); } |
We now generalize DrawableString to handle an array of strings: class DrawableStrings extends Font { int n; String[] string; Color color; DrawableStrings( String[] string ); void centerDraw( Component c ); } |
The API of the DrawableStrings class is essentially the same as before |
Our test applet is more complicated, however |
For complete generality, all data are obtained from the HTML document, namely:
|
A typical <APPLET> tag looks like this... |
<applet code="DrawableStringsTest.class" ...> <param name="numStrings" value="3"> <param name="string0" value="..."> <param name="string1" value="..."> <param name="string2" value="..."> <param name="fontName" value="SansSerif"> <param name="fontStyle" value="BOLD"> <param name="fontSize" value="24"> <param name="bgColor" value="#ffffff"> <param name="fgColor" value="#000000"> </applet> |
Since HTML parameters are passed to the applet as strings, the text to be drawn and the font name may be used straightaway |
The font size must be converted, however, since it is an integer: String fontSizeStr = getParameter( "fontSize" ) ; int fontSize = Integer.parseInt( fontSizeStr ); |
The colors are fairly easy to convert, too: String bgColorStr = getParameter( "bgColor" ); bgColorStr = bgColorStr.trim(); if ( bgColorStr.charAt(0) == "#" ) { bgColorStr = bgColorStr.substring(1); } int bgColorInt = Integer.parseInt( bgColorStr, 16 ); Color bgColor = new Color( bgColorInt ); |
But the font style is somewhat tricky: String fontStyleStr = getParameter( "fontStyle" ); fontStyleStr = fontStyleStr.trim(); fontStyleStr = fontStyleStr.toUpperCase(); int fontStyle = 0; if ( fontStyleStr.indexOf("BOLD") > -1 ) fontStyle += Font.BOLD; if ( fontStyleStr.indexOf("ITALIC") > -1 ) fontStyle += Font.ITALIC; |
Earlier we designed classes of geometric objects such as Triangle, Quadrilateral, Octagon, and Hexagon |
In this unit, we write additional classes (Rectangle, Square, Parallelogram, and Rhombus) and impose further class structure on the geometry package (superclass DrawablePolygon, for instance) |
In Java, a Rectangle is given by its:
|
For example, the following code gets the rectangular dimensions of an applet: int x, y, w, h; Rectangle r = this.getBounds(); x = r.x; y = r.y; w = r.width; h = r.height; |
There are many Rectangle constructors |
Also, there are methods for translation, union, intersection, and containment |
There are no methods for coloring, drawing, or filling rectangles, however, and no methods for advanced geometric operations, such as rotation |
For that, we will have to write our own... |
A rectangle is a quadrilateral: class Rectangle extends Quadrilateral { ... } |
Our Rectangle class has two constructors (see subsequent foils) but no methods or variables (evidently, these are defined in the superclass Quadrilateral or above) |
There are two Rectangle constructors |
The first constructor is straightforward: public Rectangle( int x, int y, int w, int h ) { super(); // a constructor of the superclass this.addPoint( x, y ); this.addPoint( x + w, y ); this.addPoint( x + w, y + h ); this.addPoint( x, y + h ); } |
The second constructor relies on the first: public Rectangle( int x, int y, int w, int h, double theta ) { // invoke constructor #1: this( x, y, w, h ); this.rotate( theta ); } |
The important rotate(...) method will be discussed later |
Our test applet draws a series of rotated rectangles: double theta = 0; for ( int i = 0; i < N; i++ ) { theta += angle; rectangle = new Rectangle( x, y, w, h, theta ); rectangle.draw( g ); } |
The Square class is almost trivial: class Square extends Rectangle { public Square( int x, int y, int size ) { super( x, y, size, size ); } public Square( int x, int y, int size, double theta ) { super( x, y, size, size, theta ); } } |
Our test applet draws an 8x8 checkerboard: y = 0; for ( int i = 0; i < 8; i++ ) { x = 0; y += length; for ( int j = 0; j < 8; j++ ) { x += length; square = new Square( x, y, length ); // draw or fill square here... } } |
On a checkerboard, every other square is filled |
To accomplish this, we use the modulus operator (%) on the sum of the loop variables: if ( ( i + j ) % 2 == 0 ) { square.draw( g ); } else { square.fill( g ); } |
Basically, "x % 2 == 0" checks if x is even |
A parallelogram is also a quadrilateral: class Parallelogram extends Quadrilateral { ... } |
Like Rectangle, the Parallelogram class also has two constructors, but we are faced with some difficult design decisions... |
We define a parallelogram by five quantities:
|
As seen from the diagram, the remaining vertices of the parallelogram depend on the height h and displacement d |
We solve for these using simple trigonometry: |
Now translate these equations to Java syntax: double PI = Math.PI; // for brevity d_double = b*Math.cos( PI - alpha ); h_double = b*Math.sin( PI - alpha ); // convert double values to int: d = ( int ) Math.round( d_double ); h = ( int ) Math.round( h_double ); |
Note: All methods and variables of the Math class are static and must be qualified |
The height h and displacement d are instance variables of the Parallelogram class: class Parallelogram extends Quadrilateral { private int h, d; public int getHeight() {...} public int getDisplacement() {...} ... } |
Assuming 0 ? ? ? ?, we have: public Parallelogram( int x, int y, int a, int b, double alpha ) { super(); // a constructor of the superclass this.addPoint( x, y ); this.addPoint( x + a, y ); // compute d and h as before... this.addPoint( x + a - d, y + h ); this.addPoint( x - d, y + h ); } |
Again the second constructor relies on the first: public Parallelogram( int x, int y, int a, int b, double alpha, double theta ) { // invoke constructor #1: this( x, y, a, b, alpha ); this.rotate( theta ); } |
Once again we defer discussion of rotate(...) |
Our ParallelogramTest applet is simple: parallelogram1 = new Parallelogram( x, y, side1, side2, alpha ); parallelogram2 = parallelogram1.rotate( theta ); ... public void paint( Graphics g ) { parallelogram1.fill( g ); parallelogram2.fill( g ); } |
The Rhombus class is straightforward: class Rhombus extends Parallelogram { public Rhombus( int x, int y, int size, double alpha ) { super( x, y, size, size, alpha ); } public Rhombus( int x, int y, int size, double alpha, double theta ) { super( x, y, size, size, alpha, theta ); } } |
Such is the beauty of object-oriented design! |
Like SquareTest, our test applet draws an 8x8 checkerboard of rhombi |
The orientation of the checkerboard depends on the angle ? and, hence, on d: rhombus = new Rhombus( 0, 0, length, alpha ); d = rhombus.getDisplacement(); h = rhombus.getHeight(); xoffset = ( d < 0 ) ? 0 : 8*d; |
Now take into account d, h, and xoffset: y = 0; for ( int i = 0; i < 8; i++ ) { x = xoffset - i*d; y += h; for ( int j = 0; j < 8; j++ ) { x += length; square = new Rhombus( x, y, length, alpha ); rhombus.draw( g ); } } |
We now look at the Quadrilateral class, the superclass of the previous four classes: class Quadrilateral extends DrawablePolygon { ... } |
Note that Quadrilateral is a subclass of a new class called DrawablePolygon |
The Quadrilateral class has a no-argument constructor that calls the no-argument constructor of the superclass: public Quadrilateral() { super(); } |
It also has a constructor that takes eight ints, and another constructor that takes four Points |
This is the most important such constructor: public Quadrilateral( int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4 ) { super(); this.addPoint( x1, y1 ); this.addPoint( x2, y2 ); this.addPoint( x3, y3 ); this.addPoint( x4, y4 ); } |
This constructor is for convenience only: public Quadrilateral( Point p1, Point p2, Point p3, Point p4 ) { // invoke constructor #2: this( p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y ); } |
Here, x and y are variables of the Point class |
The remaining three subclasses of DrawablePolygon (Hexagon, Octagon, and Triangle) are very similar to Quadrilateral |
The only difference is the number of vertices! |
We encourage you to write out these classes before looking at the accompanying examples... |
One of our test applets uses a new method of the superclass DrawablePolygon: for ( int i = 0; i < n; i++ ) { hexagon.centerRotate( theta ).draw( g ); } |
Method centerRotate(...) rotates the polygon about its center of gravity, whereas rotate(...) rotates the polygon about the top left-hand corner of its bounding box |
Another of our test applets recursively applies the following geometric algorithm to a filled equilateral triangle: |
The result is a fractal called Sierpinski's Triangle: |
The DrawablePolygon class is the most important class in the geometry package: class DrawablePolygon extends Polygon implements Drawable { ... } |
The new Drawable interface defines methods for drawing and filling a polygon |
The Drawable interface defines six methods: interface Drawable { public Color getColor(); public void setColor( Color color ); public void draw( Graphics g ); public void draw( Component c ); public void fill( Graphics g ); public void fill( Component c ); } |
Notice the two forms of draw(...) and fill(...) |
The DrawablePolygon class has four constructors |
First, it has a no-argument constructor: public DrawablePolygon() { super(); } |
Another constructor is solely for compatibility with the superclass Polygon... |
This constructor mimics the corresponding constructor of the superclass Polygon: public DrawablePolygon( int[] xpoints, int[] ypoints, int npoints ) { super( xpoints, ypoints, npoints ); } |
Constructor #2 is not used much in practice, however, since repeated calls to the addPoint(...) method are usually more convenient |
The third constructor is unique to the DrawablePolygon class: public DrawablePolygon( Point[] points, int npoints ) { this(); // invoke constructor #1 for ( int i = 0; i < npoints; i++ ) { this.addPoint( points[i] ); } } |
We defer discussion of constructor #4 until later... |
Observe that constructor #3 used a method of the DrawablePolygon class: public void addPoint( Point p ) { super.addPoint( p.x, p.y ); } |
As you can see, it relies on an overloaded Polygon method of the same name |
The next method is even more interesting... |
This method returns all vertices of a polygon: public Point[] getPoints() { Point[] points = new Point[ npoints ]; for ( int i = 0; i < npoints; i++ ) { points[i] = new Point( xpoints[i], ypoints[i] ); } return points; } |
It uses certain variables of the Polygon class: xpoints[], ypoints[], and npoints |
Suppose we had a method that rotated a polygon about an arbitrary point: public DrawablePolygon rotate( double theta, Point p ); (Note: This method returns the rotated polygon as a side effect) |
The following methods are easy consequences of this... |
Rotate a polygon about the top left-hand corner of its bounding box: public DrawablePolygon rotate( double theta ) { Rectangle r = this.getBounds(); Point p = new Point( r.x, r.y ); return this.rotate( theta, p ); } |
The above refers to java.awt.Rectangle, not our Rectangle class! |
Rotating about the polygon's center of gravity is not much harder: public DrawablePolygon centerRotate( double theta ) { Rectangle r = this.getBounds(); int x0 = r.x + r.width/2; int y0 = r.y + r.height/2; Point p = new Point( x0, y0 ); return this.rotate( theta, p ); } |
To rotate a polygon about an arbitrary point p, we must do the following:
|
So rotation of polygons boils down to rotations of points about the origin |
Unfortunately, the Point class is of no help! |
The DrawablePoint class is a subclass of the Point class: class DrawablePoint extends Point implements Drawable { ... } |
It, too, implements the Drawable interface |
Like DrawablePolygon, the DrawablePoint class implements all six methods of the Drawable interface |
DrawablePoint also has three constructors: a trivial no-argument constructor and two other constructors |
These constructors are analogous to those of the Point class and will not be discussed |
Problem: What are the coordinates of the rotated point (x?, y?) in terms of x, y, and ?? |
We will need the following: |
r |
r |
(x, y) |
(x?, y?) |
? |
?0 |
Using the formulas for the sine and cosine of a sum: |
The 2x2 rotation matrix is important in computer graphics: |
At any rate, the next to last pair of equations in the previous derivation solve the original problem |
We now translate those equations into Java syntax... |
To rotate a point about the origin: public DrawablePoint rotate( double theta ) { final double cos_t = Math.cos( theta ); final double sin_t = Math.sin( theta ); double x_double, y_double; x_double = this.x*cos_t - this.y*sin_t; y_double = this.x*sin_t + this.y*cos_t; int new_x = ( int ) Math.round( x_double ); int new_y = ( int ) Math.round( y_double ); this.move( new_x, new_y ); return this; } |
To rotate a polygon about the origin, we simply rotate each vertex: for ( int i = 0; i < npoints; i++ ) { x = xpoints[i]; y = ypoints[i]; q = new DrawablePoint( x, y ); q.rotate( theta ); xpoints[i] = q.x; ypoints[i] = q.y; } |
The variables npoints, xpoints[], and ypoints[] are from the Polygon class |
We're finally ready to show how to rotate a polygon about an arbitrary point: public DrawablePolygon rotate( double theta, Point p ) { int x, y; DrawablePoint q; this.translate( -p.x, -p.y ); // rotate each vertex as in the previous foil this.bounds = null; this.translate( p.x, p.y ); return this; } |
The translate(...) method is a handy method of the Polygon class |
The bounds variable (which holds the bounding rectangle of the polygon) is a protected variable of the Polygon class |
Rather than manually compute a new bounding rectangle, we nullify bounds and let the Polygon class worry about it |
There is a DrawablePolygon constructor for regular n-gons |
The basic idea is to repeatedly rotate an initial vertex by a fixed amount: this.addPoint( p ); for ( int i = 1; i < n; i++ ) { // rotate the previous point: p = p.rotate( theta ); this.addPoint( p ); } |
The constructor for regular n-gons follows: public DrawablePolygon( int x, int y, int r, int n ) { this(); // invoke constructor #1 double theta = 2*Math.PI/n; DrawablePoint p = new DrawablePoint( 0, -r ); // rotate and add as in the previous foil... this.translate( x, y ); } |
To animate an object within the applet window, do the following:
|
Be careful when moving the object so that it does not exit the applet window! |
Any algorithm involving an infinite loop will hog processor cycles and prevent the browser from doing its job |
To avoid this, we employ an independent thread of execution, which permits the Java Virtual Machine to share cycles among competing tasks |
An animation, in particular, requires a thread |
There are two ways to implement a thread: by 1) extending the Thread class, or 2) implementing the Runnable interface |
Our examples will implement Runnable, which has one method, the run() method |
The run() method, which contains the infinite loop, is called automatically by the thread's start() method... |
The applet's start() method controls the start() method of the thread: // Override java.applet.Applet.start: public void start() { if ( thread == null ) { thread = new Thread( this ); thread.start(); } } |
Likewise, the applet's stop() method controls the stop() method of the thread: // Override java.applet.Applet.stop: public void stop() { if ( thread != null ) { thread.stop(); thread = null; } } |
The run() method contains the infinite loop: public void run() { while ( thread != null ) { try { Thread.sleep( 20 ); } catch ( InterruptedException e ) {} repaint(); } } |
The thread goes to sleep every 20 milliseconds |
Suppose we wanted to move a square across the applet window: public void paint( Graphics g ) { // Move and fill the square: square.translate( 1, 1 ); square.fill( g ); } |
The problem is: Eventually, the square will move off the applet window and never return! |
Here is a better approach: int dx = 1, dy = 1; public void paint( Graphics g ) { checkBounds( this.getBounds() ); square.translate( dx, dy ); square.fill( g ); } |
The checkBounds(...) method reverses the direction of motion, if necessary... |
This ensures the square stays within r: private void checkBounds( Rectangle r ) { // Compute dimensions of the square: Rectangle bb = square.getBounds(); int w = bb.width, h = bb.height; int new_x = bb.x + dx, new_y = bb.y + dy; // If square is out of bounds, reverse direction: if ( ( new_x < r.x ) || ( new_x + w > r.x + r.width ) ) dx *= -1; if ( ( new_y < r.y ) || ( new_y + h > r.y + r.height ) ) dy *= -1; } |
Note: The method refers to java.awt.Rectangle! |
A new MovablePolygon class has all the functionality of DrawablePolygon plus the ability to move (like the square in the previous example): class MovablePolygon extends DrawablePolygon { ... } |
The MovablePolygon class has four constructors like DrawablePolygon |
Each constructor invokes the corresponding constructor of the superclass and then initializes instance variables dx and dy |
The MovablePolygon class has three methods: setDelta(...), move(), and checkBounds(...) |
The first two methods are trivial: public void setDelta( int dx, int dy ) { this.dx = dx; this.dy = dy; } public void move() { this.translate( dx, dy ); } |
The checkBounds(...) method is exactly the same as in the previous sample program |
Our sample program instantiates an array of MovablePolygons: public void init() { // create a square of "radius" 50: polygon[0] = new MovablePolygon( 75, 75, 50, 4 ); polygon[0].setDelta( 2, 3 ); polygon[0].setColor( Color.red ); ... } |
The paint(...) method is called repeatedly from the run() method: public void paint( Graphics g ) { Rectangle r = this.getBounds(); for ( int i = 0; i < numPoly; i++ ) { polygon[i].checkBounds( r ); polygon[i].move(); polygon[i].fill( g ); } } |
The previous animations exhibit a noticeable flicker caused by the continuous clearing and repainting of the applet window |
Flicker can be avoided by double buffering |
A buffer is an off-screen graphics image |
All drawing is done in the buffer, which is then copied to the applet window as a last step |
First, we initialize the buffer: int w = this.getSize().width; int h = this.getSize().height; Image buffer = createImage( w, h ); Graphics gOff = buffer.getGraphics(); |
The rest of the program is the same except that now we must override update(...) |
All drawing takes place in the buffer: public void update( Graphics g ) { gOff.setColor( getBackground() ); gOff.fillRect( 0, 0, w, h ); paint( gOff ); g.drawImage( buffer, 0, 0, this ); } |
The buffer is copied to the applet window as a last step |
The last argument to the drawImage(...) method is an ImageObserver object, which is notified by the drawing process as the drawing procedes |
All components (including applets) are ImageObservers |
The SystemColors applet of a previous section displayed a sample of each of the colors defined in the SystemColor class |
There are more than two dozen such colors, so the applet window was rather large |
We will write another applet that displays any number of colors in a fixed-sized window |
A new AWT component called ScrollPane was introduced in Java 1.1 |
A ScrollPane is an autonomous window with horizontal and vertical scrollbars |
It is ideal for displaying large components inside a fixed-sized viewing area |
The ScrollPane constructor takes a single argument that specifies the desired scrollbar policy |
Three static constants are provided for this purpose: SCROLLBARS_ALWAYS, SCROLLBARS_AS_NEEDED, and SCROLLBARS_NEVER |
The applet defines a separate class called ColorCanvas, a subclass of Canvas |
An instance of ColorCanvas shows a sample of each color in the SystemColor class |
A ScrollPane object the size of the applet window is created |
The (large) ColorCanvas object is then added to the ScrollPane |