Greetings...

Java Take-Off Step Five:

Rendering Text, Shapes, Images (Part One)


In graphics, a transformation manipulates geometry and places it within a scene.

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.util.*;

public class AffineTest extends Applet implements ItemListener
{  
    // the rectange to draw
    private Rectangle2D rect;
    
    // two checkboxes to allow us to specify the logical order in which 
    // to apply the transformations
    private Checkbox rotateFirst;
    private Checkbox translateFirst;
    
    public void init()
    {
	// create a CheckboxGroup containing the two Checkboxes
	setLayout(new BorderLayout());
	
	CheckboxGroup cbg = new CheckboxGroup(); 
	Panel p = new Panel();
	
	rotateFirst = new Checkbox("rotate, translate", cbg, true);
	rotateFirst.addItemListener(this);
	p.add(rotateFirst);
	translateFirst = new Checkbox("translate, rotate", cbg, false);
	translateFirst.addItemListener(this);
	p.add(translateFirst);
	add(p, BorderLayout.SOUTH);
	
	// model our rectangle about the origin
	rect = new Rectangle2D.Float(-0.5f, -0.5f, 1.0f, 1.0f); 
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// save an identity transform to clear the Graphics2D context
	final AffineTransform identity = new AffineTransform();
	
	// true if we wish to logically rotate first 
	boolean rotate = rotateFirst.getState();
	
	// create a random number generator to produce random colors
	Random r = new Random();
	
	final double oneRadian = Math.toRadians(1.0);
	for(double radians = 0.0; radians < 2.0*Math.PI; radians += oneRadian)
	    {
		// clear this Graphics2D's transform 
		g2d.setTransform(identity);
		
		// remember, operations are performed in reverse order
		// than we logically prefer them!
		
		if(rotate)
                    {
			g2d.translate(100, 100);
			g2d.rotate(radians);
                    }
		else
                    {
			g2d.rotate(radians);
			g2d.translate(100, 100);
                    }
		
		g2d.scale(10, 10);  
		
		g2d.setColor(new Color(r.nextInt()));
		g2d.fill(rect); 
	    }
    }
    
    public void itemStateChanged(ItemEvent e)
    {
	// a new Checkbox was selected, better repaint!
	repaint();
    }
} // AffineTest
EXERCISES Drawing and filling shapes in an interactive context is what we look at below.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*; 
import java.util.*;

public class MouseShapeTest extends Applet implements MouseMotionListener
{  
    // the Shape to draw
    private Shape shape;
    
    // flags whether or not the mouse cursor is over the Shape
    private boolean mouseOver;
    
    // the current draw and fill color
    private Color currentColor;
    
    public void init()
    {
	// the control points for the Shape
	int[] x = { 25, 55, 60, 75, 110, 130 };
	int[] y = { 65, 100, 133, 20, 115, 55 }; 
	
	// create a new Polygon to represent our Shape.  Remember, a
	// Polygon *is* a Shape since it implements the Shape interface
	shape = new Polygon(x, y, x.length);
	
	mouseOver = false;
	addMouseMotionListener(this);
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	g2d.setColor(currentColor);
	
	// fill the Shape if the mouse cursor is over it
	if(mouseOver)
	    {
		g2d.fill(shape); 
	    }
	// otherwise, just draw the outline of the Shape
	else
	    {
		g2d.draw(shape); 
	    }
    }
    
    public void mouseDragged(MouseEvent e) { /* do nothing */ }
    
    public void mouseMoved(MouseEvent e)
    {
	// save the previous value
	boolean prevValue = mouseOver; 
	
	// update the mouseOver flag using the Shape.contains method
	mouseOver = shape.contains(e.getPoint()) ? true : false;
	
	// repaint only if there is reason to
	if(prevValue != mouseOver)
	    {
		// why not change the current color while we're at it
		currentColor = new Color(new Random().nextInt());
		repaint();
	    }
    }           
    
} // MouseShapeTest
EXERCISES We now discuss instance modelling.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*; 
import java.util.*;

public class InstanceTest extends Applet 
{  
    // the Shape to draw 
    private Shape shape;
    
