فلاش صفحه

فلش صفحه نمایش که به آن فلش جلو یا فلش سلفی نیز گفته می‌شود، از روشنایی صفحه نمایش گوشی برای روشن کردن سوژه هنگام گرفتن عکس با دوربین جلو در شرایط کم نور استفاده می‌کند. این قابلیت در بسیاری از برنامه‌های دوربین اصلی و برنامه‌های رسانه‌های اجتماعی موجود است. از آنجایی که اکثر افراد هنگام گرفتن عکس پرتره از خود، گوشی خود را به اندازه کافی نزدیک نگه می‌دارند، این رویکرد مؤثر است.

با این حال، پیاده‌سازی صحیح این ویژگی و حفظ کیفیت خوب ضبط به طور مداوم در بین دستگاه‌ها برای توسعه‌دهندگان دشوار است. این راهنما نحوه پیاده‌سازی صحیح این ویژگی را با استفاده از Camera2 ، API سطح پایین چارچوب دوربین اندروید، نشان می‌دهد.

گردش کار عمومی

برای پیاده‌سازی صحیح این ویژگی، دو عامل کلیدی عبارتند از استفاده از توالی اندازه‌گیری پیش از ثبت (پیش از ثبت خودکار نوردهی) و زمان‌بندی عملیات. روند کلی کار در شکل ۱ نشان داده شده است.

نمودار جریانی که نحوه استفاده از رابط کاربری فلاش صفحه نمایش در Camera2 را نشان می‌دهد.
شکل ۱. گردش کار کلی برای پیاده‌سازی فلش صفحه نمایش

مراحل زیر زمانی استفاده می‌شوند که نیاز به ثبت تصویر با قابلیت فلاش صفحه نمایش باشد.

  1. تغییرات رابط کاربری مورد نیاز برای فلاش صفحه نمایش را اعمال کنید، که می‌تواند نور کافی برای گرفتن عکس با استفاده از صفحه نمایش دستگاه را فراهم کند. برای موارد استفاده عمومی، گوگل تغییرات رابط کاربری زیر را که در آزمایش‌های ما استفاده شده است، پیشنهاد می‌کند:
    • صفحه برنامه با یک لایه سفید رنگ پوشانده شده است.
    • روشنایی صفحه نمایش به حداکثر می‌رسد.
  2. در صورت پشتیبانی، حالت نوردهی خودکار (AE) را روی CONTROL_AE_MODE_ON_EXTERNAL_FLASH تنظیم کنید.
  3. با استفاده از CONTROL_AE_PRECAPTURE_TRIGGER یک توالی اندازه‌گیری پیش از ضبط را فعال کنید.
  4. صبر کنید تا نوردهی خودکار (AE) و تراز سفیدی خودکار (AWB) با هم هماهنگ شوند.

  5. پس از همگرایی، از جریان معمول ثبت عکس در برنامه استفاده می‌شود.

  6. درخواست ضبط را به چارچوب ارسال کنید.

  7. منتظر دریافت نتیجه ضبط باشید.

  8. اگر CONTROL_AE_MODE_ON_EXTERNAL_FLASH تنظیم شده بود، حالت AE را به حالت اولیه برگردانید.

  9. تغییرات رابط کاربری مربوط به فلش صفحه را پاک کنید.

کدهای نمونه Camera2

صفحه برنامه را با یک لایه سفید رنگ بپوشانید

یک View به فایل XML طرح‌بندی برنامه خود اضافه کنید. این View ارتفاع کافی برای قرار گرفتن روی سایر عناصر رابط کاربری در هنگام ضبط صفحه نمایش را دارد. این View به طور پیش‌فرض نامرئی نگه داشته می‌شود و فقط زمانی قابل مشاهده می‌شود که تغییرات رابط کاربری در صفحه نمایش اعمال شود.

در نمونه کد زیر، از رنگ سفید ( #FFFFFF ) به عنوان نمونه برای view استفاده شده است. برنامه‌ها می‌توانند بر اساس نیاز کاربران، رنگ را انتخاب کنند یا چندین رنگ را به آنها ارائه دهند.

<View
    android:id="@+id/white_color_overlay"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:visibility="invisible"
    android:elevation="8dp" />

حداکثر کردن روشنایی صفحه نمایش

روش‌های مختلفی برای تغییر روشنایی صفحه نمایش در یک برنامه اندروید وجود دارد. یک راه مستقیم، تغییر پارامتر screenBrightness WindowManager در مرجع Activity Window است.

کاتلین

private var previousBrightness: Float = -1.0f

private fun maximizeScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            previousBrightness = screenBrightness
            screenBrightness = 1f
            window.attributes = this
        }
    }
}

