Topik ini menunjukkan cara menyiapkan kasus penggunaan CameraX dalam aplikasi Anda untuk mendapatkan
gambar dengan informasi rotasi yang benar, baik dari
kasus penggunaan ImageAnalysis
atau ImageCapture
. Jadi:
Analyzer
kasus penggunaanImageAnalysis
akan menerima frame dengan rotasi yang benar.- Kasus penggunaan
ImageCapture
harus mengambil gambar dengan rotasi yang benar.
Terminologi
Topik ini menggunakan terminologi berikut, sehingga memahami arti dari setiap istilah itu penting:
- Orientasi tampilan
- Ini mengacu pada sisi perangkat mana yang berada di posisi atas, dan dapat berupa salah satu dari empat nilai: potret, lanskap, potret terbalik, atau lanskap terbalik.
- Rotasi layar
- Ini adalah nilai yang dikembalikan oleh
Display.getRotation()
, dan mewakili derajat rotasi perangkat diputar berlawanan arah jarum jam dari orientasi naturalnya. - Rotasi target
- Ini mewakili jumlah derajat yang digunakan untuk memutar perangkat searah jarum jam untuk mencapai orientasi naturalnya.
Cara menentukan rotasi target
Contoh berikut menunjukkan cara menentukan rotasi target untuk perangkat berdasarkan orientasi naturalnya.
Contoh 1: Orientasi natural potret
Contoh perangkat: Pixel 3 XL | |
---|---|
Orientasi natural = Potret Rotasi tampilan = 0 |
|
Orientasi natural = Potret Rotasi tampilan = 90 |
Contoh 2: Orientasi natural lanskap
Contoh perangkat: Pixel C | |
---|---|
Orientasi natural = Lanskap Rotasi tampilan = 0 |
|
Orientasi natural = Lanskap Rotasi tampilan = 270 |
Rotasi gambar
Sisi mana yang di atas? Orientasi sensor ditentukan di Android sebagai nilai konstan, yang mewakili derajat (0, 90, 180, 270). Sensor diputar dari bagian atas perangkat saat perangkat dalam posisi natural. Untuk semua kasus dalam diagram, rotasi gambar menjelaskan bagaimana data harus diputar searah jarum jam agar tampil tegak lurus.
Contoh berikut menunjukkan rotasi gambar yang seharusnya, bergantung pada orientasi sensor kamera. Contoh tersebut juga memperkirakan rotasi target ditetapkan ke rotasi tampilan.
Contoh 1: Sensor diputar 90 derajat
Contoh perangkat: Pixel 3 XL | |
---|---|
Rotasi tampilan = 0 |
|
Rotasi tampilan = 90 |
Contoh 2: Sensor diputar 270 derajat
Contoh perangkat: Nexus 5X | |
---|---|
Rotasi tampilan = 0 |
|
Rotasi tampilan = 90 |
Contoh 3: Sensor diputar 0 derajat
Contoh perangkat: Pixel C (Tablet) | |
---|---|
Rotasi tampilan = 0 |
|
Rotasi tampilan = 270 |
Menghitung rotasi gambar
ImageAnalysis
Analyzer
ImageAnalysis
menerima gambar dari kamera dalam bentuk
ImageProxy
. Setiap gambar berisi informasi rotasi, yang dapat diakses
melalui:
val rotation = imageProxy.imageInfo.rotationDegrees
Nilai ini mewakili derajat saat gambar perlu diputar
searah jarum jam agar cocok dengan rotasi target ImageAnalysis
. Dalam konteks
aplikasi Android, rotasi target ImageAnalysis
biasanya akan cocok
dengan orientasi layar.
ImageCapture
Callback disertakan pada instance ImageCapture
untuk memberi sinyal saat hasil
tangkapan sudah siap. Hasilnya dapat berupa gambar yang diambil atau error.
Saat mengambil gambar, callback yang diberikan dapat berupa salah satu dari jenis berikut:
OnImageCapturedCallback
: Menerima gambar dengan akses dalam memori dalam bentukImageProxy
.OnImageSavedCallback
: Dipanggil saat gambar yang diambil telah berhasil disimpan di lokasi yang ditentukan olehImageCapture.OutputFileOptions
. Opsi tersebut dapat menentukanFile
,OutputStream
, atau lokasi diMediaStore
.
Rotasi gambar yang diambil, apa pun formatnya (ImageProxy
,
File
, OutputStream
, MediaStore Uri
) mewakili derajat rotasi
yang diperlukan untuk memutar gambar yang diambil searah jarum jam agar sesuai dengan rotasi target ImageCapture
,
yang sekali lagi, dalam konteks aplikasi Android, biasanya
akan sesuai dengan orientasi layar.
Pengambilan rotasi gambar yang diambil dapat dilakukan dengan salah satu cara berikut:
ImageProxy
val rotation = imageProxy.imageInfo.rotationDegrees
File
val exif = Exif.createFromFile(file) val rotation = exif.rotation
OutputStream
val byteArray = outputStream.toByteArray() val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray)) val rotation = exif.rotation
MediaStore uri
val inputStream = contentResolver.openInputStream(outputFileResults.savedUri) val exif = Exif.createFromInputStream(inputStream) val rotation = exif.rotation
Memverifikasi rotasi gambar
Kasus penggunaan ImageAnalysis
dan ImageCapture
menerima ImageProxy
dari
kamera setelah permintaan pengambilan gambar berhasil. ImageProxy
menggabungkan gambar dan
informasi tentangnya, termasuk rotasinya. Informasi rotasi ini
mewakili derajat yang harus digunakan untuk memutar gambar agar sesuai dengan rotasi
target kasus penggunaan.
Pedoman rotasi target ImageCapture/ImageAnalysis
Karena banyak perangkat tidak berputar ke potret atau lanskap terbalik secara default, sebagian aplikasi Android tidak mendukung orientasi ini. Cara rotasi target kasus penggunaan diperbarui berubah berdasarkan apakah aplikasi mendukungnya atau tidak.
Di bawah ini adalah dua tabel yang menentukan cara menjaga rotasi target kasus penggunaan tetap sinkron dengan rotasi tampilan. Yang pertama menunjukkan cara melakukannya sembari mendukung keempat orientasi; yang kedua hanya menangani orientasi yang menjadi default saat perangkat diputar.
Untuk memilih panduan yang harus diikuti di aplikasi Anda:
Verifikasi apakah
Activity
kamera aplikasi Anda memiliki orientasi terkunci, orientasi tidak terkunci, atau apakah kamera tersebut mengganti perubahan konfigurasi orientasi.Tentukan apakah
Activity
kamera aplikasi Anda harus menangani keempat orientasi perangkat (potret, potret terbalik, lanskap, dan lanskap terbalik), atau apakah itu seharusnya hanya menangani orientasi yang didukung perangkat yang menjalankannya secara default.
Mendukung keempat orientasi
Tabel ini menyebutkan panduan tertentu yang harus diikuti untuk kasus saat perangkat tidak berputar ke potret terbalik. Hal yang sama dapat diterapkan pada perangkat yang tidak berputar ke lanskap terbalik.
Skenario | Panduan | Mode satu jendela | Mode layar terpisah multi-aplikasi |
---|---|---|---|
Orientasi tidak terkunci |
Siapkan kasus penggunaan setiap
kali Activity dibuat, seperti dalam callback
onCreate() Activity .
|
||
Gunakan onOrientationChanged()
OrientationEventListener .
Di dalam callback, perbarui rotasi target kasus penggunaan. Hal ini menangani kasus ketika sistem tidak
membuat ulang Activity bahkan setelah orientasi berubah, seperti
saat perangkat diputar 180 derajat.
|
Juga menangani kasus saat tampilan dalam orientasi potret terbalik dan perangkat tidak berputar ke potret terbalik secara default. |
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
|
Opsional: Tetapkan properti screenOrientation Activity
ke fullSensor di file
AndroidManifest .
|
Hal ini memungkinkan UI menjadi tegak lurus saat perangkat dalam orientasi potret
terbalik, dan memungkinkan Activity dibuat kembali oleh
sistem setiap kali perangkat diputar 90 derajat.
|
Tidak memengaruhi perangkat yang tidak berputar ke potret terbalik secara default. Mode multi-aplikasi tidak didukung saat tampilan dalam orientasi potret terbalik. | |
Orientasi terkunci |
Siapkan kasus penggunaan hanya sekali, saat
Activity pertama kali dibuat, misalnya dalam callback onCreate()
Activity .
|
||
Gunakan onOrientationChanged()
OrientationEventListener .
Di dalam callback, perbarui rotasi target kasus penggunaan kecuali Pratinjau.
|
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
||
Orientasi configChanges diganti |
Siapkan kasus penggunaan hanya sekali, saat
Activity pertama kali dibuat, misalnya dalam callback onCreate()
Activity .
|
||
Gunakan onOrientationChanged()
OrientationEventListener .
Di dalam callback, perbarui rotasi target kasus penggunaan.
|
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
||
Opsional: Tetapkan properti screenOrientation Aktivitas ke fullSensor dalam file AndroidManifest. | Memungkinkan UI menjadi tegak lurus saat perangkat dalam orientasi potret terbalik. | Tidak memengaruhi perangkat yang tidak berputar ke potret terbalik secara default. Mode multi-aplikasi tidak didukung saat tampilan berada dalam orientasi potret terbalik. |
Hanya mendukung orientasi yang didukung perangkat
Hanya mendukung orientasi yang didukung perangkat secara default (yang mungkin atau tidak termasuk potret terbalik/lanskap terbalik).
Skenario | Panduan | Mode layar terpisah multi-aplikasi |
---|---|---|
Orientasi tidak terkunci |
Siapkan kasus penggunaan setiap
kali Activity dibuat, seperti dalam callback
onCreate() Activity .
|
|
Gunakan onDisplayChanged()
DisplayListener . Di dalam
callback, perbarui rotasi target dari kasus penggunaan, seperti saat
perangkat diputar 180 derajat.
|
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
|
Orientasi terkunci |
Siapkan kasus penggunaan hanya sekali, saat
Activity pertama kali dibuat, misalnya dalam callback onCreate()
Activity .
|
|
Gunakan onOrientationChanged()
OrientationEventListener .
Di dalam callback, perbarui rotasi target kasus penggunaan.
|
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
|
Orientasi configChanges diganti |
Siapkan kasus penggunaan hanya sekali, saat
Activity pertama kali dibuat, misalnya dalam callback onCreate()
Activity .
|
|
Gunakan onDisplayChanged()
DisplayListener . Di dalam
callback, perbarui rotasi target dari kasus penggunaan, seperti saat
perangkat diputar 180 derajat.
|
Serta menangani kasus saat Activity tidak
dibuat ulang ketika perangkat berputar (misalnya, 90 derajat). Hal ini terjadi
pada perangkat dengan faktor bentuk kecil saat aplikasi menggunakan setengah layar, dan pada perangkat
yang lebih besar ketika aplikasi menggunakan dua pertiga layar.
|
Orientasi tidak terkunci
Activity
memiliki orientasi tidak terkunci saat orientasi tampilannya
(seperti potret atau lanskap) sesuai dengan orientasi fisik perangkat, dengan
pengecualian lanskap/potret terbalik, yang tidak didukung oleh sebagian perangkat
secara default. Untuk memaksa perangkat agar berputar ke keempat orientasi, tetapkan properti
screenOrientation
Activity
ke fullSensor
.
Dalam mode multi-aplikasi, perangkat yang tidak mendukung potret/lanskap terbalik
secara default tidak akan berputar ke potret/lanskap terbalik, meskipun properti
screenOrientation
ditetapkan ke fullSensor
.
<!-- The Activity has an unlocked orientation, but might not rotate to reverse portrait/landscape in single-window mode if the device doesn't support it by default. --> <activity android:name=".UnlockedOrientationActivity" /> <!-- The Activity has an unlocked orientation, and will rotate to all four orientations in single-window mode. --> <activity android:name=".UnlockedOrientationActivity" android:screenOrientation="fullSensor" />
Orientasi terkunci
Layar memiliki orientasi terkunci bila tetap dalam orientasi tampilan yang sama
(seperti potret atau lanskap), terlepas dari orientasi fisik
perangkat. Hal ini dapat dilakukan dengan menentukan properti Activity
screenOrientation
di dalam deklarasinya di file AndroidManifest.xml
.
Saat layar memiliki orientasi terkunci, sistem tidak akan menghancurkan dan
membuat ulang Activity
saat perangkat diputar.
<!-- The Activity keeps a portrait orientation even as the device rotates. --> <activity android:name=".LockedOrientationActivity" android:screenOrientation="portrait" />
Perubahan konfigurasi orientasi diganti
Saat Activity
mengganti perubahan konfigurasi orientasi, sistem
tidak akan menghancurkan dan membuatnya ulang saat orientasi fisik perangkat berubah.
Sistem memperbarui UI agar sesuai dengan orientasi fisik perangkat.
<!-- The Activity's UI might not rotate in reverse portrait/landscape if the device doesn't support it by default. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" /> <!-- The Activity's UI will rotate to all 4 orientations in single-window mode. --> <activity android:name=".OrientationConfigChangesOverriddenActivity" android:configChanges="orientation|screenSize" android:screenOrientation="fullSensor" />
Penyiapan kasus penggunaan kamera
Dalam skenario yang dijelaskan di atas, kasus penggunaan kamera dapat disiapkan saat
Activity
pertama kali dibuat.
Dalam kasus Activity
dengan orientasi tidak terkunci, penyiapan ini dilakukan
setiap kali perangkat diputar, karena sistem menghancurkan dan membuat ulang Activity
setiap kali orientasi berubah. Hal ini menghasilkan kasus penggunaan yang menetapkan
rotasi targetnya agar sesuai dengan orientasi tampilan secara default setiap waktu.
Dalam kasus Activity
dengan orientasi terkunci atau yang mengganti
perubahan konfigurasi orientasi, penyiapan ini dilakukan sekali, saat Activity
pertama kali dibuat.
class CameraActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cameraProcessFuture = ProcessCameraProvider.getInstance(this) cameraProcessFuture.addListener(Runnable { val cameraProvider = cameraProcessFuture.get() // By default, the use cases set their target rotation to match the // display’s rotation. val preview = buildPreview() val imageAnalysis = buildImageAnalysis() val imageCapture = buildImageCapture() cameraProvider.bindToLifecycle( this, cameraSelector, preview, imageAnalysis, imageCapture) }, mainExecutor) } }
Penyiapan OrientationEventListener
Penggunaan OrientationEventListener
memungkinkan Anda terus memperbarui rotasi
target dari kasus penggunaan kamera saat orientasi perangkat berubah.
class CameraActivity : AppCompatActivity() { private val orientationEventListener by lazy { object : OrientationEventListener(this) { override fun onOrientationChanged(orientation: Int) { if (orientation == ORIENTATION_UNKNOWN) { return } val rotation = when (orientation) { in 45 until 135 -> Surface.ROTATION_270 in 135 until 225 -> Surface.ROTATION_180 in 225 until 315 -> Surface.ROTATION_90 else -> Surface.ROTATION_0 } imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } } override fun onStart() { super.onStart() orientationEventListener.enable() } override fun onStop() { super.onStop() orientationEventListener.disable() } }
Penyiapan DisplayListener
Penggunaan DisplayListener
memungkinkan Anda memperbarui rotasi target kasus penggunaan
kamera dalam situasi tertentu, misalnya saat sistem tidak menghancurkan
dan membuat ulang Activity
setelah perangkat diputar 180 derajat.
class CameraActivity : AppCompatActivity() { private val displayListener = object : DisplayManager.DisplayListener { override fun onDisplayChanged(displayId: Int) { if (rootView.display.displayId == displayId) { val rotation = rootView.display.rotation imageAnalysis.targetRotation = rotation imageCapture.targetRotation = rotation } } override fun onDisplayAdded(displayId: Int) { } override fun onDisplayRemoved(displayId: Int) { } } override fun onStart() { super.onStart() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.registerDisplayListener(displayListener, null) } override fun onStop() { super.onStop() val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager displayManager.unregisterDisplayListener(displayListener) } }