    public void init()
    {
	// create a unit square modeled about the origin
	shape = new Rectangle2D.Float(-0.5f, -0.5f, 1.0f, 1.0f);
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// save an identity transform to clear the Graphics2D context 
	final AffineTransform identity = new AffineTransform(); 
       
	Random r = new Random();
	
	int width = getSize().width;
	int height = getSize().height;
	
	// create 500 instances of the shape's geometry
	for(int i = 0; i < 500; i++)
	    {
		// clear this Graphics2D's transform 
		g2d.setTransform(identity);
		
		// randomly set up the transform
		g2d.translate(r.nextInt()%width, r.nextInt()%height);
		g2d.rotate(Math.toRadians(360*r.nextDouble()));
		g2d.scale(20*r.nextDouble(), 10*r.nextDouble());
		
		// draw the shape              
		g2d.setColor(new Color(r.nextInt()));
		g2d.fill(shape);
	    }
    }
    
} // InstanceTest
EXERCISES We now switch gears, and discuss images.
EXERCISES We now switch gears, and discuss images.
import java.applet.*;
import java.awt.*;
import java.awt.geom.*; 
import java.util.*;

public class ImageTest extends Applet 
{  
    // the Images to render
    private Image[] images;
    
    // filename description of our images
    private final String[] filenames 
        = { "simon.gif", "tj2gp.gif", "blade.gif" };
    
    public void init()
    {
	// get the base URL
	java.net.URL appletBaseURL = getCodeBase();
	
	// allocate memory for the images and load 'em in 
	int n = filenames.length;
	images = new Image[n];
	for(int i = 0; i < n; i++)
	    {
		images[i] = getImage(appletBaseURL, filenames[i]);
	    }
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// save an identity transform 
	final AffineTransform identity = new AffineTransform();
	
	// used to transform our images
	AffineTransform at = new AffineTransform(); 
	
	Random r = new Random();
	
	int width = getSize().width;
	int height = getSize().height;
	
	int numImages = filenames.length;
	
	// render 100 images, each with a random transformation
	for(int i = 0; i < 100; i++)
	    {
		// clear the transformation
		at.setTransform(identity);
		
		// randomly set up the translation and rotation
		at.translate(r.nextInt()%width, r.nextInt()%height);
		at.rotate(Math.toRadians(360*r.nextDouble()));
		
		// draw the image 
		g2d.drawImage(images[i%numImages], at, this);
	    }
    }
} // ImageTest
You will need these: simon.gif, tj2gp.gif, blade.gif.

EXERCISES

Our next example investigates attributes of lines, as they are drawn.

import java.applet.*;
import java.awt.*;
import java.awt.geom.*; 

public class StrokeTest extends Applet 
{  
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// set the pen width to 3.0 pixels
	float penWidth = 3.0f;
	
	// set a plain end caps decoration and a join miter line join 
	int endCaps = BasicStroke.CAP_BUTT;
	int lineJoins = BasicStroke.JOIN_MITER;
	
	// limit the miter join trim to 10.0 pixels
	float trim = 10.0f;
	
	// set the dash pattern
	float[] dashPattern = { 5.0f, 9.0f, 3.0f };
	
	// begin the pattern right away (with no pixel offset)
	float dashOffset = 0.0f;
	
	BasicStroke stroke = new BasicStroke(penWidth, endCaps, lineJoins,
					     trim, dashPattern, dashOffset);
	
	g2d.setStroke(stroke);
	
	g2d.draw(new Line2D.Float(10.0f, 10.0f, 140.0f, 10.0f)); 
	g2d.draw(new Rectangle2D.Float(20.0f, 60.0f, 100.0f, 50.0f));
    }
} // StrokeTest
EXERCISES A gradient refers to a colored strip with two defined endpoints.
import java.applet.*;
import java.awt.*;
import java.awt.geom.*; 
import java.util.*;

public class GradientTest extends Applet 
{  
    // the Polygon to draw 
    private Polygon poly;
    
    // the two points that will define the paint's endpoints
    private Point2D p1;
    private Point2D p2;
    
