Add a tone-mapping ImageProcessor in view test app
Bug: 249593716
Test: manual test and ./gradlew bOS
Change-Id: I9b7a2a710a7ee42975ab7186fafbaed41704e59e
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RgbaImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RgbaImageProxy.java
index 388a07b..a087c88 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RgbaImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/RgbaImageProxy.java
@@ -35,7 +35,6 @@
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.core.ImageInfo;
import androidx.camera.core.ImageProxy;
@@ -91,7 +90,6 @@
*
* <p>The {@link Bitmap} must be {@link Bitmap.Config#ARGB_8888}.
*/
- @VisibleForTesting
public RgbaImageProxy(@NonNull Bitmap bitmap, @NonNull Rect cropRect, int rotationDegrees,
@NonNull Matrix sensorToBuffer, long timestamp) {
this(createDirectByteBuffer(bitmap),
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index b3c9695..28e1fde 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -133,7 +133,7 @@
}
@Test
- fun enableEffect_effectIsEnabled() {
+ fun enableEffect_previewEffectIsEnabled() {
// Arrange: launch app and verify effect is inactive.
fragment.assertPreviewIsStreaming()
val processor =
@@ -150,6 +150,23 @@
}
@Test
+ fun enableEffect_imageCaptureEffectIsEnabled() {
+ // Arrange: launch app and verify effect is inactive.
+ fragment.assertPreviewIsStreaming()
+ val effect = fragment.mToneMappingImageEffect as ToneMappingImageEffect
+ assertThat(effect.isInvoked()).isFalse()
+
+ // Act: turn on effect.
+ val effectToggleId = "androidx.camera.integration.view:id/effect_toggle"
+ uiDevice.findObject(UiSelector().resourceId(effectToggleId)).click()
+ instrumentation.waitForIdleSync()
+ fragment.assertCanTakePicture()
+
+ // Assert: verify that effect is active.
+ assertThat(effect.isInvoked()).isTrue()
+ }
+
+ @Test
fun controllerBound_canGetCameraControl() {
fragment.assertPreviewIsStreaming()
instrumentation.runOnMainSync {
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index 3881ff3..79c1b1c 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -20,8 +20,8 @@
import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
import static androidx.camera.video.VideoRecordEvent.Finalize.ERROR_NONE;
+import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
-import static java.util.Collections.singletonList;
import android.Manifest;
import android.annotation.SuppressLint;
@@ -150,6 +150,7 @@
@VisibleForTesting
ToneMappingPreviewEffect mToneMappingPreviewEffect;
+ ToneMappingImageEffect mToneMappingImageEffect;
private final ImageAnalysis.Analyzer mAnalyzer = image -> {
byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()];
@@ -221,6 +222,7 @@
// Set up post-processing effects.
mToneMappingPreviewEffect = new ToneMappingPreviewEffect();
+ mToneMappingImageEffect = new ToneMappingImageEffect();
mEffectToggle = view.findViewById(R.id.effect_toggle);
mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled());
onEffectsToggled();
@@ -372,7 +374,8 @@
private void onEffectsToggled() {
if (mEffectToggle.isChecked()) {
- mCameraController.setEffects(singletonList(mToneMappingPreviewEffect));
+ mCameraController.setEffects(
+ asList(mToneMappingPreviewEffect, mToneMappingImageEffect));
} else {
mCameraController.setEffects(emptyList());
}
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingImageEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingImageEffect.kt
new file mode 100644
index 0000000..846fa0c
--- /dev/null
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingImageEffect.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.view
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.ColorMatrixColorFilter
+import android.graphics.Paint
+import androidx.camera.core.CameraEffect
+import androidx.camera.core.ImageProcessor
+import androidx.camera.core.ImageProcessor.Response
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.imagecapture.RgbaImageProxy
+import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+
+/**
+ * A image effect that applies the same tone mapping as [ToneMappingSurfaceProcessor].
+ */
+class ToneMappingImageEffect : CameraEffect(
+ IMAGE_CAPTURE, mainThreadExecutor(), ToneMappingImageProcessor()
+) {
+
+ fun isInvoked(): Boolean {
+ return (imageProcessor as ToneMappingImageProcessor).processoed
+ }
+
+ private class ToneMappingImageProcessor : ImageProcessor {
+
+ var processoed = false
+
+ override fun process(request: ImageProcessor.Request): Response {
+ processoed = true
+ val inputImage = request.inputImages.single() as RgbaImageProxy
+ val bitmap = inputImage.createBitmap()
+ applyToneMapping(bitmap)
+ val outputImage = createOutputImage(bitmap, inputImage)
+ inputImage.close()
+ return Response { outputImage }
+ }
+
+ /**
+ * Creates output image
+ */
+ private fun createOutputImage(newBitmap: Bitmap, imageIn: ImageProxy): ImageProxy {
+ return RgbaImageProxy(
+ newBitmap,
+ imageIn.cropRect,
+ imageIn.imageInfo.rotationDegrees,
+ imageIn.imageInfo.sensorToBufferTransformMatrix,
+ imageIn.imageInfo.timestamp
+ )
+ }
+
+ /**
+ * Applies the same color matrix as [ToneMappingSurfaceProcessor].
+ */
+ private fun applyToneMapping(bitmap: Bitmap) {
+ val paint = Paint()
+ paint.colorFilter = ColorMatrixColorFilter(
+ floatArrayOf(
+ 0.5F, 0.8F, 0.3F, 0F, 0F,
+ 0.4F, 0.7F, 0.2F, 0F, 0F,
+ 0.3F, 0.5F, 0.1F, 0F, 0F,
+ 0F, 0F, 0F, 1F, 0F,
+ )
+ )
+ val canvas = Canvas(bitmap)
+ canvas.drawBitmap(bitmap, 0F, 0F, paint)
+ }
+ }
+}