diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2022-09-26 20:49:20 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2022-09-26 20:49:20 +0000 |
commit | 7063952a472076b1af836bb006bcdc7bc7250737 (patch) | |
tree | 576ad111e7f9e3cf938676f5e1ce5bb51defb4d4 | |
parent | 55bebb0c37140ddd7613218ff21e083140209714 (diff) | |
parent | 653bc8823a20986c5c307cded95afb223ebd9991 (diff) | |
download | support-7063952a472076b1af836bb006bcdc7bc7250737.tar.gz |
Merge "Remove EffectBundle and update CameraEffect" into androidx-main
23 files changed, 323 insertions, 354 deletions
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt index 49f2220a216..79a9f91fc19 100644 --- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt +++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt @@ -20,8 +20,8 @@ import android.graphics.Rect import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW import android.util.Size import android.view.Surface +import androidx.camera.core.CameraEffect import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM -import androidx.camera.core.SurfaceProcessor import androidx.camera.core.SurfaceRequest import androidx.camera.core.impl.DeferrableSurface import androidx.camera.core.impl.ImageFormatConstants @@ -304,7 +304,7 @@ class DefaultSurfaceProcessorTest { private fun createSurfaceOutput(surface: Surface = mock(Surface::class.java)) = SurfaceOutputImpl( surface, - SurfaceProcessor.PREVIEW, + CameraEffect.PREVIEW, ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE, Size(WIDTH, HEIGHT), USE_SURFACE_TEXTURE_TRANSFORM, diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java index 2445aa0a098..4d7c8ea771a 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java @@ -13,34 +13,177 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package androidx.camera.core; +import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor; +import static androidx.core.util.Preconditions.checkState; + +import android.os.Build; + import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.concurrent.Executor; /** - * The effect interface which all other effects are built on top of. + * A CameraX post-processing effects. * - * <p>A CameraEffect provides functionality to inject post processing to camera output. + * <p>A {@link CameraEffect} class contains two types of information, the processor and the + * configuration. + * <ul> + * <li> The processor is an implementation of a CameraX interface e.g. {@link SurfaceProcessor}. + * It consumes original camera frames from CameraX, applies the effect, and returns the processed + * frames back to CameraX. + * <li> The configuration provides information on how the processor should be injected into the + * CameraX pipeline. For example, the target {@link UseCase}s where the effect should be applied. + * </ul> * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public interface CameraEffect { +@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) +public class CameraEffect { /** - * Bitmask options for the targets of the effect. + * Bitmask options for the effect targets. * * @hide */ @Retention(RetentionPolicy.SOURCE) @RestrictTo(RestrictTo.Scope.LIBRARY) - @IntDef(flag = true, value = {SurfaceProcessor.PREVIEW, SurfaceProcessor.VIDEO_CAPTURE, - ImageEffect.IMAGE_CAPTURE}) - @interface Targets { + @IntDef(flag = true, value = {PREVIEW, VIDEO_CAPTURE, IMAGE_CAPTURE}) + public @interface Targets { + } + + /** + * Bitmask option to indicate that CameraX should apply this effect to {@link Preview}. + */ + public static final int PREVIEW = 1; + + /** + * Bitmask option to indicate that CameraX should apply this effect to {@code VideoCapture}. + */ + public static final int VIDEO_CAPTURE = 1 << 1; + + /** + * Bitmask option to indicate that CameraX should apply this effect to {@link ImageCapture}. + */ + public static final int IMAGE_CAPTURE = 1 << 2; + + @Targets + private final int mTargets; + @NonNull + private final Executor mProcessorExecutor; + @Nullable + private final SurfaceProcessor mSurfaceProcessor; + + /** + * Private constructor as a workaround to allow @Nullable annotation on final fields. + */ + @SuppressWarnings("UnusedMethod") // TODO: remove once we add {@link ImageProcessor}. + private CameraEffect(@Targets int targets) { + mTargets = targets; + mProcessorExecutor = mainThreadExecutor(); + mSurfaceProcessor = null; + } + + /** + * @param targets the target {@link UseCase} to which this effect should be applied. + * @param processorExecutor the {@link Executor} on which the processor will be invoked. + * @param surfaceProcessor a {@link SurfaceProcessor} implementation. + */ + protected CameraEffect( + @Targets int targets, + @NonNull Executor processorExecutor, + @NonNull SurfaceProcessor surfaceProcessor) { + mTargets = targets; + mProcessorExecutor = processorExecutor; + mSurfaceProcessor = surfaceProcessor; + } + + /** + * Ges the target {@link UseCase}s of this effect. + */ + @Targets + public int getTargets() { + return mTargets; + } + + /** + * Gets the {@link Executor} for calling processors. + * + * <p>This method returns the value set via {@link Builder#setSurfaceProcessor}. + */ + @NonNull + public Executor getProcessorExecutor() { + return mProcessorExecutor; + } + + /** + * Gets the {@link SurfaceProcessor} associated with this effect. + * + * <p>This method returns the value set via {@link Builder#setSurfaceProcessor}. + */ + @Nullable + public SurfaceProcessor getSurfaceProcessor() { + return mSurfaceProcessor; + } + + /** + * Builder class for {@link CameraEffect}. + */ + public static class Builder { + @Targets + private final int mTargets; + @Nullable + private Executor mProcessorExecutor; + @Nullable + private SurfaceProcessor mSurfaceProcessor; + + /** + * @param targets the target {@link UseCase} of the Effect. e.g. if the + * value is {@link #PREVIEW}, CameraX will apply the effect to + * {@link Preview}. + */ + public Builder(@Targets int targets) { + mTargets = targets; + } + + /** + * Sets a {@link SurfaceProcessor} for the effect. + * + * <p>Once the effect is active, CameraX will send original camera frames to the + * {@link SurfaceProcessor} on the {@link Executor}, and deliver the processed output + * frames to the app. + * + * @param executor on which the {@link SurfaceProcessor} will be invoked. + * @param processor the post processor to be injected into CameraX pipeline. + */ + @NonNull + public Builder setSurfaceProcessor(@NonNull Executor executor, + @NonNull SurfaceProcessor processor) { + mProcessorExecutor = executor; + mSurfaceProcessor = processor; + return this; + } + + /** + * Builds a {@link CameraEffect} instance. + * + * <p>CameraX supports a selected set of configuration/processor combinations. This method + * throws a {@link UnsupportedOperationException} if the current combination is not + * supported. + */ + @NonNull + public CameraEffect build() { + checkState(mProcessorExecutor != null && mSurfaceProcessor != null, + "Must set a processor."); + return new CameraEffect(mTargets, mProcessorExecutor, mSurfaceProcessor); + } } } diff --git a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java b/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java deleted file mode 100644 index ba6b61f8db6..00000000000 --- a/camera/camera-core/src/main/java/androidx/camera/core/EffectBundle.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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.core; - -import static androidx.camera.core.SurfaceProcessor.PREVIEW; -import static androidx.core.util.Preconditions.checkArgument; - -import androidx.annotation.NonNull; -import androidx.annotation.RestrictTo; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.Executor; - -/** - * A bundle of {@link CameraEffect}s and their targets. - * - * @hide - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public class EffectBundle { - - private final Map<Integer, CameraEffect> mEffects; - - private final Executor mExecutor; - - EffectBundle(@NonNull Map<Integer, CameraEffect> effects, @NonNull Executor executor) { - mEffects = effects; - mExecutor = executor; - } - - /** - * Gets the {@link CameraEffect} and their targets. - */ - @NonNull - public Map<Integer, CameraEffect> getEffects() { - return new HashMap<>(mEffects); - } - - /** - * Gets the {@link Executor} used for calling the {@link CameraEffect}. - */ - @NonNull - public Executor getExecutor() { - return mExecutor; - } - - /** - * Builder class for {@link EffectBundle}. - */ - public static class Builder { - - private final Map<Integer, CameraEffect> mEffects; - private final Executor mExecutor; - - /** - * Creates a {@link EffectBundle} builder. - * - * @param executor on which the {@link CameraEffect}s will be invoked. - */ - public Builder(@NonNull Executor executor) { - mEffects = new HashMap<>(); - mExecutor = executor; - } - - /** - * Adds a {@link CameraEffect} with its targets. - * - * @param targets on which the effect will be applied. CameraX only supports - * {@link SurfaceProcessor#PREVIEW} for now. - * @param cameraEffect the effect implementation. - * @throws IllegalArgumentException if the configuration is illegal. - */ - @NonNull - public Builder addEffect( - @CameraEffect.Targets int targets, - @NonNull CameraEffect cameraEffect) { - checkArgument(!mEffects.containsKey(targets), "The target already has an effect"); - checkArgument(targets == PREVIEW, "Only allows PREVIEW target."); - if (cameraEffect instanceof SurfaceProcessor) { - mEffects.put(targets, cameraEffect); - } else { - throw new UnsupportedOperationException( - "CameraX only supports SurfaceProcessor for now."); - } - return this; - } - - /** - * Builds the {@link EffectBundle}. - * - * @throws IllegalArgumentException if the bundle contains no effect. - */ - @NonNull - public EffectBundle build() { - checkArgument(mEffects.size() > 0, "The bundle cannot be empty"); - return new EffectBundle(new HashMap<>(mEffects), mExecutor); - } - } -} diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessor.java index 9e32538e1c0..03d7ef7bcd5 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/ImageEffect.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageProcessor.java @@ -24,12 +24,6 @@ import androidx.annotation.RestrictTo; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public interface ImageEffect extends CameraEffect { - - /** - * Bitmask option to indicate that CameraX applies this effect to {@link ImageCapture}. - */ - int IMAGE_CAPTURE = 1 << 2; - +public interface ImageProcessor { // TODO(b/229629890): create the public interface for post-processing images. } diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java index 062cdea7e05..dab5cfd59d2 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java @@ -312,7 +312,7 @@ public final class Preview extends UseCase { // Create nodes and edges. mNode = new SurfaceProcessorNode(camera, USE_SURFACE_TEXTURE_TRANSFORM, mSurfaceProcessor); SettableSurface cameraSurface = new SettableSurface( - SurfaceProcessor.PREVIEW, + CameraEffect.PREVIEW, resolution, ImageFormat.PRIVATE, new Matrix(), diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java index fd55b33575f..56e6aec64ec 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceProcessor.java @@ -34,17 +34,7 @@ import androidx.core.util.Consumer; * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) -public interface SurfaceProcessor extends CameraEffect { - - /** - * Bitmask option to indicate that CameraX applies this effect to {@link Preview}. - */ - int PREVIEW = 1; - - /** - * Bitmask option to indicate that CameraX applies this effect to {@code VideoCapture}. - */ - int VIDEO_CAPTURE = 1 << 1; +public interface SurfaceProcessor { /** * Invoked when CameraX requires an input {@link Surface} for reading original frames. diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java index d5a76f28e36..3848aa571c1 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java @@ -37,21 +37,18 @@ import java.util.List; */ @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java public final class UseCaseGroup { - @Nullable private final ViewPort mViewPort; - @NonNull private final List<UseCase> mUseCases; - @NonNull - private final EffectBundle mEffectBundle; + private final List<CameraEffect> mEffects; UseCaseGroup(@Nullable ViewPort viewPort, @NonNull List<UseCase> useCases, - @NonNull EffectBundle effectBundle) { + @NonNull List<CameraEffect> effects) { mViewPort = viewPort; mUseCases = useCases; - mEffectBundle = effectBundle; + mEffects = effects; } /** @@ -71,30 +68,27 @@ public final class UseCaseGroup { } /** - * Gets the {@link EffectBundle}. + * Gets the {@link CameraEffect}s. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @NonNull - public EffectBundle getEffectBundle() { - return mEffectBundle; + public List<CameraEffect> getEffects() { + return mEffects; } /** * A builder for generating {@link UseCaseGroup}. */ public static final class Builder { - private ViewPort mViewPort; - private final List<UseCase> mUseCases; - - @Nullable - private EffectBundle mEffectBundle; + private final List<CameraEffect> mEffects; public Builder() { mUseCases = new ArrayList<>(); + mEffects = new ArrayList<>(); } /** @@ -107,17 +101,17 @@ public final class UseCaseGroup { } /** - * Sets the {@link EffectBundle} for the {@link UseCase}s. + * Adds a {@link CameraEffect} to the collection * - * <p>Once set, CameraX will use the {@link SurfaceProcessor}s to process the outputs of + * <p>Once added, CameraX will use the {@link CameraEffect}s to process the outputs of * the {@link UseCase}s. * * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) @NonNull - public Builder setEffectBundle(@NonNull EffectBundle effectBundle) { - mEffectBundle = effectBundle; + public Builder addEffect(@NonNull CameraEffect cameraEffect) { + mEffects.add(cameraEffect); return this; } @@ -136,8 +130,7 @@ public final class UseCaseGroup { @NonNull public UseCaseGroup build() { checkArgument(!mUseCases.isEmpty(), "UseCase must not be empty."); - return new UseCaseGroup(mViewPort, mUseCases, mEffectBundle); + return new UseCaseGroup(mViewPort, mUseCases, mEffects); } } - } diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java index a19cccb74e0..a2da5d91edb 100644 --- a/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java +++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/CameraUseCaseAdapter.java @@ -16,6 +16,9 @@ package androidx.camera.core.internal; +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; + import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; @@ -33,11 +36,9 @@ import androidx.camera.core.CameraControl; import androidx.camera.core.CameraEffect; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraSelector; -import androidx.camera.core.EffectBundle; import androidx.camera.core.ImageCapture; import androidx.camera.core.Logger; import androidx.camera.core.Preview; -import androidx.camera.core.SurfaceProcessor; import androidx.camera.core.UseCase; import androidx.camera.core.ViewPort; import androidx.camera.core.impl.AttachedSurfaceInfo; @@ -52,19 +53,16 @@ import androidx.camera.core.impl.SurfaceConfig; import androidx.camera.core.impl.UseCaseConfig; import androidx.camera.core.impl.UseCaseConfigFactory; import androidx.camera.core.impl.utils.executor.CameraXExecutors; -import androidx.camera.core.processing.SurfaceProcessorInternal; import androidx.camera.core.processing.SurfaceProcessorWithExecutor; import androidx.core.util.Preconditions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.concurrent.Executor; /** * A {@link CameraInternal} adapter which checks that the UseCases to make sure that the resolutions @@ -97,8 +95,8 @@ public final class CameraUseCaseAdapter implements Camera { private ViewPort mViewPort; @GuardedBy("mLock") - @Nullable - private EffectBundle mEffectBundle; + @NonNull + private List<CameraEffect> mEffects = emptyList(); // Additional configs to apply onto the UseCases when added to this Camera @GuardedBy("mLock") @@ -176,9 +174,9 @@ public final class CameraUseCaseAdapter implements Camera { /** * Set the effects that will be used for the {@link UseCase} attached to the camera. */ - public void setEffectBundle(@Nullable EffectBundle effectBundle) { + public void setEffects(@Nullable List<CameraEffect> effects) { synchronized (mLock) { - mEffectBundle = effectBundle; + mEffects = effects; } } @@ -202,8 +200,8 @@ public final class CameraUseCaseAdapter implements Camera { } List<UseCase> allUseCases = new ArrayList<>(mUseCases); - List<UseCase> requiredExtraUseCases = Collections.emptyList(); - List<UseCase> removedExtraUseCases = Collections.emptyList(); + List<UseCase> requiredExtraUseCases = emptyList(); + List<UseCase> removedExtraUseCases = emptyList(); if (isCoexistingPreviewImageCaptureRequired()) { // Collects all use cases that will be finally bound by the application @@ -244,7 +242,7 @@ public final class CameraUseCaseAdapter implements Camera { throw new CameraException(e.getMessage()); } updateViewPort(suggestedResolutionsMap, useCases); - updateEffects(mEffectBundle, useCases); + updateEffects(mEffects, useCases); // Saves the updated extra use cases set after confirming the use case combination // can be supported. @@ -293,7 +291,7 @@ public final class CameraUseCaseAdapter implements Camera { try { // Calls addUseCases with empty list to add required extra fake use case. - addUseCases(Collections.emptyList()); + addUseCases(emptyList()); } catch (CameraException e) { // This should not happen because the extra fake use case should be only // added to replace the removed one which the use case combination can be @@ -442,27 +440,25 @@ public final class CameraUseCaseAdapter implements Camera { } @VisibleForTesting - static void updateEffects(@Nullable EffectBundle effectBundle, + static void updateEffects(@NonNull List<CameraEffect> effects, @NonNull Collection<UseCase> useCases) { - Map<Integer, CameraEffect> effectsWithExecutors = new HashMap<>(); - // Wrap external effects with the executor to make sure they are thread safe. - if (effectBundle != null) { - Executor executor = effectBundle.getExecutor(); - for (Map.Entry<Integer, CameraEffect> entry : effectBundle.getEffects().entrySet()) { - CameraEffect effect = entry.getValue(); - int targets = entry.getKey(); - if (effect instanceof SurfaceProcessor) { - effectsWithExecutors.put(targets, new SurfaceProcessorWithExecutor( - (SurfaceProcessor) effect, executor)); - } - } + Map<Integer, CameraEffect> effectsByTargets = new HashMap<>(); + for (CameraEffect effect : effects) { + effectsByTargets.put(effect.getTargets(), effect); } + // Set effects on the UseCases. This also removes existing effects if necessary. for (UseCase useCase : useCases) { if (useCase instanceof Preview) { - ((Preview) useCase).setProcessor( - (SurfaceProcessorInternal) effectsWithExecutors.get( - SurfaceProcessor.PREVIEW)); + Preview preview = ((Preview) useCase); + CameraEffect effect = effectsByTargets.get(CameraEffect.PREVIEW); + if (effect == null) { + preview.setProcessor(null); + continue; + } + preview.setProcessor(new SurfaceProcessorWithExecutor( + requireNonNull(effect.getSurfaceProcessor()), + effect.getProcessorExecutor())); } } } @@ -649,7 +645,7 @@ public final class CameraUseCaseAdapter implements Camera { Map<UseCase, ConfigPair> configs = getConfigs(Arrays.asList(useCases), mCameraConfig.getUseCaseConfigFactory(), mUseCaseConfigFactory); calculateSuggestedResolutions(mCameraInternal.getCameraInfoInternal(), - Arrays.asList(useCases), Collections.emptyList(), configs); + Arrays.asList(useCases), emptyList(), configs); } catch (IllegalArgumentException e) { return false; } diff --git a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt deleted file mode 100644 index de2b34687c5..00000000000 --- a/camera/camera-core/src/test/java/androidx/camera/core/EffectBundleTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.core - -import android.os.Build -import androidx.camera.core.SurfaceProcessor.PREVIEW -import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor -import androidx.camera.testing.fakes.FakeSurfaceProcessor -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner -import org.robolectric.annotation.Config -import org.robolectric.annotation.internal.DoNotInstrument - -/** - * Unit tests for [EffectBundle]. - */ -@RunWith(RobolectricTestRunner::class) -@DoNotInstrument -@Config(minSdk = Build.VERSION_CODES.LOLLIPOP) -class EffectBundleTest { - - @Test(expected = IllegalArgumentException::class) - fun noEffect_throwsException() { - EffectBundle.Builder(mainThreadExecutor()).build() - } - - @Test(expected = IllegalArgumentException::class) - fun addMoreThanOnePreviewEffect_throwsException() { - val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor()) - EffectBundle.Builder(mainThreadExecutor()) - .addEffect(PREVIEW, surfaceProcessor) - .addEffect(PREVIEW, surfaceProcessor) - } - - @Test - fun addPreviewEffect_hasPreviewEffect() { - // Arrange. - val surfaceProcessor = - FakeSurfaceProcessor(mainThreadExecutor()) - // Act. - val effectBundle = EffectBundle.Builder(mainThreadExecutor()) - .addEffect(PREVIEW, surfaceProcessor) - .build() - // Assert. - assertThat(effectBundle.effects.values.first() as SurfaceProcessor).isEqualTo( - surfaceProcessor - ) - } -}
\ No newline at end of file diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt index bd9048ea2a1..0444977a39e 100644 --- a/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt +++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/CameraUseCaseAdapterTest.kt @@ -22,10 +22,10 @@ import android.os.Build import android.util.Rational import android.util.Size import android.view.Surface -import androidx.camera.core.EffectBundle +import androidx.camera.core.CameraEffect +import androidx.camera.core.CameraEffect.PREVIEW import androidx.camera.core.ImageCapture import androidx.camera.core.Preview -import androidx.camera.core.SurfaceProcessor.PREVIEW import androidx.camera.core.UseCase import androidx.camera.core.ViewPort import androidx.camera.core.impl.CameraConfig @@ -70,7 +70,7 @@ private const val CAMERA_ID = "0" class CameraUseCaseAdapterTest { private lateinit var surfaceProcessor: FakeSurfaceProcessor - private lateinit var effectBundle: EffectBundle + private lateinit var effects: List<CameraEffect> private lateinit var executor: ExecutorService private lateinit var fakeCameraDeviceSurfaceManager: FakeCameraDeviceSurfaceManager @@ -86,7 +86,9 @@ class CameraUseCaseAdapterTest { fakeCameraSet.add(fakeCamera) surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor()) executor = Executors.newSingleThreadExecutor() - effectBundle = EffectBundle.Builder(executor).addEffect(PREVIEW, surfaceProcessor).build() + effects = listOf( + CameraEffect.Builder(PREVIEW).setSurfaceProcessor(executor, surfaceProcessor).build() + ) } @After @@ -339,8 +341,8 @@ class CameraUseCaseAdapterTest { assertThat(fakeUseCase.sensorToBufferTransformMatrix).isEqualTo(Matrix().apply { // From 4032x3024 to 4032x3022 with Crop Inside, no scale and Y shift 1. setValues(floatArrayOf(/*scaleX=*/1f, 0f, /*translateX=*/0f, - 0f, /*scaleY=*/1f, /*translateY=*/-1f, - 0f, 0f, 1f)) + 0f, /*scaleY=*/1f, /*translateY=*/-1f, + 0f, 0f, 1f)) }) } @@ -617,14 +619,14 @@ class CameraUseCaseAdapterTest { fun updateEffects_effectsAddedAndRemoved() { // Arrange. val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build() - // Act: update use cases with effects bundle - CameraUseCaseAdapter.updateEffects(effectBundle, listOf(preview)) + // Act: update use cases with effects. + CameraUseCaseAdapter.updateEffects(effects, listOf(preview)) // Assert: preview has processor wrapped with the right executor. val previewProcessor = preview.processor as SurfaceProcessorWithExecutor assertThat(previewProcessor.processor).isEqualTo(surfaceProcessor) assertThat(previewProcessor.executor).isEqualTo(executor) - // Act: update again with null effects bundle - CameraUseCaseAdapter.updateEffects(null, listOf(preview)) + // Act: update again with no effects. + CameraUseCaseAdapter.updateEffects(listOf(), listOf(preview)) // Assert: preview no longer has processors. assertThat(preview.processor).isNull() } diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt index a409ec67fe8..64bebab04ad 100644 --- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt +++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SettableSurfaceTest.kt @@ -24,9 +24,9 @@ import android.os.Build import android.os.Looper.getMainLooper import android.util.Size import android.view.Surface +import androidx.camera.core.CameraEffect import androidx.camera.core.SurfaceOutput import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM -import androidx.camera.core.SurfaceProcessor import androidx.camera.core.SurfaceRequest import androidx.camera.core.SurfaceRequest.Result.RESULT_REQUEST_CANCELLED import androidx.camera.core.SurfaceRequest.TransformationInfo @@ -70,7 +70,7 @@ class SettableSurfaceTest { @Before fun setUp() { settableSurface = SettableSurface( - SurfaceProcessor.PREVIEW, Size(640, 480), ImageFormat.PRIVATE, + CameraEffect.PREVIEW, Size(640, 480), ImageFormat.PRIVATE, Matrix(), true, Rect(), 0, false ) fakeSurfaceTexture = SurfaceTexture(0) diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt index e6d0b85a12d..621eb1de70f 100644 --- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt +++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceOutputImplTest.kt @@ -22,9 +22,9 @@ import android.os.Build import android.os.Looper import android.util.Size import android.view.Surface +import androidx.camera.core.CameraEffect import androidx.camera.core.SurfaceOutput.GlTransformOptions import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM -import androidx.camera.core.SurfaceProcessor import androidx.camera.core.impl.utils.TransformUtils.sizeToRect import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor import com.google.common.truth.Truth.assertThat @@ -46,7 +46,7 @@ import org.robolectric.annotation.internal.DoNotInstrument class SurfaceOutputImplTest { companion object { - private const val TARGET = SurfaceProcessor.PREVIEW + private const val TARGET = CameraEffect.PREVIEW private const val FORMAT = PixelFormat.RGBA_8888 private val OUTPUT_SIZE = Size(640, 480) private val INPUT_SIZE = Size(640, 480) diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt index abbbda5c6de..0724a8ddb21 100644 --- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt +++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorNodeTest.kt @@ -23,10 +23,10 @@ import android.os.Build import android.os.Looper.getMainLooper import android.util.Size import android.view.Surface +import androidx.camera.core.CameraEffect.PREVIEW import androidx.camera.core.SurfaceOutput.GlTransformOptions import androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING import androidx.camera.core.SurfaceOutput.GlTransformOptions.USE_SURFACE_TEXTURE_TRANSFORM -import androidx.camera.core.SurfaceProcessor.PREVIEW import androidx.camera.core.SurfaceRequest import androidx.camera.core.SurfaceRequest.TransformationInfo import androidx.camera.core.impl.utils.TransformUtils.is90or270 diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java index 65416c37ca7..972d36c8bc9 100644 --- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java +++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/LifecycleCameraRepositoryTest.java @@ -18,6 +18,8 @@ package androidx.camera.lifecycle; import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.emptyList; + import androidx.camera.core.impl.CameraInternal; import androidx.camera.core.internal.CameraUseCaseAdapter; import androidx.camera.testing.fakes.FakeCamera; @@ -116,7 +118,7 @@ public final class LifecycleCameraRepositoryTest { public void lifecycleCameraIsNotActive_bindUseCase_whenLifecycleIsNotStarted() { LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); // LifecycleCamera is inactive before the lifecycle state becomes ON_START. assertThat(lifecycleCamera.isActive()).isFalse(); @@ -126,7 +128,7 @@ public final class LifecycleCameraRepositoryTest { public void lifecycleCameraIsActive_lifecycleStartedAfterBindUseCase() { LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); mLifecycle.start(); // LifecycleCamera is active after the lifecycle state becomes ON_START. @@ -138,7 +140,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); mLifecycle.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); // LifecycleCamera is active after binding a use case when lifecycle state is ON_START. @@ -150,13 +152,13 @@ public final class LifecycleCameraRepositoryTest { // Creates first LifecycleCamera with use case bound. LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Creates second LifecycleCamera with use case bound to the same Lifecycle. LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(mLifecycle, createNewCameraUseCaseAdapter()); - mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(), Collections.singletonList(new FakeUseCase())); } @@ -167,7 +169,7 @@ public final class LifecycleCameraRepositoryTest { mCameraUseCaseAdapter); mLifecycle.start(); FakeUseCase useCase = new FakeUseCase(); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(useCase)); // Unbinds the use case that was bound previously. @@ -186,7 +188,7 @@ public final class LifecycleCameraRepositoryTest { mLifecycle.start(); FakeUseCase useCase0 = new FakeUseCase(); FakeUseCase useCase1 = new FakeUseCase(); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Arrays.asList(useCase0, useCase1)); // Only unbinds one use case but another one is kept in the LifecycleCamera. @@ -203,7 +205,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); mLifecycle.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Unbinds all use cases from all LifecycleCamera by the unbindAll() API. @@ -219,7 +221,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); mLifecycle.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Starts second lifecycle with use case bound. @@ -227,7 +229,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(lifecycle1, createNewCameraUseCaseAdapter()); lifecycle1.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(), Collections.singletonList(new FakeUseCase())); // The previous LifecycleCamera becomes inactive after new LifecycleCamera becomes active. @@ -242,7 +244,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); mLifecycle.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Starts second lifecycle with use case bound. @@ -250,11 +252,11 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera(lifecycle1, createNewCameraUseCaseAdapter()); lifecycle1.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Binds new use case to the next most recent active LifecycleCamera. - mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(), Collections.singletonList(new FakeUseCase())); // The next most recent active LifecycleCamera becomes active after binding new use case. @@ -270,7 +272,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera0 = mRepository.createLifecycleCamera(mLifecycle, mCameraUseCaseAdapter); mLifecycle.start(); - mRepository.bindToLifecycleCamera(lifecycleCamera0, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera0, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Starts second lifecycle with use case bound. @@ -279,7 +281,7 @@ public final class LifecycleCameraRepositoryTest { createNewCameraUseCaseAdapter()); lifecycle1.start(); FakeUseCase useCase = new FakeUseCase(); - mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(), Collections.singletonList(useCase)); // Unbinds use case from the most recent active LifecycleCamera. @@ -298,7 +300,7 @@ public final class LifecycleCameraRepositoryTest { LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); FakeUseCase useCase = new FakeUseCase(); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(useCase)); assertThat(useCase.isDetached()).isFalse(); @@ -321,7 +323,7 @@ public final class LifecycleCameraRepositoryTest { // Starts first lifecycle and check LifecycleCamera active state is true. LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); mLifecycle.start(); assertThat(firstLifecycleCamera.isActive()).isTrue(); @@ -330,7 +332,7 @@ public final class LifecycleCameraRepositoryTest { FakeLifecycleOwner secondLifecycle = new FakeLifecycleOwner(); LifecycleCamera secondLifecycleCamera = mRepository.createLifecycleCamera(secondLifecycle, createNewCameraUseCaseAdapter()); - mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); secondLifecycle.start(); assertThat(secondLifecycleCamera.isActive()).isTrue(); @@ -342,7 +344,7 @@ public final class LifecycleCameraRepositoryTest { // Starts first lifecycle and check LifecycleCamera active state is true. LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); mLifecycle.start(); assertThat(firstLifecycleCamera.isActive()).isTrue(); @@ -351,7 +353,7 @@ public final class LifecycleCameraRepositoryTest { FakeLifecycleOwner secondLifecycle = new FakeLifecycleOwner(); LifecycleCamera secondLifecycleCamera = mRepository.createLifecycleCamera(secondLifecycle, createNewCameraUseCaseAdapter()); - mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(secondLifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); secondLifecycle.start(); assertThat(secondLifecycleCamera.isActive()).isTrue(); @@ -368,7 +370,7 @@ public final class LifecycleCameraRepositoryTest { // Starts first LifecycleCamera with use case bound. LifecycleCamera firstLifecycleCamera = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(firstLifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); mLifecycle.start(); assertThat(firstLifecycleCamera.isActive()).isTrue(); @@ -395,7 +397,7 @@ public final class LifecycleCameraRepositoryTest { // Starts second LifecycleCamera with use case bound to the same Lifecycle. LifecycleCamera lifecycleCamera1 = mRepository.createLifecycleCamera( mLifecycle, createNewCameraUseCaseAdapter()); - mRepository.bindToLifecycleCamera(lifecycleCamera1, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera1, null, emptyList(), Collections.singletonList(new FakeUseCase())); // Starts third LifecycleCamera with no use case bound to the same Lifecycle. @@ -450,7 +452,7 @@ public final class LifecycleCameraRepositoryTest { // Starts LifecycleCamera with use case bound. LifecycleCamera lifecycleCamera = mRepository.createLifecycleCamera( mLifecycle, mCameraUseCaseAdapter); - mRepository.bindToLifecycleCamera(lifecycleCamera, null, null, + mRepository.bindToLifecycleCamera(lifecycleCamera, null, emptyList(), Collections.singletonList(new FakeUseCase())); mLifecycle.start(); assertThat(lifecycleCamera.isActive()).isTrue(); diff --git a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt index 0bb2cbe7708..2f7d239b85b 100644 --- a/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt +++ b/camera/camera-lifecycle/src/androidTest/java/androidx/camera/lifecycle/ProcessCameraProviderTest.kt @@ -22,11 +22,11 @@ import android.content.ContextWrapper import android.content.pm.PackageManager import androidx.annotation.OptIn import androidx.annotation.RequiresApi +import androidx.camera.core.CameraEffect +import androidx.camera.core.CameraEffect.PREVIEW import androidx.camera.core.CameraSelector import androidx.camera.core.CameraXConfig -import androidx.camera.core.EffectBundle import androidx.camera.core.Preview -import androidx.camera.core.SurfaceProcessor.PREVIEW import androidx.camera.core.UseCaseGroup import androidx.camera.core.impl.CameraFactory import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor @@ -80,11 +80,10 @@ class ProcessCameraProviderTest { // Arrange. ProcessCameraProvider.configureInstance(FakeAppConfig.create()) val surfaceProcessor = FakeSurfaceProcessor(mainThreadExecutor()) - val effectBundle = - EffectBundle.Builder(mainThreadExecutor()).addEffect(PREVIEW, surfaceProcessor).build() + val effect = CameraEffect.Builder(PREVIEW) + .setSurfaceProcessor(mainThreadExecutor(), surfaceProcessor).build() val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build() - val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview) - .setEffectBundle(effectBundle).build() + val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview).addEffect(effect).build() runBlocking(MainScope().coroutineContext) { // Act. diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java index 8fc8a6ba9bc..706c842ac4b 100644 --- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java +++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/LifecycleCameraRepository.java @@ -20,7 +20,7 @@ import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.camera.core.EffectBundle; +import androidx.camera.core.CameraEffect; import androidx.camera.core.UseCase; import androidx.camera.core.ViewPort; import androidx.camera.core.impl.CameraInternal; @@ -39,6 +39,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -250,14 +251,14 @@ final class LifecycleCameraRepository { * * @param lifecycleCamera The LifecycleCamera which the use cases will be bound to. * @param viewPort The viewport which represents the visible camera sensor rect. - * @param effectBundle The effects applied to the camera outputs. + * @param effects The effects applied to the camera outputs. * @param useCases The use cases to bind to a lifecycle. * @throws IllegalArgumentException If multiple LifecycleCameras with use cases are * registered to the same LifecycleOwner. Or all use cases will exceed the capability of the * camera after binding them to the LifecycleCamera. */ void bindToLifecycleCamera(@NonNull LifecycleCamera lifecycleCamera, - @Nullable ViewPort viewPort, @Nullable EffectBundle effectBundle, + @Nullable ViewPort viewPort, @NonNull List<CameraEffect> effects, @NonNull Collection<UseCase> useCases) { synchronized (mLock) { Preconditions.checkArgument(!useCases.isEmpty()); @@ -278,7 +279,7 @@ final class LifecycleCameraRepository { try { lifecycleCamera.getCameraUseCaseAdapter().setViewPort(viewPort); - lifecycleCamera.getCameraUseCaseAdapter().setEffectBundle(effectBundle); + lifecycleCamera.getCameraUseCaseAdapter().setEffects(effects); lifecycleCamera.bind(useCases); } catch (CameraUseCaseAdapter.CameraException e) { throw new IllegalArgumentException(e.getMessage()); diff --git a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java index 066a237cf98..33dabcac7b8 100644 --- a/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java +++ b/camera/camera-lifecycle/src/main/java/androidx/camera/lifecycle/ProcessCameraProvider.java @@ -16,6 +16,8 @@ package androidx.camera.lifecycle; +import static java.util.Collections.emptyList; + import android.app.Application; import android.content.Context; import android.os.Handler; @@ -28,13 +30,13 @@ import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; import androidx.annotation.RestrictTo.Scope; import androidx.camera.core.Camera; +import androidx.camera.core.CameraEffect; import androidx.camera.core.CameraFilter; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraInfoUnavailableException; import androidx.camera.core.CameraSelector; import androidx.camera.core.CameraX; import androidx.camera.core.CameraXConfig; -import androidx.camera.core.EffectBundle; import androidx.camera.core.ImageAnalysis; import androidx.camera.core.ImageCapture; import androidx.camera.core.InitializationException; @@ -358,7 +360,7 @@ public final class ProcessCameraProvider implements LifecycleCameraProvider { public Camera bindToLifecycle(@NonNull LifecycleOwner lifecycleOwner, @NonNull CameraSelector cameraSelector, @NonNull UseCase... useCases) { - return bindToLifecycle(lifecycleOwner, cameraSelector, null, null, useCases); + return bindToLifecycle(lifecycleOwner, cameraSelector, null, emptyList(), useCases); } /** @@ -380,7 +382,7 @@ public final class ProcessCameraProvider implements LifecycleCameraProvider { @NonNull CameraSelector cameraSelector, @NonNull UseCaseGroup useCaseGroup) { return bindToLifecycle(lifecycleOwner, cameraSelector, - useCaseGroup.getViewPort(), useCaseGroup.getEffectBundle(), + useCaseGroup.getViewPort(), useCaseGroup.getEffects(), useCaseGroup.getUseCases().toArray(new UseCase[0])); } @@ -433,7 +435,7 @@ public final class ProcessCameraProvider implements LifecycleCameraProvider { * @param cameraSelector The camera selector which determines the camera to use for set of * use cases. * @param viewPort The viewPort which represents the visible camera sensor rect. - * @param effectBundle The effects applied to the camera outputs. + * @param effects The effects applied to the camera outputs. * @param useCases The use cases to bind to a lifecycle. * @return The {@link Camera} instance which is determined by the camera selector and * internal requirements. @@ -448,7 +450,7 @@ public final class ProcessCameraProvider implements LifecycleCameraProvider { @NonNull LifecycleOwner lifecycleOwner, @NonNull CameraSelector cameraSelector, @Nullable ViewPort viewPort, - @Nullable EffectBundle effectBundle, + @NonNull List<CameraEffect> effects, @NonNull UseCase... useCases) { Threads.checkMainThread(); // TODO(b/153096869): override UseCase's target rotation. @@ -534,7 +536,7 @@ public final class ProcessCameraProvider implements LifecycleCameraProvider { } mLifecycleCameraRepository.bindToLifecycleCamera(lifecycleCameraToBind, viewPort, - effectBundle, Arrays.asList(useCases)); + effects, Arrays.asList(useCases)); return lifecycleCameraToBind; } diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java index b23e20e60b9..94d174604a6 100644 --- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java +++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java @@ -16,6 +16,7 @@ package androidx.camera.video; +import static androidx.camera.core.CameraEffect.VIDEO_CAPTURE; import static androidx.camera.core.SurfaceOutput.GlTransformOptions.APPLY_CROP_ROTATE_AND_MIRRORING; import static androidx.camera.core.impl.ImageOutputConfig.OPTION_DEFAULT_RESOLUTION; import static androidx.camera.core.impl.ImageOutputConfig.OPTION_MAX_RESOLUTION; @@ -65,7 +66,6 @@ import androidx.camera.core.AspectRatio; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.Logger; -import androidx.camera.core.SurfaceProcessor; import androidx.camera.core.SurfaceRequest; import androidx.camera.core.UseCase; import androidx.camera.core.ViewPort; @@ -501,7 +501,7 @@ public final class VideoCapture<T extends VideoOutput> extends UseCase { VideoCapabilities.from(camera.getCameraInfo()), timebase, mediaSpec, resolution, targetFpsRange)); SettableSurface cameraSurface = new SettableSurface( - SurfaceProcessor.VIDEO_CAPTURE, + VIDEO_CAPTURE, resolution, ImageFormat.PRIVATE, getSensorToBufferTransformMatrix(), diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt index 8abbab7489d..e61af75126f 100644 --- a/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt +++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/CameraControllerDeviceTest.kt @@ -22,13 +22,13 @@ import android.provider.MediaStore import android.view.View import androidx.camera.camera2.Camera2Config import androidx.camera.camera2.pipe.integration.CameraPipeConfig +import androidx.camera.core.CameraEffect +import androidx.camera.core.CameraEffect.PREVIEW import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector.LENS_FACING_BACK import androidx.camera.core.CameraSelector.LENS_FACING_FRONT import androidx.camera.core.CameraXConfig -import androidx.camera.core.EffectBundle import androidx.camera.core.ImageCapture -import androidx.camera.core.SurfaceProcessor.PREVIEW import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.testing.CameraPipeConfigTestRule @@ -127,26 +127,17 @@ class CameraControllerDeviceTest( } waitUtilPreviewViewIsReady(previewView!!) - // Act: set an EffectBundle - instrumentation.runOnMainSync { - controller!!.setEffectBundle( - EffectBundle.Builder(mainThreadExecutor()) - .addEffect(PREVIEW, - FakeSurfaceProcessor( - mainThreadExecutor() - ) - ) - .build() - ) - } + // Act: set an effect + val effect = CameraEffect.Builder(PREVIEW).setSurfaceProcessor( + mainThreadExecutor(), FakeSurfaceProcessor(mainThreadExecutor()) + ).build() + instrumentation.runOnMainSync { controller!!.setEffects(listOf(effect)) } // Assert: preview has effect assertThat(controller!!.mPreview.processor).isNotNull() - // Act: clear the EffectBundle - instrumentation.runOnMainSync { - controller!!.setEffectBundle(null) - } + // Act: clear the effects + instrumentation.runOnMainSync { controller!!.setEffects(listOf()) } // Assert: preview no longer has the effect. assertThat(controller!!.mPreview.processor).isNull() diff --git a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java index a0f314e1920..711938c37a8 100644 --- a/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java +++ b/camera/camera-view/src/main/java/androidx/camera/view/CameraController.java @@ -20,6 +20,8 @@ import static androidx.camera.core.impl.utils.Threads.checkMainThread; import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor; import static androidx.camera.view.CameraController.OutputSize.UNASSIGNED_ASPECT_RATIO; +import static java.util.Collections.emptyList; + import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Matrix; @@ -41,11 +43,11 @@ import androidx.annotation.VisibleForTesting; import androidx.camera.core.AspectRatio; import androidx.camera.core.Camera; import androidx.camera.core.CameraControl; +import androidx.camera.core.CameraEffect; import androidx.camera.core.CameraInfo; import androidx.camera.core.CameraInfoUnavailableException; import androidx.camera.core.CameraSelector; import androidx.camera.core.CameraUnavailableException; -import androidx.camera.core.EffectBundle; import androidx.camera.core.FocusMeteringAction; import androidx.camera.core.FocusMeteringResult; import androidx.camera.core.ImageAnalysis; @@ -80,6 +82,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicBoolean; @@ -306,8 +309,8 @@ public abstract class CameraController { final MutableLiveData<Integer> mTapToFocusState = new MutableLiveData<>( TAP_TO_FOCUS_NOT_STARTED); - @Nullable - private EffectBundle mEffectBundle; + @NonNull + private List<CameraEffect> mEffects = emptyList(); private final Context mAppContext; @@ -1664,13 +1667,13 @@ public abstract class CameraController { /** * Sets post-processing effects. * - * @param effectBundle the effects applied to camera output. + * @param effects the effects applied to camera output. * @hide - * @see UseCaseGroup.Builder#getEffectBundle() + * @see UseCaseGroup.Builder#addEffect */ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) - public void setEffectBundle(@Nullable EffectBundle effectBundle) { - if (mEffectBundle == effectBundle) { + public void setEffects(@NonNull List<CameraEffect> effects) { + if (Objects.equals(mEffects, effects)) { // Same effect. No change needed. return; } @@ -1678,7 +1681,7 @@ public abstract class CameraController { // Unbind to make sure the pipelines will be recreated. mCameraProvider.unbindAll(); } - mEffectBundle = effectBundle; + mEffects = effects; startCameraAndTrackStates(); } @@ -1763,8 +1766,8 @@ public abstract class CameraController { } builder.setViewPort(mViewPort); - if (mEffectBundle != null) { - builder.setEffectBundle(mEffectBundle); + for (CameraEffect effect : mEffects) { + builder.addEffect(effect); } return builder.build(); } 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 f949eb557df..0f908436c13 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 @@ -134,7 +134,9 @@ class CameraControllerFragmentTest( fun enableEffect_effectIsEnabled() { // Arrange: launch app and verify effect is inactive. fragment.assertPreviewIsStreaming() - assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isFalse() + val processor = + fragment.mToneMappingPreviewEffect.surfaceProcessor as ToneMappingSurfaceProcessor + assertThat(processor.isSurfaceRequestedAndProvided()).isFalse() // Act: turn on effect. val effectToggleId = "androidx.camera.integration.view:id/effect_toggle" @@ -142,7 +144,7 @@ class CameraControllerFragmentTest( instrumentation.waitForIdleSync() // Assert: verify that effect is active. - assertThat(fragment.mSurfaceProcessor.isSurfaceRequestedAndProvided()).isTrue() + assertThat(processor.isSurfaceRequestedAndProvided()).isTrue() } @Test 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 97a9bde23ff..b7c539ceb04 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 @@ -19,6 +19,9 @@ package androidx.camera.integration.view; import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect; import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + import android.annotation.SuppressLint; import android.app.Dialog; import android.content.ContentResolver; @@ -54,13 +57,11 @@ import androidx.annotation.OptIn; import androidx.annotation.RestrictTo; import androidx.annotation.VisibleForTesting; import androidx.camera.core.CameraSelector; -import androidx.camera.core.EffectBundle; import androidx.camera.core.ImageAnalysis; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; import androidx.camera.core.ImageProxy; import androidx.camera.core.Logger; -import androidx.camera.core.SurfaceProcessor; import androidx.camera.core.ZoomState; import androidx.camera.core.impl.utils.futures.FutureCallback; import androidx.camera.core.impl.utils.futures.Futures; @@ -126,7 +127,7 @@ public class CameraControllerFragment extends Fragment { private ImageAnalysis.Analyzer mWrappedAnalyzer; @VisibleForTesting - ToneMappingSurfaceProcessor mSurfaceProcessor; + ToneMappingPreviewEffect mToneMappingPreviewEffect; private final ImageAnalysis.Analyzer mAnalyzer = image -> { byte[] bytes = new byte[image.getPlanes()[0].getBuffer().remaining()]; @@ -182,7 +183,7 @@ public class CameraControllerFragment extends Fragment { }); // Set up post-processing effects. - mSurfaceProcessor = new ToneMappingSurfaceProcessor(); + mToneMappingPreviewEffect = new ToneMappingPreviewEffect(); mEffectToggle = view.findViewById(R.id.effect_toggle); mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled()); onEffectsToggled(); @@ -352,16 +353,14 @@ public class CameraControllerFragment extends Fragment { mExecutorService.shutdown(); } mRotationProvider.removeListener(mRotationListener); - mSurfaceProcessor.release(); + mToneMappingPreviewEffect.release(); } private void onEffectsToggled() { if (mEffectToggle.isChecked()) { - mCameraController.setEffectBundle(new EffectBundle.Builder(mainThreadExecutor()) - .addEffect(SurfaceProcessor.PREVIEW, mSurfaceProcessor) - .build()); - } else if (mSurfaceProcessor != null) { - mCameraController.setEffectBundle(null); + mCameraController.setEffects(singletonList(mToneMappingPreviewEffect)); + } else { + mCameraController.setEffects(emptyList()); } } diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt new file mode 100644 index 00000000000..25bdc1cfd77 --- /dev/null +++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt @@ -0,0 +1,31 @@ +/* + * 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 androidx.camera.core.CameraEffect +import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor + +/** + * A tone mapping effect for preview UseCase. + */ +internal class ToneMappingPreviewEffect : + CameraEffect(PREVIEW, mainThreadExecutor(), ToneMappingSurfaceProcessor()) { + + fun release() { + (surfaceProcessor as? ToneMappingSurfaceProcessor)?.release() + } +}
\ No newline at end of file |