diff options
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.java | 387 |
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; + } + } +} |