MokaByte 53 - Giugno 2001
Foto dell'autore non disponibile
di
Paolo Mascia
Java e WAP
Codificare immagini in formato 
WBMP con Java
Il formato WBMP Wireless BitMap è lo standard grafico utilizzato per le immagini visualizzabili sui terminali WAP. Vediamo come è possibile, con poche istruzioni Java, codificare immagini in questo formato.


La migrazione dei contenuti delle pagine Web verso il mondo dei terminali Wap comporta degli adattamenti dettati dalle differenti specifiche del Wireless Application Protocol. Uno di questi adattamenti è il formato WBMP (Wireless BitMap) introdotto dal Wireless Application Forum come formato standard per le immagini visualizzabili sulle periferiche mobili. 
 
 
 

Il formato WBMP
Il formato WBMP è un formato molto semplice ed ancora in fase di definizione. Le informazioni relative la codifica dell'immagine (l'organizzazione dei pixel, la palette dei colori, l'animazione e la compressione) sono specificati nel primo byte, definito come campo di tipo. 
Attualmente l'unico valore dell'identificatore di tipo supportato dai terminali WAP è il tipo 0. Quest’ultimo comporta le seguenti specifiche: 

  • Nessuna compressione 
  • Profondità di colore un bit (monocromatica) 


Quando viene inizializzata una sessione con un server WAP, lo user agent riporta tutti i tipi di WBMP supportati. Questi sono comunicati usando gli header standard WSP/HTTP  Accept e Content-Type 

Esempio:

Accept: image/vnd.wap.wbmp; level=0
Content-Type: image/vnd.wap.wbmp; level=0

Stabilita la funzione del primo byte di un file WBMP, analizziamo la struttura completa di una immagine del tipo 0.
Questa è costituita da due parti: un header e un blocco dati.
L'header ha lo scopo di fornire le informazioni riguardo l'immagine da visualizzare ed è formato dai seguenti campi:

  • TypeField: 1 byte (il campo del tipo = 0)
  • FixHeaderField : 1 byte
  • ExtHeaderField : per questo formato non è richiesto alcun byte aggiuntivo 
  • Width : larghezza dell'immagine in pixel espressa in interi multi-byte
  • Height : altezza dell'immagine in pixel espressa in interi multi-byte


I parametri width ed height sono espressi in interi multi-byte. Questi ultimi consistono in una serie di byte, dove il bit più significativo ha la funzione di flag  di continuazione, usato per indicare che il byte non è l'ultimo della sequenza. 
Un intero è, così, codificato in una sequenza di N byte. I primi N-1 byte hanno il flag di continuazione a 1 mentre l'ultimo lo ha settato a zero. Gli altri 7 bit di ogni byte sono codificati nell'ordine big-endian (il bit più significativo per primo) così come i byte dell'intero. Ad esempio il valore 0xA0 è codificato con i due byte 0x81 0x20  mentre 0x60 con un unico byte 0x60.
 
 
 

Esadecimale 
Binario 
Multibyte 
0xA0 
10010000 
 1 0000001  - 0 0010000
0x60
01100000
0 1100000

L'header è seguito dai dati dell'immagine organizzati in righe di byte. Ogni bit rappresenta l'intensità del rispettivo pixel (con bianco = 1 e il nero = 0). Nei casi in cui la larghezza dell’immagine non sia divisibile per 8, ogni nuova riga  deve iniziare all'inizio del byte successivo, e tutti i bit inutilizzati devono essere settati a zero. I bytes sono disposti in un ordine big-endian. Il bit più significativo in una riga rappresenta l'intensità del pixel più a sinistra e la prima riga dei dati rappresenta la riga superiore dell'immagine. 
 
 
 

La classe WbmpEncoder
Vediamo ora come utilizzare queste informazioni per creare una immagine in formato WBMP.
Il nostro obbiettivo è quello di scrivere una classe WbmpEncoder che ci consenta di codificare un file WBMP partendo da un oggetto java.awt.Image.
La nostra classe avrà un unico costruttore del tipo WbmpEncoder(Image image) al quale passeremo l'immagine da codificare tramite il metodo encode(OutputStream out).

La prima cosa che dobbiamo fare è estrarre dall'immagine le informazioni riguardanti i singoli pixel e memorizzarli all'interno di una matrice. A tale scopo utilizziamo la classe java.awt.image.PixelGrabber : 

public class WbmpEncoder {
  private int w ; // La larghezza dell'immagine in pixel
  private int h ; // La larghezza dell'immagine in pixel

