تدوير حالة استخدام CameraX

يعرض هذا الموضوع كيفية إعداد حالات استخدام CameraX داخل تطبيقك للحصول على صور تتضمّن معلومات صحيحة عن التدوير، سواء كانت من حالة الاستخدام ImageAnalysis أو حالة الاستخدام ImageCapture. وبالتالي:

  • يجب أن تتلقّى Analyzer حالة الاستخدام Analyzer لقطات ذات اتجاه دوران صحيح.ImageAnalysis
  • يجب أن تلتقط حالة استخدام ImageCapture صورًا بالاتجاه الصحيح.

المصطلحات

يستخدم هذا الموضوع المصطلحات التالية، لذا من المهم فهم معنى كل مصطلح:

اتجاه العرض
يشير ذلك إلى جانب الجهاز الذي يكون في الوضع العلوي، ويمكن أن يكون إحدى القيم الأربع التالية: عمودي أو أفقي أو عمودي معكوس أو أفقي معكوس.
تدوير الشاشة
هذه هي القيمة التي تعرضها دالة Display.getRotation()، وتمثل الدرجات التي يتم من خلالها تدوير الجهاز عكس اتجاه عقارب الساعة عن اتجاهه الطبيعي.
دورة الاستهداف
يشير ذلك إلى عدد الدرجات التي يجب تدوير الجهاز بها باتجاه عقارب الساعة للوصول إلى الاتجاه الطبيعي.

كيفية تحديد معدّل دوران الإعلانات المستهدَفة

توضِّح الأمثلة التالية كيفية تحديد التناوب المستهدَف لجهاز معيّن استنادًا إلى اتجاهه الطبيعي.

المثال 1: الاتجاه العمودي الطبيعي

مثال على الجهاز: Pixel 3 XL

الاتجاه الطبيعي = عمودي
الاتجاه الحالي = عمودي

دوران الشاشة = 0
دوران الهدف = 0

الاتجاه الطبيعي = عمودي
الاتجاه الحالي = أفقي

دوران الشاشة = 90
دوران الهدف = 90

المثال 2: الاتجاه الأفقي الطبيعي

مثال على الجهاز: Pixel C

الاتجاه الطبيعي = أفقي
الاتجاه الحالي = أفقي

دوران الشاشة = 0
دوران الهدف = 0

الاتجاه الطبيعي = أفقي
الاتجاه الحالي = عمودي

دوران شاشة العرض = 270
دوران الهدف = 270

تدوير الصورة

أيّهما الجهة العلوية؟ يتم تعريف اتجاه المستشعر في Android على أنّه قيمة ثابتة تمثّل الدرجات (0 و90 و180 و270) التي يتم تدوير المستشعر بها من أعلى الجهاز عندما يكون الجهاز في وضع طبيعي. في جميع الحالات الواردة في المخططات البيانية، يصف دوران الصورة كيفية تدوير البيانات باتجاه عقارب الساعة لتظهر بشكلٍ رأسي.

توضِّح الأمثلة التالية كيفية دوران الصورة استنادًا إلى اتجاه ملف تعريف حساس الكاميرا. ويفترضون أيضًا أنّه تم ضبط التدوير المستهدَف على تدوير الشاشة.

المثال 1: تم تدوير أداة الاستشعار 90 درجة

مثال على الجهاز: Pixel 3 XL

دوران الشاشة = 0
الاتجاه الأفقي للشاشة = عمودي
دوران الصورة = 90

دوران الشاشة = 90
اتجاه الشاشة = أفقي
تدوير الصورة = 0

المثال 2: تم تدوير أداة الاستشعار 270 درجة

مثال على الجهاز: Nexus 5X

دوران الشاشة = 0
اتجاه الشاشة = عمودي
تدوير الصورة = 270

دوران الشاشة = 90
الاتجاه الأفقي للشاشة = أفقي
تدوير الصورة = 180

المثال 3: تم تدوير أداة الاستشعار 0 درجة

مثال على الجهاز: Pixel C (جهاز لوحي)

دوران الشاشة = 0
اتجاه الشاشة = أفقي
تدوير الصورة = 0

