Rotationen der CameraX-Anwendungsfälle

In diesem Thema wird gezeigt, wie Sie CameraX-Anwendungsfälle in Ihrer App einrichten, um Bilder mit den richtigen Drehinformationen zu erhalten, unabhängig davon, ob es sich um den Anwendungsfall ImageAnalysis oder ImageCapture handelt. So gehts:

  • Der Analyzer des Anwendungsfalls ImageAnalysis sollte Frames mit der richtigen Drehung erhalten.
  • Beim Anwendungsfall ImageCapture sollten Bilder mit der richtigen Drehung aufgenommen werden.

Terminologie

In diesem Thema werden die folgenden Begriffe verwendet. Es ist wichtig, die Bedeutung der einzelnen Begriffe zu kennen:

Displayausrichtung
Hier wird angegeben, welche Seite des Geräts nach oben zeigt. Es kann einer der vier Werte sein: „Porträt“, „Querformat“, „Invertiertes Porträt“ oder „Invertiertes Querformat“.
Displayausrichtung
Das ist der Wert, der von Display.getRotation() zurückgegeben wird. Er gibt an, um wie viele Grad das Gerät gegen den Uhrzeigersinn von seiner natürlichen Ausrichtung gedreht ist.
Zielrotation
Dies ist die Anzahl der Grad, um die das Gerät im Uhrzeigersinn gedreht werden muss, um seine natürliche Ausrichtung zu erreichen.

Zieldrehung ermitteln

In den folgenden Beispielen wird gezeigt, wie die Zieldrehung für ein Gerät anhand seiner natürlichen Ausrichtung ermittelt wird.

Beispiel 1: Natürliches Hochformat

Gerätebeispiel: Google Pixel 3 XL

Natürliche Ausrichtung = Hochformat
Aktuelle Ausrichtung = Hochformat

Displayausrichtung = 0
Ausrichtung des Ziels = 0

Natürliche Ausrichtung = Hochformat
Aktuelle Ausrichtung = Querformat

Displayausrichtung = 90
Zielausrichtung = 90

Beispiel 2: Natürliche Ausrichtung im Querformat

Gerätebeispiel: Google Pixel C

Natürliche Ausrichtung = Querformat
Aktuelle Ausrichtung = Querformat

Displayausrichtung = 0
Ausrichtung des Ziels = 0

Natürliche Ausrichtung = Querformat
Aktuelle Ausrichtung = Hochformat

Displayausrichtung = 270
Zielausrichtung = 270

Bilddrehung

Welches Ende ist oben? Die Sensorausrichtung wird in Android als konstanter Wert definiert, der die Gradzahl (0, 90, 180, 270) angibt, um die der Sensor von der Oberseite des Geräts gedreht wird, wenn sich das Gerät in einer natürlichen Position befindet. In allen Fällen in den Diagrammen beschreibt die Bilddrehung, wie die Daten im Uhrzeigersinn gedreht werden müssen, damit sie richtig herum angezeigt werden.

Die folgenden Beispiele zeigen, wie die Bilddrehung je nach Ausrichtung des Kamerasensors sein sollte. Außerdem wird davon ausgegangen, dass die Zielausrichtung auf die Displayausrichtung festgelegt ist.

Beispiel 1: Sensor um 90 Grad gedreht

Gerätebeispiel: Google Pixel 3 XL

Displayausrichtung = 0
Displayausrichtung = Hochformat
Bildausrichtung = 90

Displayausrichtung = 90
Displayausrichtung = Querformat
Bildausrichtung = 0

Beispiel 2: Sensor um 270 Grad gedreht

Beispiel für ein Gerät: Nexus 5X

Displayausrichtung = 0
Displayausrichtung = Hochformat
Bilddrehung = 270

Displaydrehung = 90
Displayausrichtung = Querformat
Bilddrehung = 180

Beispiel 3: Sensor um 0 Grad gedreht

Gerätebeispiel: Google Pixel C (Tablet)

