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
|