Il
mese precedente abbiamo visto quello che sarà il programma del corso
ed abbiamo iniziato a vedere i modelli di scomposizione dei colori delle
immagini RGB e HSB. Questo mese vedremo come si carica un'immagine e come
si utilizza creando qualche piccola animazione.
Come
visto le immagini non sono altro che un insieme di colori posizionati in
maniera opportuna. Questo concetto sembra stupido ma è alla base
della gestione delle immagini in Java che ci accingiamo ad affrontare.
Java, attraverso il JDK, permette la gestione di due tipi di file immagini,
GIF e JPEG, anche se, come vedremo nella prossima puntata, sara' possibile
elaborare altri formati. Il GIF (Graphics Interchange Format) è
di proprietà della Compuserve. Essa utilizza un metodo di compressione
che permette di rappresentare immagini grandi in file piccoli (LZW). Il
JPEG (Joint Photographic Experts Group) è uno standard internazionale
utilizzato principalmente per materiale fotografico. Il metodo di compressione
del JPEG utilizza la coseno-trasformata discreta (DCT) per rimuovere quello
che l'occhio umano non riesce a vedere in maniera da ridurre il numero
dei dati memorizzati. Si dice che la coseno-trasformata è "lossy"
in quanto con essa, si ha perdita di informazione. Il metodo LZW si dice
"lossless" cioè senza perdite. La DCT rimuove quei particolari che
non sono visibili all'occhio umano per cui non si ha necessita' di memorizzazione.
Il metodo LZW (Lempel Ziv Welch) consiste nella creazione di quello che
si chiama un alfabeto esteso. Esso contiene, oltre ad i caratteri normali,
anche le sequenze di caratteri. Per ognuno di questi si utilizzano almeno
9 bit da cui l'alfabeto sarà di almeno 2^9 caratteri. Il metodo
LZW si basa su di un principio di memorizzazione delle sequenze di caratteri
come nuovi caratteri. Ad ogni ripresentazione della sequenza viene associato
un solo carattere. Ad esempio la sequenza AAABBAACCAACC permetterà
la creazione di caratteri corrispondenti alla tripla A, alle doppia A,
B e C da cui si avranno 6 caratteri di 9 bit invece di 13 da 8, con un
risparmio di 13*8-6*9=54 bit che rappresenta quasi il 50%. Il metodo JPEG
sfrutta una caratteristica dell'occhio umano. Abbiamo visto come le immagini
(o meglio i colori che le compongono) possano essere scomposte in varie
componenti. Se scomponiamo una immagine nelle componenti YCrCb cioè
in una componente di luminanza (Y) e nelle due di crominanza (Cr e Cb)
si ha un fatto particolare (questa scomposizione è quella che si
ha nella trasmissioni televisive PAL). L'occhio umano è, infatti,
più sensibile alle variazioni di luminosità rispetto a quelle
di colore. Il metodo di compressione JPEG non fa altro che diminuire la
risoluzione delle componenti di crominanza introducendo delle perdite ma
prive di importanza. E' un modo per non memorizzare le cose che non ci
interessano perchè non visibili. Per quello che riguarda le componenti
YCrCb sono legate da regole molto semplici a quelle RGB.
Y =0.299R +
0.587G + 0.114B
Cb=-0.169R -0.331G
+ 0.5B + 128
Cr =0.5R -0.419G
-0.081B + 128
Dopo
questa breve digressione sulle tecniche di compressione delle immagini
torniamo a Java. Come prima cosa diciamo che una immagine è caratterizzata
da un oggetto di tipo Image del package java.awt. Per caricare una immagine
si possono utilizzare i seguenti due metodi:
Il
primo metodo può essere utilizzato solamente in un applet in quanto
è ereditato dalla classe java.applet.Applet da cui ogni applet deriva.
Il secondo metodo può essere utilizzato sia negli applet che nelle
applicazioni. Consiglio di utilizzare il primo metodo negli applet ed il
secondo nelle applicazioni. Infatti, in un applet è inutile, salvo
applicazioni particolari che richiedano un utilizzo del Toolkit, ricorrere
a tale metodo. Nelle applicazioni consiglio il secondo metodo anche perchè
è l'unico possibile!!. Un cosa importante è il fatto che
i metodi getImage ritornano subito senza attendere il caricamento dell'immagine
corrispondente. Per caricare l'immagine bisogna visualizzarla. Questo procedimento
di gestione delle immagini è stato fatto per non appesantire la
memoria del calcolatore. In un applet, per esempio, è inutile caricare
un'immagine se non la si visualizza. La classe Applet fornisce due versioni
per il metodo getImage le cui firme sono:
public
Image getImage( URL indirizzoimg );
public
Image getImage( URL indir_base, String nomefile );
Il
secondo metodo è il piu' conveniente se si utilizzano immagini residenti
sul proprio server. E' cosa utile e ormai diffusa mettere le immagini in
un subdirectory \images nel directory in cui vi è la pagina HTML
oppure la classe .class. Nel caso in cui si mettano le immagini relativamente
al direttorio in cui vi è il codice dell'applet si utilizza il metodo
getCodeBase(), altrimenti nel caso in cui il riferimento sia la pagina
HTML che contiene l'applet si utilizza il metodo getDocumentBase(). Questi
metodi appartengono alla classe java.applet.Applet e forniscono l'URL corrispondente,
rispettivamente, al file .class ed al file .htm o .html. Nel caso si mettano
le immagini nella subdirectory \images del codice si scriverà:
Image immagine
= getImage( getCodeBase(),"images/"+<nomefile>);
Analogamente nel
caso del getDocumentBase() se il subdirectory è relativo a quello
in cui c'è il file HTML:
Image
immagine = getImage(getDocumentBase(),"images/"+<nomefile>);
Anche la classe
java.awt.Toolkit dispone i due metodi getImage():
Notiamo subito come
qui sia possibile caricare l'immagine a partire dal nome del file dell'immagine.
Questo nel caso dell'applet, non è possibile per i noti motivi di
sicurezza in quanto attraverso il solo nome si esprime solo un file locale.
Una volta che
è stato instanziato un oggetto Image non resta che visualizzare
l'immagine corrispondente. Per fare questo si utilizza il seguente metodo
della classe Graphics:
g.drawImage(immagine,
x, y, this);
La
variabile immagine è ovviamente l'oggetto Image da visualizzare.
Le variabili intere x e y rappresentano le coordinate del vertice superiore
sinistro dell'immagine nel sistema costituito dal componente che dovrà
contenere l'immagine stessa. Una cosa importante è rappresentata
dall'ultimo parametro this. Esso è riferito ad un oggetto ImageObserver.
Sappiamo che ImageObserver è una interfaccia del package java.awt.image
la quale è implementata da ogni oggetto che deriva dalla classe
Component tra cui l'Applet. Un oggetto definito di un tipo interfaccia
rappresenta una qualunque instanza di una classe che implementa l'interfaccia
stessa. Il this, a questo punto, risulta chiaro se il metodo drawImage
è inserito nella paint() di un applet. L'interfaccia ImageObserver
sarà esaminata nella prossima puntata, per ora vediamo velocemente
le altre firme dei metodi drawImage della classe Graphics.
Questa
permette di specificare il colore di background
public
boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver
observer);
Queste
permettono di specificare le dimensioni dell'immagine. Se queste ultime
sono diverse da quelle originali, l'immagine viene automaticamente ingrandita
o rimpicciolita.
public
boolean drawImage(Image img, int x, int y, int width, int height, Color
bgcolor, ImageObserver observer);
public
boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver
observer);
Fino
ad ora abbiamo imparato a creare un oggetto Image ed a visualizzarlo. Come
detto l'immagine viene effettivamente caricata all'atto della visualizzazione
e non alla creazione dell'oggetto Image. Talvolta vi è la necessità
di caricare l'immagine prima di visualizzarla. Se volessimo conoscere,
per esempio, le dimensioni dell'immagine dobbiamo prima caricarla e non
vogliamo necessariamente visualizzarla per conoscere tali informazioni.
Infatti, se usiamo i metodi getHeight() e getWidth() della classe Image
solo dopo la dichiarazione dell'oggetto Image stesso, si ha -1 in entrambi
i casi. Infatti per conoscere le dimensioni dell'immagine bisogna caricarla.
Per fare questo il JDK mette a disposizione la classe java.awt.MediaTracker.
Essa sfrutta un concetto che riguarda l'interfaccia ImageObserver che vedremo
il prossimo mese. Questa classe permette di caricare l'immagine senza il
bisogno di visualizzarla. Vediamo come si fa. Supponiamo di essere nella
init() di un applet. Per prima cosa definiamo l'oggetto img di tipo Image.
Supponiamo di leggere il file figura.jpg
Image
img=getImage(getCodeBase(),"images/"+"figura.jpg");
Ora si definisce
l'oggetto MediaTracker il quale ha come argomento un oggetto di tipo Component
come è un Applet. La cosa importante, come vedremo, è il
fatto che un Component implementa la interfaccia ImageObserver. Si ha quindi:
MediaTracker
mt = new MediaTracker(this);
Il this è
relativo all'applet che è un Component e quindi implementa ImageObserver.
Attraverso questa
istruzione diciamo all'oggetto MediaTracker di considerare l'immagine img.
Ad essa viene associato anche un valore intero che sarà una specie
di identificatore dell'immagine nel MediaTracker. Per caricare effettivamente
l'immagine basterà scrivere:
try{
mt.waitForID(0);
}
catch
(InterruptedExceptionie){
System.out.println("Ho finito di caricare");
}//
Fine catch
Il metodo waitForID(0)
permette di attendere fino a che l'immagine associata al valore 0 sia completamente
caricata. Nel caso di più immagini basterà associare diversi
valori per ciascuna ed attendere che ciascuna sia caricata. Per fare questo
si puo' utilizzare anche il metodo waitForAll() che attende che tutte le
immagini registrate nel MediaTracker siano caricate.
Ora
vediamo un esempio completo di un applet che visualizza delle animazioni.
Questo applet è molto semplice e permette il caricamento di 3 immagini
e la visualizzazione alternata delle stesse. Notiamo che attraverso l'utilizzo
del MediaTracker tutte le immagini saranno disponibili contemporaneamente
in quanto saranno visualizzate solo se tutte e tre caricate.Il risultato
ottenibile con questo semplice applet lo si può osservare nella
animazione in funzione accanto al titolo. Il codice Java invece è
qui.
Per
questo mese abbiamo detto abbastanza. Il prossimo parleremo delle famose
interfaccie relative alla gestione delle immagini vedendo cosa fa effettivamente
il MediaTracker e incontraremo quella che è la gestione dei modelli
cioè la classe ColorModel.