Orientamenti della videocamera

Se la tua app per Android utilizza le videocamere, ci sono alcune considerazioni speciali da tenere presente quando gestisci gli orientamenti. Questo documento presuppone che tu conosca i concetti di base dell'API Android camera2. Per una panoramica di camera2, puoi leggere il nostro post del blog o il nostro riepilogo. Ti consigliamo inoltre di provare a scrivere prima un'app fotocamera prima di consultare questo documento.

Sfondo

La gestione degli orientamenti nelle app fotocamera per Android è complessa e deve tenere conto dei seguenti fattori:

  • Orientamento naturale: l'orientamento del display quando il dispositivo è nella posizione "normale" per il design del dispositivo, in genere l'orientamento verticale per i cellulari e l'orientamento orizzontale per i laptop.
  • Orientamento del sensore: l'orientamento del sensore montato fisicamente sul dispositivo.
  • Rotazione del display: di quanto il dispositivo è ruotato fisicamente rispetto all'orientamento naturale.
  • Dimensioni del mirino: le dimensioni del mirino utilizzato per visualizzare l'anteprima della videocamera.
  • Dimensioni dell'immagine di output della videocamera.

La combinazione di questi fattori introduce un gran numero di possibili configurazioni dell'interfaccia utente e dell'anteprima per le app della videocamera. Questo documento ha lo scopo di mostrare agli sviluppatori come gestire correttamente gli orientamenti della videocamera nelle app per Android.

Per semplificare un po' le cose, supponi che tutti gli esempi riguardino una videocamera posteriore, se non diversamente indicato. Inoltre, tutte le foto seguenti sono simulate per rendere le illustrazioni visivamente più chiare.

Tutto sugli orientamenti

Orientamento naturale

L'orientamento naturale è definito come l'orientamento del display quando il dispositivo si trova nella posizione in cui è normalmente previsto. Per gli smartphone, l'orientamento naturale è spesso verticale. In altre parole, gli smartphone hanno una larghezza inferiore e un'altezza maggiore. Per i laptop, l'orientamento naturale è orizzontale, il che significa che hanno larghezze maggiori e altezze minori. I tablet sono un po' più complicati: possono essere orientati in verticale o in orizzontale.

Illustrazione dell'orientamento naturale con uno smartphone, un laptop e un oggetto dal lato dell'osservatore

Orientamento del sensore

In termini tecnici, l'orientamento del sensore viene misurato in base ai gradi di rotazione in senso orario necessari per allineare un'immagine di output del sensore all'orientamento naturale del dispositivo. In altre parole, l'orientamento del sensore è il numero di gradi di rotazione in senso antiorario prima di essere montato sul dispositivo. Quando guardi lo schermo, la rotazione sembra avvenire in senso orario, perché il sensore della fotocamera posteriore è installato sul lato "posteriore" del dispositivo.

Secondo la definizione di compatibilità di Android 10 7.5.5 Orientamento della fotocamera, le fotocamere anteriore e posteriore "DEVONO essere orientate in modo che la dimensione lunga della fotocamera sia allineata alla dimensione lunga dello schermo".

I buffer di output delle videocamere sono di dimensioni orizzontali. Poiché l'orientamento naturale degli smartphone è in genere verticale, l'orientamento del sensore è in genere di 90 o 270 gradi rispetto all'orientamento naturale, in modo che il lato lungo del buffer di output corrisponda al lato lungo dello schermo. L'orientamento del sensore è diverso per i dispositivi il cui orientamento naturale è orizzontale, come i Chromebook. Su questi dispositivi, i sensori di immagine sono posizionati in modo che il lato lungo del buffer di output corrisponda al lato lungo dello schermo. Poiché hanno entrambi dimensioni orizzontali, gli orientamenti corrispondono e l'orientamento del sensore è di 0 o 180 gradi.

Illustrazione dell'orientamento naturale con uno smartphone, un laptop e un oggetto dal lato dell'osservatore

Le seguenti illustrazioni mostrano l'aspetto delle cose dal punto di vista di un osservatore che guarda lo schermo del dispositivo:

Illustrazione dell'orientamento del sensore con uno smartphone, un laptop e un oggetto dal lato dell'osservatore

