Orientasi kamera

Jika aplikasi Android Anda menggunakan kamera, ada beberapa pertimbangan khusus saat menangani orientasi. Dokumen ini mengasumsikan bahwa Anda memahami konsep dasar Android camera2 API. Anda dapat membaca postingan blog atau ringkasan kami untuk mengetahui ringkasan camera2. Sebaiknya Anda mencoba menulis aplikasi kamera terlebih dahulu sebelum membaca dokumen ini.

Latar belakang

Menangani orientasi di aplikasi kamera Android cukup rumit dan perlu mempertimbangkan faktor-faktor berikut:

  • Orientasi alami: Orientasi layar saat perangkat berada dalam posisi 'normal' untuk desain perangkat - biasanya orientasi potret untuk ponsel dan orientasi lanskap untuk laptop.
  • Orientasi sensor: Orientasi sensor yang dipasang secara fisik pada perangkat.
  • Rotasi layar: Seberapa banyak perangkat diputar secara fisik dari orientasi naturalnya.
  • Ukuran jendela bidik: Ukuran jendela bidik yang digunakan untuk menampilkan pratinjau kamera.
  • Ukuran gambar yang dihasilkan oleh kamera.

Gabungan faktor ini menghasilkan sejumlah besar kemungkinan konfigurasi UI dan pratinjau untuk aplikasi kamera. Dokumen ini dimaksudkan untuk menunjukkan cara developer dapat menavigasi dan menangani orientasi kamera dengan benar di aplikasi Android.

Untuk mempermudah, asumsikan semua contoh melibatkan kamera yang menghadap ke belakang kecuali jika disebutkan lain. Selain itu, semua foto berikut disimulasikan untuk membuat ilustrasi lebih jelas secara visual.

Semua hal tentang orientasi

Orientasi Alami

Orientasi alami ditentukan sebagai orientasi tampilan saat perangkat berada dalam posisi yang biasanya diharapkan. Untuk ponsel, orientasi alaminya sering kali potret. Dengan kata lain, ponsel memiliki lebar yang lebih pendek dan tinggi yang lebih panjang. Untuk laptop, orientasi alaminya adalah lanskap, yang berarti memiliki lebar yang lebih panjang dan tinggi yang lebih pendek. Tablet sedikit lebih rumit daripada ini - tablet dapat berupa potret atau lanskap.

Ilustrasi orientasi alami dengan ponsel, laptop, dan objek dari sisi pengamat

Orientasi Sensor

Secara formal, orientasi sensor diukur berdasarkan derajat gambar output dari sensor yang perlu diputar searah jarum jam agar sesuai dengan orientasi alami perangkat. Dengan kata lain, orientasi sensor adalah jumlah derajat sensor diputar berlawanan arah jarum jam sebelum dipasang di perangkat. Saat melihat layar, rotasi tampak searah jarum jam, hal ini karena sensor kamera belakang dipasang di sisi "belakang" perangkat.

Menurut Android 10 Compatibility Definition 7.5.5 Camera Orientation, kamera depan dan belakang "HARUS diorientasikan sehingga dimensi panjang kamera sejajar dengan dimensi panjang layar".

Buffer output dari kamera berukuran lanskap. Karena orientasi alami ponsel biasanya potret, orientasi sensor biasanya 90 atau 270 derajat dari orientasi alami agar sisi panjang buffer output cocok dengan sisi panjang layar. Orientasi sensor berbeda untuk perangkat yang orientasi alaminya adalah lanskap, seperti Chromebook. Pada perangkat ini, sensor gambar ditempatkan lagi sehingga sisi panjang buffer output cocok dengan sisi panjang layar. Karena keduanya berukuran lanskap, orientasinya cocok dan orientasi sensornya adalah 0 atau 180 derajat.

Ilustrasi orientasi alami dengan ponsel, laptop, dan objek dari sisi pengamat

Ilustrasi berikut menunjukkan tampilan dari sudut pandang pengamat, yang melihat layar perangkat:

Ilustrasi orientasi sensor dengan ponsel, laptop, dan objek dari sisi pengamat

Pertimbangkan adegan berikut:

Adegan dengan figurin Android yang imut (bugdroid)

Ponsel Laptop
Ilustrasi gambar dari melihat melalui sensor kamera belakang di ponsel Ilustrasi gambar dari melihat melalui sensor kamera belakang di laptop

