Geometry of Interaction

Java Take-Off Step Six:

Geometry of Interaction

(Rendering Shapes, Text, Images - Part Two)


Here's a first program.

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

public class CollisionTest extends Applet implements MouseListener, 
						     MouseMotionListener
{  
    // the number of rectangles contained in our scene
    private final int NUM_RECTS = 10;
    
    // a list of rectangles
    private LinkedList rectangles;
    
    // an AlphaCompisite to show semi-transparent rectangles
    private AlphaComposite alpha;          
    
    // the rectangle that is currently selected
    private Rectangle2D pick;
    
    public void init()
    {
	rectangles = new LinkedList();
	pick = null;
	
	// create an AlphaComposite with 50% transparency
	alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
	
	// create NUM_RECTS rectangles at random positions and add them
	// to the list
	Random r = new Random();
	int width = (int)getSize().getWidth();
	int height = (int)getSize().getHeight();
	
	for(int i = 0; i < NUM_RECTS; i++)
	    {
		rectangles.add(new Rectangle2D.Double
		    (
		     (double)(   Math.abs(r.nextInt())%width),
		     (double)(   Math.abs(r.nextInt())%height),
		     (double)(20+Math.abs(r.nextInt())%50), 
		     (double)(20+Math.abs(r.nextInt())%50))
		    );
	    } 
	
	// don't forget to register the applet to listen for mouse events
	addMouseListener(this);
	addMouseMotionListener(this);
    }
    
    public void paint(Graphics g)
    {
	Graphics2D g2d = (Graphics2D)g;
	
	// tell our Graphics2D context to use transparency
	g2d.setComposite(alpha);
	
	// draw the rectangles
	g2d.setPaint(Color.black);
	for(int i = 0; i < NUM_RECTS; i++)
	    {
		g2d.draw((Rectangle2D.Double)rectangles.get(i));
	    }
	
	// if pick refers to a valid rectangle, test it for collisions
	if(pick != null)
	    {
		Rectangle2D rect;
		g2d.setPaint(Color.red.darker());
		for(int i = 0; i < NUM_RECTS; i++)
                    {
			// get the ith rectangle in the list
			rect = (Rectangle2D)rectangles.get(i);
			
			// test for intersection-- note we shouldn't test
			// pick against itself
			if(pick != rect && pick.intersects(rect))
			    {
				// fill collisions
				g2d.fill(rect);
			    }                                          
                    } 
		
		// fill the pick rectangle
		g2d.setPaint(Color.blue.brighter());
		g2d.fill(pick);
	    }
    }
    
    // methods inherited from the MouseListener interface
    
    public void mouseClicked(MouseEvent e) { }
    
    public void mouseEntered(MouseEvent e) { }
    
    public void mouseExited(MouseEvent e)  { }
    
    public void mousePressed(MouseEvent e)
    {
	// attempt to pick up a rectangle 
	if(pick == null)
	    {
		Rectangle2D rect;
		for(int i = 0; i < NUM_RECTS; i++)
                    {
			rect = (Rectangle2D)rectangles.get(i);
			
			// if the rectangle contains the mouse position,
			// 'pick' it up
			if(rect.contains(e.getPoint()))
			    {
				pick = rect;
				return;
			    }
                    } 
	    }
    }
    
    public void mouseReleased(MouseEvent e)
    {
	// release the pick rectangle and repaint the scene
	pick = null;
	repaint();
    } 
    
    // methods inherited from the MouseMotionListener interface
    
    public void mouseDragged(MouseEvent e)
    {
	// if we have a picked rectangle, set its position to the 
	// current mouse position and repaint
	if(pick != null)
	    {
		pick.setRect(e.getX(), 
			     e.getY(), 
			     pick.getWidth(), 
			     pick.getHeight());
		repaint();
	    }
    }
    
    public void mouseMoved(MouseEvent e)  { } 
    
} // CollisionTest
What does it do? (Compile and run it).

Here's another one:

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

