Open letter to MIDP standard mantainers and hardware manufacturers from Anfy Team Wireless Java™ development section.




        MIDP 1.1+ SUGGESTED IMPROVEMENTS by Anfy Team

            document version 1.3, August 6, 2001


Introduction:

Anfy Team produce multimedia and graphically intensite Java applets
since early 1996, and have previous experience on old 8bit computers
with constrained resources (64kb ram, 2Mhz processors).
We found the MIDP 1.0 specifications is missing important features
for videogaming and multimedia: in hour hopinion, with different gfx
API interface we may 2x up to 10x speed-up a wide range of multimedia
applications and videogames, using same hardware (and same costs).
Here's Anfy Team tech suggestion, for future midp enhancements,
and/or for com.* extra packages from manufacturers.


********************************************************
 Image Transparency support
********************************************************

1) Image transparency must be supported, following the PNG
   transparency, or ALPHA informations in 0xAARRGGBB int.
   Alpha byte AA, where at least 2 states are supported:
   FF for solid and 00 for transparent, or use bit 31 
   (1 for an opaque pixel, 0 for a transparent pixel),
   with bits 24-30 ignored (00 - 7F is transparent,
   80 - FF is opaque.)
   In japanese NTT-docomo i-appli's the drawImage could
   paint with transparency and this permits "sprites"
   with circular contours rather than the overlapping
   rectangles of MIDP 1.0 non-transparency.
   Results similiar to transparent image drawing can be
   obtained with multiple clipping but this is quite
   a complex to program and not optimized operation.

   This could be implemented adding transparency supp.
   in standard drawImage(Image). Or to mantain full
   compatibility with MIDP 1.0 (standard Image object
   does not consider transparency even if it's present),
   adding a special com.manufacturer.Image object able
   to retain transparency mask information, and then add
   a new draw method variant such as:
   drawImage(com.manufacturer.Image)
   which accepts such transparent-enabled image and
   plots it eventually using the transparency(alpha).
   Separated Image/drawImage() implementations for
   transparent-non transparent plotting may let
   optimize memory/KVM usage (alpha mask stored and
   parsed only if special Image/drawImage() is used).
   It's important to implement such method in native
   code, and to store image internally in native
   format, in order to plot it with transparency
   (or even without it) as fast as possible, to
   permit fluid animation of many moving images.

********************************************************
 RGB Image content access in both Read and Write
********************************************************

2) A method to create images from non gif/png encoded
   int[] arrays with raw 0x00RRGGBB data is missing.
   This is foundamental for paint programs, some
   video games effects and optimizations, and other.
   We suggest adding such method:

    public static Image createImage(int[] rgbData, int offset,
            int width, int height) throws ArrayIndexOutOfBoundsException

   Explanation of parameters:

   int[] rgbData: an array with 0x00RRGGBB pixel informations.
   If image transparency is supported, 0xAARRGGBB.

   int offset: offset to start reading data from within rgbData[]

   int width, int height: width and height of returned Image.
   If (width*height)>(rgbData[]lenght-offset): ArrayIndexOutOfBoundsException

   Better would be to have a variant (or just this):

    public static Image createImage(int[] rgbData, int offset,
            int width, int height, int scanline)
            throws ArrayIndexOutOfBoundsException

  Why add the "scanline" parameter too?
  The method will behave that way: will read "width" pixels
  starting from "offset", then rather than increment by "width"
  again, it will increment by "scanline".
  Function will have same behaviour if width=scanline, but
  may help in tricks like scrolling or copying part of images.
  This, because we can have in the array (memory) a screen
  bigger than the real one, but create images from
  a (variable/scrollable) horizontal area, leaving
  the extra horiz part (width-scanline) hidden.

    Another method which may help while dealing
    with generation of (multiple) images starting from
    a single one with only parts changed, could be:

    public static void editImage(Image img, int[] rgbData, int imgOffset,
            int dataOffset) throws ArrayIndexOutOfBoundsException

    This will edit an already instantiated Image, overwriting
    parts (or whole) of it's contents with RGB pixels described
    in rgbData[] array. If a version with w,h,scanline is
    also provided, this may permit easier update of rectangles
    which are not 100% image wide.
    Also, it's possible to only make available an editImage()
    method passing a previously created Image, rather than
    having both createImage() and editImage() variants.
    Having only a createImage() which return a new Image
    is not suitable instead to make multiple editing of an
    image, because we would create a new Image object all
    the times and this consume memory.

- Note on animation:

On Java2 the methods are run in multithread and
the end of method execution should be tracked
with mediaTracker or imageProducers; that's a
too much complex system; however, it should be
possible to know if Image.setArray() "completed",
before use the image again, or we can see on
video the image half completed.
Especially, in animation, the dynamic update
of the Image contents should be "synchronized" so,
we see only completed frames.
On Java, there's the setAnimated(true) method,
to set on an Image, then a newPixels() is called
each time.
Double buffer is also useful but it's another matter.
We should make sure createImage() or editImage()
does not return until the operation is done, if
no other mediatracker-like methods are available.
So, if the array[] with image contents may be changed
(updated for animation), we don't have to create a
new image or array, garbage collect the old ones
and such things: we simply overwrite the image
array, and call the Image.editImage() specifying
the same array all the times, with an implicit
"newPixels()" which make sure all the times we
plot the image, latest content is 100% done and
displayable, still using only 1 Image object
and 1 rgb array for maximum memory and System.gc()
optimization.

An example of how the code could look like
to make a dynamically animated image:

// Create the array
 int[] rgbarray = new int[w*h];

 // We write in rgbarray the first image RGB's
 writeSomethingIn(rgbarray);

 Image img=null;

// Create the image from rgbarray data:

 try {
  img = Image createImage(rgbarray, 0,w,h,w);
 }
 catch( ArrayIndexOutOfBoundsException e ) { }

 while(looping) {  // Show an animation

// Each frame we change the array contents to make
// some kind of animation

 writeSomethingIn(rgbarray);

 // We write in rgbarray the updated image RGB's
   editImage(img, rgbarray, 0, 0);  // Refresh: image = latest rgbarray content.
                           // this include a newPixels()-like call,
                           // so even if we specify the same array
                           // previously specified, it's contents
                           // are copied into the image.

 Graphics.drawImage(img);  // At this point, we should make sure the
                           // Image painted is the latest and it's
                           // completed. In Java J2SE the Image updates
                           // include Imageproducers in multithreaded
                           // environments and may be risky to plot
                           // without mediaTrackers or similiar to
                           // wait the image update completion, in
                           // MIDP this does not exist.
 }

3) A method to retrieve RGB array from an Image object,
   either loaded from a PNG or created from an array,
   like the pixelGrabber() method in Java2:

    public void getImageData(int[] rgbData, int offset,
     int x, int y, int width, int height, int scanline)
     throws ArrayIndexOutOfBoundsException

   int[] rgbData: the array where image data will be written;
                such image data may be not the same as passed
                to same image with a createImage() in case the
                device display is not 24bit: lower bits may
                be lost (set to zero) if native image format
                does not store them to save memory.
                
   int offset: offset from start of rgbData, where start writing
   int x, int y: coordinate from where start copying inside Image
   int width, int height: amount of pixels to copy=w*h
   int scanline: if == width copy continuously, otherwise this
                 may be useful to copy parts of horizontal image
                 width (copied int[] rgb rectangle w < image w).


********************************************************
 NATIVE Colors, and Image content access
********************************************************

4) As MIDP 1.0 is now, you can detect if colors or grayscales
   are present, and how much colors or grays are available:

       myDisplay = Display.getDisplay(this);
// Check if color is supported or not:
	boolean colors = myDisplay.isColor();
	numcolors = myDisplay.numColors();
	if(colors) {
		System.out.println("colors number: "+numcolors);
	}
	else {
		System.out.println("grays number: "+numcolors);
	}

   That system seems to belong to an indexed mode with
   a palette, whole instead all methods in Graphics class
   accepts only RGB values 0x00rrggbb, apparently 24bit depth.
   But, most device displays does not support natively 0xAARRGGBB
   format (supported natively mostly by PC graphic cards).
   LCD displays may have native modes with 2, 256, 4096, 65535
   colors as 1, 12, 15, 16 bits, indexed modes with fixed or
   dynamic palette, or other.
   Could be possible to obtain the "native" color value
   at startup with extra methods like:

   int Graphics.getColorOfRGB(0xrrggbb);
   int Graphics.getColorOfRGB(int r,int g,int b);
   int[] getNativeImageData()

   If native image format require only a short
   or a byte for each pixel:
   
   short Graphics.getColorOfRGB(0xrrggbb);
   byte[] getNativeImageData()

   This would even save memory.
   Then, proceed directly operating with native color
   data obtained with methods like:

  - Graphics.setNativeColor(int col);
  - Image.editImageNative()

