Rotasi kasus penggunaan CameraX

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 penggunaan ImageAnalysis 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
Orientasi saat ini = Potret

Rotasi tampilan = 0
Rotasi target = 0

Orientasi natural = Potret
Orientasi saat ini = Lanskap

Rotasi tampilan = 90
Rotasi target = 90

Contoh 2: Orientasi natural lanskap

Contoh perangkat: Pixel C

Orientasi natural = Lanskap
Orientasi saat ini = Lanskap

Rotasi tampilan = 0
Rotasi target = 0

Orientasi natural = Lanskap
Orientasi saat ini = Potret

Rotasi tampilan = 270
Rotasi target = 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
Orientasi tampilan = Potret
Rotasi gambar = 90

Rotasi tampilan = 90
Orientasi tampilan = Lanskap
Rotasi gambar = 0

Contoh 2: Sensor diputar 270 derajat

Contoh perangkat: Nexus 5X

Rotasi tampilan = 0
Orientasi tampilan = Potret
Rotasi gambar = 270

Rotasi tampilan = 90
Orientasi tampilan = Lanskap
Rotasi gambar = 180

Contoh 3: Sensor diputar 0 derajat

Contoh perangkat: Pixel C (Tablet)

Rotasi tampilan = 0
Orientasi tampilan = Lanskap
Rotasi gambar = 0

Rotasi tampilan = 270
Orientasi tampilan = Potret
Rotasi gambar = 90

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 bentuk ImageProxy.
  • OnImageSavedCallback: Dipanggil saat gambar yang diambil telah berhasil disimpan di lokasi yang ditentukan oleh ImageCapture.OutputFileOptions. Opsi tersebut dapat menentukan File, OutputStream, atau lokasi di MediaStore.

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.

Alur verifikasi rotasi gambar

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:

  1. Verifikasi apakah Activity kamera aplikasi Anda memiliki orientasi terkunci, orientasi tidak terkunci, atau apakah kamera tersebut mengganti perubahan konfigurasi orientasi.

  2. 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)
    }
}