class BoundedImage extends Object
{
    // the Image and bounding data for this container
    private Image       image;
    private Rectangle2D bounds;
    
    public BoundedImage(Image img, ImageObserver obs)
    {
	image = img;
	
	// set the bounds to (0,0) with the width and height of
	// the image data
	bounds = new Rectangle2D.Double(0, 0,
					image.getWidth(obs),
					image.getHeight(obs));
    }
    
    public Rectangle2D getBounds2D()
    {     return bounds; 
    }
    
    public Image getImage()
    {     return image;
    }
    
    public AffineTransform getTransform()
    {     return AffineTransform.getTranslateInstance
	      (bounds.getX(), 
	       bounds.getY());
    }
    
    public void moveTo(Point p)
    {    
	bounds.setRect((double)p.x, (double)p.y,
		       bounds.getWidth(), 
		       bounds.getHeight());
    }
}    // BoundedImage

public class BoundedImageTest extends Applet implements MouseListener,
							MouseMotionListener
{  
    // global reference to the image filename for easy editing
    private final String FILENAME = "simon.gif";
    
    // the number of images contained in our scene
    private final int NUM_IMAGES = 3;
    
    // a list of BoundedImages
    private LinkedList images;
    
    // the BoundedImage that is currently selected
    private BoundedImage pick;
    
    // an AlphaCompisite to highlight collisions
    private AlphaComposite alpha;  
    
    public void init()
    {
	images = new LinkedList();
	pick = null;
	
	// create NUM_IMAGES images at random positions and add them
	// to the list
	
	Random r = new Random();
	int width = (int)getSize().getWidth();
	int height = (int)getSize().getHeight();
	
	// create a MediaTracker object so our images are fully loaded  
	// before being passed to the BoundedImage class
	MediaTracker mt = new MediaTracker(this);
	
	BoundedImage bi;     // BoundedImage to add to the list
	Image img;           // a single Image
	
	for(int i = 0; i < NUM_IMAGES; i++)
	    {    
		img = getImage(getCodeBase(), FILENAME);
		mt.addImage(img, i);
		try
                    {     mt.waitForID(i);
                    }
		catch(InterruptedException e) { /* do nothing */ }
		
		bi = new BoundedImage(img, this);
		bi.moveTo
		    (new Point
			(Math.abs
			 (r.nextInt())%width,
			 Math.abs(r.nextInt())%height)
			);
		
		images.add(bi);
	    } 
	
	// create an AlphaComposite with 40% transparency
	alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.4f);
	
	// don't forget to register the applet to listen for mouse events
	addMouseListener(this);
	addMouseMotionListener(this);
    }
    
    public void paint(Graphics g)
    {
	Graphics2D g2d = (Graphics2D)g;
	
	// draw the images
	BoundedImage bi;
	for(int i = 0; i < NUM_IMAGES; i++)
	    {
		bi = (BoundedImage)images.get(i);
		g2d.drawImage(bi.getImage(), bi.getTransform(), this);
	    }
	
	// if pick refers to a valid image, test it for collisions
	if(pick != null)
	    {
		for(int i = 0; i < NUM_IMAGES; i++)
                    {
			// get the ith image in the list
			bi = (BoundedImage)images.get(i);
			
			// test for intersection
			if(imageCollision(pick, bi))
			    {
				// fill the bounding rectangle to highlight 
				// the collision
				g2d.setComposite(alpha);
				g2d.setPaint(Color.red.darker());
				g2d.fill(bi.getBounds2D());
			    }                                          
                    } 
		
		// draw and fill the pick rectangle
		g2d.setPaint(Color.blue.brighter());
		g2d.setComposite(alpha);
		g2d.drawImage(pick.getImage(), pick.getTransform(), this);
		g2d.fill(pick.getBounds2D());
	    }
    }
    
    // determines if two BoundedImages intersect (collide). this method
    // will return false if i1 and i2 refer to the same object 
    private boolean imageCollision(BoundedImage i1, BoundedImage i2)
    {
	return (i1 == i2) ? false : 
	    i1.getBounds2D().intersects(i2.getBounds2D());
    }
    
    // methods inherited from the MouseListener interface
    
    public void mouseClicked(MouseEvent e) { }
    
    public void mouseEntered(MouseEvent e) { }
    
    public void mouseExited(MouseEvent e)  { }
    
    public void mousePressed(MouseEvent e)
    {
	// attempt to pick up an image
	if(pick == null)
	    {
		BoundedImage bi;
		for(int i = 0; i < NUM_IMAGES; i++)
                    {
			bi = (BoundedImage)images.get(i);
			
			// if the BoundedImage contains the mouse position,
			// 'pick' it up
			if(bi.getBounds2D().contains(e.getPoint()))
			    {
				pick = bi;
				return;
			    }
                    } 
	    }
    }
    
    public void mouseReleased(MouseEvent e)
    {
	// release the pick image and repaint the scene
	pick = null;
	repaint();
    } 
    
    // methods inherited from the MouseMotionListener interface
    
    public void mouseDragged(MouseEvent e)
    {
	// if we have a picked image, set its position to the 
	// current mouse position and repaint
	if(pick != null)
	    {
		pick.moveTo(e.getPoint());
		repaint();
	    }
    }
    
    public void mouseMoved(MouseEvent e) { } 
    
} // BoundedImageTest
What does it do?

