يوضّح هذا الموضوع كيفية إعداد حالات استخدام CameraX داخل تطبيقك للحصول على صور تتضمّن معلومات التدوير الصحيحة، سواء كانت من حالة استخدام ImageAnalysis أو ImageCapture. وبالتالي:
- يجب أن يتلقّى
Analyzerفي حالة استخدامImageAnalysisالإطارات بالتدوير الصحيح. - يجب أن تلتقط حالة استخدام
ImageCaptureالصور بالتدوير الصحيح.
المصطلحات
يستخدم هذا الموضوع المصطلحات التالية، لذا من المهم فهم معنى كل مصطلح:
- اتجاه العرض
- يشير ذلك إلى جانب الجهاز الذي يكون في الوضع العلوي، ويمكن أن يكون إحدى القيم الأربع: عمودي أو أفقي أو عمودي معكوس أو أفقي معكوس.
- تدوير شاشة العرض
- هذه هي القيمة التي تعرضها الدالة
Display.getRotation()، وتمثّل الدرجات التي يتم تدوير الجهاز بها عكس اتجاه عقارب الساعة من اتجاهه الطبيعي. - التغيير المستهدَف
- يمثّل هذا عدد الدرجات التي يجب تدوير الجهاز من خلالها في اتجاه عقارب الساعة للوصول إلى اتجاهه الطبيعي.
كيفية تحديد التدوير المستهدَف
توضّح الأمثلة التالية كيفية تحديد التدوير المستهدَف لجهاز استنادًا إلى اتجاهه الطبيعي.
المثال 1: الاتجاه الطبيعي العمودي
| مثال على الجهاز: Pixel 3 XL | |
|---|---|
|
الاتجاه الطبيعي = عمودي تدوير شاشة العرض = 0 |
|
|
الاتجاه الطبيعي = عمودي تدوير شاشة العرض = 90 |
|
المثال 2: الاتجاه الطبيعي الأفقي
| مثال على الجهاز: Pixel C | |
|---|---|
|
الاتجاه الطبيعي = أفقي تدوير شاشة العرض = 0 |
|
|
الاتجاه الطبيعي = أفقي تدوير شاشة العرض = 270 |
|
تدوير الصورة
أي طرف هو الأعلى؟ يتم تحديد اتجاه أداة الاستشعار في Android كقيمة ثابتة، والتي تمثّل الدرجات (0 و90 و180 و270) التي يتم تدوير أداة الاستشعار بها من أعلى الجهاز عندما يكون الجهاز في وضع طبيعي. في جميع الحالات الموضّحة في المخططات البيانية، يصف تدوير الصورة كيفية تدوير البيانات في اتجاه عقارب الساعة لتظهر بشكل عمودي.
توضّح الأمثلة التالية كيفية تدوير الصورة حسب اتجاه أداة استشعار الكاميرا. وتفترض أيضًا أنّه تم ضبط التدوير المستهدَف على تدوير الشاشة.
المثال 1: تم تدوير أداة الاستشعار بمقدار 90 درجة
| مثال على الجهاز: Pixel 3 XL | |
|---|---|
|
تدوير شاشة العرض = 0 |
|
|
تدوير شاشة العرض = 90 |
|
المثال 2: أداة استشعار تم تدويرها بمقدار 270 درجة
| مثال على الجهاز: Nexus 5X | |
|---|---|
|
تدوير شاشة العرض = 0 |
|
|
تدوير شاشة العرض = 90 |
|
المثال 3: تم تدوير أداة الاستشعار بمقدار 0 درجة
| مثال على الجهاز: Pixel C (جهاز لوحي) | |
|---|---|
|
تدوير شاشة العرض = 0 |
|
|
تدوير شاشة العرض = 270 |
|
احتساب دوران الصورة
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 لا تتيح هذه الاتجاهات. ويؤدي توفُّر هذه الميزة في التطبيق أو عدم توفُّرها إلى تغيير طريقة تعديل الاتجاه المستهدَف لحالات الاستخدام.
في ما يلي جدولان يوضّحان كيفية الحفاظ على مزامنة تدوير الهدف لحالات الاستخدام مع تدوير الشاشة. يوضح الأول كيفية القيام بذلك مع دعم جميع الاتجاهات الأربعة؛ يتعامل الثاني فقط مع الاتجاهات التي يدور الجهاز إليها افتراضيًا.
لاختيار الإرشادات التي يجب اتّباعها في تطبيقك:
تحقَّق مما إذا كانت كاميرا تطبيقك
Activityذات اتجاه مقفل أو اتجاه غير مقفل أو إذا كانت تتجاهل تغييرات إعدادات الاتجاه.حدِّد ما إذا كان يجب أن تتعامل كاميرا تطبيقك
Activityمع جميع اتجاهات الجهاز الأربعة (العمودي والعمودي العكسي والأفقي والأفقي العكسي)، أو ما إذا كان يجب أن تتعامل فقط مع الاتجاهات التي يتيحها الجهاز الذي تعمل عليه تلقائيًا.
إتاحة جميع الاتجاهات الأربعة
يذكر هذا الجدول إرشادات معيّنة يجب اتّباعها في الحالات التي لا يتم فيها تدوير الجهاز إلى الوضع العمودي العكسي. يمكن تطبيق الشيء نفسه على الأجهزة التي لا تدور إلى الوضع الأفقي العكسي.
| السيناريو | الإرشادات | وضع النافذة الواحدة | وضع تقسيم الشاشة إلى نوافذ متعددة |
|---|---|---|---|
| الاتجاه غير المقفل |
يجب إعداد حالات الاستخدام في كل مرة يتم فيها إنشاء Activity، مثلاً في دالة الاستدعاء onCreate() في Activity.
|
||
استخدِم onOrientationChanged() في "OrientationEventListener".
داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام. يعالج هذا الإجراء الحالات التي لا يعيد فيها النظام إنشاء Activity حتى بعد تغيير الاتجاه، مثل تدوير الجهاز بمقدار 180 درجة.
|
ويتعامل أيضًا مع الحالات التي تكون فيها الشاشة في وضع عمودي معكوس ولا يتم تدوير الجهاز إلى الوضع العمودي المعكوس تلقائيًا. |
ويتعامل أيضًا مع الحالات التي لا تتم فيها إعادة إنشاء Activity عند تدوير الجهاز (90 درجة، على سبيل المثال). يحدث ذلك على الأجهزة ذات التصميم الصغير عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
|
|
اختياري: اضبط السمة screenOrientation الخاصة بـ Activity على fullSensor في ملف AndroidManifest.
|
يسمح هذا الإجراء بأن تكون واجهة المستخدم في وضع عمودي عندما يكون الجهاز في وضع عمودي معكوس، ويسمح للنظام بإعادة إنشاء Activity كلما تم تدوير الجهاز بمقدار 90 درجة.
|
وليس له أي تأثير على الأجهزة التي لا يتم تدويرها إلى الوضع العمودي العكسي تلقائيًا. لا يتوفّر وضع النوافذ المتعددة عندما تكون الشاشة في الاتجاه العمودي المعكوس. | |
| الاتجاه مقفل |
يجب إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثلاً في دالة الاستدعاء onCreate() الخاصة بـ Activity.
|
||
استخدِم onOrientationChanged() في "OrientationEventListener".
داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام باستثناء "المعاينة".
|
ويتعامل أيضًا مع الحالات التي لا تتم فيها إعادة إنشاء Activity عند تدوير الجهاز (90 درجة، على سبيل المثال). يحدث ذلك على الأجهزة ذات التصميم الصغير عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
|
||
| تم إلغاء configChanges للاتجاه |
يجب إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثلاً في دالة الاستدعاء onCreate() الخاصة بـ Activity.
|
||
استخدِم onOrientationChanged() في "OrientationEventListener".
داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام.
|
ويتعامل أيضًا مع الحالات التي لا تتم فيها إعادة إنشاء Activity عند تدوير الجهاز (90 درجة، على سبيل المثال). يحدث ذلك على الأجهزة ذات التصميم الصغير عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
|
||
| اختياري: اضبط السمة screenOrientation الخاصة بالنشاط على fullSensor في ملف AndroidManifest. | يسمح هذا الإعداد بأن تكون واجهة المستخدم في وضع عمودي عندما يكون الجهاز في وضع عمودي معكوس. | وليس له أي تأثير على الأجهزة التي لا يتم تدويرها إلى الوضع العمودي العكسي تلقائيًا. لا يتوفّر وضع النوافذ المتعددة عندما تكون الشاشة في الاتجاه العمودي المعكوس. |
إتاحة الاتجاهات المتوافقة مع الجهاز فقط
يجب أن يتيح التطبيق الاتجاهات التي يتيحها الجهاز تلقائيًا فقط (والتي قد تشمل أو لا تشمل الاتجاه العمودي العكسي أو الاتجاه الأفقي العكسي).
| السيناريو | الإرشادات | وضع تقسيم الشاشة إلى نوافذ متعددة |
|---|---|---|
| الاتجاه غير المقفل |
يجب إعداد حالات الاستخدام في كل مرة يتم فيها إنشاء Activity، مثلاً في دالة الاستدعاء onCreate() في Activity.
|
|
استخدِم onDisplayChanged() في DisplayListener. داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام، مثلاً عند تدوير الجهاز بمقدار 180 درجة.
|
ويتعامل أيضًا مع الحالات التي لا تتم فيها إعادة إنشاء Activity عند تدوير الجهاز (90 درجة، على سبيل المثال). يحدث ذلك على الأجهزة ذات التصميم الصغير عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
|
|
| الاتجاه المقفل |
يجب إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثلاً في دالة الاستدعاء onCreate() الخاصة بـ Activity.
|
|
استخدِم onOrientationChanged() في "OrientationEventListener".
داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام.
|
ويتعامل أيضًا مع الحالات التي لا تتم فيها إعادة إنشاء Activity عند تدوير الجهاز (90 درجة، على سبيل المثال). يحدث ذلك على الأجهزة ذات التصميم الصغير عندما يشغل التطبيق نصف الشاشة، وعلى الأجهزة الأكبر حجمًا عندما يشغل التطبيق ثلثي الشاشة.
|
|
| تم إلغاء configChanges للاتجاه |
يجب إعداد حالات الاستخدام مرة واحدة فقط عند إنشاء Activity لأول مرة، مثلاً في دالة الاستدعاء onCreate() الخاصة بـ Activity.
|
|
استخدِم onDisplayChanged() في DisplayListener. داخل دالة الاستدعاء، عدِّل التدوير المستهدَف لحالات الاستخدام، مثلاً عند تدوير الجهاز بمقدار 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 : ComponentActivity() { 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) } }