Considera la seguente scena:

Una scena con una simpatica statuetta di Android (bugdroid)

Telefono Laptop
Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di uno smartphone Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di un laptop

Poiché l'orientamento del sensore è in genere di 90 o 270 gradi sugli smartphone, senza tener conto dell'orientamento del sensore, le immagini che otterresti avrebbero questo aspetto:

Telefono Laptop
Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di uno smartphone Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di un laptop

Supponiamo che l'orientamento del sensore in senso antiorario sia memorizzato nella variabile sensorOrientation. Per compensare l'orientamento del sensore, devi ruotare i buffer di output di `sensorOrientation` in senso orario per riallineare l'orientamento con quello naturale del dispositivo.

In Android, le app possono utilizzare TextureView o SurfaceView per visualizzare l'anteprima della fotocamera. Entrambi possono gestire l'orientamento del sensore se le app li utilizzano correttamente. Nelle sezioni seguenti spiegheremo in dettaglio come tenere conto dell'orientamento del sensore.

Rotazione del display

La rotazione del display è definita formalmente dalla rotazione della grafica disegnata sullo schermo, che è la direzione opposta alla rotazione fisica del dispositivo rispetto al suo orientamento naturale. Le sezioni seguenti presuppongono che le rotazioni del display siano tutte multipli di 90. Se recuperi la rotazione del display in gradi assoluti, arrotondala al valore più vicino tra {0, 90, 180, 270}.

"Orientamento del display" nelle sezioni seguenti si riferisce al fatto che un dispositivo sia tenuto fisicamente in posizione orizzontale o verticale ed è diverso da "rotazione del display".

Supponiamo di ruotare i dispositivi di 90 gradi in senso antiorario rispetto alle posizioni precedenti, come mostrato nella figura seguente:

Illustrazione della rotazione del display di 90 gradi con uno smartphone, un laptop e un oggetto dal lato dell'osservatore

Supponendo che i buffer di output siano già ruotati in base all'orientamento del sensore, avrai i seguenti buffer di output:

Telefono Laptop
Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di uno smartphone Illustrazione dell'immagine vista attraverso il sensore della fotocamera posteriore di un laptop

Se la rotazione del display è memorizzata nella variabile displayRotation, per ottenere l'immagine corretta devi ruotare i buffer di output in senso antiorario in base a displayRotation.

Per le videocamere anteriori, la rotazione del display agisce sui buffer delle immagini nella direzione opposta rispetto allo schermo. Se hai a che fare con una fotocamera anteriore, devi ruotare i buffer in senso orario in base a displayRotatation.

Avvertenze

La rotazione del display misura la rotazione in senso antiorario del dispositivo. Ciò non vale per tutte le API di orientamento/rotazione.

Ad esempio,

La cosa importante da notare qui è che la rotazione del display è relativa all'orientamento naturale. Ad esempio, se ruoti fisicamente uno smartphone di 90 o 270 gradi, lo schermo assume una forma orizzontale. Se ruoti un laptop dello stesso angolo, otterrai uno schermo a forma di ritratto. Le app devono sempre tenerlo presente e non fare mai ipotesi sull'orientamento naturale di un dispositivo.

Esempi

Utilizziamo le figure precedenti per illustrare cosa sono gli orientamenti e le rotazioni.

Illustrazione dell'orientamento combinato con uno smartphone e un laptop non ruotati e un oggetto

Telefono Laptop
Orientamento naturale = verticale Orientamento naturale = orizzontale
Orientamento sensore = 90 Orientamento del sensore = 0
Rotazione del display = 0 Rotazione del display = 0
Orientamento del display = Verticale Orientamento del display = Orizzontale

Illustrazione dell'orientamento combinato con uno smartphone e un laptop non ruotati e un oggetto

Telefono Laptop
Orientamento naturale = verticale Orientamento naturale = orizzontale
Orientamento sensore = 90 Orientamento del sensore = 0
Rotazione display = 90 Rotazione display = 90
Orientamento del display = Orizzontale Orientamento del display = Verticale

Dimensioni del mirino