private fun restoreScreenBrightness() {
    activity?.window?.let { window ->
        window.attributes?.apply {
            screenBrightness = previousBrightness
            window.attributes = this
        }
    }
}

جاوا

private float mPreviousBrightness = -1.0f;

private void maximizeScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    mPreviousBrightness = attributes.screenBrightness;
    attributes.screenBrightness = 1f;
    window.setAttributes(attributes);
}

private void restoreScreenBrightness() {
    if (getActivity() == null || getActivity().getWindow() == null) {
        return;
    }

    Window window = getActivity().getWindow();
    WindowManager.LayoutParams attributes = window.getAttributes();

    attributes.screenBrightness = mPreviousBrightness;
    window.setAttributes(attributes);
}

حالت AE را روی CONTROL_AE_MODE_ON_EXTERNAL_FLASH تنظیم کنید.

CONTROL_AE_MODE_ON_EXTERNAL_FLASH با سطح API 28 یا بالاتر در دسترس است. با این حال، این حالت AE در همه دستگاه‌ها در دسترس نیست، بنابراین بررسی کنید که آیا حالت AE در دسترس است یا خیر و مقدار آن را بر اساس آن تنظیم کنید. برای بررسی در دسترس بودن، از CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES استفاده کنید.

کاتلین

private val characteristics: CameraCharacteristics by lazy {
    cameraManager.getCameraCharacteristics(cameraId)
}

@RequiresApi(Build.VERSION_CODES.P)
private fun isExternalFlashAeModeAvailable() =
    characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES)
        ?.contains(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) ?: false

جاوا

try {
    mCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

@RequiresApi(Build.VERSION_CODES.P)
private boolean isExternalFlashAeModeAvailable() {
    int[] availableAeModes = mCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);

    for (int aeMode : availableAeModes) {
        if (aeMode == CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH) {
            return true;
        }
    }
    return false;
}

اگر برنامه درخواست ضبط تکراری داشته باشد (که برای پیش‌نمایش لازم است)، حالت AE باید روی درخواست تکراری تنظیم شود. در غیر این صورت، ممکن است در ضبط تکراری بعدی، توسط حالت AE پیش‌فرض یا حالت AE تنظیم‌شده توسط کاربر لغو شود. اگر این اتفاق بیفتد، ممکن است دوربین زمان کافی برای انجام تمام عملیاتی که معمولاً برای حالت AE فلاش خارجی انجام می‌دهد، نداشته باشد.

برای اطمینان از اینکه دوربین درخواست به‌روزرسانی حالت AE را به طور کامل پردازش می‌کند، نتیجه ضبط را در فراخوانی تکرارشونده ضبط بررسی کنید و منتظر بمانید تا حالت AE در نتیجه به‌روزرسانی شود.

ضبط تماس‌های برگشتی که می‌توانند منتظر به‌روزرسانی حالت AE باشند

قطعه کد زیر نشان می‌دهد که چگونه می‌توان این کار را انجام داد.

کاتلین

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

جاوا

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

private final AwaitingCaptureCallback mRepeatingCaptureCallback = new AwaitingCaptureCallback();

یک درخواست تکرارشونده برای فعال یا غیرفعال کردن حالت AE تنظیم کنید

با فعال بودن تابع فراخوانی capture، نمونه‌های کد زیر نحوه تنظیم یک درخواست تکراری را نشان می‌دهند.

کاتلین

/** [HandlerThread] where all camera operations run */
private val cameraThread = HandlerThread("CameraThread").apply { start() }

/** [Handler] corresponding to [cameraThread] */
private val cameraHandler = Handler(cameraThread.looper)

private suspend fun enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
                set(
                    CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH
                )
            }.build(), repeatingCaptureCallback, cameraHandler
        )

        // Wait for the request to be processed by camera
        repeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH)
    }
}

private fun disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        session.setRepeatingRequest(
            camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW).apply {
                addTarget(previewSurface)
            }.build(), repeatingCaptureCallback, cameraHandler
        )
    }
}

جاوا

private void setupCameraThread() {
    // HandlerThread where all camera operations run
    HandlerThread cameraThread = new HandlerThread("CameraThread");
    cameraThread.start();

    // Handler corresponding to cameraThread
    mCameraHandler = new Handler(cameraThread.getLooper());
}