Since some devices will finally operate natively as bitmasks
(2 colors), indexed (4-4096 colors), 15-16 bit RGB etc.,
all the graphic methods will have to execute a slow converter
algorithm for each operation. Passing instead the data
in the final format (or very similiar) will remove all
unnecessary conversions, reads/writes, extra memory needs.

Of course supporting different native formats in a
platform independent API would require dozens of extra methods
just for detecting, and entering/retrieving data's in all
the possible available configurations.
Native color/image access it's more likely to be a feature
for the various com.manufacturer.* API added to MIDP sets,
with just the native methods for their particular device.

For paletted systems some dedicated methods may be added:

  boolean Display.isDynamicPalette()
  int[] Display.getPalette()
  Display.setPalette(int[])

An example usage of native methods could be to
load a PNG image, copy it's contents into
an RGB array with getImageData(), or place in
such array a serie of colors we want to use,
then convert all:

 for(int i=0; i<w*h; i++) {
  nativearray[i] = Graphics.getColorOfRGB(rgbarray[i]);
 }

Or add a method to directly handle this job like
Image.getNativeImageData().
A problem in whole native image processing at once
may arise, in the case the image format is not
ordered like CRT ones:

 0  1  2  3  4  5
 6  7  8  9 10 11
12 13 14 15 16 17

In the case native image format is differently
ordered, the getNativeColor()/setNativeColor()
does not have problems, but get/set Image may
be not easily editable if you don't know how
pixels will be ordered.
In this case we may use a semi-native format
where the pixels ordering should be at least
assured. Or, use a com.manufacturer.* non
standard API for each vendor with specific
standard and documentation.

Some examples of native format methods to add:

 int Display.getRGBformat();

For 24 bit displays, it should return "888"
For 15 bit displays,
It may return "555", meaning color bit-mask:

111111
5432109876543210
XRRRRRGGGGGBBBBB

"565" for color bit-mask:

111111
5432109876543210
RRRRRXGGGGGBBBBB

// Detect raw/native pixels system:

   int bitsRGB = Display.bitsRGB();
   int RGBformat = Display.getRGBformat();

   if(bitsRGB!=-1) {        // RGB mode?
    if(bitsRGB==24) {
      PixelCol = colRed<<16 | colGreen<<8 | colBlue);
    }
    else if(bitsRGB==15|bitsRGB==16) {
     if (RGBformat==565) {
      PixelCol = (colRed<<11 | colGreen<<5 | colBlue);
     } else if (RGBformat==555) {
      PixelCol = (colRed<<10 | colGreen<<5 | colBlue);
    }
   }
   else {

// indexed mode

   }

Finally, for the phones with simply 2 colors, we
may add the following methods:

 Image.setBitmap(byte[] bitmask)
 byte[] Image.getBitmap()

So we specify 1 bit for each pixel: 001001110...
This is much more efficient and specific if Display.numColors()
results = 2, than using the other methods.

********************************************************
 fillPolygon() or at least fillTriangle() supported
********************************************************

5) Have a Graphics.fillPolygon(), or at least a
   Graphics.fillTriangle() method added. It's
   important to optimize the code for triangles plotting.
   In fact, most of the 3d is plotted via triangles and there
   are special drawing algorithms to speed-up the special
   case of polygons with 3 vertexes. This could also
   permit better 2D vectorial graphics like the "macromedia
   Flash" technology for html pages.


********************************************************
  Audio support (at least a "beep"!)
********************************************************

6) Pure MIDP 1.0 midlets have to be mute, silent...
Some vendors, such as ntt-docomo added an API able to play
a .MID modified subset, up to 16 voices, called .MLD.
These files are very short, usually less than 1Kb per
melody, and are sufficient to add special effects and
background musics for games and applications.
Considering MIDP resources constraints supposed for
graphics, I think playing PC formats like .WAV or MP3
definitely out of these limits.
Even if some MIDP devices will be able to play MP3
or WAV files using their special hardware, this is not
supposed to be a generic available capability.

Instead, I supposed when some audio capabilities are
present on the devices, these should all include option
of generating different tones with different play
times and pauses between tones, at least one voice.

The best formats for this are subsets of .MID in my
hopinion, similiar to the ones downloaded often from
the net to change cell phones rings. I would not require
a conversion to .MLD or other custom format however, since
.MID is a standard and could be easily subsetted by
providing a simple limitations list (voices, instruments,
and so on). This would require new Midi object and 3
methods like:

 Midi audio1 = loadMidi("123.mid"); // Load midi
 audio1.play();               // Play midi
 audio1.stop();               // Stop midi

