يعرض هذا الموضوع كيفية إعداد حالات استخدام 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 |
حساب دوران الصورة
تحليل الصور
يتلقّى جهاز "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
الصورة ومعلومات
عنها، بما في ذلك دورانها. تمثل معلومات التدوير هذه الدرجات التي يجب تدوير الصورة بها لمطابقة التدوير المستهدف لحالة الاستخدام.
إرشادات تدوير الهدف في 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
الخاصة بالسمة screenOrientation
على fullSensor
.Activity
في وضع النوافذ المتعددة، إذا كان الجهاز لا يتيح عكس اتجاه أفقي أو عمودي،
لن يتم عكس اتجاه أفقي أو عمودي، حتى إذا تم ضبط سمة 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) } }