RotateImage Canvas
Sommario
Introduzione
Il progetto prevede l'implementazione di una classe che, data
una immagine, la ruoti di 90 gradi in 90 gradi. Per velocizzare l'operazione
creiamo 4 Image contenenti i vari "stadi" della
rotazione, anche se avremmo potuto ricalcolarli volta per volta
per risparmiare risorse.
Il codice è un buon esempio su come utilizzare le classi java.awt.image.PixelGrabber e java.awt.image.MemoryImageSource.
Indice
Demo
Ecco l'applet in funzione (nota: ho utilizzato il modello
degli eventi JDK1.1 e successivo. Se l'applet non funziona è
probabilmente dovuto al fatto che non avete un browser aggiornato
:-( ). L'immagine è del team Forum: se vi capita date un'occhiata
al loro sito perchè spaccano davvero...
 |
 |
 |
 |
Applet 1 - Clicca per ruotare |
|
 |
 |
 |
I parametri supportati dall'applet di esempio sono i seguenti:
| Nome |
Tipo
| Descrizione
|
FileName
| java.lang.String
| Immagine da visualizzare (formato GIF o JPG) |
Indice
RotateCanvas
Il canvas dovrebbe essere molto semplice da
gestire dall'esterno: lo si costruisce passandogli una Image e si
chiama il metodo rotate(void) quando si vuole ruotare l'immagine.
In seguito si potrebbe potenziare la classe, per esempio
aggiungendo un costruttore che accetti un ImageProducer oppure
implementando un metodo che ruoti l'immagine di un multiplo di 90
gradi definito da un parametro. Sono cambiamenti triviali che
lascio a voi come esercizio...
class RotateCanvas extends Canvas implements Runnable {
Come spesso nelle mie classi multithreaded,
imposto un boolean che mi dice in ogni momento se ho tutto l'occorrente
per visualizzare l'immagine o se devo ancora dire all'utente di
aspettare. In questo caso l'ho chiamato (con fantasia) boolean
done;
Nota: Stranamente il mio
browser (IE 5) non gestisce bene i Vector. L'implementazione
iniziale dell'elenco degli ActionListener era tramite un Vector
ma, dopo svariati tentativi infruttuosi, ho ripiegato su un'array
a lunghezza fissa. Ciò va bene perchè difficilmente avrete
bisogno di più di 10 ActionListener però il codice perde di
flessibilità. Di necessità virtù: guadagnamo in velocità! (magra
consolazione)...
Image[] img;
ActionListener[] al;
int countListeners = 0;
boolean done = false;
int curNum = 0;
Color sfondo;
public final static int MAX_LISTENERS = 10;
public RotateCanvas(Image image, Color sfondo) {
super();
img = new Image[4];
this.sfondo = sfondo;
this.img[0] = image;
al = new ActionListener[MAX_LISTENERS];
Thread t = new Thread(this);
t.start();
}
In run() impostiamo PixelGrabber sull'immagine
dataci nel costruttore. Forziamo con "true" all'ultimo
parametro i dati nel formato RGB per evitare problematiche dovute
al formato del file (GIF o JPG). Perdiamo in velocità ma
semplifichiamo notevolmente il progetto. Nei casi reali avremmo
implementato il nostro ColorModel su misura. Ricordiamoci di
chiamare grabPixels() per essere sicuri che il PixelGrabber abbia
svolto il suo lavoro prima di ridare il controllo del Thread
corrente al nostro programma. In seguito prendiamo i pixels con
getPixels() (il cast in int[] è sicuro perchè abbiamo impostato
true il campo forceRGB del costruttore).
public void run() {
PixelGrabber pg = new PixelGrabber(img[0], 0, 0, img[0].getWidth(null),
img[0].getHeight(null), true);
try {
pg.grabPixels();
} catch(InterruptedException e) { }
int[] pix = (int[]) pg.getPixels();
int[] copy = new int[pix.length];
Ora invertiamo l'immagine di 90 gradi.
int w = img[0].getHeight(null);
int h = img[0].getWidth(null);
int count = 0;
for(int col = (w-1); col>=0; col-=1) {
for(int riga = 0; riga<h; riga++) {
copy[col + riga*w] = pix[count++];
}
}
Creiamo una immagine passando attraverso
MemoryImageSource (costruttore più semplice che assume che i
dati siano in formato RGB). Poi chiamiamo la funzione java.awt.Component.createImage(ImageProducer
ip) col nostro MemoryImageSource e, grazie a MediaTracker, ci
sinceriamo che l'immagine sia effettivamente pronta prima di
continuare.
MemoryImageSource mis = new MemoryImageSource(w, h, copy, 0, w);
img[1] = createImage(mis);
MediaTracker mt = new MediaTracker(this);
mt.addImage(img[1], 0);
try {
mt.waitForAll();
} catch(InterruptedException e) { }
pix = copy;
Come potete vedere l'operazione è ripetuta 4
volte uguale nel codice sorgente completo:
questo perchè un'algoritmo iterativo (tramite "for")
mi creava qualche problema. Credo che si trattasse di un bug
della JVM che ottimizzava il codice interno sovrapponendosi al
lavoro compiuto dal Gabage Collector. Risultato? 2 o 3 immagini
erano corrette ma le restanti avevano alcune righe inverite...
Così ho aggirato il problema.
copy = new int[pix.length];
w = img[1].getHeight(null);
h = img[1].getWidth(null);
count = 0;
for(int col = (w-1); col>=0; col-=1) {
for(int riga = 0; riga<h; riga++) {
copy[col + riga*w] = pix[count++];
}
}
mis = new MemoryImageSource(w, h, copy, 0, w);
img[2] = createImage(mis);
mt = new MediaTracker(this);
mt.addImage(img[2], 0);
try {
mt.waitForAll();
} catch(InterruptedException e) { }
pix = copy;
copy = new int[pix.length];
w = img[2].getHeight(null);
h = img[2].getWidth(null);
count = 0;
for(int col = (w-1); col>=0; col-=1) {
for(int riga = 0; riga<h; riga++) {
copy[col + riga*w] = pix[count++];
}
}
mis = new MemoryImageSource(w, h, copy, 0, w);
img[3] = createImage(mis);
mt = new MediaTracker(this);
mt.addImage(img[3], 0);
try {
mt.waitForAll();
} catch(InterruptedException e) { }
pix = copy;
Terminata la fase di creazione delle immagini
provvediamo ad informare gli ActionListener registrati e
ridisegnamo la finestra.
ActionEvent ae = new ActionEvent(this, 0, "Immagini pronte!");
for(int i=0; i<countListeners; i++) {
al[i].actionPerformed(ae);
}
done = true;
repaint();
}
Il metodo paint è diviso in 2 parti. Se le
immagini non sono ancora pronte informo l'utente, altrimenti
visualizzo l'immagine corrente. Come si nota provvedo a
cancellare il canvas prima di disegnare l'immagine, cosa
necessaria in caso di immagini non quadrate. Se non facessi così,
l'immagine precedente lascierebbe un "ricordo" assai
poco gradevole...
public void update(Graphics g) { paint(g); }
public void paint(Graphics g) {
if(!done) {
g.setColor(sfondo);
g.fillRect(0, 0, getSize().width, getSize().height);
g.setColor(Color.yellow);
g.drawString("Preparazione delle immagini.", 20, getSize().height/2 - 10);
g.drawString("Attendere prego...", 20, getSize().height/2 + 10);
g.setColor(Color.cyan);
g.drawString("MindFlavor :-)", 5, getSize().height-20);
} else {
g.setColor(sfondo);
g.fillRect(0, 0, getSize().width, getSize().height);
g.drawImage(img[curNum], 0, 0, null);
}
}
Il metodo rotate() non fa altro che cambiare l'immagine
corrente e chiamare il ridisegno della finestra.
public void rotate() {
if(++curNum >= 4)
curNum = 0;
repaint();
}
Indice
L'Applet di esempio
L'applet di esempio mostra come si può
utilizzare la classe RotateCanvas: si carica l'immagine, si crea
il RotateCanvas con l'immagine passata e gli si dice quando deve
ruotare (nel nostro caso rispondiamo al click del mouse).
public class RotateImage extends Applet {
public final static String FILE_NAME = "FileName";
Image img = null;
RotateCanvas rotate;
public void init() {
String name = getParameter(FILE_NAME);
if(name == null) {
showStatus("Errore!");
return;
}
img = getImage(getCodeBase(), name);
MediaTracker mt = new MediaTracker(this);
mt.addImage(img, 0);
try {
mt.waitForAll();
} catch(InterruptedException e) { }
rotate = new RotateCanvas(img, Color.black);
rotate.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
rotate.rotate();
repaint();
}
});
setLayout(new BorderLayout());
add(rotate, BorderLayout.CENTER);
}
}
Indice
Download
| Descrizione |
Dimensione
| Download
|
| Sorgenti e binari compilati |
6 Kb
|
|
Indice
|