(Please notice you need this: simon.gif)

How does it do what it does?

Here's a third program:

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

public class AreaTest extends Applet 
{  
    // an array of Area objects and an associated array of String
    // geometry descriptions
    private Area[] shapes;
    private String[] ops;
    
    public void init()
    {
	// create 4 Areas and Strings
	shapes = new Area[4];
	ops = new String[4];
	
	// create two circles that overlap about the origin
	Ellipse2D e1 = new Ellipse2D.Double(-0.125, 0.0, 0.5, 0.5);
	Ellipse2D e2 = new Ellipse2D.Double(+0.125, 0.0, 0.5, 0.5);
	
	// create a Union between the shapes
	shapes[0] = new Area(e1);
	shapes[0].add(new Area(e2));
	ops[0] = "Union";
	
	// Substract e2 from e1
	shapes[1] = new Area(e1);
	shapes[1].subtract(new Area(e2));
	ops[1] = "Subtraction";
	
	// create an Intersection between the shapes
	shapes[2] = new Area(e1);
	shapes[2].intersect(new Area(e2));
	ops[2] = "Intersection";
	
	// use the Exclusive OR operation between the shapes
	shapes[3] = new Area(e1);
	shapes[3].exclusiveOr(new Area(e2));
	ops[3] = "XOR";
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// create a stroke to outline the shapes
	g2d.setStroke(new BasicStroke(2.0f/100.0f));
	
	// a Random object for creating random colors
	Random r = new Random();
	
	// render the shapes and the operation descriptions
	for(int i = 0; i < 4; i++)
	    {
		g2d.setTransform(new AffineTransform());
		g2d.translate(50+(i*100), 40);
		
		g2d.drawString(ops[i], 0, 70);                 
		
		g2d.scale(100, 100);
		
		g2d.setPaint(new Color(r.nextInt()));
		g2d.fill(shapes[i]);
		g2d.setPaint(Color.black);
		g2d.draw(shapes[i]);
	    }
    }
    
} // AreaTest
That's called additive geometry and I hope you like it.

Here's something related:

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

public class ClipTest extends Applet implements ItemListener
{  
    // the Image and clip region to render
    private Image image;
    private Polygon clip;
    
    // a Choice to display the Image and clip region
    private Choice dropBox; 
    
    // indices for our drop box
    private final int SHOW_IMAGE_ONLY    = 0;
    private final int SHOW_CLIP_ONLY     = 1;
    private final int SHOW_CLIPPED_IMAGE = 2;
    
