1 /*
2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.camera.testing.impl.fakes;
18 
19 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.directExecutor;
20 
21 import static java.util.Objects.requireNonNull;
22 
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.ColorMatrixColorFilter;
26 import android.graphics.Paint;
27 
28 import androidx.camera.core.CameraEffect;
29 import androidx.camera.core.ImageProcessor;
30 import androidx.camera.core.ImageProxy;
31 import androidx.camera.core.imagecapture.RgbaImageProxy;
32 
33 import org.jspecify.annotations.NonNull;
34 import org.jspecify.annotations.Nullable;
35 
36 /**
37  * Create a grayscale image effect for testing.
38  */
39 public class GrayscaleImageEffect extends CameraEffect {
40 
GrayscaleImageEffect()41     public GrayscaleImageEffect() {
42         super(IMAGE_CAPTURE,
43                 directExecutor(),
44                 new GrayscaleProcessor(),
45                 throwable -> {
46                 });
47     }
48 
getInputImage()49     public @Nullable ImageProxy getInputImage() {
50         return ((GrayscaleProcessor) requireNonNull(getImageProcessor())).getInputImage();
51     }
52 
53     static class GrayscaleProcessor implements ImageProcessor {
54 
55         private ImageProxy mImageIn;
56 
57         @Override
process(@onNull Request request)58         public @NonNull Response process(@NonNull Request request) {
59             mImageIn = requireNonNull(request.getInputImage());
60             Bitmap bitmapIn = ((RgbaImageProxy) mImageIn).createBitmap();
61             Bitmap bitmapOut = createProcessedBitmap(bitmapIn);
62             return () -> createOutputImage(bitmapOut, mImageIn);
63         }
64 
getInputImage()65         @Nullable ImageProxy getInputImage() {
66             return mImageIn;
67         }
68 
69         /**
70          * Creates output image
71          */
createOutputImage(Bitmap newBitmap, ImageProxy imageIn)72         private ImageProxy createOutputImage(Bitmap newBitmap, ImageProxy imageIn) {
73             return new RgbaImageProxy(
74                     newBitmap,
75                     imageIn.getCropRect(),
76                     imageIn.getImageInfo().getRotationDegrees(),
77                     imageIn.getImageInfo().getSensorToBufferTransformMatrix(),
78                     imageIn.getImageInfo().getTimestamp()
79             );
80         }
81 
82         /**
83          * Apply grayscale on the [Bitmap]
84          */
createProcessedBitmap(Bitmap bitmapIn)85         private Bitmap createProcessedBitmap(Bitmap bitmapIn) {
86             Paint paint = new Paint();
87             // R/G/B = 1/3 R + 1/3 G + 1/3 B
88             float oneThird = 1 / 3F;
89             paint.setColorFilter(new ColorMatrixColorFilter(new float[]{
90                     oneThird, oneThird, oneThird, 0F, 0F,
91                     oneThird, oneThird, oneThird, 0F, 0F,
92                     oneThird, oneThird, oneThird, 0F, 0F,
93                     0F, 0F, 0F, 1F, 0F
94             }));
95             Bitmap bitmapOut = Bitmap.createBitmap(bitmapIn.getWidth(), bitmapIn.getHeight(),
96                     Bitmap.Config.ARGB_8888);
97             Canvas canvas = new Canvas(bitmapOut);
98             canvas.drawBitmap(bitmapIn, 0F, 0F, paint);
99             return bitmapOut;
100         }
101     }
102 }
103