aboutsummaryrefslogtreecommitdiff
path: root/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java')
-rw-r--r--integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java387
1 files changed, 387 insertions, 0 deletions
diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java
new file mode 100644
index 000000000..bcb0891b9
--- /dev/null
+++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/integrationtests/nativegraphics/ShadowNativeSweepGradientTest.java
@@ -0,0 +1,387 @@
+package org.robolectric.integrationtests.nativegraphics;
+
+import static android.os.Build.VERSION_CODES.O;
+import static android.os.Build.VERSION_CODES.Q;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.Log;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+@RunWith(AndroidJUnit4.class)
+@Config(minSdk = O)
+public class ShadowNativeSweepGradientTest {
+ private static final int SIZE = 200;
+ private static final int CENTER = SIZE / 2;
+ private static final int RADIUS = 80;
+ private static final int NUM_STEPS = 100;
+ private static final int TOLERANCE = 10;
+
+ private Paint paint;
+ private Canvas canvas;
+ private Bitmap bitmap;
+
+ @Before
+ public void setup() {
+ paint = new Paint();
+ bitmap = Bitmap.createBitmap(SIZE, SIZE, Bitmap.Config.ARGB_8888);
+ bitmap.eraseColor(Color.TRANSPARENT);
+ canvas = new Canvas(bitmap);
+ }
+
+ @Test
+ public void test2Colors() {
+ final int[] colors = new int[] {Color.GREEN, Color.RED};
+ final float[] positions = new float[] {0f, 1f};
+ Shader shader = new SweepGradient(CENTER, CENTER, colors[0], colors[1]);
+ paint.setShader(shader);
+ canvas.drawRect(new Rect(0, 0, SIZE, SIZE), paint);
+ verifyColors(colors, positions, TOLERANCE);
+ }
+
+ @Test
+ public void testColorArray() {
+ final int[] colors = new int[] {Color.GREEN, Color.RED, Color.BLUE};
+ final float[] positions = new float[] {0f, 0.3f, 1f};
+ Shader shader = new SweepGradient(CENTER, CENTER, colors, positions);
+ paint.setShader(shader);
+ canvas.drawRect(new Rect(0, 0, SIZE, SIZE), paint);
+
+ verifyColors(colors, positions, TOLERANCE);
+ }
+
+ @Test
+ public void testMultiColor() {
+ final int[] colors = new int[] {Color.GREEN, Color.RED, Color.BLUE, Color.GREEN};
+ final float[] positions = new float[] {0f, 0.25f, 0.5f, 1f};
+
+ Shader shader = new SweepGradient(CENTER, CENTER, colors, positions);
+ paint.setShader(shader);
+ canvas.drawRect(new Rect(0, 0, SIZE, SIZE), paint);
+
+ verifyColors(colors, positions, TOLERANCE);
+ }
+
+ private void verifyColors(int[] colors, float[] positions, int tolerance) {
+ final double twoPi = Math.PI * 2;
+ final double step = twoPi / NUM_STEPS;
+
+ // exclude angle 0, which is not defined
+ for (double rad = step; rad <= twoPi - step; rad += step) {
+ int x = CENTER + (int) (Math.cos(rad) * RADIUS);
+ int y = CENTER + (int) (Math.sin(rad) * RADIUS);
+
+ float relPos = (float) (rad / twoPi);
+ int idx;
+ int color;
+ for (idx = 0; idx < positions.length; idx++) {
+ if (positions[idx] > relPos) {
+ break;
+ }
+ }
+ if (idx == 0) {
+ // use start color
+ color = colors[0];
+ } else if (idx == positions.length) {
+ // clamp to end color
+ color = colors[positions.length - 1];
+ } else {
+ // linear interpolation
+ int i1 = idx - 1; // index of next lower color and position
+ int i2 = idx; // index of next higher color and position
+ double delta = (relPos - positions[i1]) / (positions[i2] - positions[i1]);
+ int alpha =
+ (int) ((1d - delta) * Color.alpha(colors[i1]) + delta * Color.alpha(colors[i2]));
+ int red = (int) ((1d - delta) * Color.red(colors[i1]) + delta * Color.red(colors[i2]));
+ int green =
+ (int) ((1d - delta) * Color.green(colors[i1]) + delta * Color.green(colors[i2]));
+ int blue = (int) ((1d - delta) * Color.blue(colors[i1]) + delta * Color.blue(colors[i2]));
+ color = Color.argb(alpha, red, green, blue);
+ }
+
+ int pixel = bitmap.getPixel(x, y);
+
+ try {
+ assertEquals(Color.alpha(color), Color.alpha(pixel), tolerance);
+ assertEquals(Color.red(color), Color.red(pixel), tolerance);
+ assertEquals(Color.green(color), Color.green(pixel), tolerance);
+ assertEquals(Color.blue(color), Color.blue(pixel), tolerance);
+ } catch (Error e) {
+ Log.w(
+ getClass().getName(),
+ "rad="
+ + rad
+ + ", x="
+ + x
+ + ", y="
+ + y
+ + "pixel="
+ + Integer.toHexString(pixel)
+ + ", color="
+ + Integer.toHexString(color));
+ throw e;
+ }
+ }
+ }
+
+ @Test
+ public void testZeroScaleMatrix() {
+ SweepGradient gradient =
+ new SweepGradient(1, 0.5f, new int[] {Color.BLUE, Color.RED, Color.BLUE}, null);
+ Matrix m = new Matrix();
+ m.setScale(0, 0);
+ gradient.setLocalMatrix(m);
+
+ Bitmap bitmap = Bitmap.createBitmap(2, 1, Bitmap.Config.ARGB_8888);
+ bitmap.eraseColor(Color.BLACK);
+ Canvas canvas = new Canvas(bitmap);
+
+ Paint paint = new Paint();
+ paint.setShader(gradient);
+ canvas.drawPaint(paint);
+
+ // red to left, blue to right
+ ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(0, 0), 1);
+ ColorUtils.verifyColor(Color.BLACK, bitmap.getPixel(1, 0), 1);
+ }
+
+ @Test
+ public void testNullColorInts() {
+ int[] colors = null;
+ assertThrows(
+ NullPointerException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, colors, null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testNullColorLongs() {
+ long[] colors = null;
+ assertThrows(
+ NullPointerException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, colors, null);
+ });
+ }
+
+ @Test
+ public void testNoColorInts() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new int[0], null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testNoColorLongs() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new long[0], null);
+ });
+ }
+
+ @Test
+ public void testOneColorInts() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new int[1], null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testOneColorLongs() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new long[1], null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testMismatchColorLongs() {
+ long[] colors = new long[2];
+ colors[0] = Color.pack(Color.BLUE);
+ colors[1] = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, colors, null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testMismatchColorLongs2() {
+ long color0 = Color.pack(Color.BLUE);
+ long color1 = Color.pack(.5f, .5f, .5f, 1.0f, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, color0, color1);
+ });
+ }
+
+ @Test
+ public void testMismatchPositionsInts() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new int[2], new float[3]);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testMismatchPositionsLongs() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, new long[2], new float[3]);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testInvalidColorLongs() {
+ long[] colors = new long[2];
+ colors[0] = -1L;
+ colors[0] = -2L;
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, colors, null);
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testInvalidColorLong() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, -1L, Color.pack(Color.RED));
+ });
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testInvalidColorLong2() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ SweepGradient unused = new SweepGradient(1, 0.5f, Color.pack(Color.RED), -1L);
+ });
+ }
+
+ private String toString(double angle) {
+ return String.format("%.2f", angle) + "(pi)";
+ }
+
+ @Test
+ @Config(minSdk = Q)
+ public void testColorLong() {
+ ColorSpace p3 = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+ long red = Color.pack(1, 0, 0, 1, p3);
+ long blue = Color.pack(0, 0, 1, 1, p3);
+ SweepGradient gradient = new SweepGradient(50, 50, red, blue);
+
+ Bitmap bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGBA_F16);
+ bitmap.eraseColor(Color.TRANSPARENT);
+ Canvas canvas = new Canvas(bitmap);
+
+ Paint paint = new Paint();
+ paint.setShader(gradient);
+ canvas.drawPaint(paint);
+
+ final ColorSpace bitmapColorSpace = bitmap.getColorSpace();
+ Function<Long, Color> convert =
+ (l) -> {
+ return Color.valueOf(Color.convert(l, bitmapColorSpace));
+ };
+
+ Color lastColor = null;
+ double lastAngle = 0;
+ for (double angle = Math.PI / 8.0; angle < Math.PI * 2.0; angle += Math.PI / 8.0) {
+ // currentColor is the Color at this angle.
+ Color currentColor = null;
+ double lastRadius = 0;
+ for (double radius = 4; radius < 25; radius += 4) {
+ double dx = Math.cos(angle) * radius;
+ double dy = Math.sin(angle) * radius;
+ int x = 50 + (int) dx;
+ int y = 50 + (int) dy;
+ Color c = bitmap.getColor(x, y);
+ if (currentColor == null) {
+ // Checking the first radius at this angle.
+ currentColor = c;
+ if (lastColor == null) {
+ // This should be pretty close to the initial color.
+ ColorUtils.verifyColor(
+ "First color (at angle "
+ + toString(angle)
+ + " and radius "
+ + radius
+ + " should be mostly red",
+ convert.apply(red),
+ c,
+ .08f);
+ lastColor = currentColor;
+ lastAngle = angle;
+ } else {
+ assertTrue(
+ "Angle "
+ + toString(angle)
+ + " should be less red than prior angle "
+ + toString(lastAngle),
+ c.red() < lastColor.red());
+ assertTrue(
+ "Angle "
+ + toString(angle)
+ + " should be more blue than prior angle "
+ + toString(lastAngle),
+ c.blue() > lastColor.blue());
+ }
+ } else {
+ // Already have a Color at this angle. This one should match.
+ ColorUtils.verifyColor(
+ "Radius "
+ + radius
+ + " at angle "
+ + toString(angle)
+ + " should match same angle with radius "
+ + lastRadius,
+ currentColor,
+ c,
+ .05f);
+ }
+ lastRadius = radius;
+ }
+
+ lastColor = currentColor;
+ lastAngle = angle;
+ }
+ }
+}