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

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

  • يجب أن تتلقّى حالة استخدام "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

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

تحليل الصور

يتلقّى جهاز "Analyzer" الخاص بـ "ImageAnalysis" الصور من الكاميرا على شكل ImageProxy تحتوي كل صورة على معلومات حول الدوران، والتي يمكن الوصول إليها عبر:

val rotation = imageProxy.imageInfo.rotationDegrees

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

التقاط الصور

يتم إرفاق معاودة الاتصال بمثيل 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 إلى التفاف صورة ومعلومات حولها، بما في ذلك دورانها. معلومات هذا التناوب الدرجات التي يجب تدوير الصورة بها لتتوافق مع الاستخدام التدوير المستهدف للحالة.

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

إرشادات تدوير الهدف في التقاط الصور/تحليل الصور

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

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

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

  1. يمكنك التحقّق مما إذا كان اتجاه الكاميرا (Activity) في تطبيقك مُقفلاً، الاتجاه الذي تم إلغاء قفله، أو إذا ألغى تغييرات تهيئة الاتجاه.

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

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

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

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

إتاحة الاتجاهات المتوافقة مع الجهاز فقط

دعم الاتجاهات التي يدعمها الجهاز فقط بشكل افتراضي (والتي قد أو لا يمكن أن يشتمل على أوضاع رأسية عكسية/أفقية).

السيناريو الإرشادات وضع تقسيم الشاشة في نوافذ متعددة
فتح الاتجاه إعداد حالات الاستخدام كل وقت إنشاء Activity، كما هو الحال في معاودة الاتصال على onCreate() لـ "Activity"
استخدام حساب "DisplayListener" onDisplayChanged() داخل يمكن تحديث التناوب المستهدف لحالات الاستخدام، مثل حالة يتم تدوير الجهاز 180 درجة. تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
قفل الاتجاهات عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما تم إنشاء Activity لأول مرة، كما هو الحال في Activity. معاودة الاتصال onCreate().
استخدام حساب "OrientationEventListener" onOrientationChanged() داخل طلب معاودة الاتصال، عدِّل التغيير المستهدف لحالات الاستخدام. تعالج أيضًا الحالات التي لا تتم فيها معالجة Activity تتم إعادة إنشاؤه عند تدوير الجهاز (مثلاً 90 درجة). يحدث هذا في على الأجهزة الصغيرة الحجم عندما يشغل التطبيق نصف الشاشة وعلى أجهزة أكبر الأجهزة عندما يشغل التطبيق ثلثي الشاشة.
تم إلغاء تغييرات ضبط الاتجاه عليك إعداد حالات الاستخدام مرة واحدة فقط، عندما تم إنشاء Activity لأول مرة، كما هو الحال في Activity. معاودة الاتصال onCreate().
استخدام حساب "DisplayListener" onDisplayChanged() داخل يمكن تحديث التناوب المستهدف لحالات الاستخدام، مثل حالة يتم تدوير الجهاز 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 يتم إنشاؤه لأول مرة.

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