  public WbmpEncoder(Image image,int width,int height)
                      throws   Exception  {

  w = width ;
  h = height ;

  int[] pixels = new int[w*h] ; 

  PixelGrabber pg=new PixelGrabber(image,0,0,w,h,pixels,0,w); 
  try { 
    pg.grabPixels(); 
  } 
  catch (InterruptedException e) {
    throw new Exception("Interrupted waiting for pixels!");
  } 

  if ((pg.getStatus() & ImageObserver.ABORT) != 0) { 
    throw new Exception("Image fetch aborted or errored"); 
  } 

Letti i pixel dell'immagine creiamo la matrice di byte che rappresenterà il corpo dei dati del file WBMP. La larghezza della matrice di byte sarà pari alla larghezza dell'immagine in pixel diviso 8. Se la dimensione orizzontale non è divisibile per 8 dobbiamo aggiungere un ulteriore byte per i pixel in piu'.

  int w_byte = w / 8 ;
  if ((w%8)!=0) w_byte ++ ;
  byte[] data_buffer = new byte[w_byte * h] ;

A questo punto scandiamo l'immagine un pixel per volta e, quando il colore del pixel non è il nero, settiamo il corrispondente bit nella matrice dei dati a 1.

  for (int y = 0; y < h; y++) 
  { for (int x = 0; x < w; x++) 
    { int index = x + y * w ;
      int c = pixels[index] & 0x00FFFFFF; 
      if(c!=0)
       { setBit(data_buffer,w_byte,h,x,y) ; 
       } 
    }
  } 

} // Fine del costruttore

Il costruttore utilizza il metodo accessorio setBit che non fa altro che settare un singolo bit, in base alla sua posizione x e y, all'interno di una matrice di byte. 

private void setBit(byte[] matrix,int w, int h, int x, int y ) {
  int byteIndex = y * w + (x / 8) ;
  int bitIndex  = x % 8 ;
  int b      = matrix[byteIndex] ;
  int mask      = 128 >> bitIndex ;
  matrix[byteIndex] = (byte)( b | mask) ;
}

Nel costruttore abbiamo inizializzato i byte che costituiscono i dati dell'immagine, possiamo quindi salvare questi dati direttamente in uno stream facendoli precedere dall'header.

public void encode(OutputSream out) { 
  byte header[] = new byte[2] ;
  //
  header[0] = 0x0   ;   // TypeField = 0 
  header[1] = 0x0   ;   // FixHeaderField = 0 
  out.write(header) ; 
  //
  out.write(intToMultiByte(w)) ; // Larghezza dell'immagine 
  out.write(intToMultiByte(h)) ; // Altezza dell'immagine
  out.write(data_buffer)       ; // I dati dell'immagine 
}

Il metodo encode utilizza il metodo intToMultiByte che ritorna un vettore di byte che esprime in formato multi-byte l'intero passato come argomento. 

private byte[] intToMultiByte(int value){
int    numBytes     = value / 128  ;
  byte[] bytes = new byte[numBytes + 1] ;
  int mask = 127 ;
  for (int i=0; i<=numBytes; i++)
   { int bv = value & 127 ;
     if (i!=0) bv |= 128 ;
     bytes[numBytes-i] = (byte)bv;
     value >>= 7 ; 
   }
  return bytes ;
}

La classe WbmpEncoder è completa, vediamo quindi come utilizzarla in una applicazione pratica.
 
 

Un utilizzo pratico 
Un utilizzo pratico è quello della generazione dinamica di grafici da visualizzare sui terminali Wap. 

A tale scopo è possibile creare un  oggetto BufferedImage ed utilizzare i metodi standard della classe Graphics per disegnare direttamente all'interno dell'immagine.

.........
bufferedImage = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_ARGB) ;
graphics      = bufferedImage.createGraphics() ;

graphics.seColor(Color.black) ;

// Riempimento dello sfondo
graphics.fillRect(0,0,WIDTH,HEIGHT) ; 

graphics.setColor(Color.white) ; 
// disegno dell'asse y
graphics.drawLine(0,0,0,HEIGHT) ; 
// disegno dell'asse x
graphics.drawLine(0,HEIGHT,WIDTH,HEIGHT) ; 

.........
// Disegno del grafico
// mediante le funzioni della classe Graphics 
.........

Una volta tracciata l’immagine, è possibile salvarla in un file con le seguenti righe di codice.

// Salvataggio dell’immagine in un file 
try
{ WbmpEncoder we = new WbmpEncoder((Image)bufferedImage()) ;
  FileOutputStream out = new FileOutputStream(filename);
  we.encode(out) ;
  out.close() ;
}
catch(Exception ex)
{ .........
}
 

Conclusioni
Il formato WBMP di tipo 0 è un formato piuttosto povero, che consente la visualizzazione di piccole immagini monocromatiche. La classe WbmpEncoder, partendo da un oggetto Image, ci consente di creare in maniera semplice delle applicazioni grafiche rivolte al mondo dei terminali WAP. 
 
 
 

Bibliografia
[1] Wireless Application Forum - WAP WAE Specification 
 

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it