چرخش‌های مورد استفاده CameraX

این موضوع نشان می‌دهد که چگونه موارد استفاده CameraX را در برنامه خود راه‌اندازی کنید تا تصاویر با اطلاعات چرخش صحیح دریافت کنید، چه از ImageAnalysis باشد یا از مورد استفاده ImageCapture. بنابراین:

  • Analyzer مورد استفاده ImageAnalysis باید فریم‌ها را با چرخش صحیح دریافت کند.
  • مورد استفاده ImageCapture باید عکس‌ها را با چرخش صحیح بگیرد.

اصطلاحات

این موضوع از اصطلاحات زیر استفاده می‌کند، بنابراین درک معنای هر اصطلاح مهم است:

جهت نمایش
این به این اشاره دارد که کدام طرف دستگاه در موقعیت رو به بالا قرار دارد و می‌تواند یکی از چهار مقدار باشد: عمودی، افقی، عمودی معکوس، یا افقی معکوس.
چرخش نمایش
این مقداری است که توسط Display.getRotation() برگردانده می‌شود و نشان‌دهنده درجه‌ای است که دستگاه در خلاف جهت عقربه‌های ساعت از جهت طبیعی خود چرخیده است.
چرخش هدف
این نشان‌دهنده تعداد درجه‌ای است که دستگاه باید در جهت عقربه‌های ساعت بچرخد تا به جهت طبیعی خود برسد.

نحوه تعیین چرخش هدف

نمونه‌های زیر نشان می‌دهند که چگونه می‌توان چرخش هدف را برای دستگاه براساس جهت طبیعی آن تعیین کرد.

مثال ۱: جهت طبیعی عمودی

نمونه دستگاه: Pixel 3 XL

جهت طبیعی = عمودی
جهت فعلی = عمودی

چرخش نمایش = 0
چرخش هدف = 0

جهت طبیعی = عمودی
جهت فعلی = افقی

چرخش نمایش = ۹۰
چرخش هدف = ۹۰

مثال ۲: جهت طبیعی افقی

نمونه دستگاه: Pixel C

جهت طبیعی = افقی
جهت فعلی = افقی

چرخش نمایش = 0
چرخش هدف = 0

جهت طبیعی = افقی
جهت فعلی = عمودی

چرخش نمایش = ۲۷۰
چرخش هدف = ۲۷۰

چرخش تصویر

کدام طرف بالا است؟ جهت حسگر در Android به‌عنوان مقدار ثابتی تعریف می‌شود که نشان‌دهنده درجه (۰، ۹۰، ۱۸۰، ۲۷۰) چرخش حسگر از بالای دستگاه است، وقتی دستگاه در موقعیت طبیعی قرار دارد. برای همه موارد در نمودارها، چرخش تصویر نحوه چرخش داده‌ها در جهت عقربه‌های ساعت را برای نمایش عمودی توصیف می‌کند.

مثال‌های زیر نشان می‌دهند که چرخش تصویر بسته به جهت حسگر دوربین باید چگونه باشد. همچنین فرض می‌کنند که چرخش هدف روی چرخش نمایشگر تنظیم شده است.

مثال ۱: حسگر ۹۰ درجه چرخیده است

نمونه دستگاه: Pixel 3 XL

چرخش نمایشگر = ۰
جهت نمایشگر = عمودی
چرخش تصویر = ۹۰

چرخش نمایشگر = ۹۰
جهت نمایشگر = افقی
چرخش تصویر = ۰

مثال ۲: حسگر ۲۷۰ درجه چرخیده است

نمونه دستگاه: Nexus 5X

چرخش نمایش = ۰
جهت نمایش = عمودی
چرخش تصویر = ۲۷۰

چرخش نمایشگر = ۹۰
جهت نمایشگر = افقی
چرخش تصویر = ۱۸۰

مثال ۳: حسگر ۰ درجه چرخیده است

نمونه دستگاه: Pixel C (رایانه لوحی)

چرخش نمایش = ۰
جهت نمایش = افقی
چرخش تصویر = ۰

چرخش نمایش = ۲۷۰
جهت نمایش = عمودی
چرخش تصویر = ۹۰

محاسبه چرخش تصویر

ImageAnalysis

Analyzer ImageAnalysis تصاویر را از دوربین به‌صورت ImageProxy دریافت می‌کند. هر تصویر حاوی اطلاعات چرخش است که ازطریق زیر قابل‌دسترسی است:

val rotation = imageProxy.imageInfo.rotationDegrees

این مقدار نشان‌دهنده درجه‌ای است که تصویر باید در جهت ساعت‌گرد بچرخد تا با چرخش هدف ImageAnalysis مطابقت داشته باشد. در بافت برنامه Android، چرخش هدف ImageAnalysis معمولاً با جهت صفحه مطابقت دارد.

ImageCapture

یک فراخوانی برگشتی به نمونه ImageCapture پیوست شده است تا وقتی نتیجه ضبط آماده شد، سیگنال دهد. نتیجه می‌تواند تصویر ثبت‌شده یا خطا باشد.