دوران الشاشة = 270
الاتجاه الأفقي للشاشة = عمودي
تدوير الصورة = 90

احتساب درجة دوران الصورة

ImageAnalysis

يتلقّى Analyzer في ImageAnalysis الصور من الكاميرا في شكل ImageProxy. تحتوي كل صورة على معلومات حول التدوير، ويمكن الوصول إليها من خلال:

val rotation = imageProxy.imageInfo.rotationDegrees

تمثّل هذه القيمة عدد الدرجات التي يجب تدوير الصورة بها في اتجاه عقارب الساعة لمطابقة التدوير المستهدَف في ImageAnalysis. في سياق أحد تطبيقات Android، سيتطابق عادةً دوران ImageAnalysis المستهدَف مع اتجاه الشاشة.

ImageCapture

يتم إرفاق طلب إعادة اتصال بمثيل ImageCapture للإشارة إلى أنّ نتيجة الالتقاط جاهزة. يمكن أن تكون النتيجة إما الصورة التي تم التقاطها أو خطأ.

عند التقاط صورة، يمكن أن يكون المرجع المقدَّم من أحد الأنواع التالية:

  • OnImageCapturedCallback: تتلقّى صورة يمكن الوصول إليها في الذاكرة بأحد ImageProxy.
  • OnImageSavedCallback: يتمّ استدعاؤها عند تخزين الصورة التي تمّ التقاطها بنجاح في الموقع المحدّد من قِبل ImageCapture.OutputFileOptions. يمكن أن تحدِّد الخيارات File أو OutputStream أو موقعًا جغرافيًا في MediaStore.

يمثّل تدوير الصورة التي تم التقاطها، بغض النظر عن تنسيقها (ImageProxy File وOutputStream وMediaStore Uri)، درجات التدوير التي يجب تدوير الصورة التي تم التقاطها بها في اتجاه عقارب الساعة لمطابقة تدويرImageCapture المستهدف، والذي سيطابق عادةً، في سياق تطبيق Android، اتجاه الشاشة.

يمكن استعادة دوران الصورة التي تم التقاطها باتّباع إحدى الخطوات التالية:

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

التحقّق من تدوير الصورة

تتلقّى حالات الاستخدام ImageAnalysis وImageCapture ImageProxy من الكاميرا بعد نجاح طلب الالتقاط. تُحيط علامة ImageProxy بالصورة ومعلومات عنها، بما في ذلك معلومات حول تدويرها. تمثّل معلومات التدوير هذه الدرجات التي يجب تدوير الصورة بها لمطابقة التدوير المستهدَف لحالة الاستخدام.

مسار التحقّق من دوران الصورة

إرشادات حول تدوير الهدف في ImageCapture/ImageAnalysis

بما أنّ العديد من الأجهزة لا تدور تلقائيًا إلى الوضع العمودي أو الوضع الأفقي المعكوس، لا تتوافق بعض تطبيقات Android مع هذين الوضعَين. ويؤدي ما إذا كان التطبيق يتوافق مع هذه الميزة أم لا إلى تغيير طريقة تعديل تدوير الاستهداف في حالات الاستخدام.

في ما يلي جدولان يحدّدان كيفية الحفاظ على تزامن التدوير المستهدَف لحالات الاستخدام مع تدوير الشاشة. يعرض الأول كيفية إجراء ذلك مع إتاحة جميع الوضعيات الأربعة، بينما يتعامل الثاني فقط مع الوضعيات التي يدور إليها الجهاز تلقائيًا.

لاختيار الإرشادات التي يجب اتّباعها في تطبيقك:

  1. تحقَّق مما إذا كانت كاميرا Activity في تطبيقك ذات اتجاه قفل أو اتجاه غير مقفل أو إذا كانت تلغي تغييرات إعدادات الاتجاه.

  2. حدِّد ما إذا كان يجب أن تتعامل كاميرا تطبيقك Activity مع جميع أوضاع الجهاز الأربعة (العمودي والعمودي العكسي والأفقي والأفقي العكسي)، أو ما إذا كان يجب أن تتعامل فقط مع الأوضاع التي يتيحها الجهاز الذي يعمل عليه التطبيق بشكل تلقائي.

