Skip to main content

Low-Level Display Access in MIDlets

February 2, 2005

{cs.r.title}









Contents
A Little Bit of History Repeating
Canvas-Based Painting
The Graphics Class
   Color Handling
   Anchor Points
   Fonts and Text Handling
The Canvas Class
   The paint Method
   Repaints Initiated by the MIDlet
   Keystrokes
Working with Offscreen Buffers
Conclusion
Resources

In the second part of this series, I introduced high-level J2ME user interface components that provide the same look and feel as the native applications of a MIDP device. They offer functionality that is usually needed in general desktop programs. However, these productivity components are totally ill-suited to games, 3D, or multimedia, for which would we probably be better off writing the visuals directly to the display. Fortunately, this need is addressed by javax.microedition.lcdui.Canvas and related classes.

A Little Bit of History Repeating

javax.microedition.lcdui.Display is the only means for MIDlets to gain access to the screen. Therefore, as far as a MIDlet is concerned, the Display is the screen. There is exactly one instance of this class for each MIDlet. It can be obtained by calling Display.getDisplay(this). As we will see soon, Display provides important information concerning device capabilities, so it is good practice to save it as a member variable of your MIDlet base class. The setCurrent(...) method is used to display new content, which has to be encapsulated in classes extending Displayable. This is true for children of Screen such as Alert, List, or TextBox, which are high-level user interface components, and Canvas.

High-level UI elements are general-purpose components, and can be used in a broad range of applications. Each one is either a (top-level) container or focusses on some sort of data (for example, text, dates, or lists). To provide a familiar user experience, they look and feel like native UI components. This implies that the programmer has very little influence on their visual appearance. A common look is of course desirable for business-type programs. But games or multimedia MIDlets may wish to provide a more individual experience, possibly using vibrant colors. Action games in particular require fast and immediate access to the display and input devices. The Canvas class is intended for such purposes.

Canvas-Based Painting

Contrary to the Screen-based classes, Canvas does not rely on a component hierarchy and is not data-centric. Instead, it provides a drawing area that is accessed through graphics primitives that are implemented as methods of a helper class. Subsequent calls to these methods produce the contents of the display, so what is shown on screen is completely under control of the programmer. As we have already seen, high-level components hide their visual appearance from the developer. Canvas, on the other hand, provides free and immediate access to the display and input facilities.

I have implemented a small game called CanvasMIDlet to show you how to use Canvas and its related classes. It is played as follows: the player is presented a color that is identified by a number. He/she has to press this number on the numeric keypad of the cellphone within five seconds. Then another number is shown. This time the player has to press the first number, followed by the second. Each time a sequence has been completed successfully, a new number is appended to it.

Figure 1 shows CanvasMIDlet running in a MIDP emulator on Mac OS X. Please have a look at the Resources section to download the source code and binaries.

Figure 1
Figure 1. CanvasMIDlet running in the MIDP emulator on Mac OS X Panther

In J2ME, Canvas-based painting is very similar to the AWT and Swing painting mechanism. The basic idea is that Canvas provides a drawing area that is accessed each time its paint method is invoked. The actual drawing is done by a helper class called Graphics, which provides several drawing primitives and the means to control how painting takes place (for example, which colors, fonts, or line styles are used).

Though CanvasMIDlet uses several interesting classes (for example, Timer and TimerTask) we will focus on display input and output. One very important thing to note is that the paint method of Canvas is abstract, so you need to subclass Canvas and provide an implementation of paint. Here is what paint looks like.

[prettify]
public void paint(Graphics g) {
  if (displayedNumber == -1) {
    g.setColor(0);
    g.fillRect(0, 0, canvasW, canvasH);
    return;
  }
  String str = Integer.toString(displayedNumber);
  g.setColor(colors[displayedNumber]);
  g.fillRect(0, 0, canvasW, canvasH);
  g.setFont(font);
  int strWidth = font.stringWidth(str);
  g.setColor(0);
  int x, y;
  for (x = 0; x < canvasW; x += strWidth)
    for (y = 0; y < canvasH; y += fontHeight)
      g.drawString(str, x, y, Graphics.TOP|Graphics.LEFT);
}
[/prettify]

The Graphics Class