هنگام گرفتن عکس، callback ارائه‌شده می‌تواند یکی از انواع زیر باشد:

  • ‫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 را حتی پس‌از تغییر جهت بازسازی نمی‌کند، مانند زمانی که دستگاه ۱۸۰ درجه چرخانده می‌شود. همچنین زمانی را که نمایشگر در جهت عمودی معکوس است و دستگاه به‌طور پیش‌فرض به جهت عمودی معکوس نمی‌چرخد مدیریت می‌کند. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.
اختیاری: ویژگی screenOrientation مربوط به Activity را در فایل AndroidManifest روی fullSensor تنظیم کنید. این کار باعث می‌شود وقتی دستگاه در حالت عمودی معکوس است، میانای کاربر به‌صورت عمودی باشد و به Activity اجازه می‌دهد هروقت دستگاه ۹۰ درجه چرخانده شد، توسط سیستم بازسازی شود. بر دستگاه‌هایی که به‌طور پیش‌فرض به حالت عمودی معکوس نمی‌چرخند تأثیری ندارد. وقتی نمایشگر در جهت عمودی معکوس باشد، حالت چندپنجره‌ای پشتیبانی نمی‌شود.
جهت قفل‌شده موارد استفاده را فقط یک‌بار، زمانی که Activity برای اولین‌بار ایجاد می‌شود، مثلاً در onCreate() callback مربوط به Activity، تنظیم کنید.
از OrientationEventListener onOrientationChanged() استفاده کنید. درون فراخوانی برگشتی، چرخش هدف موارد استفاده را به‌جز «پیش‌نمایش» به‌روز کنید. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.
configChanges جهت لغو شد موارد استفاده را فقط یک‌بار، زمانی که Activity برای اولین‌بار ایجاد می‌شود، مثلاً در onCreate() callback مربوط به Activity، تنظیم کنید.
از OrientationEventListener onOrientationChanged() استفاده کنید. درون فراخوانی برگشتی، چرخش هدف موارد استفاده را به‌روز کنید. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.
اختیاری: ویژگی screenOrientation مربوط به Activity را در فایل AndroidManifest روی fullSensor تنظیم کنید. به میانای کاربر اجازه می‌دهد وقتی دستگاه در حالت عمودی معکوس است، به‌صورت عمودی قرار بگیرد. بر دستگاه‌هایی که به‌طور پیش‌فرض به حالت عمودی معکوس نمی‌چرخند تأثیری ندارد. وقتی نمایشگر در جهت عمودی معکوس باشد، حالت چندپنجره‌ای پشتیبانی نمی‌شود.

فقط از جهت‌های پشتیبانی‌شده توسط دستگاه پشتیبانی کنید

فقط از جهت‌هایی پشتیبانی کنید که دستگاه به‌طور پیش‌فرض پشتیبانی می‌کند (که ممکن است شامل عمودی معکوس/ افقی معکوس باشد یا نباشد).

سناریو رهنمودها حالت صفحه دونیمه چندپنجره‌ای
جهت بازشده موارد استفاده را هربار که Activity ایجاد می‌شود، مثلاً در فراخوانی برگشتی onCreate() مربوط به Activity، راه‌اندازی کنید.
از onDisplayChanged() DisplayListener استفاده کنید. درون فراخوانی برگشتی، چرخش هدف موارد استفاده را به‌روز کنید، مثلاً وقتی دستگاه ۱۸۰ درجه می‌چرخد. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.
جهت قفل‌شده موارد استفاده را فقط یک‌بار، زمانی که Activity برای اولین‌بار ایجاد می‌شود، مثلاً در onCreate() callback مربوط به Activity، تنظیم کنید.
از OrientationEventListener onOrientationChanged() استفاده کنید. درون فراخوانی برگشتی، چرخش هدف موارد استفاده را به‌روز کنید. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.
configChanges جهت لغو شد موارد استفاده را فقط یک‌بار، زمانی که Activity برای اولین‌بار ایجاد می‌شود، مثلاً در onCreate() callback مربوط به Activity، تنظیم کنید.
از onDisplayChanged() DisplayListener استفاده کنید. درون فراخوانی برگشتی، چرخش هدف موارد استفاده را به‌روز کنید، مثلاً وقتی دستگاه ۱۸۰ درجه می‌چرخد. همچنین مواردی را که Activity هنگام چرخش دستگاه (مثلاً ۹۰ درجه) بازسازی نمی‌شود، مدیریت می‌کند. این اتفاق در دستگاه‌های با ابعاد کوچک زمانی رخ می‌دهد که برنامه نیمی از صفحه را اشغال کند، و در دستگاه‌های بزرگ‌تر زمانی رخ می‌دهد که برنامه دو سوم صفحه را اشغال کند.

جهت قفل‌نشده

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 را پس‌از چرخش ۱۸۰ درجه‌ای دستگاه ازبین نمی‌برد و دوباره ایجاد نمی‌کند.

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