private void enableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // Wait for the request to be processed by camera
        mRepeatingCaptureCallback.awaitAeModeUpdate(CaptureRequest.CONTROL_AE_MODE_ON_EXTERNAL_FLASH);
    }
}

private void disableExternalFlashAeMode() {
    if (Build.VERSION.SDK_INT >= 28 && isExternalFlashAeModeAvailable()) {
        try {
            CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            requestBuilder.addTarget(mPreviewSurface);
            mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

یک توالی پیش‌ثبت‌نام را فعال کنید

برای شروع یک توالی اندازه‌گیری پیش از ضبط، می‌توانید یک CaptureRequest با مقدار CONTROL_AE_PRECAPTURE_TRIGGER_START که برای درخواست تنظیم شده است، ارسال کنید. باید منتظر پردازش درخواست باشید و سپس منتظر بمانید تا AE و AWB همگرا شوند.

اگرچه پیش‌ضبط با یک درخواست ضبط واحد فعال می‌شود، اما انتظار برای همگرایی AE و AWB به پیچیدگی بیشتری نیاز دارد. می‌توانید با استفاده از یک فراخوانی ضبط که روی یک درخواست تکراری تنظیم شده است، وضعیت AE و وضعیت AWB را پیگیری کنید.

به‌روزرسانی همان فراخوانی تکراری به شما امکان می‌دهد سادگی کد را داشته باشید. برنامه‌ها اغلب به یک پیش‌نمایش نیاز دارند که برای آن هنگام تنظیم دوربین، یک درخواست تکراری تنظیم می‌کنند. بنابراین، می‌توانید یک بار فراخوانی ضبط تکراری را روی آن درخواست تکراری اولیه تنظیم کنید و سپس دوباره از آن برای بررسی نتیجه و اهداف انتظار استفاده کنید.

به‌روزرسانی کد پاسخ به تماس را ضبط کنید تا منتظر همگرایی باشید

برای به‌روزرسانی فراخوانی تکرارشونده‌ی capture، از قطعه کد زیر استفاده کنید.

کاتلین

private val repeatingCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
    private var targetAeMode: Int? = null
    private var aeModeUpdateDeferred: CompletableDeferred? = null

    private var convergenceDeferred: CompletableDeferred? = null

    suspend fun awaitAeModeUpdate(targetAeMode: Int) {
        this.targetAeMode = targetAeMode
        aeModeUpdateDeferred = CompletableDeferred()
        // Makes the current coroutine wait until aeModeUpdateDeferred is completed. It is
        // completed once targetAeMode is found in the following capture callbacks
        aeModeUpdateDeferred?.await()
    }

    suspend fun awaitAeAwbConvergence() {
        convergenceDeferred = CompletableDeferred()
        // Makes the current coroutine wait until convergenceDeferred is completed, it will be
        // completed once both AE & AWB are reported as converged in the capture callbacks below
        convergenceDeferred?.await()
    }

    private fun process(result: CaptureResult) {
        // Checks if AE mode is updated and completes any awaiting Deferred
        aeModeUpdateDeferred?.let {
            val aeMode = result[CaptureResult.CONTROL_AE_MODE]
            if (aeMode == targetAeMode) {
                it.complete(Unit)
            }
        }

        // Checks for convergence and completes any awaiting Deferred
        convergenceDeferred?.let {
            val aeState = result[CaptureResult.CONTROL_AE_STATE]
            val awbState = result[CaptureResult.CONTROL_AWB_STATE]

            val isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
                    )

            val isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
                    )

            if (isAeReady && isAwbReady) {
                // if any non-null convergenceDeferred is set, complete it
                it.complete(Unit)
            }
        }
    }

    override fun onCaptureCompleted(
        session: CameraCaptureSession,
        request: CaptureRequest,
        result: TotalCaptureResult
    ) {
        super.onCaptureCompleted(session, request, result)
        process(result)
    }
}

جاوا

static class AwaitingCaptureCallback extends CameraCaptureSession.CaptureCallback {
    private int mTargetAeMode;
    private CountDownLatch mAeModeUpdateLatch = null;

    private CountDownLatch mConvergenceLatch = null;

