1 /*
<lambda>null2  * Copyright 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.graphics
18 
19 import android.graphics.Bitmap
20 import android.graphics.BlendMode
21 import android.graphics.Canvas
22 import android.graphics.Color
23 import android.graphics.ColorSpace
24 import android.graphics.Matrix
25 import android.graphics.Outline
26 import android.graphics.Paint
27 import android.graphics.PixelFormat
28 import android.graphics.Rect
29 import android.graphics.RectF
30 import android.graphics.RenderNode
31 import android.hardware.HardwareBuffer
32 import android.os.Build
33 import android.os.Environment.DIRECTORY_PICTURES
34 import android.util.Half
35 import androidx.annotation.RequiresApi
36 import androidx.graphics.CanvasBufferedRenderer.RenderResult.Companion.SUCCESS
37 import androidx.graphics.CanvasBufferedRendererTests.TestHelper.Companion.hardwareBufferRendererTest
38 import androidx.graphics.CanvasBufferedRendererTests.TestHelper.Companion.record
39 import androidx.graphics.surface.SurfaceControlCompat
40 import androidx.hardware.DefaultFlags
41 import androidx.test.ext.junit.runners.AndroidJUnit4
42 import androidx.test.filters.LargeTest
43 import androidx.test.filters.SdkSuppress
44 import androidx.test.filters.SmallTest
45 import androidx.test.platform.app.InstrumentationRegistry
46 import java.io.File
47 import java.io.FileOutputStream
48 import java.io.IOException
49 import java.nio.ByteBuffer
50 import java.nio.ByteOrder
51 import java.util.concurrent.CountDownLatch
52 import java.util.concurrent.Executor
53 import java.util.concurrent.Executors
54 import java.util.concurrent.TimeUnit
55 import kotlin.math.abs
56 import kotlinx.coroutines.runBlocking
57 import org.junit.Assert.assertEquals
58 import org.junit.Assert.assertFalse
59 import org.junit.Assert.assertNotNull
60 import org.junit.Assert.assertThrows
61 import org.junit.Assert.assertTrue
62 import org.junit.Test
63 import org.junit.runner.RunWith
64 
65 @RunWith(AndroidJUnit4::class)
66 @SmallTest
67 class CanvasBufferedRendererTests {
68 
69     private val mExecutor = Executors.newSingleThreadExecutor()
70 
71     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
72     @Test
73     fun testRenderAfterCloseReturnsError() = hardwareBufferRendererTest { renderer ->
74         renderer.close()
75         assertThrows(IllegalStateException::class.java) {
76             renderer.obtainRenderRequest().drawAsync(mExecutor) { _ -> /* NO-OP */ }
77         }
78     }
79 
80     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
81     @Test
82     fun testIsClosed() = hardwareBufferRendererTest { renderer ->
83         assertFalse(renderer.isClosed)
84         renderer.close()
85         assertTrue(renderer.isClosed)
86     }
87 
88     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
89     @Test
90     fun testMultipleClosesDoesNotCrash() = hardwareBufferRendererTest { renderer ->
91         renderer.close()
92         renderer.close()
93         renderer.close()
94     }
95 
96     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
97     @Test
98     fun testPreservationDisabledClearsContents() = hardwareBufferRendererTest { renderer ->
99         val node =
100             RenderNode("content").apply {
101                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
102                 record { canvas -> canvas.drawColor(Color.BLUE) }
103             }
104 
105         renderer.setContentRoot(node)
106         var latch = CountDownLatch(1)
107         var bitmap: Bitmap? = null
108         renderer.obtainRenderRequest().preserveContents(true).drawAsync(mExecutor) { result ->
109             assertEquals(SUCCESS, result.status)
110             result.fence?.awaitForever()
111             bitmap = Bitmap.wrapHardwareBuffer(result.hardwareBuffer, null)
112             latch.countDown()
113         }
114 
115         assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
116         assertNotNull(bitmap)
117         assertTrue(bitmap!!.copy(Bitmap.Config.ARGB_8888, false).isAllColor(Color.BLUE))
118 
119         node.record { canvas -> canvas.drawColor(Color.RED, BlendMode.DST_OVER) }
120 
121         latch = CountDownLatch(1)
122         renderer.obtainRenderRequest().preserveContents(false).drawAsync(mExecutor) { result ->
123             assertEquals(SUCCESS, result.status)
124             result.fence?.awaitForever()
125             bitmap = Bitmap.wrapHardwareBuffer(result.hardwareBuffer, null)
126             latch.countDown()
127         }
128 
129         assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
130 
131         assertNotNull(bitmap)
132         assertTrue(bitmap!!.copy(Bitmap.Config.ARGB_8888, false).isAllColor(Color.RED))
133     }
134 
135     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
136     @Test
137     fun testPreservationEnabledPreservesContents() =
138         repeat(20) { verifyPreservedBuffer(CanvasBufferedRenderer.DEFAULT_IMPL) }
139 
140     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
141     @Test
142     fun testPreservationEnabledPreservesContentsWithRedrawStrategy() =
143         repeat(20) { verifyPreservedBuffer(CanvasBufferedRenderer.USE_V29_IMPL_WITH_REDRAW) }
144 
145     @RequiresApi(Build.VERSION_CODES.Q)
146     private fun verifyPreservedBuffer(
147         impl: Int,
148         width: Int = TEST_WIDTH,
149         height: Int = TEST_HEIGHT
150     ) {
151         val bitmap = bufferPreservationTestHelper(impl, width, height, mExecutor)
152         assertNotNull(bitmap)
153         assertTrue(bitmap!!.copy(Bitmap.Config.ARGB_8888, false).isAllColor(Color.BLUE))
154     }
155 
156     /** Helper test method to save test bitmaps to disk to verify output for debugging purposes */
157     private fun saveBitmap(bitmap: Bitmap, name: String) {
158         val filename =
159             InstrumentationRegistry.getInstrumentation()
160                 .context
161                 .getExternalFilesDir(DIRECTORY_PICTURES)
162         val testFile = File(filename!!.path + "/" + name)
163         try {
164             FileOutputStream(testFile).use { out ->
165                 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
166             }
167         } catch (e: IOException) {
168             e.printStackTrace()
169         }
170     }
171 
172     @RequiresApi(Build.VERSION_CODES.Q)
173     private fun bufferPreservationTestHelper(
174         impl: Int,
175         width: Int,
176         height: Int,
177         executor: Executor
178     ): Bitmap? {
179         val hardwareBufferRenderer =
180             CanvasBufferedRenderer.Builder(width, height).setMaxBuffers(1).setImpl(impl).build()
181 
182         hardwareBufferRenderer.use { renderer ->
183             val node =
184                 RenderNode("content").apply {
185                     setPosition(0, 0, width, height)
186                     record { canvas ->
187                         canvas.drawColor(Color.BLACK, BlendMode.CLEAR)
188                         canvas.drawColor(Color.BLUE)
189                     }
190                 }
191 
192             renderer.setContentRoot(node)
193             val firstRenderLatch = CountDownLatch(1)
194             var bitmap: Bitmap? = null
195             renderer.obtainRenderRequest().preserveContents(true).drawAsync(executor) { result ->
196                 assertEquals(SUCCESS, result.status)
197                 result.fence?.awaitForever()
198                 bitmap = Bitmap.wrapHardwareBuffer(result.hardwareBuffer, null)
199                 firstRenderLatch.countDown()
200             }
201 
202             assertTrue(firstRenderLatch.await(3000, TimeUnit.MILLISECONDS))
203 
204             node.record { canvas -> canvas.drawColor(Color.RED, BlendMode.DST_OVER) }
205 
206             val secondRenderLatch = CountDownLatch(1)
207             renderer.obtainRenderRequest().preserveContents(true).drawAsync(executor) { result ->
208                 assertEquals(SUCCESS, result.status)
209                 result.fence?.awaitForever()
210                 bitmap = Bitmap.wrapHardwareBuffer(result.hardwareBuffer, null)
211                 secondRenderLatch.countDown()
212             }
213 
214             assertTrue(secondRenderLatch.await(3000, TimeUnit.MILLISECONDS))
215 
216             return bitmap
217         }
218     }
219 
220     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
221     @Test
222     fun testHardwareBufferRender() = hardwareBufferRendererTest { renderer ->
223         val contentRoot =
224             RenderNode("content").apply {
225                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
226                 record { canvas -> canvas.drawColor(Color.BLUE) }
227             }
228         renderer.setContentRoot(contentRoot)
229 
230         val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
231         val latch = CountDownLatch(1)
232         var hardwareBuffer: HardwareBuffer? = null
233         renderer.obtainRenderRequest().setColorSpace(colorSpace).drawAsync(mExecutor) { renderResult
234             ->
235             renderResult.fence?.awaitForever()
236             hardwareBuffer = renderResult.hardwareBuffer
237             latch.countDown()
238         }
239 
240         assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
241 
242         val bitmap =
243             Bitmap.wrapHardwareBuffer(hardwareBuffer!!, colorSpace)!!.copy(
244                 Bitmap.Config.ARGB_8888,
245                 false
246             )
247 
248         assertEquals(TEST_WIDTH, bitmap.width)
249         assertEquals(TEST_HEIGHT, bitmap.height)
250         assertEquals(0xFF0000FF.toInt(), bitmap.getPixel(0, 0))
251     }
252 
253     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
254     @Test
255     fun testDrawSync() = hardwareBufferRendererTest { renderer ->
256         val contentRoot =
257             RenderNode("content").apply {
258                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
259                 record { canvas -> canvas.drawColor(Color.BLUE) }
260             }
261         renderer.setContentRoot(contentRoot)
262 
263         val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
264 
265         var renderResult: CanvasBufferedRenderer.RenderResult?
266         runBlocking {
267             renderResult = renderer.obtainRenderRequest().setColorSpace(colorSpace).draw()
268         }
269         assertNotNull(renderResult)
270         assertEquals(SUCCESS, renderResult!!.status)
271         val fence = renderResult?.fence
272         if (fence != null) {
273             // by default drawSync will automatically wait on the fence and close it leaving
274             // it in the invalid state
275             assertFalse(fence.isValid())
276         }
277 
278         val hardwareBuffer = renderResult!!.hardwareBuffer
279 
280         val bitmap =
281             Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace)!!.copy(
282                 Bitmap.Config.ARGB_8888,
283                 false
284             )
285 
286         assertEquals(TEST_WIDTH, bitmap.width)
287         assertEquals(TEST_HEIGHT, bitmap.height)
288         assertEquals(0xFF0000FF.toInt(), bitmap.getPixel(0, 0))
289     }
290 
291     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
292     @Test
293     fun testDrawSyncWithoutBlockingFence() = hardwareBufferRendererTest { renderer ->
294         val contentRoot =
295             RenderNode("content").apply {
296                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
297                 record { canvas -> canvas.drawColor(Color.BLUE) }
298             }
299         renderer.setContentRoot(contentRoot)
300 
301         val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
302 
303         var renderResult: CanvasBufferedRenderer.RenderResult?
304         runBlocking {
305             renderResult = renderer.obtainRenderRequest().setColorSpace(colorSpace).draw(false)
306         }
307         assertNotNull(renderResult)
308         assertEquals(SUCCESS, renderResult!!.status)
309         renderResult?.fence?.let { fence ->
310             fence.awaitForever()
311             fence.close()
312         }
313 
314         val hardwareBuffer = renderResult!!.hardwareBuffer
315 
316         val bitmap =
317             Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace)!!.copy(
318                 Bitmap.Config.ARGB_8888,
319                 false
320             )
321 
322         assertEquals(TEST_WIDTH, bitmap.width)
323         assertEquals(TEST_HEIGHT, bitmap.height)
324         assertEquals(0xFF0000FF.toInt(), bitmap.getPixel(0, 0))
325     }
326 
327     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
328     @Test
329     fun testContentsPreservedSRGB() = preservedContentsTest { bitmap ->
330         assertEquals(Color.RED, bitmap.getPixel(TEST_WIDTH / 2, TEST_HEIGHT / 4))
331         assertEquals(Color.BLUE, bitmap.getPixel(TEST_WIDTH / 2, TEST_HEIGHT / 2 + TEST_HEIGHT / 4))
332     }
333 
334     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
335     @Test
336     fun testContentsPreservedF16() =
337         preservedContentsTest(
338             format = PixelFormat.RGBA_F16,
339             bitmapConfig = Bitmap.Config.RGBA_F16
340         ) { bitmap ->
341             val buffer =
342                 ByteBuffer.allocateDirect(bitmap.allocationByteCount).apply {
343                     bitmap.copyPixelsToBuffer(this)
344                     rewind()
345                     order(ByteOrder.LITTLE_ENDIAN)
346                 }
347             val srcColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
348             val srcToDst = ColorSpace.connect(srcColorSpace, ColorSpace.get(ColorSpace.Named.SRGB))
349 
350             val expectedRed = srcToDst.transform(1.0f, 0.0f, 0.0f)
351             val expectedBlue = srcToDst.transform(0.0f, 0.0f, 1.0f)
352 
353             TestHelper.assertEqualsRgba16f(
354                 "TopMiddle",
355                 bitmap,
356                 TEST_WIDTH / 2,
357                 TEST_HEIGHT / 4,
358                 buffer,
359                 expectedRed[0],
360                 expectedRed[1],
361                 expectedRed[2],
362                 1.0f
363             )
364             TestHelper.assertEqualsRgba16f(
365                 "BottomMiddle",
366                 bitmap,
367                 TEST_WIDTH / 2,
368                 TEST_HEIGHT / 2 + TEST_HEIGHT / 4,
369                 buffer,
370                 expectedBlue[0],
371                 expectedBlue[1],
372                 expectedBlue[2],
373                 1.0f
374             )
375         }
376 
377     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
378     @Test
379     fun testContentsPreserved1010102() =
380         preservedContentsTest(
381             format = PixelFormat.RGBA_1010102,
382             bitmapConfig = Bitmap.Config.RGBA_1010102
383         ) { bitmap ->
384             assertEquals(Color.RED, bitmap.getPixel(TEST_WIDTH / 2, TEST_HEIGHT / 4))
385             assertEquals(
386                 Color.BLUE,
387                 bitmap.getPixel(TEST_WIDTH / 2, TEST_HEIGHT / 2 + TEST_HEIGHT / 4)
388             )
389         }
390 
391     @RequiresApi(Build.VERSION_CODES.Q)
392     private fun preservedContentsTest(
393         format: Int = PixelFormat.RGBA_8888,
394         bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
395         block: (Bitmap) -> Unit
396     ) =
397         hardwareBufferRendererTest(format = format) { renderer ->
398             val contentRoot =
399                 RenderNode("content").apply {
400                     setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
401                     record { canvas -> canvas.drawColor(Color.BLUE) }
402                 }
403             renderer.setContentRoot(contentRoot)
404             val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
405             val latch = CountDownLatch(1)
406             var hardwareBuffer: HardwareBuffer?
407             renderer
408                 .obtainRenderRequest()
409                 .setColorSpace(colorSpace)
410                 .preserveContents(true)
411                 .drawAsync(mExecutor) { renderResult ->
412                     renderResult.fence?.awaitForever()
413                     hardwareBuffer = renderResult.hardwareBuffer
414                     latch.countDown()
415                 }
416 
417             assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
418 
419             val latch2 = CountDownLatch(1)
420             contentRoot.record { canvas ->
421                 val paint = Paint().apply { color = Color.RED }
422                 canvas.drawRect(0f, 0f, TEST_WIDTH.toFloat(), TEST_HEIGHT / 2f, paint)
423             }
424             renderer.setContentRoot(contentRoot)
425 
426             hardwareBuffer = null
427             renderer
428                 .obtainRenderRequest()
429                 .setColorSpace(colorSpace)
430                 .preserveContents(true)
431                 .drawAsync(mExecutor) { renderResult ->
432                     renderResult.fence?.awaitForever()
433                     hardwareBuffer = renderResult.hardwareBuffer
434                     latch2.countDown()
435                 }
436 
437             assertTrue(latch2.await(3000, TimeUnit.MILLISECONDS))
438 
439             val bitmap =
440                 Bitmap.wrapHardwareBuffer(hardwareBuffer!!, colorSpace)!!.copy(bitmapConfig, false)
441 
442             assertEquals(TEST_WIDTH, bitmap.width)
443             assertEquals(TEST_HEIGHT, bitmap.height)
444             block(bitmap)
445         }
446 
447     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
448     @Test
449     fun testTransformRotate0TallWide() =
450         TestHelper.quadTest(
451             mExecutor,
452             width = TEST_WIDTH * 2,
453             height = TEST_HEIGHT,
454             transform = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
455         ) { bitmap ->
456             TestHelper.assertBitmapQuadColors(
457                 bitmap,
458                 topLeft = Color.RED,
459                 topRight = Color.BLUE,
460                 bottomRight = Color.YELLOW,
461                 bottomLeft = Color.GREEN
462             )
463         }
464 
465     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
466     @Test
467     fun testTransformRotate0TallRect() =
468         TestHelper.quadTest(
469             mExecutor,
470             width = TEST_WIDTH,
471             height = TEST_HEIGHT * 2,
472             transform = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY
473         ) { bitmap ->
474             TestHelper.assertBitmapQuadColors(
475                 bitmap,
476                 topLeft = Color.RED,
477                 topRight = Color.BLUE,
478                 bottomRight = Color.YELLOW,
479                 bottomLeft = Color.GREEN
480             )
481         }
482 
483     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
484     @Test
485     fun testTransformRotate90WideRect() =
486         TestHelper.quadTest(
487             mExecutor,
488             width = TEST_WIDTH * 2,
489             height = TEST_HEIGHT,
490             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90
491         ) { bitmap ->
492             TestHelper.assertBitmapQuadColors(
493                 bitmap,
494                 topLeft = Color.GREEN,
495                 topRight = Color.RED,
496                 bottomRight = Color.BLUE,
497                 bottomLeft = Color.YELLOW
498             )
499         }
500 
501     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
502     @Test
503     fun testTransformRotate90TallRect() =
504         TestHelper.quadTest(
505             mExecutor,
506             width = TEST_WIDTH,
507             height = TEST_HEIGHT * 2,
508             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90
509         ) { bitmap ->
510             TestHelper.assertBitmapQuadColors(
511                 bitmap,
512                 topLeft = Color.GREEN,
513                 topRight = Color.RED,
514                 bottomLeft = Color.YELLOW,
515                 bottomRight = Color.BLUE
516             )
517         }
518 
519     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
520     @Test
521     fun testTransformRotate180WideRect() =
522         TestHelper.quadTest(
523             mExecutor,
524             width = TEST_WIDTH * 2,
525             height = TEST_HEIGHT,
526             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180
527         ) { bitmap ->
528             TestHelper.assertBitmapQuadColors(
529                 bitmap,
530                 topLeft = Color.YELLOW,
531                 topRight = Color.GREEN,
532                 bottomLeft = Color.BLUE,
533                 bottomRight = Color.RED
534             )
535         }
536 
537     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
538     @Test
539     fun testTransformRotate180TallRect() =
540         TestHelper.quadTest(
541             mExecutor,
542             width = TEST_WIDTH,
543             height = TEST_HEIGHT * 2,
544             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180
545         ) { bitmap ->
546             TestHelper.assertBitmapQuadColors(
547                 bitmap,
548                 topLeft = Color.YELLOW,
549                 topRight = Color.GREEN,
550                 bottomLeft = Color.BLUE,
551                 bottomRight = Color.RED
552             )
553         }
554 
555     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
556     @Test
557     fun testTransformRotate270WideRect() =
558         TestHelper.quadTest(
559             mExecutor,
560             width = TEST_WIDTH * 2,
561             height = TEST_HEIGHT,
562             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270
563         ) { bitmap ->
564             TestHelper.assertBitmapQuadColors(
565                 bitmap,
566                 topLeft = Color.BLUE,
567                 topRight = Color.YELLOW,
568                 bottomRight = Color.GREEN,
569                 bottomLeft = Color.RED
570             )
571         }
572 
573     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
574     @Test
575     fun testTransformRotate270TallRect() =
576         TestHelper.quadTest(
577             mExecutor,
578             width = TEST_WIDTH,
579             height = TEST_HEIGHT * 2,
580             transform = SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270
581         ) { bitmap ->
582             TestHelper.assertBitmapQuadColors(
583                 bitmap,
584                 topLeft = Color.BLUE,
585                 topRight = Color.YELLOW,
586                 bottomRight = Color.GREEN,
587                 bottomLeft = Color.RED
588             )
589         }
590 
591     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
592     @Test
593     fun testUnknownTransformThrows() = hardwareBufferRendererTest { renderer ->
594         val root =
595             RenderNode("content").apply {
596                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
597                 record { canvas ->
598                     with(canvas) {
599                         drawColor(Color.BLUE)
600                         val paint = Paint().apply { color = Color.RED }
601                         canvas.drawRect(0f, 0f, TEST_WIDTH / 2f, TEST_HEIGHT / 2f, paint)
602                     }
603                 }
604             }
605         renderer.setContentRoot(root)
606 
607         val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
608         val latch = CountDownLatch(1)
609 
610         assertThrows(IllegalArgumentException::class.java) {
611             renderer
612                 .obtainRenderRequest()
613                 .setColorSpace(colorSpace)
614                 .setBufferTransform(42)
615                 .drawAsync(mExecutor) { renderResult ->
616                     renderResult.fence?.awaitForever()
617                     latch.countDown()
618                 }
619         }
620     }
621 
622     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
623     @Test
624     fun testColorSpaceDisplayP3() =
625         TestHelper.colorSpaceTest(mExecutor, ColorSpace.get(ColorSpace.Named.DISPLAY_P3))
626 
627     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
628     @Test
629     fun testColorSpaceProPhotoRGB() =
630         TestHelper.colorSpaceTest(mExecutor, ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB))
631 
632     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
633     @Test
634     fun testColorSpaceAdobeRGB() =
635         TestHelper.colorSpaceTest(mExecutor, ColorSpace.get(ColorSpace.Named.ADOBE_RGB))
636 
637     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
638     @Test
639     fun testColorSpaceDciP3() =
640         TestHelper.colorSpaceTest(mExecutor, ColorSpace.get(ColorSpace.Named.DCI_P3))
641 
642     @RequiresApi(Build.VERSION_CODES.Q)
643     private fun spotShadowTest(
644         transform: Int = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY,
645     ) = hardwareBufferRendererTest { renderer ->
646         val content = RenderNode("content")
647         val colorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
648         renderer.apply {
649             setLightSourceAlpha(0.0f, 1.0f)
650             setLightSourceGeometry(TEST_WIDTH / 2f, 0f, 800.0f, 20.0f)
651             setContentRoot(content)
652         }
653         val childRect = Rect(25, 25, 65, 65)
654         content.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
655         with(TestHelper.Companion) {
656             content.record { parentCanvas ->
657                 val childNode = RenderNode("shadowCaster")
658                 childNode.setPosition(childRect)
659                 val outline = Outline()
660                 outline.setRect(Rect(0, 0, childRect.width(), childRect.height()))
661                 outline.alpha = 1f
662                 childNode.setOutline(outline)
663                 val childCanvas = childNode.beginRecording()
664                 childCanvas.drawColor(Color.RED)
665                 childNode.endRecording()
666                 childNode.elevation = 20f
667 
668                 parentCanvas.drawColor(Color.WHITE)
669                 parentCanvas.enableZ()
670                 parentCanvas.drawRenderNode(childNode)
671                 parentCanvas.disableZ()
672             }
673         }
674 
675         val latch = CountDownLatch(1)
676         var renderStatus = CanvasBufferedRenderer.RenderResult.ERROR_UNKNOWN
677         var hardwareBuffer: HardwareBuffer? = null
678 
679         renderer
680             .obtainRenderRequest()
681             .setColorSpace(colorSpace)
682             .setBufferTransform(transform)
683             .drawAsync(mExecutor) { renderResult ->
684                 renderStatus = renderResult.status
685                 renderResult.fence?.awaitForever()
686                 hardwareBuffer = renderResult.hardwareBuffer
687                 latch.countDown()
688             }
689 
690         assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
691         assertEquals(renderStatus, SUCCESS)
692         val bitmap =
693             Bitmap.wrapHardwareBuffer(hardwareBuffer!!, colorSpace)!!.copy(
694                 Bitmap.Config.ARGB_8888,
695                 false
696             )
697 
698         val rect = Rect(childRect.left, childRect.bottom, childRect.right, childRect.bottom + 10)
699 
700         var result =
701             bitmap.verify(rect) { actual, _, _ -> verifyPixelWithThreshold(actual, Color.RED, 10) }
702         result =
703             result ||
704                 bitmap.verify(rect.applyBufferTransform(bitmap.width, bitmap.height, transform)) {
705                     actual,
706                     _,
707                     _ ->
708                     verifyPixelGrayScale(actual, 1)
709                 }
710 
711         assertTrue(result)
712     }
713 
714     private fun Bitmap.verify(rect: Rect, block: (Int, Int, Int) -> Boolean): Boolean {
715         for (i in rect.left until rect.right) {
716             for (j in rect.top until rect.bottom) {
717                 if (!block(getPixel(i, j), i, j)) {
718                     return false
719                 }
720             }
721         }
722         return true
723     }
724 
725     /** @return True if close enough */
726     private fun verifyPixelWithThreshold(color: Int, expectedColor: Int, threshold: Int): Boolean {
727         val diff =
728             (abs((Color.red(color) - Color.red(expectedColor)).toDouble()) +
729                     abs((Color.green(color) - Color.green(expectedColor)).toDouble()) +
730                     abs((Color.blue(color) - Color.blue(expectedColor)).toDouble()))
731                 .toInt()
732         return diff <= threshold
733     }
734 
735     /**
736      * @param threshold Per channel differences for R / G / B channel against the average of these 3
737      *   channels. Should be less than 2 normally.
738      * @return True if the color is close enough to be a gray scale color.
739      */
740     private fun verifyPixelGrayScale(color: Int, threshold: Int): Boolean {
741         var average = Color.red(color) + Color.green(color) + Color.blue(color)
742         average /= 3
743         return abs((Color.red(color) - average).toDouble()) <= threshold &&
744             abs((Color.green(color) - average).toDouble()) <= threshold &&
745             abs((Color.blue(color) - average).toDouble()) <= threshold
746     }
747 
748     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
749     @Test
750     fun testSpotShadowSetup() = spotShadowTest()
751 
752     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
753     @Test
754     fun testSpotShadowRotate90() = spotShadowTest(SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90)
755 
756     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
757     @Test
758     fun testSpotShadowRotate180() = spotShadowTest(SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180)
759 
760     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
761     @Test
762     fun testSpotShadowRotate270() = spotShadowTest(SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270)
763 
764     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
765     @Test
766     fun testRendererBlocksOnBufferRelease() {
767         val renderNode =
768             RenderNode("node").apply {
769                 setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
770                 val canvas = beginRecording()
771                 canvas.drawColor(Color.RED)
772                 endRecording()
773             }
774         val renderer =
775             CanvasBufferedRenderer.Builder(TEST_WIDTH, TEST_HEIGHT).setMaxBuffers(2).build().apply {
776                 setContentRoot(renderNode)
777             }
778 
779         val executor = Executors.newSingleThreadExecutor()
780         try {
781             val latch1 = CountDownLatch(1)
782             val latch2 = CountDownLatch(1)
783             val latch3 = CountDownLatch(1)
784             var hardwareBuffer: HardwareBuffer? = null
785             renderer.obtainRenderRequest().drawAsync(executor) { result ->
786                 result.fence?.awaitForever()
787                 result.fence?.close()
788                 hardwareBuffer = result.hardwareBuffer
789                 latch1.countDown()
790             }
791 
792             assertTrue(latch1.await(1000, TimeUnit.MILLISECONDS))
793 
794             var canvas = renderNode.beginRecording()
795             canvas.drawColor(Color.BLUE)
796             renderNode.endRecording()
797 
798             renderer.obtainRenderRequest().drawAsync(executor) { _ -> latch2.countDown() }
799 
800             assertTrue(latch2.await(1000, TimeUnit.MILLISECONDS))
801 
802             canvas = renderNode.beginRecording()
803             canvas.drawColor(Color.GREEN)
804             renderNode.endRecording()
805 
806             renderer.obtainRenderRequest().drawAsync(executor) { _ -> latch3.countDown() }
807 
808             // The 3rd render request should be blocked until the buffer is released
809             assertFalse(latch3.await(1000, TimeUnit.MILLISECONDS))
810             assertNotNull(hardwareBuffer)
811             renderer.releaseBuffer(hardwareBuffer!!, null)
812             assertTrue(latch3.await(1000, TimeUnit.MILLISECONDS))
813         } finally {
814             renderer.close()
815             executor.shutdownNow()
816         }
817     }
818 
819     @RequiresApi(Build.VERSION_CODES.Q)
820     private fun Rect.applyBufferTransform(width: Int, height: Int, transform: Int): Rect {
821         val rectF = RectF(this)
822         val matrix = Matrix()
823         when (transform) {
824             SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90 -> {
825                 matrix.apply {
826                     setRotate(90f)
827                     postTranslate(width.toFloat(), 0f)
828                 }
829             }
830             SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180 -> {
831                 matrix.apply {
832                     setRotate(180f)
833                     postTranslate(width.toFloat(), height.toFloat())
834                 }
835             }
836             SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270 -> {
837                 matrix.apply {
838                     setRotate(270f)
839                     postTranslate(0f, width.toFloat())
840                 }
841             }
842             SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY -> {
843                 matrix.reset()
844             }
845             else -> throw IllegalArgumentException("Invalid transform value")
846         }
847         matrix.mapRect(rectF)
848         return Rect(
849             rectF.left.toInt(),
850             rectF.top.toInt(),
851             rectF.right.toInt(),
852             rectF.bottom.toInt()
853         )
854     }
855 
856     // See b/295332012
857     @SdkSuppress(
858         minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
859         maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE
860     )
861     @Test
862     fun testHardwareBufferRendererV34SharedFileDescriptorMonitoring() {
863         fun createHardwareBufferRenderer(
864             sharedFdMonitor: SharedFileDescriptorMonitor
865         ): CanvasBufferedRendererV34 {
866             return CanvasBufferedRendererV34(
867                 TEST_WIDTH,
868                 TEST_HEIGHT,
869                 HardwareBuffer.RGBA_8888,
870                 DefaultFlags,
871                 1,
872                 sharedFdMonitor
873             )
874         }
875 
876         val sharedFdMonitor = CanvasBufferedRendererV34.obtainSharedFdMonitor()
877         // Monitor is only returned on devices running the vulkan hwui backend
878         if (sharedFdMonitor != null) {
879             val hbr1 = createHardwareBufferRenderer(sharedFdMonitor)
880             val hbr2 = createHardwareBufferRenderer(sharedFdMonitor)
881             val hbr3 = createHardwareBufferRenderer(sharedFdMonitor)
882 
883             hbr1.close()
884 
885             assertTrue(sharedFdMonitor.isMonitoring)
886 
887             hbr2.close()
888 
889             assertTrue(sharedFdMonitor.isMonitoring)
890 
891             hbr3.close()
892 
893             assertFalse(sharedFdMonitor.isMonitoring)
894 
895             val sharedFdMonitor2 = CanvasBufferedRendererV34.obtainSharedFdMonitor()!!
896             val hbr4 = createHardwareBufferRenderer(sharedFdMonitor2)
897 
898             assertTrue(sharedFdMonitor2.isMonitoring)
899 
900             hbr4.close()
901 
902             assertFalse(sharedFdMonitor2.isMonitoring)
903         }
904     }
905 
906     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
907     @LargeTest
908     @Test
909     fun testFdCleanupAfterSeveralRenders() {
910         val hbr = CanvasBufferedRenderer.Builder(TEST_WIDTH, TEST_HEIGHT).setMaxBuffers(1).build()
911         val renderNode = RenderNode("node").apply { setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT) }
912         hbr.setContentRoot(renderNode)
913         val executor = Executors.newSingleThreadExecutor()
914         try {
915             for (i in 0 until 100000) {
916                 val canvas = renderNode.beginRecording()
917                 canvas.drawColor(Color.RED)
918                 renderNode.endRecording()
919 
920                 val latch = CountDownLatch(1)
921                 hbr.obtainRenderRequest().drawAsync(executor) { result ->
922                     hbr.releaseBuffer(result.hardwareBuffer, result.fence)
923                     latch.countDown()
924                 }
925                 latch.await()
926             }
927         } finally {
928             executor.shutdownNow()
929             hbr.close()
930         }
931     }
932 
933     companion object {
934         const val TEST_WIDTH = 90
935         const val TEST_HEIGHT = 90
936     }
937 
938     /**
939      * Helper class to move test methods that include APIs introduced in newer class levels. This is
940      * done in order to avoid NoClassFoundExceptions being thrown when the test is loaded on lower
941      * API levels even if there are corresponding @SdkSuppress annotations used in conjunction with
942      * the corresponding API version code.
943      */
944     internal class TestHelper {
945         companion object {
946 
947             @RequiresApi(Build.VERSION_CODES.Q)
948             fun quadTest(
949                 executor: Executor,
950                 width: Int = TEST_WIDTH,
951                 height: Int = TEST_HEIGHT,
952                 transform: Int = SurfaceControlCompat.BUFFER_TRANSFORM_IDENTITY,
953                 colorSpace: ColorSpace = ColorSpace.get(ColorSpace.Named.SRGB),
954                 format: Int = PixelFormat.RGBA_8888,
955                 bitmapConfig: Bitmap.Config = Bitmap.Config.ARGB_8888,
956                 block: (Bitmap) -> Unit,
957             ) {
958                 val bufferWidth: Int
959                 val bufferHeight: Int
960                 if (
961                     transform == SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90 ||
962                         transform == SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270
963                 ) {
964                     bufferWidth = height
965                     bufferHeight = width
966                 } else {
967                     bufferWidth = width
968                     bufferHeight = height
969                 }
970                 hardwareBufferRendererTest(
971                     width = bufferWidth,
972                     height = bufferHeight,
973                     format = format
974                 ) { renderer ->
975                     val root =
976                         RenderNode("content").apply {
977                             setPosition(0, 0, width, height)
978                             record { canvas ->
979                                 val widthF = width.toFloat()
980                                 val heightF = height.toFloat()
981                                 val paint = Paint().apply { color = Color.RED }
982                                 canvas.drawRect(0f, 0f, widthF / 2f, heightF / 2f, paint)
983                                 paint.color = Color.BLUE
984                                 canvas.drawRect(widthF / 2f, 0f, widthF, heightF / 2f, paint)
985                                 paint.color = Color.GREEN
986                                 canvas.drawRect(0f, heightF / 2f, widthF / 2f, heightF, paint)
987                                 paint.color = Color.YELLOW
988                                 canvas.drawRect(widthF / 2f, heightF / 2f, widthF, heightF, paint)
989                             }
990                         }
991                     renderer.setContentRoot(root)
992 
993                     val latch = CountDownLatch(1)
994                     var hardwareBuffer: HardwareBuffer? = null
995                     renderer
996                         .obtainRenderRequest()
997                         .setColorSpace(colorSpace)
998                         .preserveContents(true)
999                         .setBufferTransform(transform)
1000                         .drawAsync(executor) { renderResult ->
1001                             renderResult.fence?.awaitForever()
1002                             hardwareBuffer = renderResult.hardwareBuffer
1003                             latch.countDown()
1004                         }
1005 
1006                     assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
1007 
1008                     val bitmap =
1009                         Bitmap.wrapHardwareBuffer(hardwareBuffer!!, colorSpace)!!.copy(
1010                             bitmapConfig,
1011                             false
1012                         )
1013 
1014                     assertEquals(bufferWidth, bitmap.width)
1015                     assertEquals(bufferHeight, bitmap.height)
1016 
1017                     block(bitmap)
1018                 }
1019             }
1020 
1021             @RequiresApi(Build.VERSION_CODES.Q)
1022             fun colorSpaceTest(executor: Executor, dstColorSpace: ColorSpace) =
1023                 quadTest(
1024                     executor,
1025                     format = PixelFormat.RGBA_F16,
1026                     colorSpace = dstColorSpace,
1027                     bitmapConfig = Bitmap.Config.RGBA_F16
1028                 ) { bitmap ->
1029                     val buffer =
1030                         ByteBuffer.allocateDirect(bitmap.allocationByteCount).apply {
1031                             bitmap.copyPixelsToBuffer(this)
1032                             rewind()
1033                             order(ByteOrder.LITTLE_ENDIAN)
1034                         }
1035                     val srcColorSpace = ColorSpace.get(ColorSpace.Named.SRGB)
1036                     val srcToDst = ColorSpace.connect(srcColorSpace, dstColorSpace)
1037 
1038                     val expectedRed = srcToDst.transform(1.0f, 0.0f, 0.0f)
1039                     val expectedBlue = srcToDst.transform(0.0f, 0.0f, 1.0f)
1040                     val expectedGreen = srcToDst.transform(0.0f, 1.0f, 0.0f)
1041                     val expectedYellow = srcToDst.transform(1.0f, 1.0f, 0.0f)
1042 
1043                     assertEqualsRgba16f(
1044                         "TopLeft",
1045                         bitmap,
1046                         TEST_WIDTH / 4,
1047                         TEST_HEIGHT / 4,
1048                         buffer,
1049                         expectedRed[0],
1050                         expectedRed[1],
1051                         expectedRed[2],
1052                         1.0f
1053                     )
1054 
1055                     assertEqualsRgba16f(
1056                         "TopRight",
1057                         bitmap,
1058                         (TEST_WIDTH * 3f / 4f).toInt(),
1059                         TEST_HEIGHT / 4,
1060                         buffer,
1061                         expectedBlue[0],
1062                         expectedBlue[1],
1063                         expectedBlue[2],
1064                         1.0f
1065                     )
1066 
1067                     assertEqualsRgba16f(
1068                         "BottomLeft",
1069                         bitmap,
1070                         TEST_WIDTH / 4,
1071                         (TEST_HEIGHT * 3f / 4f).toInt(),
1072                         buffer,
1073                         expectedGreen[0],
1074                         expectedGreen[1],
1075                         expectedGreen[2],
1076                         1.0f
1077                     )
1078                     assertEqualsRgba16f(
1079                         "BottomRight",
1080                         bitmap,
1081                         (TEST_WIDTH * 3f / 4f).toInt(),
1082                         (TEST_HEIGHT * 3f / 4f).toInt(),
1083                         buffer,
1084                         expectedYellow[0],
1085                         expectedYellow[1],
1086                         expectedYellow[2],
1087                         1.0f
1088                     )
1089                 }
1090 
1091             @RequiresApi(Build.VERSION_CODES.Q)
1092             fun hardwareBufferRendererTest(
1093                 width: Int = TEST_WIDTH,
1094                 height: Int = TEST_HEIGHT,
1095                 format: Int = HardwareBuffer.RGBA_8888,
1096                 impl: Int = CanvasBufferedRenderer.DEFAULT_IMPL,
1097                 block: (renderer: CanvasBufferedRenderer) -> Unit,
1098             ) {
1099                 val usage =
1100                     HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
1101                 if (
1102                     format != HardwareBuffer.RGBA_8888 &&
1103                         !HardwareBuffer.isSupported(width, height, format, 1, usage)
1104                 ) {
1105                     // Early out if the hardware configuration is not supported.
1106                     // PixelFormat.RGBA_8888 should always be supported
1107                     return
1108                 }
1109                 val renderer =
1110                     CanvasBufferedRenderer.Builder(width, height)
1111                         .setMaxBuffers(1)
1112                         .setBufferFormat(format)
1113                         .setUsageFlags(usage)
1114                         .setImpl(impl)
1115                         .build()
1116                 try {
1117                     block(renderer)
1118                 } finally {
1119                     renderer.close()
1120                 }
1121             }
1122 
1123             @RequiresApi(Build.VERSION_CODES.Q)
1124             inline fun RenderNode.record(block: (canvas: Canvas) -> Unit): RenderNode {
1125                 block(beginRecording())
1126                 endRecording()
1127                 return this
1128             }
1129 
1130             @RequiresApi(Build.VERSION_CODES.Q)
1131             fun assertEqualsRgba16f(
1132                 message: String,
1133                 bitmap: Bitmap,
1134                 x: Int,
1135                 y: Int,
1136                 dst: ByteBuffer,
1137                 r: Float,
1138                 g: Float,
1139                 b: Float,
1140                 a: Float,
1141             ) {
1142                 val index = y * bitmap.rowBytes + (x shl 3)
1143                 val cR = dst.getShort(index)
1144                 val cG = dst.getShort(index + 2)
1145                 val cB = dst.getShort(index + 4)
1146                 val cA = dst.getShort(index + 6)
1147                 assertEquals(message, r, Half.toFloat(cR), 0.01f)
1148                 assertEquals(message, g, Half.toFloat(cG), 0.01f)
1149                 assertEquals(message, b, Half.toFloat(cB), 0.01f)
1150                 assertEquals(message, a, Half.toFloat(cA), 0.01f)
1151             }
1152 
1153             fun assertBitmapQuadColors(
1154                 bitmap: Bitmap,
1155                 topLeft: Int,
1156                 topRight: Int,
1157                 bottomLeft: Int,
1158                 bottomRight: Int,
1159             ) {
1160                 val width = bitmap.width
1161                 val height = bitmap.height
1162 
1163                 val topLeftStartX = 0
1164                 val topLeftEndX = width / 2 - 2
1165                 val topLeftStartY = 0
1166                 val topLeftEndY = height / 2 - 2
1167 
1168                 val topRightStartX = width / 2 + 2
1169                 val topRightEndX = width - 1
1170                 val topRightStartY = 0
1171                 val topRightEndY = height / 2 - 2
1172 
1173                 val bottomRightStartX = width / 2 + 2
1174                 val bottomRightEndX = width - 1
1175                 val bottomRightStartY = height / 2 + 2
1176                 val bottomRightEndY = height - 1
1177 
1178                 val bottomLeftStartX = 0
1179                 val bottomLeftEndX = width / 2 - 2
1180                 val bottomLeftStartY = height / 2 + 2
1181                 val bottomLeftEndY = height - 1
1182 
1183                 assertEquals(topLeft, bitmap.getPixel(topLeftStartX, topLeftStartY))
1184                 assertEquals(topLeft, bitmap.getPixel(topLeftEndX, topLeftStartY))
1185                 assertEquals(topLeft, bitmap.getPixel(topLeftEndX, topLeftEndY))
1186                 assertEquals(topLeft, bitmap.getPixel(topLeftStartX, topLeftEndY))
1187 
1188                 assertEquals(topRight, bitmap.getPixel(topRightStartX, topRightStartY))
1189                 assertEquals(topRight, bitmap.getPixel(topRightEndX, topRightStartY))
1190                 assertEquals(topRight, bitmap.getPixel(topRightEndX, topRightEndY))
1191                 assertEquals(topRight, bitmap.getPixel(topRightStartX, topRightEndY))
1192 
1193                 assertEquals(bottomRight, bitmap.getPixel(bottomRightStartX, bottomRightStartY))
1194                 assertEquals(bottomRight, bitmap.getPixel(bottomRightEndX, bottomRightStartY))
1195                 assertEquals(bottomRight, bitmap.getPixel(bottomRightEndX, bottomRightEndY))
1196                 assertEquals(bottomRight, bitmap.getPixel(bottomRightStartX, bottomRightEndY))
1197 
1198                 assertEquals(bottomLeft, bitmap.getPixel(bottomLeftStartX, bottomLeftStartY))
1199                 assertEquals(bottomLeft, bitmap.getPixel(bottomLeftEndX, bottomLeftStartY))
1200                 assertEquals(bottomLeft, bitmap.getPixel(bottomLeftEndX, bottomLeftEndY))
1201                 assertEquals(bottomLeft, bitmap.getPixel(bottomLeftStartX, bottomLeftEndY))
1202             }
1203         }
1204     }
1205 }
1206