    public void init()
    {
	// the radius of two circles
	final float[] radii = { 10.0f, 20.0f };
	
	// the starting point and increment level for plotting points
	double radians = 0.0;
	final double increment = Math.toRadians(15.0);
	
	poly = new Polygon();
	
	// the shape will be determined by alternating between points on
	// the perimeters of two circles
	// since we are incrementing by 15 degrees, we can fit 24 
	// points in our shape (360/15 = 24)
	for(int i = 0; i < 24; i++)
	    {                    
		poly.addPoint((int)(radii[i%2]*Math.cos(radians)), 
			      (int)(radii[i%2]*Math.sin(radians)));
		
		radians += increment;
	    }
	
	// set the endpoints of our paint.  these values will be scaled
	// by the Graphics2D object
	p1 = new Point2D.Float(0.0f, +20.0f);
	p2 = new Point2D.Float(0.0f, -20.0f);
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	AffineTransform at = new AffineTransform();
	at.translate(100,100);
	at.scale(5, 5);
	
	// draw the shape              
	g2d.setTransform(at);
	g2d.setPaint(new GradientPaint(p1, Color.orange, p2, Color.green));
	g2d.fill(poly);
    }
    
} // GradientTest
EXERCISES The following program demonstrates both cyclic and acyclic gradient paints.
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*; 

public class CycleTest extends Applet implements ItemListener
{  
    // the Rectangle to draw
    private Rectangle2D rect;
    
    // the line containing the points that will define the paint's endpoints
    private Line2D line;
    
    // checkboxes for selecting gradient cycle type
    private Checkbox cyclic;
    private Checkbox acyclic;
    
    public void init()
    {
	// create a unit square
	rect = new Rectangle2D.Float(-0.5f, -0.5f, 1.0f, 1.0f);
	
	// set the endpoints of our paint. 
	line = new Line2D.Float(-0.25f, 0.0f, 0.25f, 0.0f);
	
	setBackground(Color.orange);
	
	// create checkboxes for selecting gradient cycle type
	CheckboxGroup cbg = new CheckboxGroup();
	setLayout(new BorderLayout());
	Panel p = new Panel();
	p.setBackground(Color.green);
	cyclic = new Checkbox("cyclic", cbg, true);
	cyclic.addItemListener(this);
	p.add(cyclic);
	acyclic = new Checkbox("acyclic", cbg, false);        
	acyclic.addItemListener(this);
	p.add(acyclic);
	add(p, BorderLayout.SOUTH);
    }
    
    public void paint(Graphics g)
    {
	// the scaled width of the rectangle
	final double scaleWidth = 100.0f;
	
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// transform the shape
	g2d.translate(100,100);
	g2d.scale(scaleWidth, 50);
	
	// draw the shape              
	g2d.setPaint(new GradientPaint(
				       line.getP1(), Color.black, 
				       line.getP2(), Color.white, 
				       cyclic.getState()));
	g2d.fill(rect);
	
	// draw lines perpendicular to paint endpoints
	g2d.setPaint(Color.red);
	
	g2d.setTransform(new AffineTransform());
	g2d.translate(100-0.25*scaleWidth, 100);
	g2d.rotate(Math.PI/2);
	g2d.scale(scaleWidth/2, 1);
	g2d.draw(line);
	
	g2d.setTransform(new AffineTransform());
	g2d.translate(100+0.25*scaleWidth, 100);
	g2d.rotate(Math.PI/2);
	g2d.scale(scaleWidth/2, 1);
	g2d.draw(line);
    }
    
    public void itemStateChanged(ItemEvent e)
    {
	// update the change!
	repaint();
    }
} // CycleTest
EXERCISES Here's an example of using textures.

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.awt.geom.*; 

public class TextureTest extends Applet implements ActionListener
{  
    // field for accepting a filename
    private TextField input;
    