    public void awaitAeModeUpdate(int targetAeMode) {
        mTargetAeMode = targetAeMode;
        mAeModeUpdateLatch = new CountDownLatch(1);
        // Makes the current thread wait until mAeModeUpdateLatch is released, it will be
        // released once targetAeMode is found in the capture callbacks below
        try {
            mAeModeUpdateLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void awaitAeAwbConvergence() {
        mConvergenceLatch = new CountDownLatch(1);
        // Makes the current coroutine wait until mConvergenceLatch is released, it will be
        // released once both AE & AWB are reported as converged in the capture callbacks below
        try {
            mConvergenceLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void process(CaptureResult result) {
        // Checks if AE mode is updated and decrements the count of any awaiting latch
        if (mAeModeUpdateLatch != null) {
            int aeMode = result.get(CaptureResult.CONTROL_AE_MODE);
            if (aeMode == mTargetAeMode) {
                mAeModeUpdateLatch.countDown();
            }
        }

        // Checks for convergence and decrements the count of any awaiting latch
        if (mConvergenceLatch != null) {
            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
            Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);

            boolean isAeReady = (
                    aeState == null // May be null in some devices (e.g. legacy camera HW level)
                            || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
                            || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
            );

            boolean isAwbReady = (
                    awbState == null // May be null in some devices (e.g. legacy camera HW level)
                            || awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED
            );

            if (isAeReady && isAwbReady) {
                mConvergenceLatch.countDown();
                mConvergenceLatch = null;
            }
        }
    }

    @Override
    public void onCaptureCompleted(@NonNull CameraCaptureSession session,
            @NonNull CaptureRequest request,
            @NonNull TotalCaptureResult result) {
        super.onCaptureCompleted(session, request, result);
        process(result);
    }
}

تنظیم درخواست پاسخ به تماس (callback) به صورت تکراری در طول تنظیم دوربین

نمونه کد زیر به شما امکان می‌دهد تا در طول مقداردهی اولیه، callback را روی یک درخواست تکراری تنظیم کنید.

کاتلین

// Open the selected camera
camera = openCamera(cameraManager, cameraId, cameraHandler)

// Creates list of Surfaces where the camera will output frames
val targets = listOf(previewSurface, imageReaderSurface)

// Start a capture session using our open camera and list of Surfaces where frames will go
session = createCameraCaptureSession(camera, targets, cameraHandler)

val captureRequest = camera.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(previewSurface) }

// This will keep sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
session.setRepeatingRequest(captureRequest.build(), repeatingCaptureCallback, cameraHandler)

جاوا

// Open the selected camera
mCamera = openCamera(mCameraManager, mCameraId, mCameraHandler);

// Creates list of Surfaces where the camera will output frames
List targets = new ArrayList<>(Arrays.asList(mPreviewSurface, mImageReaderSurface));

// Start a capture session using our open camera and list of Surfaces where frames will go
mSession = createCaptureSession(mCamera, targets, mCameraHandler);

try {
    CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
    requestBuilder.addTarget(mPreviewSurface);

    // This will keep sending the capture request as frequently as possible until the
    // session is torn down or session.stopRepeating() is called
    mSession.setRepeatingRequest(requestBuilder.build(), mRepeatingCaptureCallback, mCameraHandler);
} catch (CameraAccessException e) {
    e.printStackTrace();
}

فعال‌سازی و انتظار توالی پیش‌ضبط

با تنظیم callback، می‌توانید از نمونه کد زیر برای فعال‌سازی و انتظار یک توالی precapture استفاده کنید.

کاتلین

private suspend fun runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    val captureRequest = session.device.createCaptureRequest(
        CameraDevice.TEMPLATE_PREVIEW
    ).apply {
        addTarget(previewSurface)
        set(
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
            CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START
        )
    }

    val precaptureDeferred = CompletableDeferred()
    session.capture(captureRequest.build(), object: CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
            session: CameraCaptureSession,
            request: CaptureRequest,
            result: TotalCaptureResult
        ) {
            // Waiting for this callback ensures the precapture request has been processed
            precaptureDeferred.complete(Unit)
        }
    }, cameraHandler)

    precaptureDeferred.await()

    // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
    repeatingCaptureCallback.awaitAeAwbConvergence()
}

جاوا