Karena orientasi sensor biasanya 90 atau 270 derajat pada ponsel, tanpa memperhitungkan orientasi sensor, gambar yang akan Anda dapatkan akan terlihat seperti ini:

Ponsel Laptop
Ilustrasi gambar dari melihat melalui sensor kamera belakang di ponsel Ilustrasi gambar dari melihat melalui sensor kamera belakang di laptop

Misalkan orientasi sensor berlawanan arah jarum jam disimpan dalam variabel sensorOrientation. Untuk mengompensasi orientasi sensor, Anda perlu memutar buffer output sebesar `sensorOrientation` searah jarum jam untuk menyelaraskan kembali orientasi dengan orientasi alami perangkat.

Di Android, aplikasi dapat menggunakan TextureView atau SurfaceView untuk menampilkan pratinjau kamera. Keduanya dapat menangani orientasi sensor jika aplikasi menggunakannya dengan benar. Kami akan menjelaskan secara mendetail cara memperhitungkan orientasi sensor di bagian berikut.

Rotasi Layar

Rotasi layar secara formal ditentukan oleh rotasi grafik yang digambar di layar, yang merupakan arah berlawanan dari rotasi fisik perangkat dari orientasi naturalnya. Bagian berikut mengasumsikan bahwa rotasi tampilan adalah kelipatan 90. Jika Anda mengambil rotasi tampilan berdasarkan derajat absolutnya, bulatkan ke {0, 90, 180, 270} terdekat.

"Orientasi tampilan" di bagian berikut mengacu pada apakah perangkat dipegang secara fisik dalam posisi lanskap atau potret dan berbeda dari "rotasi tampilan".

Misalkan Anda memutar perangkat 90 derajat berlawanan arah jarum jam dari posisi sebelumnya seperti yang ditunjukkan pada gambar berikut:

Ilustrasi rotasi layar 90 derajat dengan ponsel, laptop, dan objek dari sisi pengamat

Dengan asumsi buffer output sudah dirotasi berdasarkan orientasi sensor, Anda akan memiliki buffer output berikut:

Ponsel Laptop
Ilustrasi gambar dari melihat melalui sensor kamera belakang di ponsel Ilustrasi gambar dari melihat melalui sensor kamera belakang di laptop

Jika rotasi tampilan disimpan dalam variabel displayRotation, untuk mendapatkan gambar yang benar, Anda harus memutar buffer output sebesar displayRotation berlawanan arah jarum jam.

Untuk kamera depan, rotasi layar bekerja pada buffer gambar ke arah yang berlawanan dengan layar. Jika Anda berurusan dengan kamera depan, Anda harus memutar buffer dengan displayRotatation searah jarum jam.

Peringatan

Rotasi layar mengukur rotasi perangkat berlawanan arah jarum jam. Hal ini tidak berlaku untuk semua API orientasi/rotasi.

Misalnya,

Hal penting yang perlu diperhatikan di sini adalah rotasi layar terkait dengan orientasi alami. Misalnya, jika Anda memutar ponsel secara fisik sebesar 90 atau 270 derajat, Anda akan mendapatkan layar berbentuk lanskap. Sebagai perbandingan, Anda akan mendapatkan layar berbentuk potret jika memutar laptop dengan jumlah yang sama. Aplikasi harus selalu mengingat hal ini dan tidak pernah membuat asumsi tentang orientasi alami perangkat.

Contoh

Mari kita gunakan gambar sebelumnya untuk mengilustrasikan apa itu orientasi dan rotasi.

Ilustrasi orientasi gabungan dengan ponsel dan laptop yang tidak diputar, serta objek

Ponsel Laptop
Orientasi Natural = Potret Orientasi Natural = Lanskap
Orientasi Sensor = 90 Orientasi Sensor = 0
Rotasi Tampilan = 0 Rotasi Tampilan = 0
Orientasi Tampilan = Potret Orientasi Tampilan = Lanskap

Ilustrasi orientasi gabungan dengan ponsel dan laptop yang tidak diputar, serta objek

Ponsel Laptop
Orientasi Natural = Potret Orientasi Natural = Lanskap
Orientasi Sensor = 90 Orientasi Sensor = 0
Rotasi Tampilan = 90 Rotasi Tampilan = 90
Orientasi Tampilan = Lanskap Orientasi Tampilan = Potret

Ukuran Jendela Bidik