As you can see in the example above, paint usually makes heavy use of the methods in Graphics to create the display contents. This class provides several drawing primitives, including lines, arcs, and rectangles. Arcs and rectangles can be filled with a solid color, and rectangles may have rounded corners. Each of these primitives may be drawn with either a SOLID or a DOTTED stroke style using the setStrokeStyle() method. Of course, Graphics can handle images and text, too. I will focus on that later.

Drawing operations are based on a coordinate system whose origin is at the upper left-hand corner. The X-axis direction is positive towards the right, and the Y-axis direction is positive downwards. public void translate(int x, int y) can be used to translate the origin of the coordinate system.

Color Handling

Colors are coded as int values with 24 significant bits. Red, green, and blue take eight bits each (0x00RRGGBB). It is important to note, however, that not all mobile devices may not meet these requirements. Possible limitations are:

  • The display does not support colors at all, as it is black and white or grey scale.
  • The number of bits per color is less than eight.
  • The number of distinct colors is limited.

To provide the best experience on any device, it is good practice to query the capabilities of the gadget. The Display class offers several methods for this purpose. For example,

public boolean isColor()
returns true if the display supports color and public int numColors() returns the number of colors that can be represented on the device. If it does not support colors, this method returns the number of grey levels. For a black and white display this is two.

Many graphics primitives paint using a current color. This value can be obtained with public int getColor(). The public int getGrayScale() method returns the corresponding greyscale value. It is even possible to obtain a particular component of the current color. For example, public int getBlueComponent() returns the blue component. To set the current color, you can choose from:

public void setColor(int red, int green, int blue);
public void setColor(int RGB);
public void setGrayScale(int value);
Anchor Points

The drawing of text and images is based on so-called anchor points that specify the location of the respective item. Unlike in J2SE, this need not necessarily be the upper left corner of an image or the leftmost point on the baseline of a text string. Instead, an additional parameter controls how the anchor point is interpreted.

Consider the following scenario. To center a piece of text in J2SE, an application calls stringWidth() or charWidth() of FontMetrics and then performs a combination of subtraction and division to calculate the location. MIDlets may call public void drawString(String text, int x, int y, int anchor), where anchor must be one of the horizontal constants LEFT, HCENTER, or RIGHT combined (|) with one of the vertical constants TOP, BASELINE, or BOTTOM. This makes centering text trivial--pass your center point and HCENTER|BASELINE. 0 is equivalent to using TOP|LEFT.
Vertical centering of text is not specified, so VCENTER is not allowed for drawString().
Unlike text, images do not have the notion of a baseline, so BASELINE may not be used within the anchor point parameter of drawImage.

Fonts and Text Handling
Graphics provides several methods for drawing text. Among them are:

  • void drawString(String str, int x, int y, int anchor)
  • public void drawSubstring(String str, int offset, int len, int x, int y, int anchor)
  • public void drawChar(char character, int x, int y, int anchor)

There is also the notion of a current font; you do not specify a font as part of a text drawing primitive. The font that is going to be used can be determined through public Font getFont() and set with public void setFont(Font font). The Font class
represents fonts and font metrics. Applications query fonts based on attributes (style, size, and face) by calling getFont() or getDefaultfont(). The runtime environment will return a font that matches the requested attributes as closely as possible.
Below is an example of how to use getFont().

font = Font.getFont(Font.FACE_SYSTEM, 
                    Font.STYLE_PLAIN,
                    Font.SIZE_LARGE);
Font provides several methods to obtain the extents of text strings. This can be used for sophisticated layout. For example, stringWidth() returns the horizontal size of a string, and getHeight() returns the standard height of a line of text. A simple vertical spacing may be achieved as follows:

height = f.getHeight();
for (i = 0; i < txt.length; i++) {
  g.drawString(txt[i], x, y, TOP|LEFT);
  y += height;
}

The Canvas Class

So far we have concentrated on Graphics, as it performs the actual drawing operations. We will now turn to Canvas, which acts as a base class for applications that need to handle low-level events. Please remember that Canvas is interchangeable with standard Screen-based classes, as both extend Displayable. Therefore, MIDlets can safely mix Canvas with high-level screens, using them as arguments to setCurrent().

The paint Method

