JMU
Visual Content in J2ME
An Introduction


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


GUI Components for Visual Content
Static Content

The AbstractTransformableContent class can no longer be used because:

Described Static Content

An Example Revisited

j2meexamples/src/visual/statik/described/RandomRectangleCanvas.java
        package visual.statik.described;

import java.util.*;
import javax.microedition.lcdui.*;

import gui.*;

/**
 * A Canvas that renders a Rectangle with randomly
 * generated attributes each time it's paint method is called
 *
 * Changes for J2ME Version:
 *    Graphics instead of Graphics2D
 *    drawRect() instead of draw()
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
*/
public class RandomRectangleCanvas extends VisualComponent
{
    private Random        generator;
    
    
    /**
     * Default Constructor
     */
    public RandomRectangleCanvas()
    {
        super();
        generator = new Random();
    }

    
    /**
     * Render this Canvas
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
       int   height, maxHeight, maxWidth, width, x, y;
        

       maxHeight = g.getClipHeight();
       maxWidth  = g.getClipWidth();

       x      = generator.nextInt(maxWidth  - 1);
       y      = generator.nextInt(maxHeight - 1);
       width  = generator.nextInt(maxWidth  - x - 1);
       height = generator.nextInt(maxHeight - y - 1);

       // Note: There is no Color class or Shape interface
       
       // Clear the screen
       g.setColor(0x00FFFFFF);
       g.fillRect(0, 0, maxWidth, maxHeight);
       
       // Draw the rectangle
       g.setColor(0x00FF00FF);
       g.drawRect(x, y, width, height);       
    }
}
        
Sampled Static Content

The TransformableContent interface can no longer include setComposite() or setBufferedImageOp()

The factories have to change:

j2meexamples/src/visual/statik/sampled/ImageFactory.java
        package visual.statik.sampled;

import java.io.*;
import javax.microedition.lcdui.*;

/**
 * A utility class for constructing/creating BufferedImage objects
 *
 * Changes in J2ME Version:
 *
 *    Completely re-written
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 */
public class ImageFactory
{
    
    /**
     * Default Constructor
     */
    public ImageFactory()
    {
        super();
    }
    

    /**
     * Create an Image from a file 
     * containing an Image
     *
     * @param name        The name of the file
     * @return            The Image or null if an Exception was thrown
     */
    public Image createBufferedImage(String name)
    {
       Image                           result;

       result = null;
       try
       {
           InputStream is = ImageFactory.class.getResourceAsStream("/"+name); 
           if(null != is)
               result = Image.createImage(is);
       }
       catch (IOException ioe)
       {
       }
       
       return result;       
    }        


}
        
j2meexamples/src/visual/statik/sampled/ContentFactory.java
        package visual.statik.sampled;

import java.io.*;
import javax.microedition.lcdui.*;

/**
 * A utility class for constructing/creating 
 * visual.statik.sampled.Content objects
 *
 * Changes in J2ME Version:
 *
 *    Completely re-written
 * 
 * @author Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 */
public class ContentFactory
{
    private ImageFactory              imageFactory;
    
    
    /**
     * Default Constructor
     */
    public ContentFactory()
    {
        super();
        imageFactory = new ImageFactory();
    }
    
