CAPITOLO 8 La multimedialità: le immagini, le animazioni e i suoni Obiettivi • • • • Imparare a visualizzare le immagini Creare animazioni da sequenze di immagini Creare mappe di immagini Imparare a riprodurre, fermare e riprodurre continuamente suoni usando un oggetto AudioClip 8.1 Introduzione Con l’evoluzione dell’informatica, i computer hanno smesso di essere degli strumenti utili soltanto a eseguire velocemente calcoli matematici, e sono diventati strumenti per la manipolazione dei dati e delle informazioni. Una delle caratteristiche più interessanti di Java è sicuramente la multimedialità, ovvero la capacità di usare suoni, immagini, grafica e video nelle applicazioni. La programmazione multimediale offre molte sfide nuove. I nuovi computer vengono ormai venduti già provvisti di unità CD-ROM o DVD, schede audio e speciali funzionalità video. Sempre più utenti, poi, vogliono avere la possibilità di realizzare grafica tridimensionale e ad alta risoluzione. Nei prossimi dieci anni, lo sviluppo nella grafica tridimensionale renderà possibili nuove applicazioni: immaginate di avere una televisione tridimensionale nella vostra casa, in cui seguire gli eventi sportivi e gli spettacoli come se avvenissero nel vostro salotto! Gli studenti di medicina potranno vedere operazioni che si stanno svolgendo a migliaia di chilometri di distanza come se avvenissero nella stessa stanza. Le persone potranno imparare a guidare usando simulatori di automobile estremamente realistici prima ancora di salire in auto. Le possibilità sono eccitanti e potenzialmente senza fine. La multimedialità richiede ai computer un’enorme quantità di potenza. Fino a poco tempo fa, non erano disponibili dei computer economici con la potenza necessaria. I processori veloci ora disponibili, come SPARC Ultra di Sun Microsystems, Pentium e Itanium di Intel, Alpha di Hewlett-Packard, e i processori di MIPS/Silicon Graphics (per citarne alcuni) rendono possibile la multimedialità. A trarre beneficio da questa rivoluzione multimediale saranno principalmente l’industria informatica e il mondo delle comunicazioni, in quanto gli utenti saranno disposti ad acquistare processori più veloci, memorie e ampiezze di banda più grandi per le comunicazioni, tutte cose necessarie per poter supportare le applicazioni 19 - Multimedialità.p65 437 19/12/2003, 9.36 438 CAPITOLO 8 multimediali. Paradossalmente, poi, il costo di tutto ciò non sarà eccessivamente elevato, in quanto la competizione sarà altissima e, quindi, i prezzi tenderanno a scendere sempre più. Per creare applicazioni multimediali efficaci, però, sono necessari linguaggi di programmazione che facilitino la creazione di contenuti multimediali. La maggior parte dei linguaggi attualmente esistenti, infatti, non possiede capacità multimediali di tipo nativo. Al contrario di questi linguaggi, Java offre funzionalità multimediali complete, che permettono ai programmatori di sviluppare da subito applicazioni multimediali estremamente interessanti e potenti. In questo capitolo, verranno presentati vari esempi delle funzionalità multimediali più importanti per la realizzazione di applicazioni efficaci, tra cui: • • • • come manipolare le immagini come creare animazioni fluide come riprodurre suoni con l’interfaccia AudioClip come creare mappe di immagini in grado di “sentire” quando il puntatore del mouse si trova sopra di loro, anche senza un clic da parte dell’utente. Gli esercizi alla fine del capitolo proporranno decine di progetti interessanti, che scateneranno la vostra creatività. 8.2 Caricare, visualizzare e scalare immagini Le capacità multimediali di Java consentono di gestire grafica, immagini, suoni e video. Iniziamo la nostra trattazione con le immagini. L’applet della figura 8.1 mostra il caricamento di un oggetto Image (package java.awt) e di un oggetto ImageIcon (package javax.swing). L’applet visualizza l’oggetto Image nella sua dimensione originale e poi ingrandita, usando due versioni del metodo drawImage della classe Graphics. L’applet disegna inoltre l’oggetto ImageIcon usando il suo metodo paintIcon. La classe ImageIcon è più semplice da usare della classe Image, perché il suo costruttore può ricevere argomenti di formati diversi, tra cui un array di byte contente i byte dell’immagine, un oggetto Image già caricato in memoria e una stringa o un URL che rappresenta la posizione dell’immagine. 1 2 3 4 5 6 7 8 9 10 11 12 13 Figura 8.1 19 - Multimedialità.p65 // Fig. 8.1: LoadImageAndScale.java // Carica un’immagine e la visualizza nella sua dimensione originale e // raddoppiata. Carica e visualizza la stessa immagine come un ImageIcon import java.applet.Applet; import java.awt.*; import javax.swing.*; public class LoadImageAndScale extends JApplet { private Image logo1; private ImageIcon logo2; // carica immagine quando l’applet viene caricata public void init() Caricare e visualizzare un’immagine in un applet (continua) 438 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Figura 8.1 439 { logo1 = getImage( getDocumentBase(), “logo.gif” ); logo2 = new ImageIcon( “logo.gif” ); } // visualizza immagine public void paint( Graphics g ) { g.drawImage( logo1, 0, 0, this ); // disegna immagine originale // disegna immagine che riempie larghezza e altezza meno 120 pixel g.drawImage( logo1, 0, 120, getWidth(), getHeight() – 120, this ); // disegna icona usando il suo metodo paintIcon logo2.paintIcon( this, g, 180, 0 ); } } // fine classe LoadImageAndScale Caricare e visualizzare un’immagine in un applet Le righe 9 e 10 dichiarano rispettivamente un riferimento Image e un riferimento ImageIcon. La classe Image è una classe abstract; quindi, non è possibile creare direttamente un oggetto della classe Image. È necessario invece che l’applet chiami un metodo che fa in modo che il contenitore dell’applet carichi e ritorni l’oggetto Image da usare nel programma. La classe Applet (la superclasse di JApplet) fornisce il metodo getImage (riga 15 del metodo init) per caricare un Image nell’applet. Questa versione di getImage prende due argomenti: la posizione e il nome del file dell’immagine. Nel primo argomento, il metodo getDocumentBase della classe Applet viene utilizzato per ritornare un URL che rappresenta la posizione dell’immagine in Internet (o sul computer, se è da qui che proviene l’applet). In questo caso, si parte 19 - Multimedialità.p65 439 19/12/2003, 9.36 440 CAPITOLO 8 dal presupposto che l’immagine da caricare si trovi nella stessa directory del file HTML che ha invocato l’applet. Il metodo getDocumentBase ritorna la posizione in Internet del file HTML come un oggetto della classe URL. Il secondo argomento specifica un nome di file per l’immagine. Java supporta attualmente parecchi formati per le immagini, tra cui Graphics Interchange Format (GIF), Joint Photographic Experts Group (JPEG) e Portable Network Graphics (PNG). I nomi di file di ognuno di questi tipi terminano rispettivamente con l’estensione .gif, .jpg (o .jpeg) e .png. Obiettivo portabilità 8.1 La classe Image è una classe abstract, quindi gli oggetti Image non possono essere creati direttamente. Per ottenere l’indipendenza dalla piattaforma, l’implementazione Java su ognuna delle piattaforme fornisce la propria sottoclasse di Image per memorizzare le informazioni relative alle immagini. La riga 15 inizia a caricare l’immagine dal computer locale (o a scaricare l’immagine da Internet). Quando l’immagine viene richiesta dal programma, viene caricata in un thread separato di esecuzione. Questo permette al programma di continuare la propria esecuzione mentre l’immagine viene caricata. [Nota: Se il file richiesto non è disponibile, il metodo getImage non segnala un errore.] La classe ImageIcon non è una classe abstract, quindi è possibile creare un oggetto ImageIcon. La riga 16 del metodo init dell’applet crea un oggetto ImageIcon, che carica la stessa immagine logo.gif. La classe ImageIcon fornisce vari costruttori che permettono al programma di inizializzare un oggetto ImageIcon con un’immagine proveniente dal computer locale, oppure con un’immagine conservata in Internet. Il metodo paint dell’applet (righe 20-29) visualizza l’immagine. La riga 22 usa il metodo drawImage di Graphics per visualizzare un oggetto Image. Il metodo drawImage riceve quattro argomenti. Il primo argomento è un riferimento all’oggetto Image da visualizzare (logo1). Il secondo e il terzo argomento sono le coordinate x e y in cui l’immagine deve essere visualizzata all’interno dell’applet (le coordinate corrispondono all’angolo superiore sinistro dell’immagine). L’ultimo argomento è un riferimento a un oggetto ImageObserver. Questo argomento è importante se si visualizzano immagini di grandi dimensioni, che richiedono molto tempo per essere scaricate da Internet. È possibile dunque che il programma esegua il codice che visualizza l’immagine prima che questa sia stata scaricata completamente. L’oggetto ImageObserver viene avvisato di aggiornare l’immagine visualizzata mano a mano che il resto dell’immagine viene scaricato. Normalmente, l’ImageObserver è l’oggetto su cui viene visualizzata l’immagine. Un ImageObserver può essere un qualsiasi oggetto che implementa l’interfaccia ImageObserver, per esempio la classe Component (una delle superclassi indirette della classe Applet); tutti i Component, quindi (incluso il nostro applet), possono essere degli ImageObserver. Quando eseguite questo applet, osservate attentamente la visualizzazione delle varie parti dell’immagine, via via che questa viene caricata. [Nota: Con i computer più veloci, questo effetto potrebbe non essere visibile.] La riga 25 usa un’altra versione del metodo drawImage di Graphics per mostrare una versione scalata dell’immagine. Il quarto e quinto argomento specificano la larghezza e l’altezza dell’immagine ai fini della visualizzazione. L’immagine viene automaticamente scalata in base alla larghezza e all’altezza specificate. In questo esempio, il quarto argomento indica che la larghezza dell’immagine scalata deve essere pari alla larghezza dell’applet, mentre il 19 - Multimedialità.p65 440 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 441 quinto argomento indica che l’altezza deve essere l’altezza dell’applet meno 120 pixel. La larghezza e l’altezza dell’applet sono determinate con i metodi getWidth e getHeight (ereditati dalla classe Component). La riga 28 usa il metodo paintIcon di ImageIcon per visualizzare l’immagine. Il metodo richiede quattro argomenti: un riferimento al Component sul quale l’immagine verrà visualizzata, un riferimento all’oggetto Graphics che verrà utilizzato per rappresentare l’immagine sullo schermo, la coordinata x dell’angolo superiore sinistro dell’immagine, e la coordinata y dell’angolo superiore sinistro dell’immagine. Se confrontate i due modi in cui abbiamo caricato e visualizzato le immagini in questo esempio, vedrete che l’impiego di ImageIcon è più semplice. È possibile creare oggetti della classe ImageIcon direttamente, senza alcun bisogno di usare un riferimento ImageObserver durante la visualizzazione dell’immagine. È questo il motivo per cui utilizzeremo la classe ImageIcon per tutto il resto del capitolo. [Nota: Il metodo paintIcon della classe ImageIcon non permette di scalare un’immagine. Questa classe, però, fornisce il metodo getImage, che ritorna un riferimento Image da utilizzare con il metodo drawImage di Graphics per visualizzare un’immagine scalata.] 8.3 Animare una sequenza di immagini Il prossimo esempio mostra l’animazione di una sequenza di immagini che sono conservate all’interno di un array di ImageIcon. L’animazione presentata nella figura 8.2 è stata progettata come una sottoclasse di JPanel (chiamata LogoAnimator), che può essere attaccata a una finestra di applicazione, oppure a un JApplet. La classe LogoAnimator definisce inoltre un metodo main (righe 85-99) per eseguire l’animazione sotto forma di applicazione. Il metodo main definisce un’istanza della classe JFrame, aggiungendo un oggetto LogoAnimator al JFrame, per visualizzare l’animazione. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Figura 8.2 19 - Multimedialità.p65 // Fig. 8.2: LogoAnimator.java // Animazione di una sequenza di immagini import java.awt.*; import java.awt.event.*; import javax.swing.*; public class LogoAnimator extends JPanel implements ActionListener { private final static String IMAGE_NAME = “deitel”; // nome base immagine protected ImageIcon images[]; // array di immagini private int totalImages = 30; // numero di immagini private int currentImage = 0; // indice corrente di immagine private int animationDelay = 50; // ritardo in millisecondi private int width; // larghezza dell’immagine private int height; // altezza dell’immagine private Timer animationTimer; // Timer che controlla l’animazione L’animazione di una sequenza di immagini (continua) 441 19/12/2003, 9.36 442 CAPITOLO 8 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 Figura 8.2 19 - Multimedialità.p65 // inizializza LogoAnimator caricando le immagini public LogoAnimator() { images = new ImageIcon[ totalImages ]; // carica immagini for ( int count = 0; count < images.length; ++count ) images[ count ] = new ImageIcon( getClass().getResource( “images/” + IMAGE_NAME + count + “.gif” ) ); // questo esempio assume che le immagini abbiano le stesse dimensioni width = images[ 0 ].getIconWidth(); // ottiene larghezza icona height = images[ 0 ].getIconHeight(); // ottiene altezza icona } // visualizza immagine corrente public void paintComponent( Graphics g ) { super.paintComponent( g ); images[ currentImage ].paintIcon( this, g, 0, 0 ); // si sposta alla prossima immagine solo se il timer è partito if ( animationTimer.isRunning() ) currentImage = ( currentImage + 1 ) % totalImages; } // risponde a eventi del timer public void actionPerformed( ActionEvent actionEvent ) { repaint(); // ridisegna animator } // fa partire o ripartire l’animazione public void startAnimation() { if ( animationTimer == null ) { currentImage = 0; animationTimer = new Timer( animationDelay, this ); animationTimer.start(); } else // continua dall’ultima immagine visualizzata if ( ! animationTimer.isRunning() ) animationTimer.restart(); } // ferma timer dell’animazione public void stopAnimation() L’animazione di una sequenza di immagini (continua) 442 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 Figura 8.2 443 { animationTimer.stop(); } // ritorna la dimensione minima di animation public Dimension getMinimumSize() { return getPreferredSize(); } // ritorna la dimensione preferenziale di animation public Dimension getPreferredSize() { return new Dimension( width, height ); } // esegue animation in un JFrame public static void main( String args[] ) { LogoAnimator animation = new LogoAnimator(); // crea LogoAnimator JFrame window = new JFrame( “Animator test” ); // imposta finestra window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); Container container = window.getContentPane(); container.add( animation ); window.pack(); // rende la finestra abbastanza larga per la sua GUI window.setVisible( true ); // visualizza finestra animation.startAnimation(); // inizia animazione } // fine metodo main } // fine classe LogoAnimator L’animazione di una sequenza di immagini La classe LogoAnimator mantiene un array di ImageIcon che vengono caricate nel costruttore (righe 21-33). Le righe 26-32 creano ogni oggetto ImageIcon e caricano le 30 immagini dell’animazione. L’argomento del costruttore usa la concatenazione delle stringhe per assemblare il nome del file dai vari pezzi “images/”, IMAGE_NAME, count e “.gif”. Ognuna delle immagini dell’animazione è un file chiamato “deitel#.gif”, dove # è un valore 0-29 specificato dalla variabile di controllo del ciclo count. Le righe 31-32 determinano la larghezza e 19 - Multimedialità.p65 443 19/12/2003, 9.36 444 CAPITOLO 8 l’altezza dell’animazione dalle dimensioni della prima immagine nell’array images. Assumeremo che tutte le immagini abbiano la stessa larghezza e altezza. Dopo che il costruttore di LogoAnimator ha caricato le immagini, il metodo main imposta la finestra in cui apparirà l’animazione (righe 89-96) e la riga 97 chiama il metodo startAnimation della classe LogoAnimator (definito alle righe 54-64) per dare inizio all’animazione. L’animazione è guidata da un’istanza della classe Timer (package javax.swing). Un oggetto della classe Timer genera degli ActionEvent ad un intervallo di tempo fissato in millisecondi (normalmente specificato come argomento del costruttore di Timer) e notifica dell’evento tutti i suoi ActionListener registrati. Le righe 56-60 determinano se il riferimento animationTimer di tipo Timer è null. In caso affermativo, currentImage è impostato su 0 (riga 57), per indicare che l’animazione dovrebbe iniziare con l’immagine che si trova nel primo elemento dell’array images. La riga 58 assegna un nuovo oggetto Timer ad animationTimer. Il costruttore di Timer riceve due argomenti: l’attesa in millisecondi (in questo esempio, animationDelay è 50, come specificato dalla riga 14) e l’ActionListener che risponderà agli ActionEvent di Timer. La classe LogoAnimator implementa ActionListener, quindi la riga 58 specifica this come ascoltatore. La riga 59 avvia l’oggetto Timer; una volta avviato, animationTimer genererà un ActionEvent ogni 50 millisecondi. Le righe 62-63 permettono al programma di interrompere e riavviare l’animazione. Per ottimizzare un’animazione all’interno di un applet, per esempio, questa dovrebbe essere interrotta quando l’utente passa a un’altra pagina Web. Se l’utente ritorna alla pagina Web contenente l’animazione, il metodo startAnimation può essere chiamato per riavviarla. La condizione if alla riga 62 usa il metodo isRunning di Timer per determinare se Timer è attualmente in esecuzione (ovvero, se sta generando degli eventi). Se non è in esecuzione, la riga 63 chiama il metodo restart di Timer per indicare che Timer dovrebbe ricominciare a generare eventi. In risposta a ognuno degli eventi di Timer di questo esempio, il programma chiama il metodo actionPerformed (righe 48-51). La riga 50 chiama il metodo repaint di LogoAnimator che programma una chiamata al metodo paintComponent di LogoAnimator (righe 36-45). Ricordate che qualsiasi sottoclasse di JComponent che esegue dei disegni deve farlo nel suo metodo paintComponent. Come visto nel capitolo 3, la prima istruzione di qualsiasi metodo paintComponent dovrebbe essere una chiamata al metodo paintComponent della superclasse, per assicurarsi che i componenti Swing siano visualizzati correttamente. La riga 40 disegna l’ImageIcon dell’elemento currentImage all’interno dell’array. Le righe 43-44 determinano se animationTimer è in esecuzione e, in caso affermativo, si preparano a visualizzare l’immagine successiva aumentando di 1 currentImage. Notate il calcolo del resto per impostare il valore di currentImage su 0 quando si supera il valore 29 (l’ultimo elemento dell’array). L’istruzione if ci assicura che, se paintComponent viene chiamato quando Timer viene fermato, verrà visualizzata la stessa immagine. Questo potrebbe essere utile in una GUI che consente all’utente di far partire e fermare l’animazione. Per esempio, se l’animazione viene fermata e l’utente la “copre” con un’altra finestra e poi la “scopre” nuovamente, verrà chiamato il metodo paintComponent. In questo caso, non vogliamo che l’animazione mostri l’immagine successiva. Il metodo stopAnimation (righe 67-70) interrompe l’animazione chiamando il metodo della classe Timer per indicare che il Timer dovrebbe interrompere la generazione di eventi. Questo fatto, a sua volta, impedisce ad actionPerformed di chiamare repaint per avviare la visualizzazione dell’immagine successiva all’interno dell’array. stop 19 - Multimedialità.p65 444 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 445 Ingegneria del software 8.1 Quando si crea un’animazione da utilizzare all’interno di un applet, è necessario fornire un meccanismo che la disattivi quando l’utente passa a una pagina Web diversa dalla pagina in cui si trova l’animazione. I metodi getMinimumSize (righe 73-76) e getPreferredSize (righe 79-82) sovrascrivono i corrispondenti metodi ereditati dalla classe Component per aiutare il layout manager a determinare la corretta dimensione di un LogoAnimator all’interno di un layout. In questo esempio, le immagini sono larghe 160 pixel e alte 80 pixel, quindi il metodo getPreferredSize ritorna un oggetto Dimension contenente i numeri 160 e 80. Il metodo getMinimumSize chiama semplicemente getPreferredSize (una normale abitudine di programmazione) per indicare che la dimensione minima e quella preferenziale sono uguali. Alcuni layout manager ignorano le dimensioni specificate da questi metodi. Per esempio, le regioni NORTH e SOUTH di BorderLayout usano solo l’altezza preferenziale di un componente. 8.4 Mappe di immagini Una tecnica molto comune per creare pagine Web interattive è il ricorso alle mappe di immagini. Una mappa di immagini è un’immagine che possiede delle aree attive su cui l’utente può fare clic al fine di eseguire un’attività, come il caricamento di una diversa pagina Web all’interno del browser. Quando l’utente posiziona il puntatore del mouse su una di queste aree, appare normalmente un messaggio descrittivo nella barra di stato del browser o in un tool tip. La figura 8.3 carica un’immagine contenente varie icone utilizzate all’interno del libro. Il programma permette all’utente di posizionare il puntatore del mouse su di un’icona, visualizzando un messaggio descrittivo relativo a questa icona. Il gestore di eventi mouseMoved (righe 3741) prende le coordinate del mouse e le passa al metodo translateLocation (righe 59-70). Il metodo translateLocation analizza le coordinate per determinare l’icona su cui è stato posizionato il mouse quando è stato chiamato il metodo mouseMoved; il metodo ritorna quindi un messaggio che indica ciò che l’icona rappresenta; questo messaggio viene visualizzato nella barra di stato del contenitore dell’applet. Facendo clic all’interno dell’applet della figura 8.3, non viene eseguita alcuna azione. Nel capitolo 7, abbiamo parlato delle tecniche necessarie per caricare un’altra pagina Web all’interno di un browser usando oggetti URL e l’interfaccia AppletContext. Usando queste tecniche, è possibile modificare quest’applet affinché ogni icona sia associata a un URL diverso che il browser può visualizzare quando l’utente fa clic sull’icona. 1 2 3 4 5 6 7 8 9 Figura 8.3 19 - Multimedialità.p65 // Fig. 8.3: ImageMap.java // Dimostrazione di una mappa di immagini import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ImageMap extends JApplet { private ImageIcon mapImage; Una mappa di immagini (continua) 445 19/12/2003, 9.36 446 CAPITOLO 8 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Figura 8.3 19 - Multimedialità.p65 private static final String captions[] = { “Common Programming Error”, “Good Programming Practice”, “Graphical User Interface Tip”, “Performance Tip”, “Portability Tip”, “Software Engineering Observation”, “Error-Prevention Tip” }; // imposta ascoltatori del mouse public void init() { addMouseListener( new MouseAdapter() { // classe interna anonima // indica quando il puntatore del mouse esce dall’area dell’applet public void mouseExited( MouseEvent event ) { showStatus( “Pointer outside applet” ); } } // fine classe interna anonima ); // fine chiamata a addMouseListener addMouseMotionListener( new MouseMotionAdapter() { // classe interna anonima // determina l’icona su cui il mouse è posizionato public void mouseMoved( MouseEvent event ) { showStatus( translateLocation( event.getX(), event.getY() ) ); } } // fine classe interna anonima ); // fine chiamata a addMouseMotionListener mapImage = new ImageIcon( “icons.png” ); } // ottiene immagine // fine metodo init // visualizza mapImage public void paint( Graphics g ) { super.paint( g ); mapImage.paintIcon( this, g, 0, 0 ); } Una mappa di immagini (continua) 446 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 Figura 8.3 19 - Multimedialità.p65 447 // ritorna testo descrittivo associato con l’icona public String translateLocation( int x, int y ) { // se le coordinate sono fuori dall’immagine, ritorna immediatamente if ( x >= mapImage.getIconWidth() || y >= mapImage.getIconHeight() ) return “”; // determina il numero dell’icona (0 - 6) int iconWidth = mapImage.getIconWidth() / 7; int iconNumber = x / iconWidth; return captions[ iconNumber ]; // ritorna testo appropriato } } // fine classe ImageMap Una mappa di immagini (continua) 447 19/12/2003, 9.36 448 CAPITOLO 8 Figura 8.3 Una mappa di immagini 8.5 Caricare e riprodurre i clip audio I programmi Java sono in grado di utilizzare e riprodurre i clip audio, che Internet offre in grande abbondanza. Per poter riprodurre questi clip audio, il sistema deve disporre dell’apposito hardware (altoparlanti e una scheda audio). Java offre diversi meccanismi per la riproduzione dei suoni all’interno di un applet. I due più semplici sono il metodo play di Applet e il metodo play dell’interfaccia AudioClip. Funzionalità aggiuntive sono disponibili nelle librerie Java Media Framework e Java Sound. Se volete riprodurre un suono una volta all’interno di un programma, il metodo play di Applet permette di caricare questo suono e di riprodurlo una sola volta, dopodiché il suono viene contrassegnato per la garbage collection. Il metodo play di Applet ha due forme: public void play( URL location, String soundFileName ); public void play( URL soundURL ); La prima versione carica il clip audio memorizzato nel file soundFileName, alla posizione location, e ne riproduce il suono. Il primo argomento è normalmente una chiamata al 19 - Multimedialità.p65 448 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 449 metodo getDocumentBase o getCodeBase dell’applet. Il metodo getDocumentBase ritorna la posizione del file HTML che ha caricato l’applet (se l’applet è in un package, il metodo ritorna la posizione del package o del file JAR che contiene il package). Il metodo getCodeBase indica dove si trova il file .class dell’applet. La seconda versione del metodo play, invece, prende un URL che contiene la posizione e il nome del file del clip audio. L’istruzione play( getDocumentBase(), “hi.au” ); carica il clip audio memorizzato nel file hi.au, riproducendolo una volta. Il motore sonoro che riproduce i clip audio supporta vari formati di file, tra cui Sun Audio (estensione .au), Windows Wave (.wav), Macintosh AIFF (.aif o .aiff), Musical Instrument Digital Interface (MIDI) (.mid o .rmi). Le librerie Java Media Framework (JMF) e Java Sound supportano anche altri formati. Il programma della figura 8.4 mostra il caricamento e la riproduzione di un AudioClip (package java.applet). Questa tecnica è più flessibile rispetto al metodo play di Applet, in quanto permette di memorizzare il clip audio all’interno del programma, così da poterlo riutilizzare più volte durante l’esecuzione. Il metodo getAudioClip di Applet ha due forme, che prendono gli stessi argomenti del metodo play visto poco fa. Il metodo getAudioClip ritorna un riferimento a un AudioClip; una volta caricato un AudioClip, è possibile invocare tre metodi: play, loop e stop. Il metodo play riproduce il clip una volta; il metodo loop riproduce il clip in continuazione; il metodo stop interrompe la riproduzione di un clip. Nel programma, ognuno di questi metodi è associato a un pulsante dell’applet. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Figura 8.4 19 - Multimedialità.p65 // Fig. 8.4: LoadAudioAndPlay.java // Carica un clip audio e lo riproduce import import import import java.applet.*; java.awt.*; java.awt.event.*; javax.swing.*; public class LoadAudioAndPlay extends JApplet { private AudioClip sound1, sound2, currentSound; private JButton playSound, loopSound, stopSound; private JComboBox chooseSound; // carica il suono quando l’applet inizia l’esecuzione public void init() { Container container = getContentPane(); container.setLayout( new FlowLayout() ); String choices[] = { “Welcome”, “Hi” }; chooseSound = new JComboBox( choices ); chooseSound.addItemListener( new ItemListener() { Caricare e riprodurre un AudioClip (continua) 449 19/12/2003, 9.36 450 CAPITOLO 8 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 Figura 8.4 19 - Multimedialità.p65 // ferma il suono e imposta il suono sulla selezione dell’utente public void itemStateChanged( ItemEvent e ) { currentSound.stop(); currentSound = chooseSound.getSelectedIndex() == 0 ? sound1 : sound2; } } // fine classe interna anonima ); // fine chiamata a addItemListener container.add( chooseSound ); // imposta i pulsanti e il loro gestore degli eventi ButtonHandler handler = new ButtonHandler(); playSound = new JButton( “Play” ); playSound.addActionListener( handler ); container.add( playSound ); loopSound = new JButton( “Loop” ); loopSound.addActionListener( handler ); container.add( loopSound ); stopSound = new JButton( “Stop” ); stopSound.addActionListener( handler ); container.add( stopSound ); // carica suoni e imposta currentSound sound1 = getAudioClip( getDocumentBase(), “welcome.wav” ); sound2 = getAudioClip( getDocumentBase(), “hi.au” ); currentSound = sound1; } // fine metodo init // ferma il suono quando l’utente carica un’altra pagina Web public void stop() { currentSound.stop(); } // classe interna privata per gestire eventi dei pulsanti private class ButtonHandler implements ActionListener { // elabora eventi dei pulsanti play, loop e stop Caricare e riprodurre un AudioClip (continua) 450 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 Figura 8.4 451 public void actionPerformed( ActionEvent actionEvent ) { if ( actionEvent.getSource() == playSound ) currentSound.play(); else if ( actionEvent.getSource() == loopSound ) currentSound.loop(); else if ( actionEvent.getSource() == stopSound ) currentSound.stop(); } } // fine classe ButtonHandler } // fine classe LoadAudioAndPlay Caricare e riprodurre un AudioClip Le righe 58-59 del metodo init dell’applet usano getAudioClip per caricare due file audio: un file Windows Wave (welcome.wav) e un file Sun Audio (hi.au). L’utente può selezionare quale clip audio riprodurre dal JComboBox chooseSound. Notate come il metodo stop dell’applet venga sovrascritto alle righe 65-68. Quando l’utente cambia pagina Web, viene chiamato il metodo stop; questo garantisce che la riproduzione del clip audio venga interrotta, altrimenti continuerebbe in sottofondo, anche se l’applet non viene visualizzato nel browser. Questo potrebbe non essere un problema, ma potrebbe disturbare l’utente. L’esperienza dell’utente 8.1 Durante la riproduzione dei clip audio in un applet o in un’applicazione, fornite sempre all’utente un meccanismo per disabilitare l’audio. 8.6 Risorse in Internet e World Wide Web www.nasa.gov/gallery/index.html La galleria multimediale della NASA contiene un’ampia varietà di immagini, clip audio e video che possono essere scaricati e utilizzati per testare i propri programmi Java di tipo multimediale. sunsite.sut.ac.jp/multimed/ Il sito Web Sunsite Japan Multimedia Collection fornisce un’ampia varietà di immagini, clip audio e video che possono essere scaricati e utilizzati a scopi educativi. www.anbg.gov.au/anbg/index.html 19 - Multimedialità.p65 451 19/12/2003, 9.36 452 CAPITOLO 8 Il sito Web Australian National Botanic Gardens offre collegamenti ai suoni di molti animali. Provate il collegamento Common Birds. www.thefreesite.com Offre collegamenti a suoni e immagini gratuiti. www.soundcentral.com Offre audio clip nei formati WAV, AU, AIFF e MIDI. www.animationfactory.com Offre migliaia di animazioni GIF gratuite per uso personale. www.clipart.com Servizio (basato su iscrizione) che fornisce immagini e suoni. www.pngart.com Fornisce oltre 50 mila immagini gratuite in formato PNG. developer.java.sun.com/developer/techDocs/hi/repository Il Java Look-and-Feel Graphics Repository fornisce immagini standard da usare in una GUI Swing Riferimenti alle librerie Java Multimedia java.sun.com/products/java-media/jmf/ Home page di Java Media Framework (JMF) API. Qui potete scaricare l’ultima implementazione del JMF e la relativa documentazione. java.sun.com/products/java-media/sound/ Home page di Java Sound API. Fornisce funzionalità per riprodurre e registrare audio. java.sun.com/products/java-media/3D/ Home page di Java 3D API. Fornisce funzionalità per produrre immagini tridimensionali. developer.java.sun.com/developer/onlineTraining/java3d/ Fornisce un corso di Java 3D API. java.sun.com/products/java-media/jai/ Home page di Java Advanced Imaging API. Fornisce funzionalità per elaborare immagini, come aumento del contrasto, ritaglio, scalatura e trasformazioni geometriche. freetts.sourceforge.net/docs/index.php FreeTTS è un’implementazione di Java Speech API. java.sun.com/products/java-media/2D/ Home page di Java 2D API. Questa API (introdotta nel capitolo 1) fornisce funzionalità complesse per le immagini bidimensionali. java.sun.com/j2se/1.4.1/docs/guide/imageio/ 19 - Multimedialità.p65 452 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 453 Contiene una rassegna di Java Image I/O API, che permette ai programmi di definire il caricamento e il salvataggio di formati di immagini che non sono attualmente supportati dalle librerie Java. 8.7 (Caso di studio facoltativo) Pensare a oggetti: animazioni e suoni nella vista Questo caso di studio si è focalizzato principalmente sul modello MVC della nostra simulazione di ascensore. Ora che abbiamo completato la progettazione del modello, ci occupiamo della vista, che fornisce la presentazione visiva del modello. Nel nostro caso di studio, la vista, incapsulata nella classe ElevatorView, è un oggetto JPanel contenente altri oggetti JPanel “figli”, ognuno rappresentante un oggetto unico del modello MVC (per esempio, un oggetto Person, un oggetto Button, ecc.). La classe ElevatorView è la classe più grande nel caso di studio. In questa sezione, discutiamo le classi per la grafica e il suono usate da ElevatorView. Presenteremo e spiegheremo il resto del codice sul sito Web www.apogeonline.com/libri/ 02097/allegati/. Nella sezione 3.7 del volume Tecniche di Base, abbiamo costruito il diagramma di classe del nostro modello individuando sostantivi e frasi dalla specifica del problema. Abbiamo ignorato parecchi di questi sostantivi, perché non erano associati con il modello MVC. Ora, elenchiamo i sostantivi e le frasi che sono relativi alla visualizzazione del modello MVC: • • • visualizzazione audio musica dell’ascensore. Il sostantivo “visualizzazione” corrisponde alla vista, o alla presentazione visuale, del modello MVC. Come descritto nella sezione 2.7, la classe ElevatorView aggrega diverse classi. Il sostantivo “audio” si riferisce agli effetti sonori che la nostra simulazione genera quando accadono varie azioni; creeremo la classe SoundEffects per generare tali effetti sonori. La frase “musica dell’ascensore” si riferisce alla musica che viene riprodotta quando una persona viaggia sull’ascensore; useremo la classe SoundEffects anche per riprodurre questa musica. La vista visualizza gli oggetti del modello MVC. Creeremo la classe ImagePanel per rappresentare gli oggetti stazionari del modello, come ElevatorShaft. Creeremo la classe MovingPanel, che estende ImagePanel, per rappresentare gli oggetti in movimento, come Elevator. Infine, creeremo la classe AnimatedPanel, che estende MovingPanel, per rappresentare gli oggetti in movimento la cui immagine corrispondente cambia continuamente, come Person (useremo diverse immagini per mostrare la persona che cammina e preme un pulsante). Usando queste classi, presentiamo il diagramma di classe della vista per la nostra simulazione nella figura 8.5. Le note indicano i ruoli che hanno le classi nel sistema. Secondo il diagramma di classe, la classe ElevatorView rappresenta la vista, le classi ImagePanel, MovingPanel e AnimatedPanel si riferiscono alla grafica e la classe SoundEffects si riferisce ai suoni. La classe ElevatorView contiene diverse istanze delle classi ImagePanel, MovingPanel e AnimatedPanel e un’istanza della classe SoundEffects. Nella versione finale del caso di studio, assoceremo ogni oggetto del modello con una classe corrispondente nella vista. 19 - Multimedialità.p65 453 19/12/2003, 9.36 454 CAPITOLO 8 javax.swing.JPanel ImagePanel 1..* 1 ElevatorView MovingPanel 1..* 1 1 AnimatedPanel Grafica Figura 8.5 Audio 1 1..* 1 SoundEffects Vista Diagramma di classe della vista del simulatore di ascensore In questa sezione, discutiamo le classi ImagePanel, MovingPanel e AnimatedPanel per spiegare la grafica e le animazioni. Poi, discuteremo la classe SoundEffects per spiegare le funzionalità audio. ImagePanel La classe ElevatorView usa oggetti di sottoclassi di JPanel per rappresentare e visualizzare ogni oggetto nel modello (come Elevator, Person, ecc.). La classe ImagePanel (figura 8.6) è una sottoclasse di JPanel in grado di visualizzare un’immagine in una data posizione dello schermo. La classe ElevatorView usa oggetti ImagePanel per rappresentare oggetti immobili nel modello, come ElevatorShaft e i due Floor. La classe ImagePanel contiene un attributo intero, ID (riga 16), che dichiara un identificatore unico per tenere traccia dell’ImagePanel nella vista. Ciò è utile quando diversi oggetti della stessa classe sono presenti nel modello, come diversi oggetti Person. La classe ImagePanel contiene l’oggetto position della classe Point2D.Double (riga 19) per rappresentare la sua posizione sullo schermo. Vedremo più avanti che MovingPanel, che estende ImagePanel, dichiara la velocità con valori double. Convertiremo le coordinate position in valori int per posizionare l’oggetto ImagePanel sullo schermo (Java rappresenta le coordinate dello schermo come interi) nel metodo setPosition (righe 90-94). La classe ImagePanel contiene anche un oggetto ImageIcon chiamato imageIcon (riga 22), il cui metodo paintComponent (righe 54-60) visualizza l’icona sullo schermo. Le righe 41-42 inizializzano ImageIcon usando un parametro stringa che contiene il nome dell’immagine. Infine, la classe ImagePanel contiene l’oggetto panelChildren della classe Set (riga 25) che memorizza tutti gli oggetti JPanel figli (o oggetti di una sottoclasse di JPanel). Gli oggetti figli vengono visualizzati sopra il loro ImagePanel genitore: per esempio, un oggetto Person dentro un Elevator. Il primo metodo add (righe 63-67) aggiunge un oggetto a panelChildren. Il secondo metodo add (righe 70-74) inserisce un oggetto in panelChildren all’indice specificato. Il metodo setIcon (righe 84-87) imposta imageIcon con una nuova immagine. Gli oggetti della classe AnimatedPanel usano 19 - Multimedialità.p65 454 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 455 ripetutamente il metodo setIcon per cambiare l’immagine visualizzata, in modo da eseguire l’animazione. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Figura 8.6 19 - Multimedialità.p65 // ImagePanel.java // sottoclasse di JPanel per posizionare e visualizzare ImageIcon package com.deitel.jhtp5.elevator.view; // package Java di base Java import java.awt.*; import java.awt.geom.*; import java.util.*; // package Java di estensione import javax.swing.*; public class ImagePanel extends JPanel { // identificatore private int ID; // posizione sullo schermo private Point2D.Double position; // imageIcon da disegnare sullo schermo private ImageIcon imageIcon; // memorizza tutti i figli di ImagePanel private Set panelChildren; // costruttore che inizializza posizione e immagine public ImagePanel( int identifier, String imageName ) { super( null ); // specifica layout null setOpaque( false ); // rende trasparente // imposta identificatore unico ID = identifier; // imposta posizione position = new Point2D.Double( 0, 0 ); setLocation( 0, 0 ); // crea ImageIcon con imageName dato imageIcon = new ImageIcon( getClass().getResource( imageName ) ); Image image = imageIcon.getImage(); La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello (continua) 455 19/12/2003, 9.36 456 CAPITOLO 8 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 Figura 8.6 19 - Multimedialità.p65 setSize( image.getWidth( this ), image.getHeight( this ) ); // crea Set per memorizzare i figli di Panel panelChildren = new HashSet(); } // fine costruttore di ImagePanel // disegna Panel sullo schermo public void paintComponent( Graphics g ) { super.paintComponent( g ); // se l’immagine e’ pronta, disegnala sullo schermo imageIcon.paintIcon( this, g, 0, 0 ); } // aggiunge figlio di ImagePanel a ImagePanel public void add( ImagePanel panel ) { panelChildren.add( panel ); super.add( panel ); } // aggiunge figlio di ImagePanel a ImagePanel all’indice dato public void add( ImagePanel panel, int index ) { panelChildren.add( panel ); super.add( panel, index ); } // rimuove figlio di ImagePanel da ImagePanel public void remove( ImagePanel panel ) { panelChildren.remove( panel ); super.remove( panel ); } // imposta ImageIcon corrente da visualizzare public void setIcon( ImageIcon icon ) { imageIcon = icon; } // imposta posizione sullo schermo public void setPosition( double x, double y ) { position.setLocation( x, y ); La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello (continua) 456 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 Figura 8.6 setLocation( ( int ) x, 457 ( int ) y ); } // ritorna identificatore di ImagePanel public int getID() { return ID; } // ottiene posizione di ImagePanel public Point2D.Double getPosition() { return position; } // ottiene imageIcon public ImageIcon getImageIcon() { return imageIcon; } // ottiene Set di figli di ImagePanel public Set getChildren() { return panelChildren; } } La classe ImagePanel rappresenta e visualizza un oggetto immobile del modello MovingPanel La classe MovingPanel (figura 8.7) è una sottoclasse di ImagePanel in grado di cambiare la sua posizione sullo schermo in base ai valori xVelocity e yVelocity (righe 20-21). La classe ElevatorView usa oggetti MovingPanel per rappresentare oggetti mobili del modello, come Elevator. 1 2 3 4 5 6 7 8 9 10 Figura 8.7 19 - Multimedialità.p65 // MovingPanel.java // Sottoclasse di JPanel con funzionalità di movimento sullo schermo package com.deitel.jhtp5.elevator.view; // package Java di base import java.awt.*; import java.awt.geom.*; import java.util.*; // package Java di estensione La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello (continua) 457 19/12/2003, 9.36 458 CAPITOLO 8 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 Figura 8.7 19 - Multimedialità.p65 import javax.swing.*; public class MovingPanel extends ImagePanel { // MovingPanel dovrebbe cambiare posizione? private boolean moving; // numero di pixel di cui MovingPanel si muove nelle direzioni x e y // per animationDelay millisecondi private double xVelocity; private double yVelocity; // costruttore che inizializza posizione, velocità e immagine public MovingPanel( int identifier, String imageName ) { super( identifier, imageName ); // imposta velocità di MovingPanel xVelocity = 0; yVelocity = 0; } // fine costruttore di MovingPanel // aggiorna posizione e animazione di MovingPanel public void animate() { // aggiorna posizione a seconda della velocità if ( isMoving() ) { double oldXPosition = getPosition().getX(); double oldYPosition = getPosition().getY(); setPosition( oldXPosition + xVelocity, oldYPosition + yVelocity ); } // aggiorna tutti i figli di MovingPanel Iterator iterator = getChildren().iterator(); while ( iterator.hasNext() ) { MovingPanel panel = ( MovingPanel ) iterator.next(); panel.animate(); } } // fine metodo animate // MovingPanel si sta muovendo sullo schermo? public boolean isMoving() { return moving; } La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello (continua) 458 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 Figura 8.7 459 // imposta MovingPanel per muoversi sullo schermo public void setMoving( boolean move ) { moving = move; } // imposta le velocità x e y di MovingPanel public void setVelocity( double x, double y ) { xVelocity = x; yVelocity = y; } // ritorna velocità x di MovingPanel public double getXVelocity() { return xVelocity; } // ritorna velocità y di MovingPanel public double getYVelocity() { return yVelocity; } } La classe MovingPanel rappresenta e visualizza un oggetto che si muove dal modello Il metodo animate (righe 35-53) muove MovingPanel secondo i valori attuali dei campi e yVelocity. Se la variabile boolean moving (riga 16) è true, le righe 38-44 usano i campi xVelocity e yVelocity per determinare la prossima posizione di movingPanel. Le righe 47-52 ripetono il procedimento per tutti i figli. Nella nostra simulazione, ElevatorView invoca il metodo animate e il metodo paintComponent della classe ImagePanel ogni 50 millisecondi. Queste chiamate in rapida successione spostano l’oggetto MovingPanel. xVelocity AnimatedPanel La classe AnimatedPanel (figura 8.8), che estende la classe MovingPanel, rappresenta un oggetto animato del modello (cioè, oggetti in movimento la cui immagine corrispondente cambia continuamente), come Person. La classe ElevatorView anima un AnimatedPanel cambiando l’immagine associata con imageIcon. 1 2 3 4 Figura 8.8 19 - Multimedialità.p65 // AnimatedPanel.java // Sottoclasse di Panel con funzionalità di animazione package com.deitel.jhtp5.elevator.view; La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 459 19/12/2003, 9.36 460 CAPITOLO 8 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 Figura 8.8 19 - Multimedialità.p65 // package Java di base import java.awt.*; import java.util.*; // package Java di estensione import javax.swing.*; public class AnimatedPanel extends MovingPanel { // ImageIcon deve essere animato? private boolean animating; // frequenza di cambio immagine private int animationRate; private int animationRateCounter; private boolean cycleForward = true; // ImageIcon individuali usati per l’animazione private ImageIcon imageIcons[]; // memorizza tutte le sequenze di frame private java.util.List frameSequences; private int currentAnimation; // deve continuare l’animazione alla fine del ciclo? private boolean loop; // deve visualizzare l’ultima immagine alla fine dell’animazione? private boolean displayLastFrame; // determina la prossima immagine da visualizzare private int currentFrameCounter; // costruttore che prende un array di nomi di file public AnimatedPanel( int identifier, String imageName[] ) { super( identifier, imageName[0] ); // crea oggetti ImageIcon dall’array di stringhe imageName imageIcons = new ImageIcon[ imageName.length ]; for ( int i = 0; i < imageIcons.length; i++ ) { imageIcons[i] = new ImageIcon( getClass().getResource( imageName[i] ) ); } frameSequences = new ArrayList(); } // fine costruttore di AnimatedPanel La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 460 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 Figura 8.8 19 - Multimedialità.p65 461 // aggiorna posizione dell’icona e immagine dell’animazione public void animate() { super.animate(); // visualizza la prossima immagine se counter > frequenza di animazione if ( frameSequences != null && isAnimating() ) { if ( animationRateCounter > animationRate ) { animationRateCounter = 0; determineNextFrame(); } else animationRateCounter++; } } // fine metodo animate // determina prossima immagine dell’animazione private void determineNextFrame() { int frameSequence[] = ( int[] ) frameSequences.get( currentAnimation ); // se non ci sono più immagini, determina immagine finale // a meno che non si debba ripetere l’animazione if ( currentFrameCounter >= frameSequence.length ) { currentFrameCounter = 0; // se loop è false, termina animazione if ( !isLoop() ) { setAnimating( false ); if ( isDisplayLastFrame() ) // visualizza ultima immagine della sequenza currentFrameCounter = frameSequence.length - 1; } } // imposta immagine corrente dell’animazione setCurrentFrame( frameSequence[ currentFrameCounter ] ); currentFrameCounter++; } // fine metodo determineNextFrame // aggiunge animazione all’ ArrayList frameSequences La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 461 19/12/2003, 9.36 462 CAPITOLO 8 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 Figura 8.8 19 - Multimedialità.p65 public void addFrameSequence( int frameSequence[] ) { frameSequences.add( frameSequence ); } // chiede se AnimatedPanel sta eseguendo l’animazione public boolean isAnimating() { return animating; } // imposta AnimatedPanel per eseguire l’animazione public void setAnimating( boolean animate ) { animating = animate; } // imposta ImageIcon corrente public void setCurrentFrame( int frame ) { setIcon( imageIcons[ frame ] ); } // imposta frequenza di animazione public void setAnimationRate( int rate ) { animationRate = rate; } // ottiene frequenza di animazione public int getAnimationRate() { return animationRate; } // imposta se l’animazione deve ripetersi public void setLoop( boolean loopAnimation { loop = loopAnimation; } ) // ottiene se l’animazione deve ripetersi public boolean isLoop() { return loop; } La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello (continua) 462 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 149 463 // ottiene se visualizzare l’ultima immagine alla fine dell’animazione private boolean isDisplayLastFrame() { return displayLastFrame; } 150 151 152 153 154 155 // imposta se visualizzare l’ultima immagine alla fine dell’animazione public void setDisplayLastFrame( boolean displayFrame ) { displayLastFrame = displayFrame; } 156 157 158 159 160 161 162 163 164 165 166 167 168 // inizia ad eseguire la sequenza di animazione all’indice dato public void playAnimation( int frameSequence ) { currentAnimation = frameSequence; currentFrameCounter = 0; setAnimating( true ); } } Figura 8.8 La classe AnimatedPanel rappresenta e visualizza un oggetto animato dal modello La classe AnimatedPanel sceglie un oggetto ImageIcon da disegnare sullo schermo tra diversi oggetti ImageIcon memorizzati nell’array imageIcons (riga 23). La classe AnimatedPanel determina l’oggetto ImageIcon secondo una sequenza di riferimenti, memorizzata nella lista frameSequences (riga 26), che è un array di interi che memorizza la sequenza appropriata per visualizzare gli oggetti ImageIcon: in particolare, ogni intero rappresenta l’indice di un oggetto ImageIcon nell’array imageIcons. La figura 8.9 mostra la relazione tra imageIcons e frameSequences (questo non è un diagramma UML). Per esempio, la sequenza numero 2 = { 2, 1, 0 } si riferisce a { imageIcon[2], imageIcon[1], imageIcon[0] }, che porta alla sequenza di immagini { C, B, A }. Nella vista, ogni immagine è un file .png unico. Il metodo frameSequences imageIcons A B C D 0 1 2 3 Figura 8.9 19 - Multimedialità.p65 0= 0 1 2 1= 0 1 3 2= 2 1 0 3= 3 2 2 1 0 0 image sequences A B C A B D C B A D C C Relazione tra l’array imageIcons e la lista FrameSequences 463 19/12/2003, 9.36 B A A 464 CAPITOLO 8 addFrameSequences (righe 102-105) aggiunge una sequenza di animazione alla lista frameSequences. Il metodo playAnimation (righe 162-167) fa partire l’animazione associata con il parametro frameSequences. Per esempio, supponiamo di avere un oggetto AnimatedPanel chiamato personAnimatedPanel nella classe ElevatorView. Il frammen- to di codice animatedPanel.playAnimation( 1 ); genererebbe la sequenza di immagini { A, B, D, B, A } se usiamo la figura 8.9 come riferimento. Il metodo animate (righe 56-70) sovrascrive il metodo animate della superclasse MovingPanel. Le righe 61-69 determinano la successiva immagine per l’animazione a seconda del campo animationRate, che è inversamente proporzionale alla velocità dell’animazione: un valore più alto per animationRate comporta un’animazione più lenta. Per esempio, se animationRate vale 5, animate si sposta alla successiva immagine dell’animazione ogni cinque volte viene invocato. Usando questa logica, la frequenza di animazione viene massimizzata quando animationRate ha un valore 1, perché la successiva immagine viene determinata ogni volta che viene chiamato animate. Il metodo animate chiama determineNextFrame (righe 73-99) per determinare la successiva immagine da visualizzare: in particolare, viene chiamato il metodo setCurrentFrame (righe 120-123) che imposta imageIcon (l’immagine correntemente visualizzata) all’immagine ritornata dalla corrente sequenza di animazione. Le righe 84-92 di determineNextFrame vengono usate per ripetere l’animazione. Se loop vale false, l’animazione termina dopo una iterazione. L’ultima immagine della sequenza viene visualizzata se displayLastFrame vale true, e, se vale false, viene visualizzata la prima immagine della sequenza. Se loop vale true, l’animazione si ripete finché non viene fermata esplicitamente. Effetti sonori Vediamo ora come generare dei suoni nella nostra simulazione. La classe SoundEffects (figura 8.10) trasforma file audio (.au), wave (.wav) e MIDI (.mid) contenenti suoni come il campanello, i passi della persona e la musica dell’ascensore, in oggetti java.applet.AudioClip. L’oggetto ElevatorView riprodurrà gli oggetti AudioClip per generare i suoni. Tutti i file dei suoni sono nella struttura di directory com/deitel/jhtp5/elevator/view/sounds Nella nostra simulazione, useremo suoni e file MIDI disponibili gratuitamente al sito Web di Microsoft: msdn.microsoft.com/downloads/default.asp Per scaricare questi suoni, fate clic su “Graphics and Multimedia”, “Multimedia (General)”, e poi “Sounds”. La classe SoundEffects contiene i metodi getAudioClip (righe 16-27), che usa il metodo statico newAudioClip (della classe java.applet.Applet) per ritornare un oggetto AudioClip usando il parametro soundFile. Il metodo setPathPrefix (righe 30-33) permette di cambiare directory del file audio (utile se vogliamo dividere i nostri file sonori tra directory diverse). 19 - Multimedialità.p65 464 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 Figura 8.10 465 // SoundEffects.java // Ritorna oggetti AudioClip package com.deitel.jhtp5.elevator.view; // package Java di base import java.applet.*; public class SoundEffects { // posizione dei file audio private String prefix = “”; public SoundEffects() {} // ottiene AudioClip associato con soundFile public AudioClip getAudioClip( String soundFile ) { try { return Applet.newAudioClip( getClass().getResource( prefix + soundFile ) ); } // ritorna null se il file audio non esiste catch ( NullPointerException nullPointerException ) { return null; } } // imposta prefisso per la posizione di soundFile public void setPathPrefix( String string ) { prefix = string; } } La classe SoundEffects ritorna oggetti AudioClip Conclusione Avete appena completato un procedimento di progettazione orientata agli oggetti che aveva lo scopo di prepararvi per le sfide dei progetti di livello industriale. Speriamo che abbiate trovato le sezioni “Pensare a oggetti” informative e utili come complemento al materiale presentato nei vari capitoli. Inoltre, speriamo vi siate divertiti a progettare il sistema usando UML. Il linguaggio UML è stato adottato come standard dall’industria del software mondiale per la modellazione di software orientato agli oggetti. Anche se avete completato la fase di progettazione, avete solo sfiorato il processo di implementazione. Consultate il sito Web www.apogeonline.com/libri/02097/allegati/ per la completa implementazione del progetto, e le traduzioni dei diagrammi UML in un programma Java completo per la simulazione dell’ascensore. Studiare l’implementazione 19 - Multimedialità.p65 465 19/12/2003, 9.36 466 CAPITOLO 8 rafforzerà le abilità nella programmazione che avete sviluppato leggendo il libro e migliorerà la vostra comprensione del processo di progettazione. Esercizi di autovalutazione 8.1 Completate le seguenti frasi: a) Il metodo ____________ di Applet carica un’immagine all’interno di un applet. b) Il metodo ____________ di Applet ritorna, come un oggetto della classe URL, la posizione in Internet del file HTML che ha invocato l’applet. c) Il metodo ____________ di Graphics visualizza un’immagine all’interno di un applet. d) Java fornisce due meccanismi per riprodurre suoni all’interno di un applet: il metodo play di Applet e il metodo play dell’interfaccia ____________. e) Una ____________ è un’immagine che presenta delle aree attive, sulle quali l’utente può fare clic per portare a termine un’attività, come per esempio il caricamento di una diversa pagina Web. f ) Il metodo ____________ della classe ImageIcon visualizza l’immagine di ImageIcon. g) Java supporta diversi formati di immagini, tra cui ____________, ____________ e ____________ 8.2 Determinate se le seguenti affermazioni sono vere o false. Se sono false, spiegate il perché. a) Un suono viene eliminato quando ha finito di essere riprodotto. b) La classe ImageIcon fornisce dei costruttori che permettono ad un oggetto ImageIcon di essere inizializzato solo con un’immagine dal computer locale. Risposte agli esercizi di autovalutazione 8.1 a) getImage. b) getDocumentBase. c) drawImage. d) AudioClip. e) mappa di immagini. f ) g) Graphics Interchange Format (GIF), Joint Photographic Experts Group (JPEG), Portable Network Graphics (PNG). paintIcon. 8.2 a) Falso. Il suono verrà contrassegnato per essere eliminato (se non è referenziato da un AudioClip) e verrà eliminato quando il garbage collector è in grado di essere eseguito. b) Falso. ImageIcon può caricare anche immagini da Internet. Esercizi 8.3 Spiegate come sia possibile rendere un’animazione “browser friendly”. 8.4 Descrivete i metodi Java per riprodurre e utilizzare i clip audio. 8.5 gini. Spiegate l’utilizzo delle mappe di immagini. Elencate diverse applicazioni di mappe di imma- 8.6 (Cancellazione casuale di un’immagine) Supponete che un’immagine venga visualizzata in un’area rettangolare dello schermo. Un modo per cancellare l’immagine è quello di impostare immediatamente ogni pixel sullo stesso colore, ma questo produce un effetto visivo noioso. Scrivete un programma Java che visualizzi un’immagine e poi la cancelli, utilizzando la generazione casuale dei numeri per selezionare i singoli pixel da cancellare. Dopo che gran parte dell’immagine è stata cancellata, cancellate tutti i pixel rimanenti in una sola volta. È possibile fare riferimento ai singoli pixel attraverso una riga che inizia e finisce allo stesso punto. Potete provare diverse varianti di questo problema. Per esempio, potreste visualizzare le righe in modo casuale, oppure potreste visualizzare le figure in modo casuale per cancellare intere regioni dello schermo. 19 - Multimedialità.p65 466 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 467 8.7 (Text Flasher) Create un programma Java che lampeggi ripetutamente del testo sullo schermo. Completate il testo con un’immagine di sfondo di un unico colore. Permettete all’utente di controllare la velocità di lampeggiamento e di scegliere il colore o il motivo di sfondo. 8.8 (Image Flasher) Create un programma Java che lampeggi ripetutamente un’immagine sullo schermo. Completate l’immagine con un’immagine di sfondo di un unico colore. 8.9 (Orologio digitale) Implementate un programma che visualizzi un orologio digitale sullo schermo. Potete aggiungere delle opzioni per mostrare il giorno, il mese e l’anno, inserire una funzione di allarme, riprodurre determinati suoni in momenti predefiniti, e così via. 8.10 (Richiamare l’attenzione su di un’immagine) Se volete enfatizzare un’immagine, potete posizionare intorno a questa immagine una fila di luci, che lampeggino all’unisono, oppure che si accendano una dopo l’altra in sequenza. 8.11 (Zooming) Create un programma che permetta di eseguire lo zoom di un’immagine. Sezione speciale: progetti multimediali avanzati Gli esercizi precedenti servono a verificare la comprensione da parte del lettore dei concetti fondamentali del capitolo. Questa sezione contiene una collezione di progetti multimediali avanzati che il lettore dovrebbe trovare stimolanti e divertenti. La difficoltà dei problemi è considerevolmente varia. Alcuni richiedono un’ora o due di progettazione e implementazione. Altri sono utili come progetti di laboratorio che possono richiedere due o tre settimane di studio e implementazione. 8.12 (Animazione) Create un programma generico di animazione Java. Il programma dovrebbe permettere all’utente di specificare la sequenza di frame da visualizzare, la velocità con cui le immagini devono essere visualizzate, i suoni da riprodurre mentre l’animazione è in esecuzione, e così via. 8.13 (Transizione casuale di un’immagine) Questo è un effetto visivo molto interessante. Se state visualizzando un’immagine in una data area dello schermo, e volete passare a un’altra immagine nella stessa area dello schermo, memorizzate la nuova immagine in un buffer separato, e copiate in modo casuale i pixel della nuova immagine in modo da visualizzare l’area che copre i pixel che si trovavano precedentemente in quelle posizioni. Quando la maggior parte dei pixel sono stati copiati, copiate l’intera nuova immagine nell’area di visualizzazione, così da assicurarvi che state visualizzando la nuova immagine completa. Per implementare questo programma potrebbe essere necessario usare le classi PixelGrabber e MemoryImageSource (consultate la documentazione Java API per la descrizione di queste classi). Potete provare diverse varianti di questo problema. Per esempio, potreste selezionare tutti i pixel in una riga selezionata a caso, oppure creare la nuova immagine e sovrapporre questi pixel alle posizioni corrispondenti nella vecchia immagine. 8.14 (Audio in sottofondo) Aggiungete dell’audio di sottofondo a una delle vostre applicazioni preferite, utilizzando il metodo loop della classe AudioClip per riprodurre il suono di sottofondo mentre interagite con l’applicazione in modo normale. 8.15 (Marquee scorrevole) Create un programma Java che faccia scorrere dei caratteri puntati da destra verso sinistra (o da sinistra verso destra) all’interno di una fascia di tipo Marquee. Se volete, visualizzate il testo a ciclo continuo, così che quando il testo esce da una parte dello schermo riappaia dalla parte opposta. 8.16 (Immagine scorrevole) Create un programma Java che faccia scorrere un’immagine lungo uno schermo di tipo Marquee. 8.17 (Orologio analogico) Create un programma Java che visualizzi un orologio analogico con le lancette dell’ora, dei minuti e dei secondi. Le lancette si devono spostare in modo corretto in base allo scorrere del tempo. 19 - Multimedialità.p65 467 19/12/2003, 9.36 468 CAPITOLO 8 8.18 (Caleidoscopio dinamico) Sviluppate un programma caleidoscopio che visualizzi delle immagini che simulano il popolare giocattolo per bambini. Incorporate degli effetti sonori che corrispondano alle immagini che variano in modo dinamico. 8.19 (Generatore di puzzle) Create un generatore di puzzle Java. L’utente specifica un’immagine, che il programma carica e visualizza. Il programma poi suddivide l’immagine in tante forme selezionate in modo casuale, e le mischia. L’utente usa il mouse per spostare i vari pezzi del puzzle, al fine di ricomporre l’immagine. Aggiungete i suoni appropriati via via che i pezzi vengono spostati e rimessi al loro posto. Potete tenere delle etichette su ogni pezzo con l’esatta posizione, e poi usare effetti sonori per aiutare l’utente a rimettere i pezzi nella corretta posizione. 8.20 (Generatore di labirinti) Sviluppate un generatore di labirinti multimediale e un programma di attraversamento del labirinto. Permettete all’utente di personalizzare il labirinto, specificando il numero di righe e di colonne, e indicando il livello di difficoltà. Create un topolino animato che percorra il labirinto. Utilizzate l’audio per enfatizzare i movimenti del topolino. 8.21 (Slot machine) Sviluppate una simulazione multimediale di una slot machine. Create tre ruote che girano, ognuna con disegni di frutti e di altri simboli. Utilizzate la generazione casuale dei numeri per simulare il giro di ogni ruota e la sua interruzione su uno dei simboli. 8.22 (Corsa dei cavalli) Create una simulazione Java di una corsa dei cavalli, con più partecipanti. Usate l’audio per simulare un annunciatore che descrive la gara e annuncia i risultati finali. Riproducete i suoni corretti, per indicare la posizione di ognuno dei concorrenti durante la gara. Provate a simulare il tipo di gare che si vedono spesso nelle feste di paese. I giocatori possono, a turno, fare avanzare il proprio cavallo con il mouse. 8.23 (Shuffleboard) Sviluppate una simulazione multimediale del gioco shuffleboard. [Nota: Questo gioco, in origine americano, è spesso giocato a bordo delle navi di crociera, e consiste nello spingere, con apposite stecche, dei dischi di legno entro figure geometriche numerate.] Usate i corretti effetti audio e video. 8.24 (Biliardo) Create una simulazione multimediale del gioco del biliardo. Ogni giocatore usa a turno il mouse, per posizionare la stecca e colpire la palla con la giusta angolazione, affinché finisca in una delle buche. Il programma deve tenere aggiornati i punteggi. 8.25 (Artista) Progettate un programma Java che offra a un artista una grande quantità di funzionalità per disegnare, utilizzare immagini, usare animazioni e così via, al fine di creare opere multimediali dinamiche. 8.26 (Fuochi d’artificio) Create un programma Java che possa essere utilizzato per creare uno spettacolo di fuochi d’artificio. Create varie dimostrazioni e cercate di ottenere gli effetti più spettacolari. 8.27 (Arredatore) Sviluppate un programma Java che aiuti a disporre l’arredamento di una casa. Aggiungete delle caratteristiche che permettano di ottenere la migliore disposizione possibile. 8.28 (Parole crociate) Sviluppate un programma di parole crociate, che permetta al giocatore di inserire e cancellare le parole in modo semplice. Collegate il programma a un grande dizionario su computer. Il programma dovrebbe essere in grado di suggerire le parole in base alle lettere che sono già state inserite. 8.29 (Gioco del 15) Scrivete un programma Java multimediale che permetta all’utente di giocare il gioco del 15. Il gioco si compone di una scacchiera 4 × 4, per un totale di 16 caselle. Una di queste caselle è vuota, mentre le altre sono occupate da 15 numeri (dall’1 al 15). Ogni casella vicina a quella vuota può essere spostata al posto di quella vuota facendovi clic. Il programma deve creare la scacchiera con le caselle in ordine sparso. L’obiettivo è quello di sistemare tutte le caselle in ordine sequenziale, riga per riga. 19 - Multimedialità.p65 468 19/12/2003, 9.36 LA MULTIMEDIALITÀ: LE IMMAGINI, LE ANIMAZIONI E I SUONI 469 8.30 (Tempi di reazione) Create un programma Java che sposti una figura creata in modo casuale all’interno dello schermo. L’utente sposta il mouse per cercare di fermare la figura. La velocità e la dimensione della figura possono essere modificate. Producete delle statistiche circa il tempo tipicamente impiegato dall’utente per catturare una figura di una data dimensione. L’utente avrà probabilmente più difficoltà a catturare le figure veloci di piccole dimensioni. 8.31 (Calendario/Scadenziario) Create un calendario generico con uno scadenziario. Utilizzare suoni e immagini. Per esempio, il programma dovrebbe suonare “Tanti Auguri” quando l’utente lo apre il giorno del suo compleanno. Il programma deve riprodurre immagini e suoni associati a eventi importanti, e dovrebbe ricordare in anticipo questi eventi all’utente. 8.32 (Immagini rotanti) Create un programma Java che permetta di ruotare un’immagine per un determinato numero di gradi (al massimo 360). Il programma deve permettere di specificare che l’immagine dovrebbe ruotare in continuazione. Il programma deve permettere di regolare la velocità di rotazione in modo dinamico. 8.33 (Colorare immagini in bianco e nero) Create un programma Java che permetta di colorare delle immagini in bianco e nero. Fornite una paletta di colori per la selezione dei colori. Il programma deve permettere di applicare colori diversi alle varie regioni di un’immagine. 8.34 (Simulatore Simpletron multimediale) Modificate il simulatore Simpletron sviluppato nel volume Tecniche di base, al fine di includervi delle funzionalità multimediali. Aggiungete suoni simili a quelli di un computer, per indicare che il Simpletron sta eseguendo le istruzioni. Aggiungete il suono di un vetro che si rompe quando avviene un errore grave. Utilizzate delle luci lampeggianti per indicare quali celle della memoria e/o quali registri sono attualmente in uso. Usate anche altre tecniche multimediali per rendere il simulatore più utile ai fini educativi. 19 - Multimedialità.p65 469 19/12/2003, 9.36