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