    /**
     * Read a row-oriented array of Content objects from a file
     * 
     * 
     * 
     * @param file     The name of the file (without a path)
     * @param n        The number of images
     * @param channels IGNORED!
     * @return The images or null if an Exception was thrown
     */
    public Content[] createContents(String file, int n, int channels)    
    {
        Image                            composite, subimage;
        Content[]           result;
        int                              height, width;
        
        result    = null;
        composite = imageFactory.createBufferedImage(file);
        
        if (composite != null)
        {
            height = composite.getHeight();
            width  = composite.getWidth() / n;
            result = new Content[n];
            for (int j=0; j<n; j++)
            {
                    subimage  = Image.createImage(composite, 
                                                     j*width, 0, 
                                                     width, height,
                                                     0);
                    result[j] = new Content(subimage, 0, 0);
            }
        }
        return result;
    }    

    
    /**
     * Read a table of Content objects from a file
     * 
     * 
     * 
     * @param file     The name of the file (without a path)
     * @param rows     The number of rows
     * @param columns  The number of columns
     * @param channels IGNORED!
     * @return The images or null if an Exception was thrown
     */
    public Content[][] createContents(String file, int rows, int columns, int channels)    
    {
        Image                            composite, subimage;
        Content[][]         result;
        int                              height, width;
        
        result    = null;
        composite = imageFactory.createBufferedImage(file);
        
        if (composite != null)
        {
            height = composite.getHeight() / rows;
            width  = composite.getWidth() / columns;
            result = new Content[rows][columns];
            for (int i=0; i<rows; i++)
            {
                for (int j=0; j<columns; j++)
                {
                    subimage  = Image.createImage(composite, 
                                                     j*width, i*height, 
                                                     width, height,
                                                     0);
                    result[i][j] = new Content(subimage, 0, 0);
                }
            }
        }
        return result;
    }    
        
    
    /**
     * Create a Content from a file
     * 
     * 
     * 
     * @param file   The name of the file (without a path)
     * @return The image or null if an Exception was thrown
     */
    public Content createContent(String file)    
    {
       Image                           content;
       Content         result;

       result = null;
       content = imageFactory.createBufferedImage(file);
       if (content != null)
               result = new Content(content, 0, 0);
       
       return result;       
    }    
}
        
Sampled Static Content (cont.)

An Example Revisited

j2meexamples/src/visual/statik/sampled/ImageCanvas.java
        package visual.statik.sampled;

import javax.microedition.lcdui.*;

import gui.*;

/**
 * A concrete extension of a Canvas that illustrates
 * the rendering of sampled static visual content
 *
 * Changes in J2ME Version:
 *
 *    Packed int instead of Color
 * 
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 */
public class ImageCanvas extends VisualComponent
{
    private Image       image;
    
    
    /**
     * Explicit Value Constructor
     *
     * @param image   The Image to render
     */
    public ImageCanvas(Image image)
    {
        this.image = image;
    }
    
    
    /**
     * Render this Canvas
     *
     * @param g   The rendering engine to use
     */
    public void paint(Graphics g)
    {
        int         height, x, y, width;

        // Note: The Canvas class is abstract so we don't
        // call super.paint(g)
        
        x      = g.getClipX();
        y      = g.getClipY();
        width  = g.getClipWidth();
        height = g.getClipHeight();
        
        // Note:  The background needs to be "cleared"
        g.setColor(0x00FFFFFF);
        g.fillRect(x, y, width, height);
        
        // Note: The registration point must be specified
        g.drawImage(image, 0, 0, Graphics.TOP|Graphics.LEFT);
    }
}
        
Sampled Dynamic Content

Nothing (significant) has to change

Described Dynamic Content
Described Dynamic Content (cont.)

An Example Revisited

j2meexamples/src/visual/dynamic/described/FallingCharacter.java
        package visual.dynamic.described;


import java.util.*;
import javax.microedition.lcdui.*;

import geom.*;
import visual.statik.TransformableContent;


/**
 * A character that falls from the top of the screen to the bottom
 * (as in "The Matrix")
 *
 * Note: This is a simple rule-based Sprite.  It does not use key-frames
 * or tweening.  Hence, it has rules in handleTick().
 *
 * Changes in the J2ME Version:
 *
 *     render() was re-written
 *     getContent() is not needed
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 *
 */
public class FallingCharacter extends Sprite
{
    private char                character;
    private int                 green, speed;
    private Rectangle2D         bounds;
    
    private static Font         font = Font.getFont(Font.FACE_MONOSPACE, Font.STYLE_BOLD, Font.SIZE_LARGE);
    private static Random       rng  = new Random();
    
    
    
    /**
     * DefaultConstructor
     *
     */
    public FallingCharacter()
    {
        super();

        character     = (char)(33 + rng.nextInt(95));
        green         = rng.nextInt(255);

        bounds        = new Rectangle2D();
        bounds.x      = rng.nextInt(180);
        bounds.y      = -1 * rng.nextInt(196/2);
        bounds.width  = font.charWidth(character);
        bounds.height = font.getHeight();

        speed         = rng.nextInt(5) + 1;
    }

    
    
    
    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (in milliseconds)
     */
    public void handleTick(int time)
    {
        bounds.y += speed;
        if (bounds.y > 196+30)
        {
            bounds.y = 0;
            bounds.x = rng.nextInt(180);
            speed = rng.nextInt(5) + 1;
        }
    }
    
    
    