I would also add the option of creating beeps/tones and
melodies in custom way:

 Midi audio2 = new Midi();    // Create empty midi
 audio2.addTone(freq1(+freq2?)-or:instrument,volume,duration_in_ms)
 audio2.addSilence(duration_in_ms)
 audio2.addTone(freq1(+freq2?)-or:instrument,volume,duration_in_ms)
 ...

This could allow to create music/audio FX's from algorithms
too, and permits to make "piano" or audio editor midlets.
Having the opposite function (get tones+silences from .MID)
would even allow to load MID's and edit them or compose
longer MID's from smaller MID pieces and custom tones:

// Load 2 mid
 Midi audio1 = loadMidi("123.mid");
 Midi audio2 = loadMidi("456.mid");

// get the 2 mid tones into int[] arrays
 int[] tones1 = audio1.getTones()
 int[] tones2 = audio2.getTones()

 Midi audio3 = new Midi();    // Create empty midi

// Copy all or part of audio1 melody into audio3
 for(int i=0; i<tones1.length; i+=4) {
   audio3.addTone(tones1[i],tones1[i+1],tones1[i+2]):
   audio3.addSilence(tones1[i+3]):
 }

// Add custom tones to audio3
 audio3.addTone(freq1(+freq2?)-or:instrument,volume,duration_in_ms)
 audio3.addSilence(duration_in_ms)

// Copy all or part of audio2 melody into audio3
 for(int i=0; i<tones2.length; i+=4) {
   audio3.addTone(tones2[i],tones2[i+1],tones2[i+2]):
   audio3.addSilence(tones2[i+3]):
 }

We have now a playable audio3 melody made by part
of audio1, audio2 and custom added tones.
Additionally, some detect methods are required:

 boolean hasAudio();  // false = silent device
 int audioVoices();   // How much concurrent voices are playable
 int synthQuality()   // Some parameter to detect max
                         capabilities of audio wave generation,

Maybe min/max volume defaults of various devices may
vary, so a "middle" volume setting in MID's for a game
melody may be too high or noisy on some devices; setting
the "general" midlet volume should be possible too:

  setVolume(int volume)

This should multiply or divide the volumes of all the
tones in all the MID's.

If some devices can't even play single voice, tone based
beeps defined in a .MID subset, but instead for example
only 4 "fixed" predefined tones, we may add these methods:

 boolean onlyDefTones();  // return true if no custom
                              tones can be played

 playOk();       // Play the tone for "OK" operations
 playCancel();   // Play the tone for "CANCEL" operations
 playAlarm();    // Play the alarm ringing
 stopAlarm();    // Stop the alarm ringing
 ...  // Others

These generic playSomething() methods will be of course
available also if onlyDefTones()==false, as long as
hasAudio()==true.

When coming to playing waveforms (recorded audio), things
become more complex.
Audio waveforms have different bit depths (4,8,16 or more),
and Hz frequency, and may be mono or stereo.
While the CD quality is 16 bit, 44.1 KHz, stereo, we have
probably to target lower quality audio, mostly due to memory
space occupied by such formats (despite replay quality which
could be obtained).
I suggest 8bit, 8Khz mono, like the Sun .au which applets
can play, with addition of 4bit, 4Khz subset, or intermediate
8bit, 4Khz and/or 4bit/8Khz. The .au format looks ok, it's
a political matter if support .WAV, .AIFF or other.
We may support RAW 4bit/8Khz audio files too: audio is not
much compressed (except loss-quality algorithms such as
mp3, real media or wma), and so we may simply place raw
audio waveforms in the .jar file and benefit from the .jar
compression which is only a bit less efficient than .au
or .wav over audio type binaries. This save the extra
buffer memory and decompression work in exchange of a
small % of extra space of jar file (may be 5%? Someone
can make tests on this).
Methods to add may be:

 AudioClip clip1 = loadAClip("audio1.raw");
 clip1.play();
 clip1.stop();

Extra detection methods may be:

 boolean playClips();  // false = no audioclip play capability

Replay of different bit depths, Khz, and stereo support
may be covered and detected at startup with extra methods,
if required.



Fabio Ciucci
Anfy Team company CEO

Andrea Fasce
Anfy Team company CTO


www.anfyteam.com 

Copyright © 2000/2001 Anfy Team di Fabio Ciucci. Java™ and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
Fabio Ciucci is independent of Sun Microsystems, Inc.