Aplikasi harus selalu mengubah ukuran jendela bidik berdasarkan orientasi, rotasi, dan resolusi layar. Secara umum, aplikasi harus membuat orientasi jendela bidik identik dengan orientasi tampilan saat ini. Dengan kata lain, aplikasi harus menyelaraskan tepi panjang jendela bidik dengan tepi panjang layar.

Ukuran Output Gambar menurut Kamera

Saat memilih ukuran output gambar untuk pratinjau, Anda harus memilih ukuran yang sama dengan atau lebih besar dari ukuran Jendela Bidik jika memungkinkan. Biasanya, Anda tidak ingin buffer output di-scaling ke atas yang akan menyebabkan pikselasi. Anda juga tidak ingin memilih ukuran yang terlalu besar karena dapat mengurangi performa dan menggunakan lebih banyak daya baterai.

Orientasi JPEG

Mari kita mulai dengan situasi umum - mengambil foto JPEG. Di camera2 API, Anda dapat meneruskan JPEG_ORIENTATION dalam permintaan pengambilan untuk menentukan seberapa banyak JPEG output Anda ingin diputar searah jarum jam.

Berikut rangkuman singkat dari apa yang telah kita bahas:

  • Untuk menangani orientasi sensor, Anda harus memutar buffer gambar sebesar sensorOrientation searah jarum jam.
  • Untuk menangani rotasi tampilan, Anda perlu memutar buffer sebesar displayRotation berlawanan arah jarum jam untuk kamera belakang, searah jarum jam untuk kamera depan.

Dengan menjumlahkan 2 faktor tersebut, jumlah yang ingin Anda putar searah jarum jam adalah

  • sensorOrientation - displayRotation untuk kamera belakang.
  • sensorOrientation + displayRotation untuk kamera depan.

Anda dapat melihat kode contoh untuk logika ini dalam dokumentasi JPEG_ORIENTATION. Perhatikan bahwa deviceOrientation dalam kode contoh dokumentasi menggunakan rotasi perangkat searah jarum jam. Oleh karena itu, tanda untuk rotasi layar dibalik.

Pratinjau

Bagaimana dengan pratinjau kamera? Ada 2 cara utama aplikasi dapat menampilkan pratinjau kamera: SurfaceView dan TextureView. Masing-masing memerlukan pendekatan yang berbeda untuk menangani orientasi dengan benar.

SurfaceView

SurfaceView umumnya direkomendasikan untuk pratinjau kamera asalkan Anda tidak perlu memproses atau menganimasikan buffer pratinjau. Lebih berperforma dan tidak terlalu menuntut resource dibandingkan TextureView.

SurfaceView juga relatif lebih mudah ditata. Anda hanya perlu mengkhawatirkan rasio aspek SurfaceView tempat Anda menampilkan pratinjau kamera.

Sumber

Di bawah SurfaceView, platform Android memutar buffer output agar sesuai dengan orientasi tampilan perangkat. Dengan kata lain, hal ini memperhitungkan orientasi sensor dan rotasi layar. Agar lebih sederhana, saat layar kita dalam mode lanskap, kita akan mendapatkan pratinjau yang juga dalam mode lanskap, dan sebaliknya untuk mode potret.

Hal ini diilustrasikan dalam tabel berikut. Hal penting yang perlu diingat di sini adalah bahwa rotasi tampilan saja tidak menentukan orientasi sumber.

Rotasi Layar Ponsel (Orientasi Natural = Potret) Laptop (Orientasi Natural = Lanskap)
0 Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas
90 Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas
180 Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas
270 Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas

Tata Letak

Seperti yang dapat Anda lihat, SurfaceView sudah menangani beberapa hal rumit untuk kita. Namun, sekarang Anda perlu mempertimbangkan ukuran jendela bidik, atau seberapa besar pratinjau yang Anda inginkan di layar. SurfaceView secara otomatis menskalakan buffer sumber agar sesuai dengan dimensinya. Anda harus memastikan rasio aspek jendela bidik sama dengan rasio aspek sourcebuffer. Misalnya, jika Anda mencoba menyesuaikan pratinjau berbentuk potret ke dalam SurfaceView berbentuk lanskap, Anda akan mendapatkan sesuatu yang terdistorsi seperti ini:

Ilustrasi gambar yang menampilkan bugdroid yang diregangkan sebagai hasil dari menyesuaikan pratinjau berbentuk potret ke dalam jendela bidik berbentuk lanskap

