Flash dello schermo

Il flash dello schermo, chiamato anche flash anteriore o flash per selfie, utilizza la luminosità dello schermo di uno smartphone per illuminare il soggetto quando scatti foto con la fotocamera anteriore in condizioni di scarsa illuminazione. È disponibile in molte app di fotocamera native e di social media. Poiché la maggior parte delle persone tiene lo smartphone abbastanza vicino quando inquadra un autoritratto, questo approccio è efficace.

Tuttavia, è difficile per gli sviluppatori implementare correttamente la funzionalità e mantenere una buona qualità di acquisizione in modo coerente su tutti i dispositivi. Questa guida mostra come implementare correttamente questa funzionalità utilizzando Camera2, l'API del framework della fotocamera di Android a basso livello.

Flusso di lavoro generale

Per implementare correttamente la funzionalità, i due fattori chiave sono l'utilizzo della sequenza di misurazione pre-acquisizione (pre-acquisizione dell'esposizione automatica) e il tempismo delle operazioni. Il flusso di lavoro generale è mostrato nella Figura 1.

Diagramma di flusso che mostra come viene utilizzata un'interfaccia utente di flash sullo schermo in Camera2.
Figura 1. Flusso di lavoro generale per l'implementazione di screen flash.

I seguenti passaggi vengono utilizzati quando è necessario acquisire un'immagine con la funzionalità di illuminazione dello schermo.

  1. Applica le modifiche all'interfaccia utente richieste per il flash dello schermo, che può fornire illuminazione sufficiente per scattare foto utilizzando lo schermo del dispositivo. Per i casi d'uso generali, Google suggerisce le seguenti modifiche all'interfaccia utente, come utilizzato nei nostri test:
    • La schermata dell'app è coperta da un overlay di colore bianco.
    • La luminosità dello schermo è al massimo.
  2. Imposta la modalità di esposizione automatica (AE) su CONTROL_AE_MODE_ON_EXTERNAL_FLASH, se supportata.
  3. Attiva una sequenza di misurazione precedente l'acquisizione utilizzando CONTROL_AE_PRECAPTURE_TRIGGER.
  4. Attendi che l'esposizione automatica (AE) e il bilanciamento del bianco automatico (AWB) vengano convergeti.

  5. Una volta completata la convergenza, viene utilizzato il normale flusso di acquisizione di foto dell'app.

  6. Invia la richiesta di acquisizione al framework.

  7. Attendi di ricevere il risultato dell'acquisizione.

  8. Reimposta la modalità AE se è stato impostato CONTROL_AE_MODE_ON_EXTERNAL_FLASH.

  9. Cancellare le modifiche all'interfaccia utente per il flash dello schermo.

Codici di esempio di Camera2

Coprire la schermata dell'app con un overlay di colore bianco

Aggiungi una visualizzazione nel file XML del layout dell'applicazione. La visualizzazione deve essere sufficientemente elevata da trovarsi sopra tutti gli altri elementi dell'interfaccia utente durante l'acquisizione del flash sullo schermo. Viene mantenuto invisibile per impostazione predefinita e viene reso visibile solo quando vengono applicate le modifiche all'interfaccia utente della schermata di illuminazione.