    public void init()
    {
	// create a layout and add the textfield and "Ok" button
	setLayout(new BorderLayout());
	Panel p = new Panel();
	input = new TextField("", 20);
	p.add(input);
	Button ok = new Button("Ok");
	ok.addActionListener(this);
	p.add(ok);
	add(p, BorderLayout.SOUTH);
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// just draw an outline of the shape and return if the text 
	// field just contains white-space
	if("".equals(input.getText().trim()))
	    {
		g2d.translate(112, 15);
		g2d.rotate(Math.PI/4);
		g2d.draw(new Rectangle2D.Double(0, 0, 104, 104));
		return;
	    }
	
	// load an image
	// we'll look at the MediaTracker class when we discuss animation
	
	MediaTracker mt = new MediaTracker(this);
	Image image = getImage(getCodeBase(), input.getText());
	mt.addImage(image, 0);
	try
	    {     mt.waitForAll();
	    }
	catch(InterruptedException e) { /* do nothing */ }
	
	// the filename was probably invalid if the width or height of 
	// the image created is <= 0
	if(image.getWidth(this) <= 0 || image.getHeight(this) <= 0) 
	    {
		// print error message and return
		input.setText(input.getText() + " : invalid filename.");
		return;
	    }
	
	// create a new BufferedImage with the image's width and height
	BufferedImage bi = new BufferedImage
	    (image.getWidth(this), 
	     image.getHeight(this), 
	     BufferedImage.TYPE_INT_RGB);
	
	// get the Graphics2D context of the BufferedImage 
	// and render the original image onto it
	((Graphics2D)bi.getGraphics()).
	    drawImage(image, new AffineTransform(), this);
	
	// create the anchoring rectangle for the paint's 
	// image equal in size to the image's size
	Rectangle2D bounds = new Rectangle2D.Float
	    (0, 0, bi.getWidth(), bi.getHeight());
	
	// set the paint
	g2d.setPaint(new TexturePaint(bi, bounds));
	
	// transform and render!
	g2d.translate(112, 15);
	g2d.rotate(Math.PI/4);
	g2d.fill(new Rectangle2D.Double(0, 0, 104, 104));
    }
    
    public void actionPerformed(ActionEvent e)
    {
	// the "Ok" was pressed; update the changes
	repaint();
    } 
} // TextureTest
EXERCISES Alpha rendering and blending.

import java.applet.*;
import java.awt.*;
import java.awt.geom.*; 
import java.util.*;

// A quick n' dirty approach to encapsulating the properties (position, 
// size, etc) of a square so that it can be updated regularly 
class AlphaBox 
{
    // a random number generator for the class
    private static Random random = null;
    
    // all rendering will be based from a single unit square
    private static Rectangle2D square = null;
    
    // a class copy of the identity affine tranform
    private static AffineTransform identity = null;
    
    // properties of our box
    private AlphaComposite alpha;               
    private double xPos;           // x, y position
    private double yPos;
    private double xVel;           // x, y speed 
    private double yVel;
    private double size;           // width and height
    private Color  color;          // color of this instance
    private Dimension windowSize;
    
    public AlphaBox(Dimension d)
    {
	windowSize = d;
	
	// define any null objects
	if(random == null)
	    { 
		random = new Random();
	    }
	
	if(square == null)
	    { 
		square = new Rectangle2D.Float(-0.5f, -0.5f, 1.0f, 1.0f);
	    }
	
	if(identity == null)
	    {
		identity = new AffineTransform();
	    }
	
	// all composites will be SRC_OVER and very transparent
	// play around with these values (try randomizing them)
	// to get some cool effects
	alpha = AlphaComposite.getInstance(
					   AlphaComposite.SRC_OVER, 0.25f);
	
	// randomize the properties of the box
	xPos = windowSize.width*random.nextDouble();
	yPos = windowSize.height*random.nextDouble();
	xVel = 1+2*random.nextDouble();
	if(random.nextDouble() > 0.5) xVel = -xVel;
	yVel = 1+2*random.nextDouble();
	if(random.nextDouble() > 0.5) yVel = -yVel;
	size = 25+100*random.nextDouble();
	color = new Color(random.nextInt()).brighter();
    }  
    
    // paints the box to the sent Graphics2D context according 
    // to its current properties 
    public void paint(Graphics2D g2d)
    {
	// bounce the box around the window
	
	xPos += xVel;
	if(xPos > windowSize.width)
	    {  
		xPos = windowSize.width;
		xVel = -xVel;
	    }
	if(xPos < 0)
	    {
		xPos = 0;
		xVel = -xVel;
	    }
	
	yPos += yVel;
	if(yPos > windowSize.height)
	    { 
		yPos = windowSize.height;
		yVel = -yVel;
	    }
	if(yPos < 0)
	    {
		yPos = 0;
		yVel = -yVel;
	    }
	
	// render the box  
	g2d.setTransform(identity);
	g2d.translate(xPos, yPos);
	g2d.scale(size, size);
	g2d.setComposite(alpha);
	g2d.setPaint(color);
	g2d.fill(square);
    }
    
} // AlphaBox              

public class CompositeTest extends Applet implements Runnable
{  
    // a thread for animation -- we'll talk about this later 
    private volatile Thread animation;
    