Le app devono sempre ridimensionare il mirino in base all'orientamento, alla rotazione e alla risoluzione dello schermo. In generale, le app devono rendere l'orientamento del mirino identico all'orientamento di visualizzazione corrente. In altre parole, le app devono allineare il lato lungo del mirino con il lato lungo dello schermo.

Dimensioni dell'output dell'immagine per fotocamera

Quando scegli le dimensioni dell'output dell'immagine per l'anteprima, dovresti scegliere una dimensione uguale o leggermente superiore a quella del mirino, se possibile. In genere, non vuoi che i buffer di output vengano scalati, perché ciò causerebbe la pixelizzazione. Inoltre, non devi scegliere una dimensione troppo grande, perché potrebbe ridurre le prestazioni e consumare più batteria.

Orientamento JPEG

Iniziamo con una situazione comune: l'acquisizione di una foto JPEG. Nell'API camera2, puoi passare JPEG_ORIENTATION nella richiesta di acquisizione per specificare di quanto vuoi ruotare in senso orario i JPEG di output.

Un breve riepilogo di ciò che abbiamo menzionato:

  • Per gestire l'orientamento del sensore, devi ruotare il buffer dell'immagine di sensorOrientation in senso orario.
  • Per gestire la rotazione del display, devi ruotare un buffer di displayRotation in senso antiorario per le videocamere posteriori e in senso orario per le videocamere anteriori.

Sommando i due fattori, l'importo che vuoi ruotare in senso orario è

  • sensorOrientation - displayRotation per le fotocamere posteriori.
  • sensorOrientation + displayRotation per le fotocamere anteriori.

Puoi visualizzare il codice campione per questa logica nella documentazione JPEG_ORIENTATION. Tieni presente che deviceOrientation nel codice di esempio della documentazione utilizza la rotazione in senso orario del dispositivo. Pertanto, i segni per la rotazione del display sono invertiti.

Anteprima

Che ne dici dell'anteprima della videocamera? Esistono due modi principali in cui un'app può visualizzare un'anteprima della fotocamera: SurfaceView e TextureView. Ognuno richiede approcci diversi per gestire correttamente l'orientamento.

SurfaceView

SurfaceView è generalmente consigliata per le anteprime della videocamera, a condizione che non sia necessario elaborare o animare i buffer di anteprima. È più performante e richiede meno risorse rispetto a TextureView.

Anche SurfaceView è relativamente più facile da disporre. Devi preoccuparti solo delle proporzioni della SurfaceView su cui visualizzi l'anteprima della videocamera.

Fonte

Sotto SurfaceView, la piattaforma Android ruota i buffer di output in modo che corrispondano all'orientamento del display del dispositivo. In altre parole, tiene conto sia dell'orientamento del sensore sia della rotazione del display. Per semplificare ulteriormente, quando il display è orizzontale, viene visualizzata un'anteprima anch'essa orizzontale e viceversa per la modalità verticale.

Ciò è illustrato nella tabella seguente. La cosa importante da tenere presente è che la rotazione del display da sola non determina l'orientamento della sorgente.

Rotazione del display Smartphone (orientamento naturale = verticale) Laptop (orientamento naturale = orizzontale)
0 Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto
90 Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto
180 Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto
270 Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto

Layout

Come puoi vedere, SurfaceView gestisce già alcune delle cose più difficili per noi. Ora devi considerare le dimensioni del mirino o quanto vuoi che sia grande l'anteprima sullo schermo. SurfaceView ridimensiona automaticamente il buffer di origine in base alle sue dimensioni. Devi assicurarti che le proporzioni del mirino siano identiche a quelle del sourcebuffer. Ad esempio, se provi a inserire un'anteprima a forma di ritratto in una SurfaceView a forma orizzontale, otterrai un risultato distorto come questo:

Illustrazione che mostra un bugdroid allungato a causa dell'adattamento di un'anteprima a forma di ritratto in un mirino a forma orizzontale

In genere, le proporzioni (ovvero larghezza/altezza) del mirino devono essere identiche a quelle della sorgente. Se non vuoi ritagliare l'immagine nel mirino, tagliando alcuni pixel per correggere la visualizzazione, devi considerare due casi: quando aspectRatioActivity è maggiore di aspectRatioSource e quando è minore o uguale a aspectRatioSource.

aspectRatioActivity > aspectRatioSource