Nel seguente esempio di codice, il colore bianco (#FFFFFF) viene utilizzato come esempio per la visualizzazione. Le applicazioni possono scegliere il colore o offrire più colori agli utenti, in base ai loro requisiti.

<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" />

Aumentare la luminosità dello schermo

Esistono diversi modi per regolare la luminosità dello schermo in un'app per Android. Un modo diretto è modificare il parametro WindowManager screenBrightness nella documentazione di riferimento Activity Window.

Kotlin

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

Java

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

Imposta la modalità AE su CONTROL_AE_MODE_ON_EXTERNAL_FLASH

CONTROL_AE_MODE_ON_EXTERNAL_FLASH è disponibile con il livello API 28 o versioni successive. Tuttavia, questa modalità AE non è disponibile su tutti i dispositivi, quindi controlla se è disponibile e imposta il valore di conseguenza. Per controllare la disponibilità, utilizza CameraCharacteristics#CONTROL_AE_AVAILABLE_MODES.

Kotlin

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

Java

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

Se l'applicazione ha impostato una richiesta di acquisizione ripetuta (è obbligatoria per la funzionalità Anteprima), la modalità AE deve essere impostata sulla richiesta ripetuta. In caso contrario, potrebbe essere sostituita da una modalità AE predefinita o impostata dall'utente nella successiva acquisizione ripetuta. In questo caso, la videocamera potrebbe non avere tempo sufficiente per eseguire tutte le operazioni che normalmente esegue per una modalità AE con flash esterno.

Per assicurarti che la fotocamera elabori completamente la richiesta di aggiornamento della modalità AE, controlla il risultato dell'acquisizione nel callback di acquisizione ripetuta e attendi che la modalità AE venga aggiornata nel risultato.

Callback di acquisizione che può attendere l'aggiornamento della modalità AE

Il seguente snippet di codice mostra come è possibile farlo.

Kotlin

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

Java

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();

Imposta una richiesta ripetuta per attivare o disattivare la modalità AE

Una volta impostato il callback di acquisizione, i seguenti esempi di codice mostrano come impostare una richiesta ripetuta.

Kotlin

/** [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
        )
    }
}

Java

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

Attivare una sequenza di preacquisizione

Per attivare una sequenza di misurazione precedente l'acquisizione, puoi inviare un CaptureRequest con il valore CONTROL_AE_PRECAPTURE_TRIGGER_START impostato sulla richiesta. Devi attendere che la richiesta venga elaborata e poi che l'AE e l'AWB convergano.

Sebbene la preacquisizione venga attivata con una singola richiesta di acquisizione, l'attesa della convergenza AE e AWB richiede una maggiore complessità. Puoi tenere traccia dello stato AE e dello stato AWB utilizzando un callback di acquisizione impostato su una richiesta ripetuta.

L'aggiornamento dello stesso callback ripetuto ti consente di semplificare il codice. Spesso le applicazioni richiedono un'anteprima per la quale impostano una richiesta ripetuta durante la configurazione della videocamera. Pertanto, puoi impostare il callback di acquisizione ripetuta su la richiesta ripetuta iniziale una volta e poi riutilizzarlo per il controllo dei risultati e per le operazioni in attesa.

Acquisisci l'aggiornamento del codice di callback per attendere la convergenza

Per aggiornare il callback di acquisizione ripetuta, utilizza lo snippet di codice seguente.

Kotlin

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

Java

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

Impostare il callback su una richiesta ripetuta durante la configurazione della videocamera

Il seguente esempio di codice consente di impostare il callback su una richiesta ripetuta durante l'inizializzazione.

Kotlin

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

Java

// 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();
}

Attivazione e attesa della sequenza di preacquisizione

Con il callback impostato, puoi utilizzare il seguente esempio di codice per l'attivazione e l'attesa di una sequenza di preacquisizione.

Kotlin

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

Java

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

Unisci tutto

Una volta pronti tutti i componenti principali, ogni volta che è necessario scattare una foto, ad esempio quando un utente fa clic sul pulsante di acquisizione per scattare una foto, tutti i passaggi possono essere eseguiti nell'ordine indicato nella discussione precedente e negli esempi di codice.

Kotlin

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

Java

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

Immagini di esempio

Di seguito sono riportati alcuni esempi di cosa succede quando lo sfarfallio dello schermo viene implementato in modo errato e quando viene implementato correttamente.

Se eseguita in modo errato

Se il flash dello schermo non è implementato correttamente, i risultati sono incoerenti su più acquisizioni, dispositivi e condizioni di illuminazione. Spesso le immagini acquisite presentano un problema di esposizione o tinta del colore. Per alcuni dispositivi, questi tipi di bug diventano più evidenti in condizioni di illuminazione specifiche, ad esempio in un ambiente con poca luce anziché completamente buio.

La seguente tabella mostra alcuni esempi di questi problemi. Sono state scattate nell'infrastruttura del laboratorio CameraX, con le sorgenti di luce mantenute di colore bianco caldo. Questa sorgente di luce bianca calda ti consente di vedere come la tinta blu sia un problema reale, non un effetto collaterale di una sorgente di luce.

Ambiente Sottoesposizione Sovresposizione Tinta colore
Ambiente buio (nessuna fonte di luce tranne lo smartphone) Foto quasi completamente scura Foto eccessivamente chiara Foto con una tinta violacea
Luce scarsa (sorgente di luce aggiuntiva di circa 3 lux) Foto leggermente scura Foto eccessivamente chiara Foto con una tinta bluastra

Se eseguita correttamente

Quando viene utilizzata l'implementazione standard per gli stessi dispositivi e le stesse condizioni, puoi visualizzare i risultati nella tabella seguente.

Ambiente Sottoesposizione (correzione) Sovresposizione (correzione) Tinta colore (fissa)
Ambiente buio (nessuna fonte di luce tranne lo smartphone) Foto chiara Foto chiara Foto chiara senza alcuna tinta
Luce scarsa (sorgente di luce aggiuntiva di circa 3 lux) Foto chiara Foto chiara Una foto chiara senza sfumature

Come osservato, la qualità delle immagini migliora notevolmente con l'implementazione standard.