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.
|