    // an array of AlphaBox objects
    private AlphaBox[] boxes;
    
    public void init()
    {
	animation = new Thread(this);
	
	// create the boxes
	final int n = 10;
	boxes = new AlphaBox[n];
	Dimension size = this.getSize();
	for(int i = 0; i < n; i++)
	    {
		boxes[i] = new AlphaBox(size);
	    }
    }
    
    public void start()
    {
	animation.start();
    }
    
    public void stop()
    {
	animation = null;
    }
    
    // override the update method so that it doesn't clear the window 
    public void update(Graphics g)
    {
	paint(g);
    }
    
    public void paint(Graphics g)
    {
	Graphics2D g2d = (Graphics2D)g;
	
	// paint each AlphaBox
	for(int i = 0; i < boxes.length; i++)
	    {
		boxes[i].paint(g2d);
	    }
    }
    
    public void run()
    {
	// we'll talk about this stuff later!
	Thread t = Thread.currentThread();
	while (t == animation)
	    {
		try 
                    {
			t.sleep(10);
                    }
		catch (InterruptedException e)
                    {
                    }
		repaint();
	    }      
    }
    
} // CompositeTest
EXERCISES How can you tell which fonts are available on any particular system?
import java.io.*;
import java.awt.*;

public class FontListing
{  
    public static void pause()
    {
	System.out.println("\nPress Enter to Continue");
	try
	    {
		System.in.read();
	    }
	catch(IOException e)
	    {
	    }               
    }
    
    public static void main(String[] args) 
    {
	String[] availableFonts = GraphicsEnvironment.
	    getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
	
	for(int i = 0; i < availableFonts.length; i++)
	    {
		System.out.println(availableFonts[i]);
		
		if(i > 0 && i%20 == 0)
                    {
			pause();
                    }
	    }
    }
    
} // FontListing
EXERCISES Take a look at this program, which is rotating text.
import java.applet.*;
import java.awt.*;
import java.awt.font.*;

public class FontTest extends Applet
{  
    // Color constants for rendering different colored text
    static final Color[] colors = { 
	Color.red, Color.blue, Color.orange, Color.darkGray };
    
    // paints some text to the screen
    public void paint(Graphics g)
    {
	// remember to cast to a vaild Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// we don't need an explicit reference to the font, so we'll
	// just specify it in one line.  applets that use multiple fonts
	// would want to save a copy of each font.
	g2d.setFont(new Font("Helvetica", Font.BOLD, 1));
	
	// scale the font, then translate it to be centered on the screen
	g2d.translate(150, 150);
	g2d.scale(20, 20);
	
	// render "Fonts are FUN!" using each color 
	for(int i = 0; i < colors.length; i++)
	    {
		// set the current color
		g2d.setPaint(colors[i]);
		
		// render the String at (0,0); g2d's transform will take care
		// of the actual rendering position
		g2d.drawString("Fonts are FUN!", 0, 0);
		
		// rotate by 60 degrees
		g2d.rotate(Math.PI/3.0);
	    }
    } // paint
    
} // FontTest
EXERCISES
import java.applet.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;

public class FontBoundsTest extends Applet
{  
    private final String MESSAGE = "Trapped!";
    
    public void paint(Graphics g)
    {
	Graphics2D g2d = (Graphics2D)g;
	
	// create a Font Object.
	Font baseFont = new Font("Helvetica", Font.PLAIN, 50);
	
	// get the FontRenderContext for the Graphics2D context
	FontRenderContext frc = g2d.getFontRenderContext();
	
	// get the layout of our message and font, using the above
	// FontRenderContext
	TextLayout layout = new TextLayout(MESSAGE, baseFont, frc);
	
	// get the bounds of the layout
	Rectangle2D textBounds = layout.getBounds();
	
	// draw the message and the bounding rectangle at (45, 50)
	g2d.setFont(baseFont);
	g2d.setPaint(Color.black);
	g2d.drawString(MESSAGE, 45, 50);
	
	g2d.translate(45, 50);
	g2d.setPaint(Color.red);
	g2d.draw(textBounds);
    }
} // FontBoundsTest
Whew... This was a long chapter!


Last updated: Apr 7, 2002 by Adrian German for A201/A597/I210/A348/A548/T540/NC009