Displayausrichtung = 0
Displayausrichtung = Quer
Bildausrichtung = 0

Displaydrehung = 270
Displayausrichtung = Hochformat
Bilddrehung = 90

Drehung eines Bildes berechnen

ImageAnalysis

Der Analyzer von ImageAnalysis empfängt Bilder von der Kamera in Form von ImageProxys. Jedes Bild enthält Informationen zur Drehung, auf die Sie über folgende Methoden zugreifen können:

val rotation = imageProxy.imageInfo.rotationDegrees

Dieser Wert gibt an, um wie viele Grad das Bild im Uhrzeigersinn gedreht werden muss, damit es der Zieldrehung von ImageAnalysis entspricht. Im Kontext einer Android-App entspricht die Zieldrehung von ImageAnalysis in der Regel der Bildschirmausrichtung.

ImageCapture

Mit einer ImageCapture-Instanz wird ein Callback verknüpft, um anzuzeigen, wenn ein Aufnahmeergebnis verfügbar ist. Das Ergebnis kann entweder das aufgenommene Bild oder ein Fehler sein.

Beim Aufnehmen eines Fotos kann der bereitgestellte Rückruf einen der folgenden Typen haben:

  • OnImageCapturedCallback:Empfängt ein Bild mit In-Memory-Zugriff in Form eines ImageProxy.
  • OnImageSavedCallback:Wird aufgerufen, wenn das aufgenommene Bild an dem von ImageCapture.OutputFileOptions angegebenen Speicherort erfolgreich gespeichert wurde. Die Optionen können eine File, eine OutputStream oder einen Ort in MediaStore angeben.

Die Drehung des aufgenommenen Bildes, unabhängig von seinem Format (ImageProxy, File, OutputStream, MediaStore Uri), entspricht der Drehung in Grad, um die das aufgenommene Bild im Uhrzeigersinn gedreht werden muss, um der Zieldrehung von ImageCapture zu entsprechen. Im Kontext einer Android-App entspricht dies in der Regel der Ausrichtung des Bildschirms.

So rufen Sie die Drehung des aufgenommenen Bildes ab:

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

Drehung eines Bildes prüfen

Bei den Anwendungsfällen ImageAnalysis und ImageCapture werden nach einer erfolgreichen Aufnahmeanfrage ImageProxys von der Kamera empfangen. Ein ImageProxy umschließt ein Bild und Informationen dazu, einschließlich der Drehung. Diese Drehinformationen geben an, um wie viele Grad das Bild gedreht werden muss, damit es der Zieldrehung des Anwendungsfalls entspricht.

Ablauf der Überprüfung der Bilddrehung

Richtlinien für die Ausrichtung von Zielen für ImageCapture/ImageAnalysis

Da viele Geräte standardmäßig nicht in die umgekehrte Hoch- oder Querformatansicht wechseln, unterstützen einige Android-Apps diese Ausrichtungen nicht. Ob eine App diese Funktion unterstützt oder nicht, wirkt sich darauf aus, wie die Zielrotation der Anwendungsfälle aktualisiert werden kann.

Unten finden Sie zwei Tabellen, in denen beschrieben wird, wie Sie die Ausrichtung der Zielansicht der Anwendungsfälle mit der Displayausrichtung synchronisieren. Im ersten Beispiel wird gezeigt, wie Sie dies bei Unterstützung aller vier Ausrichtungen tun. Im zweiten Beispiel werden nur die Ausrichtungen berücksichtigt, in die das Gerät standardmäßig gedreht wird.

So wählen Sie aus, welche Richtlinien in Ihrer App gelten sollen:

  1. Prüfen Sie, ob die Kamera Activity Ihrer App eine gesperrte oder entriegelte Ausrichtung hat oder ob Änderungen an der Ausrichtungskonfiguration überschrieben werden.

  2. Legen Sie fest, ob die Kamera Ihrer App Activity alle vier Geräteausrichtungen (Hochformat, umgekehrtes Hochformat, Querformat und umgekehrtes Querformat) unterstützen soll oder nur die Ausrichtungen, die das Gerät, auf dem sie ausgeführt wird, standardmäßig unterstützt.