Puoi considerare la richiesta come un'attività "più ampia". Di seguito viene riportato un esempio in cui hai un'attività in formato 16:9 e una sorgente in formato 4:3.

aspectRatioActivity = 16/9 ≈ 1.78
aspectRatioSource = 4/3 ≈ 1.33

Innanzitutto, il mirino deve essere in formato 4:3. Dopodiché, inserisci l'origine e il mirino nell'attività come segue:

Illustrazione di un'attività il cui aspect ratio è maggiore di quello del mirino all'interno

In questo caso, devi fare in modo che l'altezza del mirino corrisponda all'altezza dell'attività, mentre le proporzioni del mirino devono essere identiche a quelle della sorgente. Lo pseudocodice è il seguente:

viewfinderHeight = activityHeight;
viewfinderWidth = activityHeight * aspectRatioSource;
aspectRatioActivity ≤ aspectRatioSource

L'altro caso è quando l'attività è "più stretta" o "più alta". Possiamo riutilizzare l'esempio precedente, tranne che in questo esempio ruoti il dispositivo di 90 gradi, rendendo l'attività 9:16 e la sorgente 3:4.

aspectRatioActivity = 9/16 = 0.5625
aspectRatioSource = 3/4 = 0.75

In questo caso, devi inserire la sorgente e il mirino nell'attività nel seguente modo:

Illustrazione di un'attività le cui proporzioni sono inferiori a quelle del mirino all'interno

Devi fare in modo che la larghezza del mirino corrisponda a quella dell'attività (anziché all'altezza, come nel caso precedente), mentre le proporzioni del mirino devono essere identiche a quelle della sorgente. Pseudocodice:

viewfinderWidth = activityWidth;
viewfinderHeight = activityWidth / aspectRatioSource;
Ritagli

AutoFitSurfaceView.kt (github) degli esempi di Camera2 esegue l'override di SurfaceView e gestisce le proporzioni non corrispondenti utilizzando un'immagine uguale o "leggermente più grande" dell'attività in entrambe le dimensioni e poi taglia i contenuti che superano i limiti. Questo è utile per le app che vogliono che l'anteprima copra l'intera attività o riempia completamente una visualizzazione di dimensioni fisse, senza distorcere l'immagine.

Caveat

L'esempio precedente tenta di massimizzare lo spazio sullo schermo rendendo l'anteprima leggermente più grande dell'attività, in modo che non rimanga spazio vuoto. Ciò si basa sul fatto che le parti in overflow vengono ritagliate dal layout principale (o ViewGroup) per impostazione predefinita. Il comportamento è coerente con RelativeLayout e LinearLayout, ma NON con ConstraintLayout. Un ConstraintLayout potrebbe ridimensionare le visualizzazioni secondarie per adattarle al layout, il che interromperebbe l'effetto "ritaglio centrale" previsto e causerebbe l'allungamento delle anteprime. Puoi fare riferimento a questo commit.

TextureView

TextureView offre il massimo controllo sui contenuti dell'anteprima della videocamera, ma comporta un costo in termini di prestazioni. Inoltre, è necessario più lavoro per visualizzare correttamente l'anteprima della videocamera.

Fonte

Sotto TextureView, la piattaforma Android ruota i buffer di output in base all'orientamento del sensore in modo che corrispondano all'orientamento naturale del dispositivo. Sebbene TextureView gestisca l'orientamento del sensore, non gestisce le rotazioni del display. Allinea i buffer di output all'orientamento naturale del dispositivo, il che significa che dovrai gestire personalmente le rotazioni del display.

Ciò è illustrato nella tabella seguente. Se provi a ruotare le cifre in base alla rotazione del display corrispondente, otterrai le stesse cifre in SurfaceView.

Rotazione del display Smartphone (orientamento naturale = verticale) Laptop (orientamento naturale = orizzontale)
0 Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto
90 Un'immagine a forma di ritratto con la testa di Bugdroid rivolta a destra Un'immagine in formato orizzontale con la testa di Bugdroid rivolta a destra
180 Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto
270 Un'immagine in formato orizzontale con la testa di Bugdroid rivolta verso l'alto Un'immagine a forma di ritratto con la testa di Bugdroid rivolta verso l'alto