Umumnya, Anda ingin rasio aspek (yaitu, lebar/tinggi) jendela bidik sama dengan rasio aspek sumber. Jika Anda tidak ingin memangkas gambar di jendela bidik - memotong beberapa piksel untuk memperbaiki tampilan, ada 2 kasus yang perlu dipertimbangkan, yaitu saat aspectRatioActivity lebih besar dari aspectRatioSource dan saat aspectRatioActivity kurang dari atau sama dengan aspectRatioSource

aspectRatioActivity > aspectRatioSource

Anda dapat menganggap kasus ini sebagai aktivitas yang "lebih luas". Di bawah ini, kita akan mempertimbangkan contoh saat Anda memiliki aktivitas 16:9 dan sumber 4:3.

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

Pertama, Anda juga ingin jendela bidik Anda berukuran 4:3. Kemudian, Anda ingin menyesuaikan sumber dan jendela bidik ke dalam aktivitas seperti ini:

Ilustrasi aktivitas yang rasio aspeknya lebih besar daripada rasio aspek jendela bidik di dalamnya

Dalam hal ini, Anda harus membuat tinggi jendela bidik sesuai dengan tinggi aktivitas sambil membuat rasio aspek jendela bidik identik dengan rasio aspek sumber. Kode semunya adalah sebagai berikut:

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

Kasus lainnya adalah saat aktivitas "lebih sempit" atau "lebih tinggi". Kita dapat menggunakan kembali contoh sebelumnya, kecuali pada contoh berikut Anda memutar perangkat sebesar 90 derajat, sehingga aktivitas menjadi 9:16 dan sumber menjadi 3:4.

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

Dalam hal ini, Anda ingin menyesuaikan sumber dan jendela bidik ke dalam aktivitas seperti berikut:

Ilustrasi aktivitas yang rasio aspeknya lebih kecil daripada rasio aspek jendela bidik di dalamnya

Anda harus membuat lebar jendela bidik cocok dengan lebar aktivitas (berbeda dengan tinggi pada kasus sebelumnya) sekaligus membuat rasio aspek jendela bidik identik dengan rasio aspek sumber. Kode semu:

viewfinderWidth = activityWidth;
viewfinderHeight = activityWidth / aspectRatioSource;
Klip

AutoFitSurfaceView.kt (github) dari contoh Camera2 menggantikan SurfaceView dan menangani rasio aspek yang tidak cocok dengan menggunakan gambar yang sama dengan atau "lebih besar" dari aktivitas dalam kedua dimensi, lalu memangkas konten yang meluap. Hal ini berguna untuk aplikasi yang ingin pratinjau mencakup seluruh aktivitas atau mengisi tampilan dimensi tetap sepenuhnya, tanpa mendistorsi gambar.

Peringatan

Contoh sebelumnya mencoba memaksimalkan ruang layar dengan membuat pratinjau lebih besar dari aktivitas sehingga tidak ada ruang yang tidak terisi. Hal ini bergantung pada fakta bahwa bagian yang meluap dipangkas oleh tata letak induk (atau ViewGroup) secara default. Perilakunya konsisten dengan RelativeLayout dan LinearLayout, tetapi TIDAK dengan ConstraintLayout. ConstraintLayout dapat mengubah ukuran Tampilan turunan agar pas di dalam tata letak, yang akan merusak efek "center-crop" yang diinginkan dan menyebabkan pratinjau meregang. Anda dapat merujuk commit ini sebagai referensi.

TextureView

TextureView memberikan kontrol maksimum atas konten pratinjau kamera, tetapi menimbulkan biaya performa. Selain itu, diperlukan lebih banyak upaya untuk menampilkan pratinjau kamera dengan benar.

Sumber

Di bawah TextureView, platform Android memutar buffer output sesuai dengan orientasi sensor agar sesuai dengan orientasi alami perangkat. Meskipun TextureView menangani orientasi sensor, TextureView tidak menangani rotasi tampilan. Hal ini menyelaraskan buffer output dengan orientasi alami perangkat, yang berarti Anda harus menangani rotasi layar sendiri.

Hal ini diilustrasikan dalam tabel berikut. Coba putar gambar dengan rotasi tampilan yang sesuai, Anda akan mendapatkan gambar yang sama di SurfaceView.

Rotasi Layar Ponsel (Orientasi Natural = Potret) Laptop (Orientasi Natural = Lanskap)
0 Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas
90 Gambar berbentuk potret dengan kepala bugdroid mengarah ke kanan Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke kanan
180 Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas
270 Gambar berbentuk lanskap dengan kepala bugdroid mengarah ke atas Gambar berbentuk potret dengan kepala bugdroid mengarah ke atas