Alle vier Ausrichtungen unterstützen

In dieser Tabelle werden bestimmte Richtlinien für den Fall erwähnt, dass sich das Gerät nicht ins Querformat drehen lässt. Dasselbe gilt für Geräte, die sich nicht ins Hochformat drehen lassen.

Szenario Richtlinien Einzelfenstermodus Mehrfenstermodus mit Splitscreen
Ausrichtung bei entsperrtem Display Richten Sie die Use Cases jedes Mal ein, wenn die Activity erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie OrientationEventListeners onOrientationChanged(). Aktualisieren Sie im Callback die Zielrotation der Anwendungsfälle. So wird verhindert, dass das System die Activity auch nach einer Änderung der Ausrichtung nicht neu erstellt, z. B. wenn das Gerät um 180 Grad gedreht wird. Wird auch verwendet, wenn sich das Display im umgekehrten Hochformat befindet und das Gerät standardmäßig nicht ins umgekehrte Hochformat gedreht wird. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.
Optional: Legen Sie in der Datei AndroidManifest das Attribut screenOrientation von Activity auf fullSensor fest. So ist die Benutzeroberfläche auch im umgekehrten Hochformat lesbar und Activity kann vom System neu erstellt werden, wenn das Gerät um 90 Grad gedreht wird. Hat keine Auswirkungen auf Geräte, die standardmäßig nicht ins umgekehrte Hochformat gedreht werden. Der Modus „Mehrere Fenster“ wird nicht unterstützt, wenn das Display im umgekehrten Hochformat ist.
Ausrichtung gesperrt Richte die Anwendungsfälle nur einmal ein, wenn die Activity zum ersten Mal erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie OrientationEventListeners onOrientationChanged(). Aktualisieren Sie im Rückruf die Zielrotation der Anwendungsfälle mit Ausnahme der Vorschau. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.
Ausrichtungs-Konfigurationsänderungen überschrieben Richten Sie die Anwendungsfälle nur einmal ein, wenn die Activity zum ersten Mal erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie OrientationEventListeners onOrientationChanged(). Aktualisieren Sie im Callback die Zielrotation der Anwendungsfälle. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.
Optional: Legen Sie in der AndroidManifest-Datei die Eigenschaft „screenOrientation“ der Aktivität auf „fullSensor“ fest. Ermöglicht es, die Benutzeroberfläche im Hochformat anzuzeigen, wenn sich das Gerät im umgekehrten Hochformat befindet. Hat keine Auswirkungen auf Geräte, die standardmäßig nicht ins umgekehrte Hochformat gedreht werden. Der Modus mit mehreren Fenstern wird nicht unterstützt, wenn das Display im umgekehrten Hochformat ist.

Nur vom Gerät unterstützte Ausrichtungen unterstützen

Es werden nur Ausrichtungen unterstützt, die das Gerät standardmäßig unterstützt. Dies kann auch das umgekehrte Hoch-/Querformat umfassen.

Szenario Richtlinien Modus für geteilten Bildschirm mit mehreren Fenstern
Ausrichtung bei entsperrtem Display Richten Sie die Use Cases jedes Mal ein, wenn die Activity erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie DisplayListeners onDisplayChanged(). Aktualisieren Sie im Callback die Zieldrehung der Anwendungsfälle, z. B. wenn das Gerät um 180 Grad gedreht wird. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.
Ausrichtung gesperrt Richten Sie die Anwendungsfälle nur einmal ein, wenn die Activity zum ersten Mal erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie OrientationEventListeners onOrientationChanged(). Aktualisieren Sie im Callback die Zielrotation der Anwendungsfälle. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.
Ausrichtungs-Konfigurationsänderungen überschrieben Richten Sie die Anwendungsfälle nur einmal ein, wenn die Activity zum ersten Mal erstellt wird, z. B. im onCreate()-Callback der Activity.
Verwenden Sie DisplayListeners onDisplayChanged(). Aktualisieren Sie im Rückruf die Zieldrehung der Anwendungsfälle, z. B. wenn das Gerät um 180 Grad gedreht wird. Außerdem wird damit umgegangen, wenn das Activity nicht neu erstellt wird, wenn das Gerät gedreht wird (z. B. um 90 Grad). Das passiert auf Geräten mit kleinem Formfaktor, wenn die App die Hälfte des Displays einnimmt, und auf größeren Geräten, wenn die App zwei Drittel des Displays einnimmt.