    public void init()
    {
	// load an image from file
	MediaTracker mt = new MediaTracker(this);
	image = getImage(getCodeBase(), "blabber.gif");
	mt.addImage(image, 0);
	try
	    {     mt.waitForID(0);
	    }
	catch(InterruptedException e) { /* do nothing */ }
	
	// create an 8-sided clip region about an anchor point; the 
	// anchor point will be located at the center of our Image
	
	clip = new Polygon();
	int anchor = image.getWidth(this)/2;
	for(int i = 0; i < 8; i++)
	    {
		clip.addPoint
		    (anchor+(int)(anchor*Math.cos(Math.toRadians(i*45))),  
		     anchor+(int)(anchor*Math.sin(Math.toRadians(i*45))));
	    }               
	
	// add the drop box to the bottom of the container, along with the 
	// three render choices
	setLayout(new BorderLayout());
	dropBox = new Choice();
	dropBox.add("Show Image");
	dropBox.add("Show Clip Region");
	dropBox.add("Show Clipped Image");
	dropBox.addItemListener(this);
	add(dropBox, BorderLayout.SOUTH);
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// set the transform to the identity transform
	final AffineTransform at = new AffineTransform();
	
	// render based on the current selection
	switch(dropBox.getSelectedIndex())
	    {
	    case SHOW_IMAGE_ONLY:
		{
		    // just draw the Image
		    g2d.drawImage(image, at, this);
		    break;
		}
		
	    case SHOW_CLIP_ONLY:
		{
		    // just draw the clipping region
		    g2d.setTransform(at);
		    g2d.draw(clip);
		    break;
		}
		
	    case SHOW_CLIPPED_IMAGE:
		{
		    // draw the Image with respect to the clipping region
		    g2d.setClip(clip);
		    g2d.drawImage(image, at, this);
		    break;
		}
		
	    default:
		{    
		    // invalid index
		    System.out.println("Invalid Choice: " + 
				       dropBox.getSelectedIndex());
		    break;
		}
	    }
    }
    
    public void itemStateChanged(ItemEvent e)
    {
	// our drop box has changed-- redraw the scene
	repaint();
    }
    
} // ClipTest
Note that you need this (blabber.gif)

Hope you like this stuff.

Here's another program:

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

public class BufferedImageTest extends Applet 
{  
    // a BufferedImage, along with its creation width and height
    private BufferedImage image;
    private final int BI_WIDTH  = 100;
    private final int BI_HEIGHT = 100;
    
    // for generating random colors and screen positions
    private java.util.Random random;
    
    public void init()
    {
	random = new java.util.Random();
	
	// create a (BI_WIDTH x BI_HEIGHT) buffered image 
	image = new BufferedImage(BI_WIDTH, 
				  BI_HEIGHT, 
				  BufferedImage.TYPE_INT_RGB);
	
	// create a Graphics2D context for our BufferedImage.  Remember 
	// this has nothing to do with our Applet's Graphics2D context	
	Graphics2D g2d = image.createGraphics();
	
	// we will render some stripes to the BufferedImage
	// the width and height of our stripes will be one-tenth 
	// the total width and height of the image                
	final int stripWidth  = BI_WIDTH  / 10;
	final int stripHeight = BI_HEIGHT / 10;
	
	// fill the image with a random color
	g2d.setPaint(new Color(random.nextInt()));
	g2d.fill(new Rectangle(0, 0, BI_WIDTH, BI_HEIGHT));
	
	// render the vertical strips using a random color
	g2d.setPaint(new Color(random.nextInt()));
	int x = stripWidth / 2;
	while(x < BI_WIDTH)
	    {
		g2d.fill(new Rectangle(x, 0, stripWidth, BI_HEIGHT));   
		x += 2 * stripWidth;
	    }
	
	// set a transparency channel for our stripes
	g2d.setComposite
	    (AlphaComposite.getInstance
	     (AlphaComposite.SRC_OVER, 0.5f));
	
	// render the horizontal strips using a random color
	g2d.setPaint(new Color(random.nextInt()));
	int y = stripHeight / 2;
	while(y < BI_HEIGHT)
	    {
		g2d.fill(new Rectangle(0, y, BI_WIDTH, stripHeight));    
		y += 2 * stripHeight;
	    }
	
	// render a dark opaque outline around the perimeter of the image
	g2d.setStroke(new BasicStroke(2.0f));
	g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
	g2d.setPaint(Color.black);
	g2d.draw(new Rectangle(0, 0, BI_WIDTH, BI_HEIGHT));
    }
    
