MokaByte
Numero 07 - Aprile 1997
|
|||
|
|
||
Massimo Carli |
Puntata numero 5 | ||
Dopo la puntata relativa alla lettura di una immagine di formato diverso da quelli JPG e GIF (ricordiamo che si possono leggere anche quelli in formato GIF89a, le famose GIF animate), vediamo ora di realizzare dei filtri grafici in Java.
Prima cosa vediamo quale potrebbe essere l'utilità di tali filtri. Come sappiamo uno degli aspetti più importanti nel caso di applet è il tempo che essi impiegano ad essere scaricati dal browser. Il tempo aumenta nel caso in cui si debbano trasferire anche delle immagini. La regola è quella, quindi, di limitare il più possibile tali tempi. La Sun ci è venuta incontro creando la possibilità di costruire files Jar che sappiamo comprimere sia le classi che le immagini ed il suono in un solo file. Questo ha il vantaggio di permettere il trasferimento degli oggetti in una unica sessione oltre al fatto di comprimere gli stessi secondo le specifiche classiche dello ZIP. Non per questo dobbiamo approfittarne e riempire gli applet di immagini da scaricare, specialmente se le immagini scaricabili sono legate tra loro da somiglianze di vario tipo. Si cerca, quindi, di elaborare le immagini presenti nel browser piuttosto che scaricarne di nuove. Un esempio può essere quello di un bottone. Se ci siamo costruiti un pulsante con una immagine (attraverso un Canvas ecc.) vogliamo che l'immagine dia l'impressione del fatto che il pulsante stesso sia premuto oppure no. Per fare questo basterà rendere l'immagine un po' più scura. Questo sarà possibile attraverso un filtro piuttosto che scaricando una nuova immagine. Ovviamente anche qui si ha a che fare con quello che si chiama trade-off cioè con il fatto che se ci si guadagna da una parte ci si perde dall'altra. Lo svantaggio, in questo caso, sta nel fatto che si da un numero maggiore di elaborazioni all'applet ed il risultato non sarà bello come nel caso in cui si costruiscano le varie fasi del pulsante una ad una.
L'uso di filtri non è comunque consigliato solo nei casi precedentemente descritti. Ci sono applicazioni in cui l'uso di filtri di immagini è l'unica soluzione. Per esempio nel caso in cui si vogliano creare vari effetti applicando un filtro più volte ad una immagine. Sarebbe assurdo pensare al caricamento di 100 immagini attraverso la rete.
Qualcuno
di voi potrebbe dire che non è necessario creare dei filtri per
elaborare delle immagini. Per esempio, basterà gestire opportunamente
le classi incontrate nelle puntate precedenti: MemoryImageSource
o PixelGrabber. La cosa potrebbe anche essere vera, ma dobbiamo
sempre ricordare che Java è un linguaggio Object Oriented.
Uno dei fondamentali
vantaggi dell'Object Orienting è il riutilizzo del software. Si
potrebbe creare un applet che data un'immagine ne fa quello che vuole trasformandola
in vettore di pixel e poi ricreandone un'altra. Questa è cosa semplice.
Ma cosa succede se si volessero fare diverse elaborazioni dell'immagine.
Bisognerebbe creare vari metodi della classe ciascuno per ogni elaborazione.
Fin qui tutto bene, ma cosa succede se dobbiamo creare un altro applet
che utilizzi gli stessi effetti? Basterà fare un cut&paste dei
metodi, ma la cosa non è certo delle migliori quando si ha a che
fare con un linguaggio OO. Si potrebbe creare una classe di utilità
con tutte le varie operazioni. Va beh. Ma cosa succede se dobbiamo aggiungere
un nuovo effetto? Dobbiamo aggiungere un nuovo metodo e rimettere mano
alla classe di utilità. Se si ha ache fare con l'OO deve essere
limitato al massimo il "rimettere mano". Se l'oggetto, poi, è serializzabile
e lo avete salvato avreste addirittura problemi di versione e non riuscireste
più a ripritinarlo se non con una accorta gestione del versioning.
La soluzione naturale è quella di creare una classe per ogni effetto
o filtro. Se dobbiamo creare un nuovo effetto creeremo una nuova classe
che fungerà, come vedremo, da filtro. Gli effetti, o filtri, precedenti
non si toccano più. Cosa fondamentale, l'uso dei filtri, come vedremo,
è cosa semplicissima.
Cosa
è un filtro in Java
Nel package
java.awt.image esistono alcune classi che permetono la creazione di filtri.
Prima di vedere tali classi vediamo come, dato un filtro, si crea l'immagine
filtrata. Il procedimento è simile a quello utilizzato per la classe
MemoryImageSource. Sappiamo che essa è una classe ImageProducer
utilizzata poi nella createImage per la creazione dell'immagine stessa.
Nel caso dei filtri esiste la classe FilteredImageSource che funziona in
modo analogo alla MemoryImageSource. Anche FilteredImageSource è
infatti un ImageProducer. Supponiamo di avere una immagine img da filtrare.
Image img = getImage("esempio.gif");Nella prima riga prendiamo l'immagine da elaborare. Nella seconda creiamo un oggetto filtro che sarà appunto il filtro che andremo ad applicare. Dal filtro e dal Producer dell'immagine ottenuto con il metodo getSource(), creiamo un ImageProducer per l'immagine filtrata che poi andremo a creare.ImageFilter filtro = new RGBImageFilter();
FilteredImageSource fis = new FilteredImageSource(img.getSource(),filtro);
Image fil_img = createImage(fis);
Una
volta visto come, dato il filtro, si crei la versione filtrata di un'immagine,
vediamo come si creano e gestiscono i filtri. Come prima cosa notiamo che
la classe FilteredImageSource vuole come secondo parametro un oggetto ImageFilter.
Tutti i filtri, in Java,estendono la classe ImageFilter che implementa
l'interfaccia ImageConsumer e ci risparmia di dover definire metodi che
non ci interessano come per esempio quello del settaggio delle proprietà
setProperties(). Oltre ai metodi previsti dalla ImageConsumer sono presenti
un metodo clone() per l'interfaccia Cloneable ed i metodi getFilteredInstance()
e resendTopDownLeftRight(ImageProducer).
Il funzionamento
di ogni oggetto ImageFilter è legato al fatto che esso deve fungere
sia da ImageProducer che da ImageConsumer. Esso deve essere ImageConsumerImageProducer
per l'immagine filtrata.
Ricordiamo che
il procedimento nelle chiamate all'ImageConsumerImageConsumer.
Inizialmente l'ImageConsumer acquisisce le dimensioni dell'immagine sorgente attraverso la chiamata, da parte dell'ImageProducer, del metodo setDimension(). Dopo questo, l'ImageConsumer si aspetta l'insieme delle informazioni relative ai pixel. E' possibile, però, definire anche quale sarà il modello di colore utilizzato cioè il significato dei valori che saranno forniti come dati relativi ai pixel. Questo potrà essere fatto dalla setColorModel() se il ColorModel no è quello di default. E' possibile poi assegnare delle proprietà all'immagine attraverso il metodo setProperties() che permette di associare una Hahtable all'immagine stessa. Un metodo importante è setHints(). Esso specifica, infatti, come i pixel saranno forniti dall'ImageProducer all'ImageConsumer. Esistono varie possibilità definite nell'interfaccia ImageConsumer e date da costanti ed espresse da un insieme di flag. Esse sono le seguenti:
1 RANDOMPIXELORDER : Non si danno informazioni riguardo al metodo di fornire i pixel.2 TOPDOWNLEFTRIGHT : Si forniscono i pixel andando dall'alto al basso e da sx verso dx
4 COMPLETESCANLINES : Si forniscono i pixel dando una riga alla volta
8 SINGLEPASS : Si forniscono i pixel dell'immagine tutti in una volta.
16 SINGLEFRAME : L'immagine è costituita da un unico frame statico
Una volta
specificato il metodo con cui l'ImageProducer fornisce le informazioni
relative ai pixel si avranno una o più chiamate al metodo setPixels()
che fornirà le informazioni relative ai pixel stessi. Esistono due
versioni di questo metodo a seconda che si vogliano fornire i dati sotto
forma di vettori di byte o di int. Notiamo che in ogni chiamata compare
il ColorModel utilizzato per cui quello fornito con il metodo setColorModel()
serve colamente come indicazione generale per cui non si hanno molti vantaggi
utilizzandolo.
Una volta che
tutti i pixel sono stati forniti l'ImageProducer chiamerà il metodo
imageComplete() dell'ImageConsumer fornendo informazioni sull'esito dell'elaborazione.
Da
quello detto si deduce che per la creazione di un filtro basterà
ridefinire alcuni dei metodi appena descritti elaborando, di volta in volta,
i pixel che vengono forniti dall'ImageProducer. Questo sarà fatto
nei metodi setPixels in quanto è qui che i dati relativi ai pixel
sono forniti.
I filtri di Java Prima di creare alcuni esempi vediamo quelli che sono i filtri forniti con il JDK. Essi sono rappresentati dalle seguenti classi:
RBGImageFilter
: Questa è una classe astratta e come tale può essere solo
estesa. Essa prevede la creazione di tutti i metodi per la realizzazione
di filtro supponendo che il modello utilizzato sia il modello di default
corrispondente alla rappresentazione RGB dei pixel. Per creare un filtro
nel modello RGB basterà estendere questa classe e definire solamente
il metodo filterRGB(int x, int y, int rgb). Questo metodo permette, infatti,
di convertire un pixel nella posizione (x,y) nel valore indicato dal parametro
rgb. Il filtro non farà altro che percorrere ad uno ad uno i pixel
forniti dall'ImageProducer e convertirli con la regola specificata nel
metodo filterRGB().
CropImageFilter
: Questa rappresenta un filtro vero e proprio nel senso che non ha bisogno
di essere estesa. Se utilizziamo questo filtro possiamo ottenere una immagine
da un'altra prendendone una parte. Infatti possiamo specificare il rettangolo
da estrarre.
AverageScaleFilter
e ReplicateScaleFilter : Queste due classi sono state introdotte nella
versione 1.1 del JDK. Esse hanno lo scopo fornire due algoritmi per creare
delle immagini scalate rispetto a quelle originali. In precedenza, infatti,
le immagini si potevano rappresentare nelle volute dimensioni attraverso
il metodo drawImage() della classe Graphics specificando le dimensioni.
Questo non era, però, molto efficente in quanto non dava la possibilità
di ottenere un oggetto distinto rappresentativo dell'immagine scalata e
perchè ciascuna versione scalata di una immagine era memorizzata
in una cache. Se l'immagine doveva essere modificata nelle dimensioni un
numero elevato di volte si aveva una occupazione di memoria elevata. Questo
doveva avere il vantaggio di una veloce visualizzazione. Ora che i calcolatori
sono molto potenti, questo non ha più bisogno di esistere per cui
si possono creare versioni scalate di una immagine anche onthefly senza
problemi. Per ovviare a questi problemi sono allora state introdotti i
due filtri citati oltre ad un nuovo metodo della classe Image createScaledImage().
Qualche semplice esempio Una volta che abbiamo visto come si utilizzano i filtri, creiamone alcuni molto semplici rimandando al mese prossimo la creazione di filtri più complicati ma sicuramente più belli. Come primi esempi utilizziamo i filtri già disponibili con il JDK RGBImageFilter ed CropImageFilter.
Iniziamo
con utilizzare un filtro RGBImageFilter. Come sappiamo questa classe non
puo' essere istanziata perchè classe astratta in quanto il metodo
filterRGB è un metodo abstract. Per creare un filtro bisognerà,
quindi, estendere la classe RGBImageFilter e definire il metodo filterRGB.
Nel nostro esempio realizziamo un filtro che estrae una delle componenti
RGB dall'immagine. Per fare questo creiamo la classe ComponentFilter. Essa
ha un unico costruttore che permette di decidere quale componente filtrare.
Nel nostro caso le componenti RGB sono assegnate rispettivamente ai valori
0, 1 e 2 del parametro tipo. La classe ComponentFilter un listato molto
semplice ComponentFilter.java
Come
si può notare la nostra classe estende la classe RGBImageFilter
e ridefinisce il metodo filterRGB testando il tipo di filtro e sovrapponendo
una opportuna maschera a ciascun valore rgb.
Nel seguente
applet ( listato AppletComponentFilter.java)
si può osservare il risultato.
Proviamo ora ad utilizzare il filtro CropImageFilter. In questo caso la classe non è astratta e rappresenta un vero e proprio filtro che permette di estrarre una immagine da un altra specificandone l'area. Facciamo un semplice applet (listato AppletCropFilter.java ) che estrae 4 parti della nostra immagine e le posiziona (nel senso di stampa e non di immagine) nelle posizioni opposte diagonalmente. Se avessimo voluto realizzare questa operazione attraverso un filtro avremmo dovuto creare un filtro estendendo la ImageFilter avendo controllo su tutti i pixel dell'immagine nel suo insieme. Vedremo come nella prossima puntata.
Conclusioni
In questa puntata
bbiamo visto come si realizza un filtro,abbiamo esaminato le classi fornite
nel JDK ed abbiamo realizzato due piccoli esempi di filtraggio. Il prossimo
mese vedremo come realizzare filtri più complessi che considerino
l'intera immagine e non i singoli pixel.
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|