Layout

Il layout è un po' complicato nel caso di TextureView. In precedenza, era stato suggerito di utilizzare una matrice di trasformazione per TextureView, ma questo metodo non funziona per tutti i dispositivi. Ti consigliamo di seguire i passaggi descritti qui.

La procedura in tre passaggi per disporre correttamente le anteprime su una TextureView:

  1. Imposta le dimensioni di TextureView in modo che siano identiche a quelle dell'anteprima scelta.
  2. Ridimensiona TextureView potenzialmente allungato alle dimensioni originali dell'anteprima.
  3. Ruota TextureView di displayRotation in senso antiorario.

Supponi di avere uno smartphone con una rotazione del display di 90 gradi.

Illustrazione di uno smartphone con una rotazione del display di 90 gradi e un oggetto

1. Imposta le dimensioni di TextureView in modo che siano identiche a quelle dell'anteprima scelta

Supponiamo che la dimensione dell'anteprima che hai scelto sia previewWidth × previewHeight, dove previewWidth > previewHeight (l'output del sensore è orizzontale per natura). Quando si configura una sessione di acquisizione, è necessario chiamare SurfaceTexture#setDefaultBufferSize(int width, height) per specificare le dimensioni dell'anteprima (previewWidth × previewHeight).

Prima di chiamare setDefaultBufferSize, è importante impostare anche le dimensioni di TextureView su `previewWidth × previewHeight` con View#setLayoutParams(android.view.ViewGroup.LayoutParams). Il motivo è che TextureView chiama SurfaceTexture#setDefaultBufferSize(int width, height) con la larghezza e l'altezza misurate. Se le dimensioni di TextureView non vengono impostate in modo esplicito in anticipo, può verificarsi una condizione di competizione. Questo problema viene mitigato impostando esplicitamente le dimensioni di TextureView.

Ora TextureView potrebbe non corrispondere alle dimensioni della sorgente. Nel caso degli smartphone, la sorgente ha una forma verticale, mentre TextureView ha una forma orizzontale a causa dei layoutParams che hai appena impostato. Il risultato sarebbe un'anteprima allungata, come illustrato qui:

Illustrazione di un'immagine di anteprima a forma di ritratto allungata per adattarsi a una TextureView delle stesse dimensioni dell'anteprima scelta

2. Ridimensiona TextureView potenzialmente allungato alle dimensioni originali dell'anteprima

Per ridimensionare l'anteprima allungata alle dimensioni della sorgente, tieni presente quanto segue.

Le dimensioni dell'origine (sourceWidth × sourceHeight) sono:

  • previewHeight × previewWidth, se l'orientamento naturale è verticale o verticale inverso (l'orientamento del sensore è di 90 o 270 gradi)
  • previewWidth × previewHeight, se l'orientamento naturale è orizzontale o orizzontale inverso (l'orientamento del sensore è 0 o 180 gradi)

Correggi lo stretching utilizzando View#setScaleX(float) e View#setScaleY(float)

  • setScaleX(sourceWidth / previewWidth)
  • setScaleY(sourceHeight / previewHeight)

Illustrazione che mostra la procedura di ridimensionamento dell'anteprima allungata alle dimensioni originali

3. Ruota l'anteprima di `displayRotation` in senso antiorario

Come accennato in precedenza, devi ruotare l'anteprima di displayRotation in senso antiorario per compensare la rotazione del display.

Puoi farlo View#setRotation(float)

  • setRotation(-displayRotation), poiché esegue una rotazione in senso orario.

Illustrazione che mostra la procedura di rotazione dell'anteprima in base all'orientamento del display del dispositivo

Anteprima

Nota:se in precedenza hai utilizzato la matrice di trasformazione per TextureView nel tuo codice, l'anteprima potrebbe non essere visualizzata correttamente su un dispositivo in formato orizzontale naturale come i Chromebook. Probabilmente la matrice di trasformazione presuppone erroneamente che l'orientamento del sensore sia di 90 o 270 gradi. Per una soluzione alternativa, puoi fare riferimento a questo commit su GitHub, ma ti consigliamo vivamente di eseguire la migrazione dell'app per utilizzare il metodo descritto qui.