MokaByte 98 - Lug/Ago 2005
 
MokaByte 98 - Lug/Ago 2005 Prima pagina Cerca Home Page

 

 

 

Multimedialità su J2ME
III parte: catturare, salvare e modificare una foto

Questo articolo descrive come catturare una foto o una serie di foto dopo aver settato a proprio piacimento la camera del telefono, come visualizzarle, salvarle su RMS o file system, modificarle ridimensionandole o applicando effetti particolari.

Introduzione
Uno degli usi più comuni che si fanno con i telefoni di terza generazione è quello di scattare fotografie. Il telefono cellulare è infatti un dispositivo sempre presente nelle nostre tasche, e utilizzarlo per immortalare momenti della nostra vita quotidiana è ormai semplice e di uso comune. I telefoni dotati di videocamera hanno ovviamente spesso incluso anche il software che permette di controllarla, per scattare le foto, salvarle e visualizzarle. Tuttavia per tutti gli sviluppatori che abbiano bisogno di controllare la cattura delle foto, gestirne visualizzazione e memorizzazione, effettuare delle elaborazioni o applicare degli effetti grafici, all'interno di un'unica applicazione, questo articolo fornisce i dettagli delle funzionalità attuali e future della piattaforma J2ME.
Come già per l'audio, anche in questo caso la trattazione affronta prima le MMAPI e poi le AMMS.

 

Cattura di una foto con MMAPI
Il primo passo da fare per realizzare una MIDlet che catturi una foto è costruire una classe che estenda la classe Canvas.
La classe Canvas è la classe base per scrivere applicazioni in cui bisogna disegnare sul display o comunque averne un controllo a livello grafico. Per questo le Canvas sono utilizzate soprattutto nell'implementazione di giochi.
Il metodo paint ( ) è dichiarato astratto e deve essere implementato dallo sviluppatore.
Tutto quello che andremo in seguito a descrivere per poter catturare una foto deve essere compreso all'interno di una classe che estenda Canvas.
Il secondo passo per poter catturare le foto consiste nel connettere la MIDlet al dispositivo camera del telefono. L'oggetto di cui abbiamo bisogno lo forniscono le MMAPI ed è il Player.
Esso è ottenuto tramite un Manager e uno speciale locator:

Player player = Manager.createPlayer("capture://video");

Se il dispositivo non supporta la cattura video viene lanciata una MediaException.
Occorre poi che il Player sia nello stato realized per ottenere le risorse di cui ha bisogno per catturare immagini:

player.realize ( );

Per visualizzare sul display del telefono ciò che la camera riprende, utilizziamo un oggetto derivato dall'interfaccia Control, il VideoControl. Per ottenere un VideoControl basta richiederlo al Player:

VideoControl videoControl = (VideoControl)(player.getControl("VideoControl"));

A questo punto inizializziamo il VideoControl secondo la modalità con cui vogliamo che il flusso ripreso venga visualizzato:

videoControl.initDisplayMode(VideoControl.USE_DIRECT_VIDEO,this);

Ora bisogna avviare il Player e rendere visibile sul display il flusso ripreso dalla camera:

player.start();
videoControl.setVisible(true);

Siamo quindi in grado di vedere sul telefono ciò che la camera riprende.
Il successivo step è quello di scattare una foto. Quest'azione sarà legata con tutta probabilità all'uso di un Command.
Ad esempio, possiamo pensare di utilizzare un tasto relativo alla voce "Cattura foto", alla cui pressione venga chiamato il metodo per catturare una foto. Codificando quanto appena detto all'interno del commandAction abbiamo:

public void commandAction (Command c, Displayable d) {
if(c == captureCommand) {
capturePicture();
}
. . .

Il metodo capturePicture ( ) è quello che in effetti cattura la foto e la memorizza in un array di byte.

public void capturePicture() {
if(player != null) {
try {
byte [] pictureData = videoControl.getSnapshot("encoding=jpeg&width=640&height=480");
} catch(MediaException me) {
. . .
}
}
}

La cattura vera e propria viene effettuata dal metodo getSnapshot(String imageType). Le caratteristiche e il formato delle immagini sono specificate nella stringa imageType.
I formati supportati possono essere richiesti con la chiamata System.getProperty ("video.snapshot.encodings"). Nel nostro caso abbiamo richiesto delle foto in formato jpeg, 640x480, ma si possono utilizzare i seguenti tipi di codifica:

video_encodings = video_enc_param *( "&" video_param )
video_enc_param = "encoding=" video_enc
video_enc = "gray8" / "rgb888" / "bgr888" /
"rgb565" / "rgb555" / "yuv444" /
"yuv422" / "yuv420" / "jpeg" / "png" /
content_type
video_param = "width=" width /
"height=" height /
"fps=" fps /
"colors=" colors /
"progressive=" progressive /
"interlaced=" interlaced /
"type=" video_type
width = pos_integer
height = pos_integer
fps = pos_number
quality = pos_integer
colors = "p" colors_in_palette /
= "rgb" r_bits g_bits b_bits /
= "gray" gray_bits
colors_in_palette = pos_integer
r_bits = pos_integer
g_bits = pos_integer
b_bits = pos_integer
gray_bits = pos_integer
progressive = boolean
video_type = jfif / exif / other_type
other_type = alphanumeric
interlaced = boolean
pos_number = 1*DIGIT [ "." 1*DIGIT ]
boolean = "true" / "false"

Una volta catturata la foto si può pensare di salvarla utilizzando un RMS, uploadarla su un server oppure, quando saranno disponibili le File Connection API, che consentiranno l'accesso al file system, di memorizzarla sul telefono. Infine si può desiderare visualizzare la foto. Ci creiamo allora un oggetto Image in questo modo:

Image pictureImg = Image.createImage (pictureData, 0, pictureData.lenght );

Avendo a disposizione un oggetto Image è immediata l'implementazione della visualizzazione in una Canvas o in un Form.

Cattura di una foto con AMMS
Le AMMS come già accennato nell'articolo http://www.mokabyte.it/2005/04/j2me_multimedia.htm
estendono le potenzialità delle MMAPI, e in particolare per la cattura delle immagini implementano uno specifico Control: SnapshotControl.
Oltre a questo però forniscono tutta una serie di controlli della camera del telefono, controlli che ci torneranno utili anche nel prossimo articolo, in cui parleremo di cattura video. Dunque tutti i controlli disponibili ed utilizzabili per la cattura di una foto sono:

  • CameraControl: controlla le caratteristiche della camera del telefono; ad esempio si può abilitare lo shutter (otturatore); si può controllare se la camera è ruotata, si può scegliere la modalità di esposizione e la risoluzione delle immagini.
  • FlashControl: per settare le impostazioni relative al flah, quali la riduzione dell'effetto "occhi rossi" (red-eye reduction).
  • ZoomControl: fornisce un controllo sullo zoom, ottico o digitale
  • ExposureControl: per settare le impostazioni relative all'esposizione, quali l'apertura e la velocità dello shutter e la sensibilità.
  • FocusControl: fornisce un controllo sul focus.
  • SnapshotControl: fornisce un controllo sulla cattura delle immagini, mettendo a disposizione "modalità sequenza", autoscatto, ecc.

Ovviamente questo tipo di controlli presuppongono di aver a disposizione una camera che si avvicina molto ad una vera e propria videocamera digitale. Se quindi, ad esempio, la camera del nostro telefono non ha lo zoom, ovviamente non è possibile utilizzare lo ZoomControl.

Poiché le funzionalità offerte da tutti questi controlli sono molteplici e talvolta complicate da spiegare, vediamo con esempi pratici come utilizzare, combinandoli, i diversi controlli.
Iniziamo utilizzando il CameraControl per abilitare l'otturatore, verificare se la camera è ruotata in qualche direzione, scegliere la modalità di esposizione e la risoluzione delle immagini:

CameraControl camera = (CameraControl) player.getControl("javax.microedition.media.control.camera.CameraControl");
camera.enableShutterFeedback(true);
int rotation = camera.getCameraRotation();
int[] resolutions = camera.getSupportedStillResolutions();
camera.setStillResolution(1);

String[] exposureModes = camera.getSupportedExposureModes();
camera.setExposureMode(exposureModes[1]);
Nel campo fotografico, l'otturatore è fisicamente uno sportello che si apre e si chiude in un tempo ben preciso. All'aumentare della luminosità, l'otturatore elettronico fa aumentare la frequenza della rilevazione, scaricando, dopo ogni rilevazione, i pixel che altrimenti si caricherebbero troppo a causa della gran quantità di luce.
Si noti come, il metodo camera.getCameraRotation() può restituire i seguenti valori: ROTATE_NONE, ROTATE_LEFT, ROTATE_RIGHT o UNKNOWN.
Come risoluzione abbiamo scelto la seconda tra quelle supportate. Le risoluzioni sono espresse come coppie larghezza per altezza. Se ad esempio il telefono supporta per le immagini le risoluzioni (1024 x 768) e (640 x 480), il metodo camera.getSupportedStillResolutions( ) restituirà l'array di interi [1024, 768, 640, 480]; camera.setStillResolution(1) imposterà come risoluzione la seconda coppia disponibile, in questo caso (640 x 480).
Come modalità di esposizione abbiamo scelto la seconda tra quelle supportate, che sarà compresa in questa lista:

  • auto (impostazioni automatiche)
  • landscape (panorama)
  • snow (neve, luce elevata)
  • beach (spiaggia, luce elevata)
  • sunset (tramonto)
  • night (notte)
  • fireworks (fuochi d'artificio)
  • portrait (il viso della persona è al centro)
    backlight (l'oggetto centrale è più buio dello sfondo)
  • spotlight (l'oggetto centrale è più luminoso dello sfondo)
    sports (oggetti che si muovono velocemente)
  • text (per copiare testi e disegni, e per leggere codici a barre)

Per i settaggi del flash utilizziamo FlashControl:

FlashControl flash = (FlashControl)
player.getControl("javax.microedition.media.control.camera.FlashControl");
int[] modes = flash.getSupportedModes();
flash.setMode(FlashControl.AUTO_WITH_REDEYEREDUCE);

Le modalità di flash che possono essere supportate dal dispositivo sono:

  • OFF (spento)
  • AUTO(impostazioni automatiche secondo le condizioni di luce)
  • AUTO_WITH_REDEYEREDUCE (impostazioni automatiche e riduzione dell'effetto occhi rossi)
  • FORCE(acceso)
  • FORCE_WITH_REDEYEREDUCE (acceso e riduzione dell'effetto occhi rossi)
  • FILLIN (flash ridotto)

Vediamo ora come controllare lo zoom. Se sono disponibili sul telefono, possiamo utilizzare zoom ottico e digitale. Lo zoom presenta una scala di valori, che nella configurazione CLDC, sarà rappresentata attraverso degli interi anziché frazioni: perciò 100 sta per 1x, 150 per 1.5x ecc. Vediamo un esempio:

if((ZoomControl zoom = (ZoomControl)
player.getControl("javax.microedition.media.control.camera.ZoomControl")) != null) {

// otteniamo il valore massimo dello zoom ottico, ad esempio 200 per 2x
int max = zoom.getMaxOpticalZoom();

// otteniamo i livelli dello zoom ottico, ad esempio 3 livelli: 1x,1.5x 2x
int levels = zoom.getOpticalZoomLevels();

// settiamo lo zoom ottico al livello più vicino a 1.4x nel nostro caso 1.5x
zoom.setOpticalZoom(140);

// settiamo lo zoom ottico al livello successivo a quello attuale (1.5x) cioè 2x
zoom.setOpticalZoom(ZoomControl.NEXT);

// otteniamo il valore attuale dello zoom digitale (ad esempio 1,5x)
int dig = zoom.getDigitalZoom();

// settiamo lo zoom digitale al livello precedente a quello attuale (1.5x) cioè 1x
zoom.setDigitalZoom(ZoomControl.PREVIOUS);
}

La modalità di esposizione può essere settata anche manualmente, usando l'ExposureControl. L'esposizione è basata su tre componenti: apertura, velocità dell'otturatore, sensibilità.
L'apertura è la dimensione dell'iride che lascia passare la luce incidente sul sensore dell'immagine. Tale dimensione dipende dal diametro dell'iride e dalla lunghezza focale. La lunghezza focale definisce il cono della luce incidente. L'apertura nei dispositivi analogici e digitali viene espressa tramite un numero "F-Stop". L'F-Stop (f/#) è il rapporto tra la lunghezza focale e il diametro dell'iride. Valori comuni dell'apertura sono f/1, f/1.4, f/2, f/2.8, f/4, f/5.6, f/8. f/11, f/16, f/22. Nel nostro caso useremo ancora interi al posto di frazioni e quindi, ad esempio, 280 sarà f/2.8.
La velocità dell'otturatore, nota anche come tempo di esposizione, è il tempo che l'otturatore è aperto per ricevere luce. Esso è solitamente espresso in frazioni di secondo. Valori tipici sono 1, 1/2, 1/4, 1/8, 1/15, 1/30, 1/60, 1/125, 1/250, 1/500, 1/1000 e 1/2000. Nel nostro caso esprimeremo il tempo di esposizione in microsecondi perciò 1/1000 cioè 1ms sarà 1000 microsec.
Per sensibilità si intende la sensibilità della pellicola o del sensore alla luce. Viene espressa in ISO 35mm film equivalenti. Valori tipici sono ISO 100, ISO 200 e ISO 400.
Vediamo un esempio:


if((ExposureControl exposure = (ExposureControl)
player.getControl("javax.microedition.media.control.camera.ExposureControl"))
!= null) {
exposure.setExposureTime(1000);
exposure.setFStop(280);
exposure.setISO(200);
}

FocusControl viene utilizzato per controllare il "focus" della camera. Con il metodo setFocus (int distance) possiamo settare la modalità autofocus (FocusControl.AUTO) oppure fornire la distanza focale in millimetri, o ancora, settare il fuoco all'infinito (Integer.MAX_VALUE):

FocusControl focus = (FocusControl)
player.getControl("javax.microedition.media.control.camera.FocusControl");
if(focus.isAutoFocusSupported()) {
focus.setFocus(FocusControl.AUTO);
} else {
int focusSet = focus.setFocus(Integer.MAX_VALUE);
}

Infine vediamo finalmente come catturare una foto o una serie di foto in modalità "burst shooting", utilizzando SnapshotControl. Le foto vengono salvate nel file system. Prima di tutto occorre chiamare il metodo setDirectory per settare la directory dove salvare le immagini. I file vengono salvati se si hanno i diritti di scrittura su quella directory. Nel caso che non si abbiano verrà restituito uno STORAGE_ERROR.
La struttura del nome del file creato è <prefix><auto generated string><suffix>, dove "prefix" è una stringa di prefisso fornita dall'utente, "auto generated string" è una stringa generata automaticamente formata inizialmente da 4 cifre che vengono incrementate da 0000 a 9999 per poi eventualmente passare a 5 cifre (…10000), "suffix" è una stringa di suffisso fornita dall'utente.
Se nella directory selezionata esiste già un file con lo stesso nome di quello che si cerca di memorizzare viene generato uno STORAGE_ERROR e la cattura termina.
Per permettere di eseguire altre azioni durante la cattura si può utilizzare il PlayerListener delle MMAPI. In questo modo al verificarsi di un certo evento collegato alla cattura multipla delle immagini, viene effettuata una certa azione.
Vediamo il seguente esempio:

SnapshotControl snapshot = (SnapshotControl)
player.getControl("javax.microedition.media.control.camera.SnapshotControl");
snapshot.setDirectory("/MyPhotos");
snapshot.setFilePrefix("Vacanza");
snapshot.setFileSuffix(".jpg");

// Vengono catturate 20 immagini

snapshot.start(20);

/* Viene catturata un'immagine e congelata la visualizzazione fino a che non viene chiamato il metodo unfreeze(false). A questo punto, poiché il PlayerListener riceve un evento WAITING_UNFREEZE, nel metodo playerUpdate si può implementare la richiesta all'utente di salvare o cancellare la foto e quindi effettuare la chiamata snapshot.unfreeze(false) */

snapshot.start(ShapshotControl.FREEZE_AND_CONFIRM);

 

Elaborazioni sulle immagini con AMMS
AMMS fornisce anche la possibilità di manipolare immagini o le stesse foto precedentemente catturate.
I controlli disponibili sono:

  • ImageFormatControl: controlla i settaggi relativi al formato delle immagini;
  • ImageEffectControl: si tratta di un filtro per rendere l'immagine monocromatica, farne il negativo, ecc.
  • ImageTonalityControl: controlla i settaggi relativi a luminosità, contrasto e gamma dell'immagine;
  • ImageTransformControl: fornisce una serie di effetti quali zoom, crop, mirror, rotazione, ecc.
  • WhiteBalanceControl: controlla il bilanciamento del bianco.

Per poter manipolare un'immagine dobbiamo innanzi tutto creare un MediaProcessor attraverso un GlobalManager e inizializzarlo:

MediaProcessor processor = GlobalManager.createMediaProcessor("image/jpeg");
InputStream inputStream = // InputStream che contiene l'immagine sorgente jpeg OutputStream outputStream = // OutputStream che conterrà l'immagine di output
processor.setInput(inputStream);
processor.setOutput(outputStream);
processor.start():

Ora vediamo come utilizzare ImageFormatControl. I formati di immagini che si possono settare sono presenti nella seguente tabella:

Vediamo come cambiare il formato dell'immagine jpeg su cui abbiamo prima costruito il MediaProcessor, in png:

ImageFormatControl imageFormat = (ImageFormatControl)
processor.getControl("javax.microedition.media.control.ImageFormatControl");
imageFormat.setFormat("image/png");
processor.complete();

Per quanto riguarda ImageEffectControl, possono essere applicati alle immagini i seguenti effetti, di cui i primi due sicuramenti supportati, gli altri opzionali (secondo il dispositivo):

  • "monochrome": l'immagine viene resa monocromatica;
  • "negative": effetto "negativo".
  • "emboss": effetto stampa in rilievo su carta;
  • "sepia": effetto seppia;
  • "solarize": effetto solarizzazione;
  • "redeyereduction": rimozione effetto occhi rossi

Esempio:

if((ImageEffectControl imageEffect = (ImageEffectControl)processor.getControl
("javax.microedition.media.control.imageeffect.ImageEffectControl"))
!= null) {
imageEffect.setPreset("monochrome");
processor.complete();
}

Come già detto ImageTonalityControl serve a cambiare i settaggi relativi a luminosità, contrasto e gamma dell'immagine.
La luminosità è il valore medio della luce di un'immagine.
Il contrasto è la differenza tra l'elemento più luminoso e quello più buio dell'immagine.
Il parametro gamma è la curvatura della diagramma della luminosità dopo che viene applicato questo effetto. Un valore più alto di gamma rende i pixel di media luminosità più luminosi, mentre un valore più basso di gamma rende tali pixel più bui. Alti valori di gamma possono essere usati, ad esempio, per rendere oggetti in ombra più luminosi o per mandenere bui i pixel più bui.
I valori di luminosità, contrasto e gamma vanno da 0 a 100. Vediamo un esempio:

if((ImageTonalityControl imageTonality = (ImageTonalityControl)processor.getControl
("javax.microedition.media.control.imageeffect.ImageTonalityControl"))
!= null) {
imageTonality.setBrightness(30);
imageTonality.setContrast(60);
imageTonality.setGamma(45);
processor.complete();
}

ImageTransformControl è usato per tagliare, capovolgere, estendere, ruotare le immagini, applicare gli effetti zoom o mirror. Per effettuare queste trasformazioni si utilizza un'unica procedura, che consiste nel chiamare i due metodi:
setSourceRect(int x, int y, int width, int height)
setTargetSize(int width, int height, int rotation)
In pratica si seleziona un rettangolo sull'immagine originale e le caratteristiche dell'immagine risultante.
Se viene selezionata l'intera immagine di partenza si può ad esempio estenderla (effetto stretching) specificando per l'immagine risultante delle dimensioni maggiori:

setSourceRect( 0, 0, origWidth, origHeight);
setTargetSize( origWidth + 130, origHeight + 130, 0); // dimensione maggiore di 130 pixel


Figura 1
- Stretching dell'immagine

 

Per capovolgere verticalmente l'immagine, l'altezza dell'immagine originale deve essere negativa:

setSourceRect( 0, origHeight, origWidth, -origHeight);
setTargetSize( 0, 0, 0);


Figura 2
- Capovolgimento dell'immagine

Analogamente si realizzano le altre trasformazioni: per tagliare l'immagine il rettangolo sorgente sarà una parte dell'immagine originale e l'immagine destinazione sarà della stessa dimensione di tale rettangolo; per lo zoom vale quanto detto tranne per il fatto che il ritaglio viene ingrandito.

Concludiamo con WhiteBalanceControl, utilizzato per cambiare il bilanciamento del colore bianco, secondo i seguenti programmi:

  • sunlight (luce solare)
  • cloudy (nuvoloso)
  • shade (ombra)
  • tungsten (illuminazione a filamento di tungsteno)
  • fluorescent (illuminazione con tubo fluorescente)
  • flash (modalità di bilanciamento del bianco ottimo per il flash del dispositivo)

Esempio d'uso:

if((WhiteBalanceControl white = (WhiteBalanceControl)player.getControl("
                  javax.microedition.media.control.camera.WhiteBalanceControl")) != null) {
  white.setPreset("tungsten");
}


Conclusioni
Catturare foto con il proprio telefono utilizzando una MIDlet, è un'operazione che potrebbe risultare molto utile in un più ampio progetto che preveda il controllo, anche in termini di temporizzazione, della camera del proprio telefono.
Le AMMS ci permettono, qualora avessimo un telefono con una camera molto sofisticata, di settarla in modo quasi professionale, per effettuare delle foto personalizzate e di altissima qualità , da modificare secondo le proprie esigenze, o alle quali applicare effetti in stile PhotoShop per realizzare delle piccole creazioni grafiche, da inviare magari via MMS.

 

Bibliografia
[1] http://jcp.org/en/jsr/detail?id=234
[2] http://java.sun.com/products/mmapi/index.jsp
[3] http://java.sun.com/products/midp/index.jsp


Vincenzo Viola
è nato a Formia (LT) il 03/11/1977, laureato in Ingegneria delle Telecomunicazioni presso l'Università Federico II di Napoli con una tesi sulla segmentazione automatica di video digitali in scene, con sviluppo software implementato su piattaforma Java - Oracle. Lavora come progettista software per una Mobile Company che offre la sua consulenza ai gestori di telefonia e alle major del settore ICT.