    /**
     * Get the bounding rectangle for this Sprite
     *
     * @return  The bounding rectangle
     */
    public Rectangle2D getBounds2D()
    {
        return bounds;
    }
    
    
    public TransformableContent getContent()
    {
        return null;
    }
    
    /**
     * Render the Sprite in its current state
     *
     * @param g  The rendering engine to use
     */
    public void render(Graphics g)
    {
        // Setup the rendering engine
        g.setFont(font);
        g.setColor(0, green, 0);
        
        // Render the character
        g.drawChar(character, bounds.x, bounds.y, Graphics.TOP|Graphics.LEFT);
    }
    

}
        
Described Dynamic Content (cont.)

Another Example Revisited

j2meexamples/src/visual/dynamic/described/Cupola.java
        package visual.dynamic.described;

import javax.microedition.lcdui.*;

import event.*;
import geom.*;
import visual.statik.TransformableContent;


/**
 * The Cupola in the Balloon game.
 *
 * J2ME Changes:
 *    Changed PointerListener to KeyboardListener for ease of game play
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 */
public class Cupola extends RuleBasedSprite implements KeyboardListener
{
    private double     left, top;

    /**
     * Explicit Value Constructor
     *
     * @param content     The static visual content
     * @param stageWidth  The width of the Stage
     * @param stageHeight The height of the Stage
     */
    public Cupola(TransformableContent content, 
                  double stageWidth, double stageHeight)
    {
       super(content);
       Rectangle2D        bounds;       

       bounds = content.getBounds2D(false);
       top    = (stageHeight - bounds.getHeight());
       left   = (stageWidth  - bounds.getWidth())/2.0;
    
       setLocation(left, top);
    }

    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       setLocation(left, top);
    }


    /**
     * Handle a "key press" event 
     * (required by KeyboardListener)
     *
     * @param key  The key
     */
    public void handleKeyPress(int key)
    {
        if (key == KeyDictionary.KEY_LEFT)
            left -= 5;
        else if (key == KeyDictionary.KEY_RIGHT)
            left += 5;
    }
}

        
j2meexamples/src/visual/dynamic/described/Balloon.java
        package visual.dynamic.described;

import java.util.Random;
import javax.microedition.lcdui.*;

import geom.*;
import visual.statik.TransformableContent;


/**
 * A Balloon that drops to the ground
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0 (J2ME)
 */
public class Balloon extends RuleBasedSprite
{
    private double          left, speed, top;
    private int             maxY, maxX, minY;
    

    private static Random   rng = new Random();
    

    

    /**
     * Explicit Value Constructor
     *
     * @param content     The static content for the balloon
     * @param stageWidth  The width of the Stage
     * @param stageHeight The height of the Stage
     */
    public Balloon(TransformableContent content, 
                   double stageWidth, double stageHeight)
    {
       super(content);
       Rectangle2D        bounds;       

       bounds = content.getBounds2D(false);

       maxX   = (int)(stageWidth  - bounds.getWidth());
       maxY   = (int)(stageHeight);
       minY   = (int)(-bounds.getHeight());       

       left   = rng.nextInt(maxX);
       top    = minY;
       speed  = 1 + rng.nextInt(3);
    }


    /**
     * Handle a tick event (generated by the Stage)
     *
     * @param time  The current time (which is not used)
     */
    public void handleTick(int time)
    {
       Sprite   cupola;

       // Check for an intersection
       cupola = null;       
       if (protagonists.size() > 0) cupola = (Sprite)protagonists.elementAt(0);

       if ((cupola != null) && (intersects(cupola)))
       {
          speed = 0;
          setVisible(false);
       }
       
       // Update the location
       top += speed;
       
       if (top > maxY)
       {
          left   = rng.nextInt(maxX);
          top    = minY;
          speed  = rng.nextInt(10);
       }
       
       
       // Set the location
       setLocation(left, top);
    }
}