Ausrichtung entsperrt

Bei einem Activity ist die Ausrichtung entsperrt, wenn die Displayausrichtung (z. B. Hoch- oder Querformat) mit der physischen Ausrichtung des Geräts übereinstimmt. Eine Ausnahme bildet das umgekehrte Hoch-/Querformat, das einige Geräte standardmäßig nicht unterstützen. Wenn das Gerät in alle vier Ausrichtungen gedreht werden soll, setzen Sie die Eigenschaft screenOrientation von Activity auf fullSensor.

Im Multifenstermodus wird ein Gerät, das das umgekehrte Hoch-/Querformat standardmäßig nicht unterstützt, nicht in das umgekehrte Hoch-/Querformat gedreht, auch wenn die screenOrientation-Eigenschaft auf fullSensor festgelegt ist.

<!-- 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" />

Ausrichtung gesperrt

Ein Display hat eine gesperrte Ausrichtung, wenn es unabhängig von der physischen Ausrichtung des Geräts immer im selben Displayformat (z. B. Hoch- oder Querformat) bleibt. Dazu geben Sie die screenOrientation-Eigenschaft eines Activity in der Deklaration in der Datei AndroidManifest.xml an.

Wenn die Displayausrichtung gesperrt ist, wird das Activity vom System nicht zerstört und neu erstellt, wenn das Gerät gedreht wird.

<!-- The Activity keeps a portrait orientation even as the device rotates. -->
<activity
   android:name=".LockedOrientationActivity"
   android:screenOrientation="portrait" />

Änderungen an der Ausrichtungskonfiguration überschrieben

Wenn ein Activity Änderungen an der Ausrichtungskonfiguration überschreibt, wird es vom System nicht gelöscht und neu erstellt, wenn sich die physische Ausrichtung des Geräts ändert. Das System aktualisiert die Benutzeroberfläche jedoch so, dass sie der physischen Ausrichtung des Geräts entspricht.

<!-- 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" />

Einrichtung von Kamera-Anwendungsfällen

In den oben beschriebenen Szenarien können die Kamera-Anwendungsfälle eingerichtet werden, wenn die Activity zum ersten Mal erstellt wird.

Bei einem Activity mit entsperrter Ausrichtung wird diese Einrichtung jedes Mal durchgeführt, wenn das Gerät gedreht wird, da das System das Activity bei Änderungen der Ausrichtung zerstört und neu erstellt. Dadurch wird die Zieldrehung der Anwendungsfälle standardmäßig so festgelegt, dass sie der Displayausrichtung entspricht.

Bei einer Activity mit einer gesperrten Ausrichtung oder einer, die Änderungen an der Ausrichtungskonfiguration überschreibt, erfolgt diese Einrichtung einmal, wenn die Activity zum ersten Mal erstellt wird.

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

OrientationEventListener einrichten

Mit einem OrientationEventListener können Sie die Zieldrehung der Kameranutzungsfälle kontinuierlich aktualisieren, wenn sich die Ausrichtung des Geräts ändert.

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()
    }
}

DisplayListener-Einrichtung

Mit einem DisplayListener können Sie die Zieldrehung der Kamera in bestimmten Situationen aktualisieren, z. B. wenn das System die Activity nicht zerstört und neu erstellt, nachdem sich das Gerät um 180 Grad gedreht hat.

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