Added tests for various RenderEffect implementations

Added RenderNodeTests to verify ColorFilter, Bitmap and Offset
RenderEffect implementations.

Bug: 143468037
Test: this
Change-Id: I16ca8f5d0009f5f3589e8dd2a2d206682a407d4d
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java
new file mode 100644
index 0000000..0f9f2d0
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/BlurPixelVerifier.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 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 android.uirendering.cts.bitmapverifiers;
+
+import android.graphics.Color;
+
+public class BlurPixelVerifier extends BitmapVerifier {
+
+    private final int mDstColor;
+    private final int mSrcColor;
+
+    /**
+     * Create a BitmapVerifier that compares pixel values relative to the
+     * provided source and destination colors. Pixels closer to the center of
+     * the test bitmap are expected to match closer to the source color, while pixels
+     * on the exterior of the test bitmap are expected to match the destination
+     * color more closely
+     */
+    public BlurPixelVerifier(int srcColor, int dstColor) {
+        mSrcColor = srcColor;
+        mDstColor = dstColor;
+    }
+
+    @Override
+    public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+
+        float dstRedChannel = Color.red(mDstColor);
+        float dstGreenChannel = Color.green(mDstColor);
+        float dstBlueChannel = Color.blue(mDstColor);
+
+        float srcRedChannel = Color.red(mSrcColor);
+        float srcGreenChannel = Color.green(mSrcColor);
+        float srcBlueChannel = Color.blue(mSrcColor);
+
+        // Calculate the largest rgb color difference between the source and destination
+        // colors
+        double maxDifference = Math.pow(srcRedChannel - dstRedChannel, 2.0f)
+                + Math.pow(srcGreenChannel - dstGreenChannel, 2.0f)
+                + Math.pow(srcBlueChannel - dstBlueChannel, 2.0f);
+
+        // Calculate the maximum distance between pixels to the center of the test image
+        double maxPixelDistance =
+                Math.sqrt(Math.pow(width / 2.0, 2.0) + Math.pow(height / 2.0, 2.0));
+
+        // Additional tolerance applied to comparisons
+        float threshold = .05f;
+        for (int x = 0; x < width; x++) {
+            for (int y = 0; y < height; y++) {
+                double pixelDistance = Math.sqrt(Math.pow(x - width / 2.0, 2.0)
+                        + Math.pow(y - height / 2.0, 2.0));
+                // Calculate the threshold of the destination color expected based on the
+                // pixels position relative to the center
+                double dstPercentage = pixelDistance / maxPixelDistance + threshold;
+
+                int pixelColor = bitmap[indexFromXAndY(x, y, stride, offset)];
+                double pixelRedChannel = Color.red(pixelColor);
+                double pixelGreenChannel = Color.green(pixelColor);
+                double pixelBlueChannel = Color.blue(pixelColor);
+                // Compare the RGB color distance between the current pixel and the destination
+                // color
+                double dstDistance = Math.sqrt(Math.pow(pixelRedChannel - dstRedChannel, 2.0)
+                        + Math.pow(pixelGreenChannel - dstGreenChannel, 2.0)
+                        + Math.pow(pixelBlueChannel - dstBlueChannel, 2.0));
+
+                // Compare the RGB color distance between the current pixel and the source
+                // color
+                double srcDistance = Math.sqrt(Math.pow(pixelRedChannel - srcRedChannel, 2.0)
+                        + Math.pow(pixelGreenChannel - srcGreenChannel, 2.0)
+                        + Math.pow(pixelBlueChannel - srcBlueChannel, 2.0));
+
+                // calculate the ratio between the destination color to the current pixel
+                // color relative to the maximum distance between source and destination colors
+                // If this value exceeds the threshold expected for the pixel distance from
+                // center then we are rendering an unexpected color
+                double dstFraction = dstDistance / maxDifference;
+                if (dstFraction > dstPercentage) {
+                    return false;
+                }
+
+                // similarly compute the ratio between the source color to the current pixel
+                // color relative to the maximum distance between source and destination colors
+                // If this value exceeds the threshold expected for the pixel distance from
+                // center then we are rendering an unexpected source color
+                double srcFraction = srcDistance / maxDifference;
+                if (srcFraction > dstPercentage) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index fa44b78..92bc5ac 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -21,6 +21,9 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.graphics.Bitmap;
+import android.graphics.BlendMode;
+import android.graphics.BlendModeColorFilter;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
@@ -28,8 +31,13 @@
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
+import android.graphics.RenderEffect;
 import android.graphics.RenderNode;
+import android.graphics.Shader;
+import android.uirendering.cts.bitmapverifiers.BlurPixelVerifier;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.RegionVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
 import androidx.test.filters.MediumTest;
@@ -364,4 +372,357 @@
         renderNode.setCameraDistance(100f);
         assertEquals(100f, renderNode.getCameraDistance(), 0.0f);
     }
+
+    @Test
+    public void testBitmapRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        bitmap.eraseColor(Color.BLUE);
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(RenderEffect.createBitmapEffect(bitmap));
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            // must have at least 1 drawing instruction
+            recordingCanvas.drawColor(Color.TRANSPARENT);
+            renderNode.endRecording();
+        }
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testOffsetImplicitInputRenderEffect() {
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(offsetEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.BLUE);
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint();
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        new Rect(
+                                                0,
+                                                0,
+                                                TEST_WIDTH - 1,
+                                                offsetY - 1
+                                        ),
+                                        new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                0,
+                                                0,
+                                                offsetX - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.RED)
+                        )
+            );
+    }
+
+    @Test
+    public void testColorFilterRenderEffectImplicitInput() {
+        RenderEffect colorFilterEffect = RenderEffect.createColorFilterEffect(
+                new BlendModeColorFilter(Color.RED, BlendMode.SRC_OVER));
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.BLUE);
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
+
+    @Test
+    public void testBlendModeRenderEffectImplicitInput() {
+        Bitmap srcBitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        srcBitmap.eraseColor(Color.BLUE);
+
+        Bitmap dstBitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        dstBitmap.eraseColor(Color.RED);
+
+        RenderEffect colorFilterEffect = RenderEffect.createBlendModeEffect(
+                RenderEffect.createBitmapEffect(dstBitmap),
+                RenderEffect.createBitmapEffect(srcBitmap),
+                BlendMode.SRC
+        );
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawColor(Color.TRANSPARENT);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testColorFilterRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(
+                bitmap, null, new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT));
+
+        RenderEffect colorFilterEffect = RenderEffect.createColorFilterEffect(
+                new BlendModeColorFilter(Color.RED, BlendMode.SRC_OVER), bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(colorFilterEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint renderNodePaint = new Paint();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, renderNodePaint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
+
+    @Test
+    public void testOffsetRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint();
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
+
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(bitmap);
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY, bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(offsetEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, new Paint());
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint();
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        new Rect(0, 0, TEST_WIDTH - 1, offsetY - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(0, 0, offsetX - 1, TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+            );
+    }
+
+    @Test
+    public void testBlurRenderEffect() {
+        final int blurRadius = 10;
+        final Rect fullBounds = new Rect(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        final Rect insetBounds = new Rect(blurRadius, blurRadius, TEST_WIDTH - blurRadius,
+                TEST_HEIGHT - blurRadius);
+
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(
+                RenderEffect.createBlurEffect(
+                        blurRadius,
+                        blurRadius,
+                        null,
+                        Shader.TileMode.DECAL
+                )
+        );
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas canvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.WHITE);
+            canvas.drawRect(fullBounds, paint);
+
+            paint.setColor(Color.BLUE);
+
+            canvas.drawRect(insetBounds, paint);
+            renderNode.endRecording();
+        }
+
+        final Rect unblurredBounds = new Rect(insetBounds);
+        unblurredBounds.inset(blurRadius, blurRadius);
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                        unblurredBounds,
+                                        new ColorVerifier(Color.BLUE))
+                                .addVerifier(
+                                        fullBounds,
+                                        new BlurPixelVerifier(Color.BLUE, Color.WHITE)
+                                )
+            );
+    }
+
+    @Test
+    public void testChainRenderEffect() {
+        Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
+        Canvas bitmapCanvas = new Canvas(bitmap);
+        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        paint.setColor(Color.BLUE);
+        bitmapCanvas.drawRect(0, 0, bitmap.getWidth(), bitmap.getHeight(), paint);
+
+        final int offsetX = 20;
+        final int offsetY = 20;
+        RenderEffect bitmapEffect = RenderEffect.createBitmapEffect(bitmap);
+        RenderEffect offsetEffect = RenderEffect.createOffsetEffect(offsetX, offsetY);
+        RenderEffect chainEffect = RenderEffect.createChainEffect(offsetEffect, bitmapEffect);
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(chainEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, new Paint());
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    Paint canvasClientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+                    canvasClientPaint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, canvasClientPaint);
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(
+                        new RegionVerifier()
+                                .addVerifier(
+                                    new Rect(
+                                            0,
+                                            0,
+                                            TEST_WIDTH - 1,
+                                            offsetY - 1
+                                    ),
+                                    new ColorVerifier(Color.RED)
+                                )
+                                .addVerifier(
+                                        new Rect(
+                                                offsetX + 1,
+                                                offsetY + 1,
+                                                TEST_WIDTH - 1,
+                                                TEST_HEIGHT - 1
+                                        ),
+                                        new ColorVerifier(Color.BLUE)
+                                )
+                                .addVerifier(
+                                        new Rect(0, 0, offsetX - 1, TEST_HEIGHT - 1),
+                                        new ColorVerifier(Color.RED)
+                                )
+            );
+    }
+
+    @Test
+    public void testBlurShaderLargeRadiiEdgeReplication() {
+        final int blurRadius = 200;
+        final int left = 0;
+        final int top = 0;
+        final int right = TEST_WIDTH;
+        final int bottom = TEST_HEIGHT;
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(
+                RenderEffect.createBlurEffect(
+                        blurRadius,
+                        blurRadius,
+                        null,
+                        Shader.TileMode.CLAMP
+                )
+        );
+        renderNode.setPosition(left, top, right, bottom);
+        {
+            Canvas canvas = renderNode.beginRecording();
+            Paint blurPaint = new Paint();
+            blurPaint.setColor(Color.BLUE);
+            canvas.save();
+            canvas.clipRect(left, top, right, bottom);
+            canvas.drawRect(left, top, right, bottom, blurPaint);
+            canvas.restore();
+            renderNode.endRecording();
+        }
+        // Ensure that blurring with large blur radii with clipped content shows a solid
+        // blur square.
+        // Previously blur radii that were very large would end up blurring pixels outside
+        // of the source with transparent leading to larger blur radii actually being less
+        // blurred than smaller radii.
+        // Because the internal SkTileMode is set to kClamp, the edges of the source are used in
+        // blur kernels that extend beyond the bounds of the source
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+
 }