Any time the contents of the display have to be redrawn, paint is invoked. As this method is declared abstract, you need to subclass Canvas and provide an implementation. You can do this with an additional class that explicitly extends Canvas (your MIDlet base class usually extends MIDLET, so it can't extend Canvas). Another possibility is to use an anonymous inner class, as shown below. You will probably choose this alternative for the sake of brevity if you use only one canvas. This has the benefit of having all MIDlet-related code in one source file. For more complex programs this may lead to less-readable code, though. If your MIDlet needs to store references to a canvas, you are better off introducing a new class.

canvas = new Canvas() {
  public void paint(Graphics g) {
    ...
  }

  protected void keyPressed(int keyCode) {
    ...
  }
};
paint should return as soon as possible. Therefore, it is good practice to perform calculations that affect screen output outside of paint if possible. For example, you can compute text sizes and other font-metrics-related values and assign them to variables that are used inside of paint. These values need to be changed only if your MIDlet (or the user) wishes to use another font.

Instances of Graphics that are passed to Canvas.paint() render directly to the display of a mobile device. MIDlets may draw using this graphics object only for the duration of the paint() method. Graphics objects that have been obtained from other sources, such as Image.getGraphics(), can be accessed at any time. They do not render to the display, but to an offscreen buffer.

Repaints Initiated by the MIDlet
paint is called each time a canvas needs to be redrawn. This is done by the runtime. But how can we achieve repaints initiated by the MIDlet? This may be necessary for several reasons:

  • A MIDlet wishes to perform an animation.
  • A game needs to put new objects on screen (such as enemies or bonuses).

The instance of Graphics that is passed to paint is valid only during execution of the paint method. Therefore, saving and passing it to paint from within the MIDlet is not possible. But a MIDlet can request repaints by Canvas.repaint(), just like in AWT. It does not call its paint directly, but asks the runtime to do so.

Keystrokes
Canvas can handle keystrokes, pointer-device actions (if supported by the mobile device), and game actions. Let us have a look at how CanvasMIDlet handles key presses.

protected void keyPressed(int keyCode) {
  if (keyCode > 0) {
    char ch = (char) keyCode;
    if ((ch >= '0') && (ch <= '9')) {
      enteredNumber = ch - '0';
      timer.cancel();
      handleGuess();
    }
  }
}

To be notified of key presses, your MIDlet has to overwrite keyPressed(). If the passed key code is greater than zero, it is the ASCII value of the pressed key.

Working with Offscreen Buffers

As we have already seen, we can issue repaint requests to achieve animation. If your MIDlet has to render a complex scene (for example, a three-dimensional object rotating in front of a scrolling background), you are likely to notice considerable flickering. To avoid this disturbing effect, it is common practice to use a technique called double buffering.

The flickering occurs because drawing operations on the Graphics object passed to paint are immediately sent to the display (unless your device supports double buffering by default). As we have a scrolling background, we need to completely replace the current display contents with the background and then paste our 3D object.

The idea of double buffering is to prepare the new contents of the display in memory and send them to the screen after it has been rendered completely. The underlying assumption is that replacing the complete contents of the screen takes less time than the immediate rendering to the display. This is true for most devices.

Fortunately, it is easy to use double buffering in MIDP applications. javax.microedition.lcdui.Image can be used to create an offscreen image buffer by calling createImage(width, height). Once you have obtained one, you call its getGraphics() method to get an instance of Graphics, which is used to paint to the buffer in just the same way as you would paint directly to the display. If your MIDlet has finished its rendering, Graphics.drawImage() is used to copy the contents of the offscreen buffer to the actual display. By the way, a MIDlet may issue requests on the offscreen graphics object at any time.

Conclusion

In this installment, I have introduced the Canvas class with its related helper classes. It provides low-level display output and immediate access to the input facilities of a MIDP device. Canvas is particularly well suited for applications that require fast and unconstrained access to the screen, which is true for games and multimedia programs.

This article concludes my series on MIDlet programming. If you would like to see other topics covered, please feel free to contact me.

Resources

width="1" height="1" border="0" alt=" " />
Thomas Kunneth works as a software architect at the German authorities, specializing in Java-based rich clients.
Related Topics >> GUI   |   Mobility   |   Programming   |