Una delle maggiori limitazioni di Flash, dovuta essenzialmente alla sicurezza, è sempre stata quella della lettura e soprattutto del salvataggio di file: per queste operazioni si affiancava all'applicazione Flash un supporto lato server.
La classe FileReference del Flash Player 10 - che già veniva usata per l'upload in combinata con linguaggi server-side nelle ultime versioni - è stata potenziata ed è ora possibile usarla anche per salvare dati sul computer dell'utente senza la necessità di passare da altri linguaggi.
Ovviamente per motivi di sicurezza l'operazione deve essere confermata dall'utente, che sceglierà anche il percorso locale del file.
Si tratta di una caratteristica molto interessante, specie se unita con alcune librerie esterne come la Actionscript 3 Core Library, sviluppata da Adobe e che include delle classi per l'encoding in JPG o in PNG.
L'accoppiata ci permetterebbe, ad esempio, di prendere uno screenshot della nostra applicazione (o di un componente) e salvarlo in formato JPG o PNG sul pc dell'utente. Per ottenere lo stesso comportamento con Flash CS3 e Flash Player 9 avremmo dovuto passare le informazioni dallo stage ad una applicazione lato server, che si sarebbe poi occupata di convertire i dati ricevuti da Flash in immagine.
Ora tutto è molto più semplice e in questo articolo creeremo una piccola applicazione che permetterà all'utente di caricare un'immagine, elaborarla in Flash e salvarla poi modificata, il tutto sfruttando la classe FileReference e la Actionscript 3 Core Library.
L'applicazione che svilupperemo nell'articolo
Caricare un file dal pc dell'utente
È bene precisare innanzitutto che il caricamento del file non comporta un parallelo upload del file sul server, ma il file viene letto e i caricato all'interno del player in un oggetto di tipo ByteArray
, classe anch'essa introdotta in Actionscript 3. Per motivi di sicurezza infatti il player riceve solo il nome e i contenuti del file ma non ad esempio il path in cui è memorizzato.
Anzitutto creiamo un pulsante sul nostro stage e gli diamo nome istanza carica
; questo pulsante dovrà aprire la finestra di scelta del file da caricare nella nostra applicazione. Il codice da utilizzare è il seguente:
var _loadFile:FileReference; carica.addEventListener(MouseEvent.CLICK, caricaFile); function caricaFile(evt:MouseEvent) { // creiamo l'oggetto FileReference _loadFile = new FileReference(); // associamo la funzione scegliFile alla selezione _loadFile.addEventListener(Event.SELECT, scegliFile); // impostiamo il filtro che permetta la scelta di sole immagini var filtro:FileFilter = new FileFilter("Immagini: (*.jpeg, *.jpg, *.gif, *.png)", "*.jpeg; *.jpg; *.gif; *.png"); // apriamo la finestra di scelta _loadFile.browse([filtro]); } function scegliFile(evt:Event) { // azioni successive alla scelta del file }
I commenti dovrebbero essere sufficienti a comprendere il codice, fin qui non c'è nulla di complesso, semplicemente sfruttiamo il metodo browse
della classe FileReference
per aprire una finestra di scelta del file. Tale metodo accetta un parametro che possiamo usare per impostare dei filtri sulla scelta dei file, in questo caso la variabile filtro
(di tipo FileFilter
) permetterà la scelta di sole immagini nei formati jpg
, gif
o png
(gli unici che il player legge nativamente).
Una volta che l'utente avrà selezionato un file, sarà chiamata la funzione scegliFile
, che si occuperà di caricare i dati del file nel player:
function scegliFile(evt) { // rimuoviamo l'evento di scelta file non più necessario _loadFile.removeEventListener(Event.SELECT, scegliFile); // creiamo il listener per l'evento COMPLETE che verrà // lanciato una volta completato il caricamento del file _loadFile.addEventListener(Event.COMPLETE, fileCaricato); // carichiamo il file all'interno del player _loadFile.load(); } function fileCaricato(evt:Event) { // azioni da eseguire a caricamento completato }
Questa funzione carica il file grazie al metodo load
e una volta completato il caricamento (e viene scatenato l'evento COMPLETE
), lancia la funzione fileCaricato
. Poichè non è più necessario abbiamo rimosso il listener relativo all'evento di scelta del file.
Leggere il file caricato in memoria
Esaminiamo ora come leggere i dati caricati. L'oggetto FileReference
possiede la proprietà 'data'di tipo ByteArray
che contiene i dati binari del file selezionato dall'utente. Quindi, per utilizzare questi dati, possiamo sfruttare i metodi della classe ByteArray
.
Nel nostro caso abbiamo a che fare con un file jpg
, gif
o png
, quindi dati direttamente interpretabili dal dal Flash Player. Questo ci evita la necessità di implementare un parser intermedio e ci permette di limitarci a mostrare l'immagine caricata sullo stage.
Anche questa volta facciamo uso di un evento, infatti utilizziamo un oggetto Loader
per ottenere i dati contenuti nell'oggetto _loadFile
e attendiamo che il trasferimento sia concluso.
function fileCaricato(evt:Event) { // rimuoviamo il listener non più necessario _loadFile.removeEventListener(Event.COMPLETE, fileCaricato); // creiamo l'oggetto Loader che caricherà l'immagine var immagine:Loader = new Loader(); // impostiamo il listener da eseguire a fine // caricamento dell'immagine immagine.contentLoaderInfo.addEventListener(Event.COMPLETE, immaginePronta); // carichiamo i bytes dall'oggetto ByteArray // all'interno del Loader immagine.loadBytes(evt.target.data); }
Dopo aver fatto pulizia del listener non più necessario, creiamo il nostro oggetto Loader
. È utile sottolineare l'utilizzo del metodo loadBytes
: stiamo caricando i dati binari ricavati dal file scelto dall'utente, ed è per questo che non usiamo il più diffuso metodo load
, a cui viee associato l'URL da caricare, ma sfruttiamo quest'altro metodo che carica i byte.
Al termine del caricamento, sarà scatenato l'evento COMPLETE
e lanciata la funzione immaginePronta
. In questa funzione aggiungiamo prima uno sprite sullo stage, che farà da contenitore dell'immagine e poi l'immagine da esso contenuta.
Utilizziamo proprio questo sprite per modificare graficamente l'immagine, non sarebbe infatti possibile operare direttamente sul Loader
. Ecco quindi le poche righe di codice necessarie all'operazione:
// dichiariamo la variabile relativa allo sprite var contenitore:Sprite; function immaginePronta(evt:Event) { // rimuoviamo il listener non pu necessario LoaderInfo(evt.target).removeEventListener(Event.COMPLETE,immaginePronta); // creiamo lo sprite contenitore = new Sprite(); // aggiungiamo l'immagine caricata allo sprite contenitore.addChild(evt.target.content); // aggiungiamo lo sprite allo stage addChild(contenitore); }
Anche questa volta rimuoviamo il listener, ma in questo caso per poter rimuovere l'evento abbiamo dovuto ricavare il target dell'evento come oggetto LoaderInfo
.
Il resto è molto semplice: dichiariamo il contenitore
, di tipo Sprite
e vi inseriamo l'immagine (contenuta nella proprietà content
del target dell'evento). Infine aggiungiamo il contenitore allo stage.
Potremmo voler applicare un ridimensionamento dell'immagine per evitare che possa essere troppo grossa per la nostra interfaccia, in questo caso basterà agire sulle proprietà width
ed height
dell'oggetto contenitore
.
Modifica dell'immagine
Proprio l'oggetto contenitore
sarà quello che sfrutteremo anche per applicare le altre modifiche alla nostra immagine. Poichè scopo dell'articolo è vedere il caricamento e il salvataggio del file, non faremo particolari operazioni sull'immagine ma ovviamente si può ampliare a proprio piacimento l'applicazione.
In questo caso ci limteremo a convertire l'immagine in scala di grigio, sfruttando il ColorMatrixFilter
; anche questa opzione la associamo ad un pulsante (con nome istanza modifica
).
modifica.addEventListener(MouseEvent.CLICK,modificaImmagine); function modificaImmagine(evt:MouseEvent) { // matrice per la trasformazione in sacala di grigio var grigi:Array = [0.33,0.33,0.33,0,0,0.33,0.33,0.33,0,0,0.33,0.33,0.33,0,0,0,0,0,1,0]; // creazione del ColorMatrixFilter var matriceColore:ColorMatrixFilter = new ColorMatrixFilter(grigi); // associazione della matrice allo sprite contenitore.filters = [matriceColore]; }
Salvare l'immagine modificata
Eccoci quindi alla novità più interessante, ovvero la possibilità di salvare il file. In questo caso, trattandosi di un'immagine dovremo ricavare l'immagine modificata in scala di grigi, convertirla grazie all'encoder della Actionscript 3 Core Library e infine salvarla, ovviamente per altri file e altri formati i passaggi "pre-salvataggio" potrebbero variare.
Esaminiamo il codice per questi passaggi, considerando che l'unico "fisso" da riusare anche in altri casi per salvare un file sarà solo quello relativo al nome del file all'oggetto FileReference salvaFile
.
// importiamo l'encoder import com.adobe.images.JPGEncoder; salva.addEventListener(MouseEvent.CLICK,salvaImmagine); function salvaImmagine(Evt:MouseEvent) { // copiamo dallo sprite l'immagine modificata var immagineModificata:BitmapData = new BitmapData(contenitore.width,contenitore.height); immagineModificata.draw(contenitore,contenitore.transform.matrix); // creiamo il bytearray che conterrà l'immagine var immagineCodificata:ByteArray; // creiamo il JPGEncoder che codificherà l'immagine var jpgEncoder:JPGEncoder = new JPGEncoder(85); // inseriamo nelByteArray l'immagine codificata immagineCodificata = jpgEncoder.encode(immagineModificata); // creiamo l'espressione regolare per il nome del file var expNomeFile:RegExp = /^(?P<fileName>.*)..*$/; // aggiungiamo al nome originale del file il suffisso _gray var NomeFile:String = expNomeFile.exec(_loadFile.name).fileName + "_gray"; // quindi l'estensione .jpg nomeFile+= ".jpg"; // creiamo l'oggetto FileReference per il salvataggio del file var salvaFile:FileReference = new FileReference(); // impostiamo i listener per salvataggio completo ed errore salvaFile.addEventListener(Event.COMPLETE, fileSalvato); salvaFile.addEventListener(IOErrorEvent.IO_ERROR, erroreSalvataggio); // salviamo il file salvaFile.save(immagineCodificata, nomeFile); } function fileSalvato(evt:Event) { trace("File salvato con successo"); } function erroreSalvataggio(evt:ErrorEvent) { trace("Errore nel salvataggio"); }
Per prima cosa abbiamo importato il JPGEncoder
ricavato dalla Actionscript 3 Core Library (sono varie classi ed è possibile copiare nel proprio progetto solo la JPGEncoder
, non è necessario averne altre nel nostro caso), quindi all'interno della funzione salvaImmagine
(associata al click sul pulsante salva
) eseguiamo diverse operazioni.
Per prima cosa creiamo un oggetto BitmapData
delle dimensioni dello sprite contenitore
, quindi vi disegniamo lo stesso sprite; notiamo che abbiamo inserito come secondo parametro la matrice di trasformazione di contenitore
, questo perchè altrimenti non avremmo copiato la versione "modificata" ma l'originale (quindi senza nessun cambio di colore e partendo dalla dimensione originale).
Dopo aver preparato l'immagine, impostiamo l'oggetto immagineCodificata
(di tipo ByteArray
), che conterrà il risultato della codifica fatta tramite il JPGEncoder
(notiamo che in questo caso abbiamo impostato per l'immagine una qualità di 85
, il range va da 1
a 100
), quindi impostiamo il nome del file ricavando il nome originale, aggiungendo _gray
e infine l'estensione (in questo caso .jpg
). L'espressione regolare expNomeFile
regola l'input che l'utente può inserire per il nome del file.
Come ultima operazione, creiamo l'oggetto salvaFile
(di tipo FileReference
), impostiamo due listener (uno per il completamento del salvataggio e uno per gli eventuali errori) e usando il metodo save
salviamo il file nella posizione scelta dall'utente, come il metodo browser
infatti anche il metodo save
aprirà una finestra con la quale scegliere la posizione in cui salvare il file e specificare un nome eventualmente diverso da quello impostato automaticamente.
Conclusioni
Abbiamo visto come la classe FileReference sia stata ulteriormente potenziata, inoltre grazie all'abbinamento con la classe ByteArray permette di estendere notevolmente le feature del player (ad esempio recentemente Lee Brimelow ha riportato sul suo blog alcuni esempi tra cui una classe per caricare file PSD in Flash). Queste caratteristiche aumentano sempre più le potenzialità di Flash in campo RIA, inoltre librerie come la Actionscript 3 Core Library consentono di eseguire facilmente operazioni come l'encoding in PNG o in JPEG, usando poche righe di codice.
La possibilità di caricare e salvare file senza l'ausilio di linguaggi server-side consente migliori performance (il passaggio dell'immagine al linguaggio server-side e il salvataggio tramite le librerie grafiche di quest'ultimo è più complesso del gestire il tutto all'interno del player) e permettono anche una più semplice diffusione dei propri lavori: prima del Flash Player 10 sarebbe stato necessario un hosting con supporto ASP, PHP o altro per supportare questo tipo di applicazione, mentre ora può essere usato praticamente qualsiasi hosting.