أن تتيح جميع الاتجاهات الأربعة

يشير هذا الجدول إلى إرشادات معيّنة يجب اتّباعها في الحالات التي لا يدور فيها الجهاز إلى الوضع العمودي العكسي. ويمكن تطبيق الإجراء نفسه على الأجهزة التي لا يمكن تحويلها إلى الوضع الأفقي العكسي.

السيناريو الإرشادات وضع النافذة الواحدة وضع تقسيم الشاشة إلى عدة نوافذ
اتجاه الجهاز غير المُقفَل يمكنك إعداد حالات الاستخدام في كل مرّة يتم فيها إنشاء Activity، مثل onCreate() المخصّص للرجوع إلى Activity.
استخدِم OrientationEventListener onOrientationChanged(). في دالة الاستدعاء، عدِّل دورة الاستهداف لحالات الاستخدام. يعالج هذا الإجراء الحالات التي لا يعيد فيها النظام إنشاء Activity حتى بعد تغيير الاتجاه، مثل عند تدوير الجهاز 180 درجة. تتعامل هذه السمة أيضًا مع الحالات التي تكون فيها الشاشة في الوضع العمودي المقلوب ولا يتم تدوير الجهاز تلقائيًا إلى الوضع العمودي المقلوب. تعالج هذه الميزة أيضًا الحالات التي لا تتم فيها مجددًا إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
اختياري: اضبط السمة screenOrientation في Activity على fullSensor في ملف AndroidManifest. يتيح ذلك عرض واجهة المستخدم بشكل عمودي عندما يكون الجهاز في الوضع العميق العمودي، كما يتيح للنظام إعادة إنشاء Activity عند تدوير الجهاز بزاوية 90 درجة. لا يؤثّر هذا الإعداد في الأجهزة التي لا تدور تلقائيًا إلى الوضع العمودي المعاكس. لا يتوفّر وضع "النوافذ المتعددة" عندما تكون الشاشة في الوضع عمودي معكوَف.
اتجاه قفل يمكنك إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثل Activity onCreate() في عملية معاودة الاتصال.
استخدِم onOrientationChanged() في OrientationEventListener. في دالة الاستدعاء، عدِّل دورة الاستهداف لحالات الاستخدام باستثناء "المعاينة". تعالج هذه الطريقة أيضًا الحالات التي لا تتم فيها إعادة إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
تم إلغاء تغييرات إعدادات الاتجاه يمكنك إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثل Activity onCreate() في عملية معاودة الاتصال.
استخدِم OrientationEventListener onOrientationChanged(). في دالة الاستدعاء، عدِّل دورة الاستهداف لحالات الاستخدام. تعالج هذه الطريقة أيضًا الحالات التي لا تتم فيها إعادة إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
اختياري: اضبط سمة screenOrientation للنشاط على fullSensor فيملف AndroidManifest. السماح بعرض واجهة المستخدم في الوضع العمودي عندما يكون الجهاز في الوضع العمودي العكسي لا يؤثّر هذا الإعداد في الأجهزة التي لا تدور تلقائيًا إلى الوضع العمودي المعاكس. لا يتوفّر وضع "النوافذ المتعددة" عندما تكون الشاشة في الوضع عمودي معكوَف.

السماح بوضعيات العرض المتوافقة مع الجهاز فقط

أن تتيح الاتجاهات التي يتيحها الجهاز تلقائيًا فقط (قد تشمل أو لا تشمل الاتجاه العمودي/الأفقي العكسي)