private void runPrecaptureSequence() {
    // Creates a new capture request with CONTROL_AE_PRECAPTURE_TRIGGER_START
    try {
        CaptureRequest.Builder requestBuilder =
                mSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        requestBuilder.addTarget(mPreviewSurface);
        requestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);

        CountDownLatch precaptureLatch = new CountDownLatch(1);
        mSession.capture(requestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                Log.d(TAG, "CONTROL_AE_PRECAPTURE_TRIGGER_START processed");
                // Waiting for this callback ensures the precapture request has been processed
                precaptureLatch.countDown();
            }
        }, mCameraHandler);

        precaptureLatch.await();

        // Precapture trigger request has been processed, we can wait for AE & AWB convergence now
        mRepeatingCaptureCallback.awaitAeAwbConvergence();
    } catch (CameraAccessException | InterruptedException e) {
        e.printStackTrace();
    }
}

همه چیز را به هم بدوزید

با آماده شدن تمام اجزای اصلی، هر زمان که نیاز به گرفتن عکس باشد، مثلاً وقتی کاربر روی دکمه ضبط کلیک می‌کند تا عکس بگیرد، تمام مراحل می‌توانند به ترتیبی که در بحث قبلی و نمونه‌های کد ذکر شده است، اجرا شوند.

کاتلین

// User clicks captureButton to take picture
captureButton.setOnClickListener { v ->
    // Apply the screen flash related UI changes
    whiteColorOverlayView.visibility = View.VISIBLE
    maximizeScreenBrightness()

    // Perform I/O heavy operations in a different scope
    lifecycleScope.launch(Dispatchers.IO) {
        // Enable external flash AE mode and wait for it to be processed
        enableExternalFlashAeMode()

        // Run precapture sequence and wait for it to complete
        runPrecaptureSequence()

        // Start taking picture and wait for it to complete
        takePhoto()

        disableExternalFlashAeMode()
        v.post {
            // Clear the screen flash related UI changes
            restoreScreenBrightness()
            whiteColorOverlayView.visibility = View.INVISIBLE
        }
    }
}

جاوا

// User clicks captureButton to take picture
mCaptureButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Apply the screen flash related UI changes
        mWhiteColorOverlayView.setVisibility(View.VISIBLE);
        maximizeScreenBrightness();

        // Perform heavy operations in a different thread
        Executors.newSingleThreadExecutor().execute(() -> {
            // Enable external flash AE mode and wait for it to be processed
            enableExternalFlashAeMode();

            // Run precapture sequence and wait for it to complete
            runPrecaptureSequence();

            // Start taking picture and wait for it to complete
            takePhoto();

            disableExternalFlashAeMode();

            v.post(() -> {
                // Clear the screen flash related UI changes
                restoreScreenBrightness();
                mWhiteColorOverlayView.setVisibility(View.INVISIBLE);
            });
        });
    }
});

تصاویر نمونه

شما می‌توانید از مثال‌های زیر متوجه شوید که چه اتفاقی می‌افتد وقتی که فلش صفحه نمایش به اشتباه پیاده‌سازی شده باشد و چه زمانی که به درستی پیاده‌سازی شده باشد.

وقتی کار اشتباهی انجام شود

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

جدول زیر نمونه‌هایی از چنین مشکلاتی را نشان می‌دهد. این نمونه‌ها در زیرساخت آزمایشگاه CameraX گرفته شده‌اند و منابع نور با رنگ سفید گرم باقی مانده‌اند. این منبع نور سفید گرم به شما این امکان را می‌دهد که ببینید چگونه ته رنگ آبی یک مشکل واقعی است، نه یک عارضه جانبی منبع نور.

محیط زیست نوردهی ناکافی قرارگیری بیش از حد در معرض بیماری رنگ مایه
محیط تاریک (بدون منبع نور به جز تلفن) عکس تقریباً کاملاً تاریکعکس بیش از حد روشن شدهعکس با ته رنگ بنفش
نور کم (منبع نور اضافی حدود ۳ لوکس) عکس کمی تاریکعکس بیش از حد روشن شدهعکس با ته رنگ آبی

وقتی درست انجام شود

وقتی پیاده‌سازی استاندارد برای دستگاه‌ها و شرایط یکسان استفاده شود، می‌توانید نتایج را در جدول زیر مشاهده کنید.

محیط زیست نوردهی ناکافی (ثابت) نوردهی بیش از حد (ثابت) رنگ ثابت (ثابت)
محیط تاریک (بدون منبع نور به جز تلفن) عکس واضحعکس واضحعکس واضح و بدون هیچ گونه سایه ای
نور کم (منبع نور اضافی حدود ۳ لوکس) عکس واضحعکس واضحعکس واضح و بدون رنگ

همانطور که مشاهده شد، کیفیت تصویر با پیاده‌سازی استاندارد به طور قابل توجهی بهبود می‌یابد.