    public void paint(Graphics g)
    {
	// cast the sent Graphics context to get a usable Graphics2D object
	Graphics2D g2d = (Graphics2D)g;
	
	// draw a bunch of images at random locations               
	for(int i = 0; i < 20; i++)
	    {
		g2d.drawImage
		    (image,

		     AffineTransform.getTranslateInstance
		     (
		      random.nextInt()%getSize().getWidth(), 
		      random.nextInt()%getSize().getHeight()
		     ),

		     this
		    );               
	    }
    }
    
} // BITextureTest
The focus should be here on the internal mechanism of the program.

Here's how we cann use image enhancement operations:

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

public class BlurTest extends Applet implements ActionListener
{  
    // the original and blurred BufferedImages
    private BufferedImage sourceImage;
    private BufferedImage destImage;
    
    // 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;
	
	// draw a friendly reminder to input a filename if the text 
	// field just contains white-space
	if("".equals(input.getText().trim()))
	    {
		g2d.drawString("Enter a filename to blur", 10, 50);
		return;
	    }
	
	// load the input image
	MediaTracker mt = new MediaTracker(this);
	Image img = getImage(getCodeBase(), input.getText());
	mt.addImage(img, 0);
	try
	    {    
		// wait until the image has loaded completely 
		// before continuing 
		mt.waitForID(0);
	    }
	catch(InterruptedException e) { /* do nothing */ }
	
	// our blur operation will require a BufferedImage object, so render
	// the input image to a new BufferedImage object
	sourceImage = new BufferedImage(img.getWidth(this),
					img.getHeight(this),
					BufferedImage.TYPE_INT_RGB);
	sourceImage.createGraphics().drawImage(img, null, this);
	
	// create the destination image 
	destImage = new BufferedImage(sourceImage.getWidth(this),
				      sourceImage.getHeight(this),
				      BufferedImage.TYPE_INT_RGB);
	
	// blur the input image
	blur(sourceImage, destImage, 0.1f, 3, 3);
	
	// draw both the source and destination images
	AffineTransform at = new AffineTransform();
	int width = sourceImage.getWidth(this);
	g2d.drawImage(sourceImage, at, this);      
	at.translate(width+20, 0);
	g2d.drawImage(destImage, at, this);      
    }
    
    // blurs an image using a ConvolveOp operation           
    public void blur(
		     BufferedImage sourceImage,  // the input image data
		     BufferedImage destImage,    // the output image data
		     float weight,               // weight of the Kernel data
		     int width,                  // width of the Kernel
		     int height                  // height of the Kernel
		     )
    {
	// this will be the data array for our Kernel
	float[] elements = new float[width*height]; 
	
	// fill the array with the weight
	java.util.Arrays.fill(elements, weight);
	
	// use the array of elements and the 
	// width and height to create a Kernel
	Kernel k = new Kernel(width, height, elements);
	ConvolveOp blurOp = new ConvolveOp(k);
	
	// blur the image
	blurOp.filter(sourceImage, destImage); 
    }
    
    public void actionPerformed(ActionEvent e)
    {
	// the "Ok" was pressed; update the changes
	repaint();
    } 
    
} // BlurTest
Here are some more images:

You may or may not need these images now.

Can you use images from the web, directly, in the program above?


Last updated: Apr 11, 2002 by Adrian German for T540