With some phones, you have to roll your own GIF decoder
Tom is a technical writer providing support for J2ME wireless technologies at KPI Consulting Inc. He can be contacted at tom_thompson@lycos.com.
Today’s mobile
are versatile devices. Besides placing calls, they offer features such
as Bluetooth connectivity, Internet support, built-in cameras that take
pictures or capture video, and 3D graphics rendering capability, to
name a few. Surprisingly, they sometimes lack certain important
features. For instance, despite their ability to connect to and surf
the Web, many mobile phones, from within their own Java environment,
are unable to display a Graphics Interchange Format (GIF) file.
I discovered this odd omission in graphics capability while writing
a program that made use of a mobile phone’s GPS APIs. The idea was to
get a GPS fix of the phone’s location (presumably its owner would be
nearby) and present a map of the area. I thought that the hard part of
the project would be to generate the location map, while the easy part
would be to get the latitude and longitude position fix from the GPS
APIs. I didn’t anticipate any problems displaying the map, given the
mobile phone’s rich and varied graphics support.
Typical of software-project scheduling, I got it all backwards:
Obtaining the map turned out to be the easy part. I discovered the U.S.
Census Department’s Tiger server that, if provided latitude and
longitude and the desired image size in pixels, renders a map and
returns it as a GIF file to you. As you can see in Figure 1,
the map is exquisitely detailed, using standard map colors for parks,
cities, state boundaries, and highways labeled with their route
numbers.
The hard part turned out to be displaying that GIF file. It took
only several crashes in the debugging emulator to trace the problem to
the Java 2 Mobility Edition (J2ME) platform, which is the phone’s Java
runtime environment. Simply put, J2ME couldn’t decode and display GIF
files. As the census server provides its maps only as GIF files,
completing the project required that I roll my own GIF decoder for
J2ME. In this article, I show how this was done. The complete source
code that implements this technique is available electronically; see
"Resource Center," page 6.
The Law of Unintended Consequences
J2ME’s inability to display GIFs is particularly baffling
considering that some mobile phones can download JPEG, BMP, and
animated GIF files as wallpaper. How could this gap in graphics
capability occur? The answer lies in the unintended consequences of how
J2ME implements security and graphics.
The J2ME platform uses a "sandbox" model to carry out its security
mechanism. MIDlets—mobile applications that are similar in design to a
web browser’s Java applet—execute within a separate memory space that’s
walled off from the phone’s native operating system and resources.
MIDlets are free to play in the sandbox, but can’t access any hardware
or resources outside of the box by themselves. Access to the phone’s
screen, audio system, and other hardware features is mediated through
J2ME API classes. Simply put, if an API isn’t available for a
particular hardware feature (such as Bluetooth), a MIDlet can’t use it.
This prevents a malicious MIDlet from crashing the phone just before a
critical call is placed, or tampering with the phone’s address book, or
doing funny things with Bluetooth.
Per the specifications of J2ME’s support APIs, the execution
environment only has to implement the display of Portable Network
Graphics (PNG) images. Ironically, support for this one particular
format has to do with the legal problems surrounding GIF files.
CompuServe invented the GIF format in the 1980s to handle the
transfer and display of online graphics. Given the limited capabilities
of the graphics hardware of the time, GIF files contained a 24-bit
color table, with pixel color information represented as 8-bit values.
The 8-bit value limits GIF files to displaying only 256 colors. Because
of the low bandwidth connections, the file contents were reduced in
size through the use of a Lempel-Ziv-Welch (LZW) compression scheme.
For a while, the fact that the LZW algorithm was patented wasn’t an
issue. Then came the Web, and with it, the demand for graphic images
soared. At the start of 1995, Unisys—the owner of the LZW patent—and
CompuServe began aggressively pursuing fees for the use of GIF images.
This event precipitated the development of the PNG format as an
alternative to the GIF format, and by late 1996, the PNG file format
was adopted as a standard by the W3C. PNG is a lossless image format
that supports images with pixel depths ranging from 1 bit
(black-and-white), up to 48 bits (what’s termed "truecolor"), an alpha
channel, and data compression. Crucially, PNG was carefully designed so
that its data structures and compression algorithms didn’t infringe on
any patents. For more information on the PNG file format, see "PNG: The
Portable Network Graphic Format," by Lee Daniel Crocker (DDJ, July 1995).
In 1999, when Sun Microsystems developed the J2ME platform
specification and its supporting APIs, PNG was chosen as the default
image format because of its graphics capabilities, small file size, and
that it was unencumbered with patent issues. GIF was, to a large
extent, sidelined as a graphics format.
The situation has improved for GIF since 2004, when the world-wide
Unisys patents expired. While GIF has a limited color palette, this
range is adequate for most graphic images, and its small size is still
valuable for the mobile phone’s limited bandwidth wireless connections.
Its animation feature is widely supported by all web browsers.
Because of GIF’s checkered history, the J2ME platform often doesn’t
support the format. For the same reason, the native operating systems
of many mobile phones don’t handle GIF files, either. Even if the
phone’s native OS happens to support GIFs, the sandbox security
mechanism blocks its use unless the vendor goes through the trouble to
expose the GIF decoder routine to J2ME.
Checking for Native GIF Support
Once I knew what the problem was, how could I go about fixing it?
It might seem that when your MIDlet receives a GIF file, the best
course of action is to simply invoke the GIF-decoder routine.
Practically, to conserve the phone’s limited working memory, you want
to check for the presence of any GIF-decoding capability. J2ME has a
collection of classes known as the Mobile Information Device Profile
(MIDP) that implement the MIDlet’s runtime environment and thus
constitute its operating system. The MIDP 2.0 implementation provides
utility methods that can query the host phone as to its capabilities
and features.
To determine if J2ME handles a specific media format, you invoke the getSupportedContentTypes()
method. This method returns a string array of supported media types,
and includes the audio and video formats along with the image formats.
The strings present this information in MIME-type format. To check for
GIF support, you scan this array, looking for the GIF MIME-type string.
If there is a match, then the phone’s J2ME implementation supports
native display of GIF files. Listing One shows how to use getSupportedContentTypes() to obtain the media types array, along with loop code that does the array scan. The code sets the Boolean flag, gifSupported, if a match is found. Because this check only has to execute once, a good place for this code is in the class’s constructor.
If the MIDlet executes on one of those rare phones with GIF
support, then displaying it becomes a matter of writing a few lines of
code. J2ME stores its graphic data in an Image object. A method in this class, createImage(),
takes as an argument an array whose data is organized into a
self-identifying image format such as PNG, GIF, and others. All that’s
required is to route the data stream returned from the server into createImage(), and it generates the Image object for you.
For the more common case, the mobile phone lacks GIF decoder
support, so you must invoke the homemade GIF decoder class (call it GifDecoder)
to handle the chore. You’d therefore make an instance of the GIF
decoder object, supply it with the data stream, and invoke one of GifDecode‘s methods (call it read()) to read and decode the data. Another method (getImage()) then extracts the converted image and returns it in an Image object. Listing Two shows the code that tests the state of the gifSupported flag and calls the appropriate method.
Undertaking the Port
Now all that was left to do was to close that gap in Listing Two
by writing the GIF decoder class. Seasoned programmers don’t want to
reinvent the wheel if they can help it, particularly because the odds
were good that a Java-based GIF decoder already existed. Therefore, I
immediately Googled to see what code was available on the Internet.
Unfortunately, Sturgeon’s Law applies equally to Internet content: 90
percent of what the search turned up was junk. Still, the remaining 10
percent appeared promising and could be sifted through quickly.
I soon happened upon a lightweight Java-based GIF decoder written by Kevin Weiner, from FM Software. His decoder class, GifDecoder,
has a small resource footprint, provides LZW decompression, and offers
methods that can read an image either from a data stream or from a file
(http://www.fmsware.com/stuff/gif.html). The decoder could even parse
animated GIF files and methods were provided to help animate such
images. In addition, Kevin offered the source code to the community at
large, with no stringent copyright conditions. GifDecoder was written
for the J2SE platform, but porting this class to J2ME appeared
manageable.
The port of GifDecoder was relatively painless, thanks in large
part to the fact that J2ME’s capabilities are identical to J2SE in many
areas. The largest variations between the two Java platforms lie in
their GUI classes. Because GifDecoder processes data behind the scenes
for other objects, this averted dealing with the GUI differences.
I started by commenting out those GifDecoder methods I didn’t need for my project. I eliminated GifDecoder’s file read()
method because my application receives its images over the air through
an HTTP connection. I would have handled this differently if the target
phones used either the Symbian OS or the JSR 75 FileConnection API,
both of which provide file access. However, I did modify the file read()
method to retrieve data from MIDlet resources. Resources are part of
the MIDlet’s archive file, and often store the graphic images the
MIDlet uses to draw its GUI.
Next, I changed all instances of BufferedInputStream to DataInputStream. The BufferedInputStream class isn’t available on J2ME, and DataInputStream‘s
capabilities were adequate for the job. I did spend some time
struggling with getting the code to read the GIF file’s color tables. I
tracked this down to a bug where, when reading an HTTP stream, DataInputStream‘s read()
method can throw an exception if the amount of bytes for the requested
read is larger than the stream buffer’s size (255 bytes). The
workaround was to write a loop that used readByte() to fetch the color table, byte by byte.
Another modification was to replace Image‘s getRaster() J2SE method with a pair of MIDP 2.0 methods, getRGB() and createRGBImage(). J2SE’s getRaster(), in one swoop, converts the decoded array of RGB pixels that comprise the picture into the Image object’s native format. Because J2ME lacks getRaster(), the data translation became a two-step process. The getRGB() call converts the decoded GIF pixels into integers, and the subsequent call to createRGBImage() translates the integer array into the Image format.
GifDecoder is also able to read GIF files that contain multiple
images for animation, plus any control blocks that contain a delay
interval associated with each image. See Figure 2 for the structure of an animated GIF file. On J2SE, GifDecoder stores the images and delay values using the ArrayList class. ArrayList isn’t implemented on J2ME, but its superclass, Vector, is. So I modified all appearances of ArrayList to use Vector, and rewrote the code to use Vector’s methods.
The entire process didn’t take long, and I had an example GIF
appearing on the phone’s screen within a few days. The bulk of that
time was spent tracking down and identifying the read() bug.
Using the GIF Decoder
Adding the GIF decoder in a mobile application is easy: Just
include the GifDecoder.java file along with the MIDlet’s source files.
In your application code, make an instance of GifDecoder, then use the
appropriate methods to read and display the GIF images. Table 1 documents the methods available. Listing Two shows how it’s done. Once you’ve made an instance of GifDecoder, you invoke the appropriate read()
method, depending upon whether you obtained the GIF file from a data
stream or are reading a MIDlet resource. If the read completes without
errors, then you call getImage(), which returns the GIF picture in an Image object. If getImage() is applied to an animated GIF file, this method returns the first image in the file.
Presenting an animated GIF is relatively straightforward, thanks to the utility methods GifDecoder provides. You first use getFrameCount()
to fetch the value that specifies how many images (or frames) make up
the animation. This becomes the termination value for a loop counter.
Within the loop body, you pass the loop’s index as an argument to both getDelay() and getFrame() to obtain each frame’s delay time and its corresponding image, respectively.
The delay values represent time intervals in hundredths of a
second. Because most computers and cellphones have timers with
millisecond resolution, getDelay() multiplies the value it obtains by 10 to convert the interval value into milliseconds. The result from getDelay() can therefore be jammed straight into J2ME’s Thread.sleep()
method to implement the delay. GifDecoder supports the drawing options
specified in the GIF file’s graphic-control extension blocks. It also
supports a transparent color, although the phone’s J2ME implementation
can affect this feature’s behavior.
You’ll execute the GIF animation code in a separate thread, either as part of a Thread class or as part of another class’s run()
method. This is done so that the MIDlet’s UI—which runs in another
thread—is free to respond to any events that the user generates. Listing Three shows how to use a run() method to read and display the images in an animated GIF file. Figure 3 shows how the successive images in a GIF can present the animation of a UFO. The serial serviceRepaints()
method acts as a simple synchronization mechanism in that it ensures
that the image is drawn completely before the next delay interval and
image are fetched and displayed. An example MIDlet, GIF_Display,
is available for download, and it displays several animated GIF images.
Note that a phone that natively supports the GIF format may only
display static images, not animated ones.
When compiled, GifDecoder is only 11 KB in size, suitable for use
in any MIDlet that has special graphics needs. Note that this size is
before an obfusticator is applied to GifDecoder to further reduce its
code footprint. The downside to using GifDecoder is that large and
lengthy image animations can consume a lot of memory. An animation that
consists of 24 144×52-pixel images can easily consume around 60 KB of
memory. On a phone with 256 KB of working memory, that consumes over 20
percent of available memory just to support a graphic. You can reduce
the animation’s memory footprint by using smaller images and fewer of
them.
Conclusion
While a GIF image has a limited color palette, it’s often adequate
for most graphics purposes, including the display of some photos. In
exchange for the limited color range, GIF images require less storage
than JPEG images. In addition, there are plenty of GIF editing
animation tools that allow you to easily add your own graphics and
animations for addition to a MIDlet’s UI. The ability to read, display,
and animate GIF images that appear on many Internet sites is an
important asset to have for any MIDlet performing network tasks.
GifDecoder is thus a valuable tool to have in your J2ME programming
toolbox.
DDJ
private final String GIF_MIME_TYPE = "image/gif";
private boolean gifSupported; // Get the media types to check for support of GIF file display
mediaTypes = Manager.getSupportedContentTypes(null);
int count = mediaTypes.length; // Check list for GIF MIME type; set support flag if present
gifSupported = false;
for (int i = 0; i < count; i++) {
if (mediaTypes[i] == GIF_MIME_TYPE)
gifSupported = true;
} // end for
String url = "http://www.nosuchsite.net/StarTrek/enterprise.gif";
HttpConnection hC
= null;DataInputStream dS = null;
Image mapImage
= null;// Open the connection as an HTTPConnection; send request
try {
hC = (HttpConnection) Connector.open(url);
hC.setRequestMethod(HttpConnection.GET);
hC.setRequestProperty("IF-Modified-Since",
"10 Nov 2000 17:29:12 GMT");
hC.setRequestProperty("User-Agent","Profile/MIDP-2.0
Configuration/CLDC–1.1");
hC.setRequestProperty("Content-Language", "en-CA");
} catch (IOException e) { } // running without safety net! // Read the data stream for the returned GIF image
int iByteCount;
iByteCount = (int)hC.getLength();
dS = hC.openDataInputStream();
// Does J2ME implementation support native GIF format decode?
if (gifSupported) {
mapImage = Image.createImage(dS); // Yes, translate data
// into an Image
} else {
// No, do it ourselves: get instance of GIF decoder and decode stream
GifDecoder d = new GifDecoder();
if (d != null) {
int err == d.read(dS);
if (err == READ_OK) {
mapImage = d.getImage();
} //end if
} end if
} // end else
public void run() {
int t;
if (gifImage != null) {
while (action) {
int n = d.getFrameCount(); // Get # of frames
for (int i = 0; i < n; i++) { // Loop through all
gifImage = d.getFrame(i); // Get frame i
// Delay duration for frame i in milliseconds
t = d.getDelay(i); // Get frame’s delay
repaint();
serviceRepaints();
try {
Thread.sleep(t); // Delay as directed
} catch (Exception ex){}
} // end for
} // end while
} // end if
} // end run
Displaying GIF Images on J2ME Mobile Phones
Figure 1: GIF map output by the Tiger server.
Displaying GIF Images on J2ME Mobile Phones
Figure 2: Format of a GIF89a animated image file.
Displaying GIF Images on J2ME Mobile Phones
Figure 3: How an animated GIF image is displayed.
Displaying GIF Images on J2ME Mobile Phones
Result | Constructor/Method | Description |
Constructor | ||
GifDecoder | GifDecoder(void) | Constructor that generates an instance of the GIF decoder object. |
Methods | ||
int | getDelay(int frameNumber) | Returns the delay time, in milliseconds, associated with the image specified by frameNumber. |
Image | getFrame(int frameNumber) | Returns the image specified by frameNumber. |
int | getFrameCount(void) | Returns the number of images (frames) in an animated GIF file. |
Image | getImage(void) | Returns the first image in an animated GIF file. |
int | getLoopCount(void) | Returns a value that represents the times the animation should loop. |
int | read(DataInputStream is) | Reads the referenced DataInputStream object. Returns a status code of zero if no errors occurred during the read. |
int | read(InputStream is) | Reads the referenced InputStream object. Returns a status code of zero if no errors occurred during the read. |
int | read(String name) | Reads the named resource in the MIDlet’s JAR file. Returns a status code of zero if no errors occurred during the read. |
Table 1: GifDecoder class and methods.