Tata Letak

Tata letak agak rumit untuk kasus TextureView. Sebelumnya, disarankan untuk menggunakan matriks transformasi untuk TextureView, tetapi metode tersebut tidak berfungsi untuk semua perangkat. Sebaiknya ikuti langkah-langkah yang dijelaskan di sini.

Proses 3 langkah untuk menata letak pratinjau dengan benar di TextureView:

  1. Tetapkan ukuran TextureView agar sama dengan ukuran pratinjau yang dipilih.
  2. Menskalakan TextureView yang berpotensi diregangkan kembali ke dimensi asli pratinjau.
  3. Putar TextureView sebesar displayRotation berlawanan arah jarum jam.

Misalkan Anda memiliki ponsel dengan rotasi layar 90 derajat.

Ilustrasi ponsel dengan rotasi layar 90 derajat, dan objek

1. Setel ukuran TextureView agar sama dengan ukuran pratinjau yang dipilih

Misalkan ukuran pratinjau yang Anda pilih adalah previewWidth × previewHeight dengan previewWidth > previewHeight (output sensor berbentuk lanskap secara alami). Saat mengonfigurasi sesi pengambilan, seseorang harus memanggil SurfaceTexture#setDefaultBufferSize(int width, height) untuk menentukan ukuran pratinjau (previewWidth × previewHeight).

Sebelum memanggil setDefaultBufferSize, Anda juga harus menetapkan ukuran TextureView menjadi `previewWidth × previewHeight` dengan View#setLayoutParams(android.view.ViewGroup.LayoutParams). Alasannya adalah karena TextureView memanggil SurfaceTexture#setDefaultBufferSize(int width, height) dengan lebar dan tinggi terukurnya. Jika ukuran TextureView tidak ditetapkan secara eksplisit sebelumnya, hal ini dapat menyebabkan kondisi persaingan. Hal ini dapat diatasi dengan menetapkan ukuran TextureView secara eksplisit terlebih dahulu.

Sekarang TextureView mungkin tidak cocok dengan dimensi sumber. Dalam kasus ponsel, sumbernya berbentuk potret, tetapi TextureView berbentuk lanskap karena layoutParams yang baru saja Anda tetapkan. Hal ini akan menghasilkan pratinjau yang diregangkan, seperti yang diilustrasikan di sini:

Ilustrasi gambar pratinjau berbentuk potret yang diregangkan agar pas di dalam TextureView dengan ukuran yang sama dengan ukuran pratinjau yang dipilih

2. Menskalakan TextureView yang berpotensi diregangkan kembali ke dimensi asli pratinjau

Pertimbangkan hal berikut untuk menskalakan pratinjau yang diregangkan kembali ke dimensi sumber.

Dimensi sumber (sourceWidth × sourceHeight) adalah:

  • previewHeight × previewWidth, jika orientasi alami adalah potret atau potret terbalik (orientasi sensor adalah 90 atau 270 derajat)
  • previewWidth × previewHeight, jika orientasi natural adalah lanskap atau lanskap terbalik (orientasi sensor adalah 0 atau 180 derajat)

Memperbaiki peregangan dengan memanfaatkan View#setScaleX(float) dan View#setScaleY(float)

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

Ilustrasi gambar yang menunjukkan prosedur pratinjau yang diregangkan dan diskalakan kembali ke dimensi aslinya

3. Putar pratinjau sebesar `displayRotation` berlawanan arah jarum jam

Seperti yang disebutkan sebelumnya, Anda harus memutar pratinjau sebesar displayRotation berlawanan arah jarum jam untuk mengompensasi rotasi tampilan.

Anda dapat melakukannya dengan View#setRotation(float)

  • setRotation(-displayRotation), karena melakukan rotasi searah jarum jam.

Ilustrasi gambar yang menunjukkan prosedur pratinjau diputar agar sesuai dengan orientasi layar perangkat

Contoh

Catatan: Jika sebelumnya Anda menggunakan matriks transformasi untuk TextureView dalam kode, pratinjau mungkin tidak terlihat benar di perangkat lanskap alami seperti Chromebook. Kemungkinan matriks transformasi Anda secara keliru mengasumsikan orientasi sensor adalah 90 atau 270 derajat. Anda dapat melihat commit ini di GitHub untuk mengetahui solusi sementara, tetapi sebaiknya migrasikan aplikasi Anda untuk menggunakan metode yang dijelaskan di sini.