At the forefront of Artificial Intelligence
  Home Articles Reviews Interviews JDK Glossary Features Discussion Search
Home » Articles » Programming » Java

Programming with the Generation5 JDK

This tutorial will briefly look at programming with the Generation5 JDK. The Generation5 JDK is broad and covers a wide-range of material, so this tutorial will focus on create a customized cellular automata renderer to create Conway's Life diagrams (see below). A separate tutorial will look at creating your own cellular automata examples.

Glider diagram

Firstly, a quick overview about how data is rendered and saved using the JDK. The JDK has an interface called Visualizable for classes that want to render data to the screen or an image file. Visualizable has two abstract methods, render and writeImage. The render method takes three parameters: the graphics context, context width and height. writeImage takes the filename, image width and height.

For writeImage we can use ImageHelper's writeVisualizedImage method (org.generation5.util package) that takes the same parameters as writeImage with an additional parameter, an instance of Visualizable. The method then uses the render method to draw onto a graphics context, then saves that graphics context to a file:

// // String filename // int width // int height // Visualizable content //
BufferedImage buffer = new BufferedImage(width, height, 1); Graphics2D graphics = buffer.createGraphics(); content.render(graphics, width, height); File file = new File(filename); ImageIO.write(buffer, "png", file);

Understanding the Problem

So now with a simple understanding of how the JDK renders images, how do we create diagrams like the one shown above? We have two initial problems: firstly, we need to change how the cellular automata are rendered to create the 'celled' effect (adding the spacing and borders for each cell) and secondly, we need to solve the problem of rendering multiple instances of the same CA on the same graphics context.

We will be creating a class called CellDiagram to solve this problem. Since we will be wanting to write the diagrams, the class has to implement Visualizable as discussed.

Rendering the Cells

The best method of rendering the cells is to derive from the Conway's Life demo (org.generation5.demos.ConwaysLifeCA) and override the render method. Since the class has little advantage outside this application, so we will implement it as an inner class. Our class currently looks like:
public class CellDiagram implements Visualizable {
    public void render(java.awt.Graphics graphics, int pw, int ph) {
        // Diagram implementation here
    }

    public void writeImage(String s, int width, int height) {
        try {
            ImageHelper.writeVisualizedImage(s, width, height, this);
        } catch (IOException e) {
            System.err.println(e);
        }
    }

    public static class CellLife extends ConwaysLifeCA {
        public void render(java.awt.Graphics graphics, int pw, int ph) {
            // Cell implementation here
        }
    }
}
The actual implementation details of drawing the cells is irrelevant (see the source code), but when overriding the render method, there are important considerations to remember. Principally, you must remember that the image dimensions can be much larger than the area your data will be drawn in. You will often need to calculate the necessary pixel positions to centre image within the graphics context.

Rendering Multiple Instances

Rendering multiple instances of the cellular automata can be achieved very easily using a similar trick to ImageHelper.writeVisualizedImage. Here we create an array of BufferedImage equal to the number of steps we want to render. Then we calculate the necessary dimensions for the images, taking into account border sizes.

Next, we render the various steps on buffered images, advancing the CA one time-step by called ca.doStep. Finally, we copy the images on to the final graphics context using drawImage and then drawing a border. To expand the usefulness of the class further, we will allow the images to be drawn across multiple rows and columns.

BufferedImage[] shots = new BufferedImage[steps];
int dw = ca.getSizeX() * ca.getCASize() + 
        (ca.getSizeX() - 1) * ca.cellSpace;
int dy = ca.getSizeY() * ca.getCASize() +
        (ca.getSizeY() - 1) * ca.cellSpace;

dw += borderSize; dy += borderSize;

for (int i=0; i<shots.length; i++)
    shots[i] = new BufferedImage(dw, dy, 1);

for (int i=0; i<shots.length; i++) {
    ca.render(shots[i].getGraphics(), dw, dy);
    ca.doStep();
}
        
g.setColor(ca.getBackgroundColor());
g.fillRect(0, 0,  width, height);
for (int i=0; i<shots.length; i++)
    g.drawImage(shots[i], (dw) * (i % columns), 
                          (dy) * (i / columns), dw, dy, null);
g.setColor(java.awt.Color.BLACK);
g.drawRect(0, 0, width-1, height-1);

Using the Class

The core of the class is in place, all that is needed is to add functionality to customize your diagrams. Most of this can be done using the CellularAutomata methods, but additional methods are added to set the number of steps to render, the size of the image borders and CA sizes. See the source code for the completed version of the class.

Now for some examples of how to use the class:

Glider - Type 1

ca.setBackgroundColor(java.awt.Color.lightGray);
ca.setWorldSize(6, 6);
ca.setWorldAtEx(1, 1, "0,0,1;1,0,1;0,1,1");
ca.init();
        
diagram.writeImage("glider01.png", diagram.getDiagramWidth(),
                                   diagram.getDiagramHeight());

Glider - Type 2

diagram.setDimensions(2,3);
ca.setWorldSize(9, 7);
ca.setWorldAtEx(1, 2, "0,0,1,1;1,1,0,1,1;1,1,1,1;0,1,1");
ca.init();

diagram.writeImage("glider02.png", diagram.getDiagramWidth(),
                                   diagram.getDiagramHeight());

Static Object

diagram.setDimensions(2,2);
diagram.setSteps(4);
ca.setWorldSize(17, 17);
ca.setWorldAtEx(2, 2, "0,0,1,1,1;0;1,0,0,0,0,1;" + 
                      "1,0,0,0,0,1;1,0,0,0,0,1;0,0,1,1,1;");
ca.setWorldAtEx(9, 2, "0,1,1,1;0;1,0,0,0,0,1;" + 
                      "1,0,0,0,0,1;1,0,0,0,0,1;0,1,1,1;");
ca.setWorldAtEx(2, 9, "0,0,1,1,1;1,0,0,0,0,1;" + 
                      "1,0,0,0,0,1;1,0,0,0,0,1;0;0,0,1,1,1;");
ca.setWorldAtEx(9, 9, "0,1,1,1;1,0,0,0,0,1;1,0,0,0,0,1;" +
                      "1,0,0,0,0,1;0;0,1,1,1;");
ca.setBackgroundColor(java.awt.Color.WHITE);
ca.setCASize(8);
ca.init();
diagram.writeImage("static01.png", diagram.getDiagramWidth(),
                                   diagram.getDiagramHeight());

Future Improvements

There are a variety of improvements you might want to make to this class. For example, instead of hard-coding diagram parameters into main, read them from an XML or text file. Furthermore, the current class is geared toward Conway's Life, generalize the class for Visualizable classes, as well as expanding the 'celled' format to all CellularAutomata classes.

Submitted: 19/08/2004

Article content copyright © James Matthews, 2004.
 Article Toolbar
Print
BibTeX entry

Search

Latest News
- Generation5 10-year Anniversary (03/09/2008)
- New Generation5 Design! (09/04/2007)
- Happy New Year 2007 (02/01/2007)
- Where has Generation5 Gone?! (04/11/2005)
- NeuroEvolving Robotic Operatives (NERO) (25/06/2005)

What's New?
- Back-propagation using the Generation5 JDK (07/04/2008)
- Hough Transforms (02/01/2008)
- Kohonen-based Image Analysis using the Generation5 JDK (11/12/2007)
- Modelling Bacterium using the JDK (19/03/2007)
- Modelling Bacterium using the JDK (19/03/2007)


All content copyright © 1998-2007, Generation5 unless otherwise noted.
- Privacy Policy - Legal - Terms of Use -