السيناريو الإرشادات وضع تقسيم الشاشة إلى عدة نوافذ
اتجاه الجهاز غير المُقفَل يمكنك إعداد حالات الاستخدام في كل مرّة يتم فيها إنشاء Activity، مثل onCreate() المخصّص للرجوع إلى Activity.
استخدِم onDisplayChanged() في DisplayListener. في دالة callback الخاصة بحالة التفعيل، عدِّل التدوير المستهدَف لحالات الاستخدام، مثل عند تدوير الجهاز بمقدار 180 درجة. تعالج هذه الطريقة أيضًا الحالات التي لا تتم فيها إعادة إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
اتجاه مُقفَل يمكنك إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثل Activity onCreate() في عملية معاودة الاتصال.
استخدِم OrientationEventListener onOrientationChanged(). في دالة الاستدعاء، عدِّل دورة الاستهداف لحالات الاستخدام. تعالج هذه الطريقة أيضًا الحالات التي لا تتم فيها إعادة إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
تم إلغاء تغييرات إعدادات الاتجاه يمكنك إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثل Activity onCreate() في عملية معاودة الاتصال.
استخدِم onDisplayChanged() في DisplayListener. في دالة callback الخاصة بحالة التفعيل، عدِّل التدوير المستهدَف لحالات الاستخدام، مثل عند تدوير الجهاز بمقدار 180 درجة. تعالج هذه الطريقة أيضًا الحالات التي لا تتم فيها إعادة إنشاء الرمز Activity عند تدوير الجهاز (90 درجة مثلاً). يحدث ذلك على الأجهزة الصغيرة عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.

الاتجاه غير المُحَدَّد

يكون اتجاه Activity غير مقفل عندما يتطابق اتجاه الشاشة (مثل الوضع العمودي أو الأفقي) مع اتجاه الجهاز، باستثناء الوضع العمودي/الأفقي العكسي الذي لا تتيحه بعض الأجهزة تلقائيًا. لفرض دوران الجهاز في جميع الاتجاهات الأربعة، اضبط سمة screenOrientation في Activity على fullSensor.

في وضع "النوافذ المتعددة"، لن يتم تلقائيًا تدوير الجهاز الذي لا يتيح الوضع العمودي/الأفقي المقلوب، حتى إذا تم ضبط الخاصية screenOrientation على 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" />

الاتجاه المُقفَل

يكون اتجاه الشاشة مقفلًا عندما يظل اتجاه الشاشة هو نفسه (مثل الوضع العمودي أو الأفقي) بغض النظر عن الاتجاه المادي للجهاز. ويمكن إجراء ذلك من خلال تحديد سمة screenOrientation Activity داخل تعريفها في ملف AndroidManifest.xml.

عندما يكون اتجاه الشاشة مقفلًا، لا يزيل النظام Activity ويعيد إنشائه عند تدوير الجهاز.

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

تم إلغاء تغييرات إعدادات الاتجاه

عندما تلغي Activity تغييرات إعدادات الاتجاه، لا يُزيلها النظام ويُعيد إنشائها عند تغيير الاتجاه المادي للجهاز. يُعدِّل النظام واجهة المستخدم لمطابقة الاتجاه المادي للجهاز.

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

إعداد حالات استخدام الكاميرا

في السيناريوهات الموضّحة أعلاه، يمكن إعداد حالات استخدام الكاميرا عند إنشاء Activity لأول مرة.

في حال استخدام Activity مع اتجاه غير مقفل، يتم إجراء هذا الإعداد في كل مرة يتم فيها تدوير الجهاز، لأنّ النظام يُزيل Activity ويعيد إنشائه عند تغيير الاتجاه. ويؤدي ذلك إلى ضبط حالات الاستخدام لإعدادات التدوير المستهدَف لمطابقة اتجاه الشاشة تلقائيًا في كل مرة.

في حال استخدام Activity بوضع ثابت أو Activity يتجاوز تغييرات إعدادات الوضع، يتم إجراء هذا الإعداد مرة واحدة عند إنشاء Activity لأول مرة.

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

يتيح لك استخدام OrientationEventListener تعديل اتجاه تصويب كاميرا التطبيقات بشكل مستمر عند تغيير اتجاه الجهاز.

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

يتيح لك استخدام DisplayListener تعديل حالات استخدام الدوران المستهدَف للكاميرا في حالات معيّنة، على سبيل المثال عندما لا يُزيل النظامActivity ويعيد إنشائه بعد دوران الجهاز بمقدار 180 درجة.

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