diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeHardwareRendererTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeHardwareRendererTest.java index f86256d487..37764e3df6 100644 --- a/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeHardwareRendererTest.java +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeHardwareRendererTest.java @@ -2,9 +2,11 @@ import static android.os.Build.VERSION_CODES.Q; import static android.os.Build.VERSION_CODES.R; +import static com.google.common.base.StandardSystemProperty.OS_NAME; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.util.reflector.Reflector.reflector; +import android.annotation.ColorInt; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.HardwareRenderer; @@ -17,6 +19,9 @@ import android.media.ImageReader; import android.view.Choreographer; import android.view.Surface; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.Locale; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -56,33 +61,113 @@ public void imageReader_readsRenderedDisplayList() { try (ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1)) { HardwareRenderer renderer = new HardwareRenderer(); - RenderNode displayList = createDisplayList(width, height); + RenderNode renderNode = new RenderNode("RedNode"); + renderNode.setPosition(0, 0, width, height); + RecordingCanvas canvas = renderNode.beginRecording(); + canvas.drawColor(Color.RED); + renderNode.endRecording(); Surface surface = imageReader.getSurface(); renderer.setSurface(surface); - Image nativeImage = imageReader.acquireNextImage(); - renderer.setContentRoot(displayList); + renderer.setContentRoot(renderNode); renderer.createRenderRequest().syncAndDraw(); + Image nativeImage = imageReader.acquireNextImage(); Plane[] planes = nativeImage.getPlanes(); Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(planes[0].getBuffer()); surface.release(); - assertThat(bitmap.getPixel(50, 50)).isEqualTo(Color.RED); + assertThat(Integer.toHexString(bitmap.getPixel(50, 50))) + .isEqualTo(Integer.toHexString(Color.RED)); + } + } + + @Test + public void hardwareRenderer_drawDisplayList_validateARGB() { + int pw = 320; + int ph = 470; + + try (ImageReader imageReader = ImageReader.newInstance(pw, ph, PixelFormat.RGBA_8888, 1)) { + // Note on pixel format: + // - ImageReader is configured as RGBA_8888 above. + // - However the native libs/hwui/pipeline/skia/SkiaHostPipeline.cpp always treats + // the buffer as BGRA_8888 on Linux and Windows, or RGBA_8888 n Mac. + + HardwareRenderer renderer = new HardwareRenderer(); + RenderNode displayList = createDisplayList(pw, ph); + + Surface surface = imageReader.getSurface(); + renderer.setSurface(surface); + Image nativeImage = imageReader.acquireNextImage(); + renderer.setContentRoot(displayList); + renderer.createRenderRequest().syncAndDraw(); + + int[] dstImageData = new int[pw * ph]; + Plane[] planes = nativeImage.getPlanes(); + IntBuffer buff = planes[0].getBuffer().order(ByteOrder.LITTLE_ENDIAN).asIntBuffer(); + buff.get(dstImageData); + + // The image drawn is here: + // https://gist.github.com/hoisie/2c78e5e86ce335f7c5a2431f72a92888#file-hw_render-png + + // Check that the pixel at (0, 0) is white. + assertThat(Integer.toHexString(dstImageData[0])).isEqualTo("ffffffff"); + if (isMac()) { + // Check for red pixels in ABGR format on Mac. + assertThat(Integer.toHexString(dstImageData[1])).isEqualTo("ff0000ff"); + assertThat(Integer.toHexString(dstImageData[2])).isEqualTo("ff0000ff"); + } else { + // Check for red pixels in ARGB format on Linux/Windows. + assertThat(Integer.toHexString(dstImageData[1])).isEqualTo("ffff0000"); + assertThat(Integer.toHexString(dstImageData[2])).isEqualTo("ffff0000"); + } + surface.release(); } } + /** + * This function draws three overlapping rectangles: a red one in the top left quadrant, a green + * on centered in the middle, and a blue one in the bottom right quadrant. Each of the rectangles + * has a shadow, and the bottom right one also has a round corner. It also contains a white point + * at the top left. + * + *
You can see the image drawn here: + * https://gist.github.com/hoisie/2c78e5e86ce335f7c5a2431f72a92888#file-hw_render-png + */ private static RenderNode createDisplayList(int width, int height) { - RenderNode renderNode = new RenderNode("RedNode"); + RenderNode renderNode = new RenderNode("MyRenderNode"); renderNode.setPosition(0, 0, width, height); RecordingCanvas canvas = renderNode.beginRecording(); - Paint paint = new Paint(); - paint.setColor(Color.RED); - canvas.drawRect(0, 0, width, height, paint); - renderNode.endRecording(); + // TODO(hoisie): simplify this drawing logic. + int w4 = width / 4; + int h4 = height / 4; + try { + // Draw a red rectangle in the top left quadrant. + canvas.drawRect(0, 0, 2 * w4, 2 * h4, createPaint(Color.RED)); + // Draw a green rectangle in the center. + canvas.drawRect(w4, h4, 3 * w4, 3 * h4, createPaint(Color.GREEN)); + // Draw a rounded blue rectangle in the bottom right quadrant. + canvas.drawRoundRect( + 2 * w4, 2 * h4, width, height, width / 10f, height / 10f, createPaint(Color.BLUE)); + // Draw a white point at the top left. + canvas.drawPoint(0.5f, 0.5f, createPaint(Color.WHITE)); + } finally { + renderNode.endRecording(); + } return renderNode; } + private static Paint createPaint(@ColorInt int color) { + Paint paint = new Paint(); + paint.setColor(color); + paint.setShadowLayer(20.f, 5.f, 5.f, color & 0xCFFFFFFF); + return paint; + } + @ForType(HardwareRenderer.class) interface HardwareRendererReflector { void setWideGamut(boolean wideGamut); } + + private static boolean isMac() { + return OS_NAME.value().toLowerCase(Locale.ROOT).contains("mac"); + } } diff --git a/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeImageReaderTest.java b/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeImageReaderTest.java new file mode 100644 index 0000000000..c10e8555b1 --- /dev/null +++ b/integration_tests/nativegraphics/src/test/java/org/robolectric/shadows/ShadowNativeImageReaderTest.java @@ -0,0 +1,91 @@ +package org.robolectric.shadows; + +import static android.os.Build.VERSION_CODES.Q; +import static com.google.common.truth.Truth.assertThat; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageReader; +import android.view.Surface; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +/** + * Test for {@link org.robolectric.shadows.ShadowNativeImageReader} and {@link + * org.robolectric.shadows.ShadowNativeImageReaderSurfaceImage}. + */ +@Config(minSdk = Q) +@RunWith(RobolectricTestRunner.class) +public class ShadowNativeImageReaderTest { + + private static final int SX = 320; + private static final int SY = 320; + + @Test + public void test_imageReader_newInstance_validateHasSurface() { + try (ImageReader imageReader = ImageReader.newInstance(SX, SY, PixelFormat.RGBA_8888, 1)) { + + assertThat(imageReader).isNotNull(); + Surface surface = imageReader.getSurface(); + try { + assertThat(surface).isNotNull(); + assertThat(surface.getClass().getSimpleName()).doesNotContain("Fake"); + assertThat(surface.isValid()).isTrue(); + } finally { + surface.release(); + } + } + } + + @Test + public void test_imageReader_getSurface_acquireNextImage() { + try (ImageReader imageReader = ImageReader.newInstance(SX, SY, PixelFormat.RGBA_8888, 1)) { + + Surface surface = imageReader.getSurface(); + try { + assertThat(surface).isNotNull(); + // Note: do not call surface.lockCanvas(), it is not implemented on the JNI side. + + try (Image image = imageReader.acquireNextImage()) { + assertThat(image).isNotNull(); + assertThat(image.getFormat()).isEqualTo(PixelFormat.RGBA_8888); + assertThat(image.getWidth()).isEqualTo(320); + assertThat(image.getHeight()).isEqualTo(320); + assertThat(image.getPlanes()).hasLength(1); + } + } finally { + surface.release(); + } + } + } + + @Test + public void test_imageReader_getSurface_lockCanvas() { + try (ImageReader imageReader = ImageReader.newInstance(SX, SY, PixelFormat.RGBA_8888, 1)) { + Surface surface = imageReader.getSurface(); + Canvas canvas = surface.lockCanvas(new Rect(0, 0, SX, SY)); + surface.unlockCanvasAndPost(canvas); + } + } + + @Test + public void testGetHardwareBuffer() throws Exception { + ImageReader reader = ImageReader.newInstance(1, 1, PixelFormat.RGBA_8888, 1); + Surface surface = reader.getSurface(); + Canvas canvas = surface.lockHardwareCanvas(); + canvas.drawColor(Color.RED); + surface.unlockCanvasAndPost(canvas); + Image image = reader.acquireNextImage(); + assertThat(image).isNotNull(); + HardwareBuffer buffer = image.getHardwareBuffer(); + // TODO(hoisie): buffer should not be null, but fixing it will require an implementation of + // HardwareBuffer on host libandroid_runtime. + assertThat(buffer).isNull(); + } +}