[go: nahoru, domu]

blob: 242d345f39fe460ef98641dab07a13f32705d86f [file] [log] [blame]
/*
* Copyright 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 androidx.ui.graphics.drawscope
import androidx.test.filters.SmallTest
import androidx.ui.geometry.Offset
import androidx.ui.geometry.Rect
import androidx.ui.geometry.Size
import androidx.ui.graphics.Canvas
import androidx.ui.graphics.Color
import androidx.ui.graphics.ImageAsset
import androidx.ui.graphics.Paint
import androidx.ui.graphics.PointMode
import androidx.ui.graphics.SolidColor
import androidx.ui.graphics.StrokeCap
import androidx.ui.graphics.compositeOver
import androidx.ui.graphics.toPixelMap
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@SmallTest
@RunWith(JUnit4::class)
class DrawScopeTest {
private val width: Int = 100
private val height: Int = 100
private val dstSize = Size(width.toFloat(), height.toFloat())
private fun createTestDstImage(): ImageAsset {
val dst = ImageAsset(width, height)
val dstCanvas = Canvas(dst)
val dstPaint = Paint().apply {
this.color = Color.White
}
dstCanvas.drawRect(
Rect.fromLTWH(0.0f, 0.0f, 200.0f, 200.0f),
dstPaint
)
return dst
}
@Test
fun testDrawRectColor() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a color parameter
// fills the canvas with red color
drawRect(color = Color.Red)
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
assertEquals(Color.Red, pixelMap[i, j])
}
}
}
@Test
fun testDrawRectBrushColor() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a brush parameter
// fills the canvas with red color
drawRect(brush = SolidColor(Color.Red))
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
assertEquals(Color.Red, pixelMap[i, j])
}
}
}
@Test
fun testDrawRectColorAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a color parameter
// fills the canvas with red color
drawRect(color = Color.Red, alpha = 0.5f)
}
val expected = Color(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
).compositeOver(Color.White)
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val result = pixelMap[i, j]
assertEquals(expected.red, result.red, 0.01f)
assertEquals(expected.green, result.green, 0.01f)
assertEquals(expected.blue, result.blue, 0.01f)
assertEquals(expected.alpha, result.alpha, 0.01f)
}
}
}
@Test
fun testDrawRectBrushColorAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a brush parameter
// fills the canvas with red color
drawRect(brush = SolidColor(Color.Red), alpha = 0.5f)
}
val expected = Color(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
).compositeOver(Color.White)
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val result = pixelMap[i, j]
assertEquals(expected.red, result.red, 0.01f)
assertEquals(expected.green, result.green, 0.01f)
assertEquals(expected.blue, result.blue, 0.01f)
assertEquals(expected.alpha, result.alpha, 0.01f)
}
}
}
@Test
fun testDrawRectColorIntrinsicAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a color parameter
// fills the canvas with red color
drawRect(color =
Color.Red.copy(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
))
}
val expected = Color(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
).compositeOver(Color.White)
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val result = pixelMap[i, j]
assertEquals(expected.red, result.red, 0.01f)
assertEquals(expected.green, result.green, 0.01f)
assertEquals(expected.blue, result.blue, 0.01f)
assertEquals(expected.alpha, result.alpha, 0.01f)
}
}
}
@Test
fun testDrawRectBrushColorIntrinsicAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a brush parameter
// fills the canvas with red color
drawRect(brush =
SolidColor(
Color.Red.copy(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
)
)
)
}
val expected = Color(
alpha = 0.5f,
red = Color.Red.red,
green = Color.Red.green,
blue = Color.Red.blue
).compositeOver(Color.White)
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val result = pixelMap[i, j]
assertEquals(expected.red, result.red, 0.01f)
assertEquals(expected.green, result.green, 0.01f)
assertEquals(expected.blue, result.blue, 0.01f)
assertEquals(expected.alpha, result.alpha, 0.01f)
}
}
}
@Test
fun testDrawTranslatedRect() {
val img = createTestDstImage()
val insetLeft = 10.0f
val insetTop = 12.0f
TestDrawScope().drawInto(Canvas(img), dstSize) {
translate(insetLeft, insetTop) {
drawRect(color = Color.Red)
}
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val expectedColor =
if (i >= insetLeft && j >= insetTop) {
Color.Red
} else {
Color.White
}
assertEquals("Coordinate: " + i + ", " + j, expectedColor, pixelMap[i, j])
}
}
}
@Test
fun testDrawInsetRect() {
val img = createTestDstImage()
val insetLeft = 10.0f
val insetTop = 12.0f
val insetRight = 11.0f
val insetBottom = 13.0f
TestDrawScope().drawInto(Canvas(img), dstSize) {
inset(insetLeft, insetTop, insetRight, insetBottom) {
drawRect(color = Color.Red)
}
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
val expectedColor =
if (i >= insetLeft && i < pixelMap.width - insetRight &&
j >= insetTop && j < pixelMap.height - insetBottom) {
Color.Red
} else {
Color.White
}
assertEquals("Coordinate: " + i + ", " + j, expectedColor, pixelMap[i, j])
}
}
}
@Test
fun testInsetRestoredAfterScopedInsetDraw() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the overload that consumes a color parameter
// fills the canvas with red color
val left = 10.0f
val top = 30.0f
val right = 20.0f
val bottom = 12.0f
inset(left, top, right, bottom) {
drawRect(color = Color.Red)
assertEquals(dstSize.width - (left + right), size.width)
assertEquals(dstSize.height - (top + bottom), size.height)
}
assertEquals(dstSize.width, size.width)
assertEquals(dstSize.height, size.height)
}
}
@Test
fun testFillOverwritesOldAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the alpha parameter used in the first draw call is overridden
// in the subsequent call that does not specify an alpha value
drawRect(color = Color.Blue, alpha = 0.5f)
drawRect(color = Color.Red)
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
assertEquals(Color.Red, pixelMap[i, j])
}
}
}
@Test
fun testFillOverwritesOldPaintBrushAlpha() {
val img = createTestDstImage()
TestDrawScope().drawInto(Canvas(img), dstSize) {
// Verify that the alpha parameter used in the first draw call is overridden
// in the subsequent call that does not specify an alpha value that goes through
// a different code path for configuration of the underlying paint
drawRect(color = Color.Blue, alpha = 0.5f)
drawRect(brush = SolidColor(Color.Red))
}
val pixelMap = img.toPixelMap()
for (i in 0 until pixelMap.width) {
for (j in 0 until pixelMap.height) {
assertEquals(Color.Red, pixelMap[i, j])
}
}
}
@Test
fun testScaleTopLeftPivot() {
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
canvasScope.drawInto(Canvas(imageAsset), size) {
drawRect(color = Color.Red)
scale(0.5f, 0.5f, pivotX = 0.0f, pivotY = 0.0f) {
drawRect(color = Color.Blue)
}
}
val pixelMap = imageAsset.toPixelMap()
assertEquals(Color.Blue, pixelMap[0, 0])
assertEquals(Color.Blue, pixelMap[99, 0])
assertEquals(Color.Blue, pixelMap[0, 99])
assertEquals(Color.Blue, pixelMap[99, 99])
assertEquals(Color.Red, pixelMap[0, 100])
assertEquals(Color.Red, pixelMap[100, 0])
assertEquals(Color.Red, pixelMap[100, 100])
assertEquals(Color.Red, pixelMap[100, 99])
assertEquals(Color.Red, pixelMap[99, 100])
}
@Test
fun testScaleCenterDefaultPivot() {
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
canvasScope.drawInto(Canvas(imageAsset), size) {
drawRect(color = Color.Red)
scale(0.5f, 0.5f) {
drawRect(color = Color.Blue)
}
}
val pixelMap = imageAsset.toPixelMap()
val left = width / 2 - 50
val top = height / 2 - 50
val right = width / 2 + 50 - 1
val bottom = height / 2 + 50 - 1
assertEquals(Color.Blue, pixelMap[left, top])
assertEquals(Color.Blue, pixelMap[right, top])
assertEquals(Color.Blue, pixelMap[left, bottom])
assertEquals(Color.Blue, pixelMap[right, bottom])
assertEquals(Color.Red, pixelMap[left - 1, top - 1])
assertEquals(Color.Red, pixelMap[left - 1, top])
assertEquals(Color.Red, pixelMap[left, top - 1])
assertEquals(Color.Red, pixelMap[right + 1, top - 1])
assertEquals(Color.Red, pixelMap[right + 1, top])
assertEquals(Color.Red, pixelMap[right, top - 1])
assertEquals(Color.Red, pixelMap[left - 1, bottom + 1])
assertEquals(Color.Red, pixelMap[left - 1, bottom])
assertEquals(Color.Red, pixelMap[left, bottom + 1])
assertEquals(Color.Red, pixelMap[right + 1, bottom + 1])
assertEquals(Color.Red, pixelMap[right + 1, bottom])
assertEquals(Color.Red, pixelMap[right, bottom + 1])
}
@Test
fun testInsetNegativeWidthThrows() {
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
try {
canvasScope.drawInto(Canvas(imageAsset), size) {
inset(100.0f, 0.0f, 101.0f, 0.0f) {
drawRect(color = Color.Red)
}
}
fail("Width must be greater than or equal to zero after applying inset")
} catch (e: IllegalArgumentException) {
// no-op
}
}
@Test
fun testInsetNegativeHeightThrows() {
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
try {
canvasScope.drawInto(Canvas(imageAsset), size) {
inset(0.0f, 100.0f, 0.0f, 101.0f) {
drawRect(color = Color.Red)
}
}
fail("Height must be greater than or equal to zero after applying inset")
} catch (e: IllegalArgumentException) {
// no-op
}
}
@Test
fun testInsetZeroHeight() {
// Verify that the inset call does not crash even though we are adding an inset
// to the right and bottom such that the drawing size after the inset is zero in both
// dimensions.
// This is useful for animations that slowly reveal the drawing bounds of the area.
// Alternatively this could happen if the a sibling UI element
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
try {
canvasScope.drawInto(Canvas(imageAsset), size) {
inset(0.0f, 100.0f, 0.0f, 100.0f) {
drawRect(color = Color.Red)
}
}
} catch (e: IllegalArgumentException) {
fail("Zero height after applying inset is allowed")
}
}
@Test
fun testInsetZeroWidth() {
// Verify that the inset call does not crash even though we are adding an inset
// to the right and bottom such that the drawing size after the inset is zero in both
// dimensions.
// This is useful for animations that slowly reveal the drawing bounds of the area.
// Alternatively this could happen if the a sibling UI element
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
try {
canvasScope.drawInto(Canvas(imageAsset), size) {
inset(100.0f, 0.0f, 100.0f, 0.0f) {
drawRect(color = Color.Red)
}
}
} catch (e: IllegalArgumentException) {
fail("Zero width after applying inset is allowed")
}
}
@Test
fun testScaleBottomRightPivot() {
val canvasScope = TestDrawScope()
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
canvasScope.drawInto(Canvas(imageAsset), size) {
drawRect(color = Color.Red)
scale(0.5f, 0.5f, width.toFloat(), height.toFloat()) {
drawRect(color = Color.Blue)
}
}
val pixelMap = imageAsset.toPixelMap()
val left = width - 100
val top = height - 100
val right = width - 1
val bottom = height - 1
assertEquals(Color.Blue, pixelMap[left, top])
assertEquals(Color.Blue, pixelMap[right, top])
assertEquals(Color.Blue, pixelMap[left, bottom])
assertEquals(Color.Blue, pixelMap[left, right])
assertEquals(Color.Red, pixelMap[left, top - 1])
assertEquals(Color.Red, pixelMap[left - 1, top])
assertEquals(Color.Red, pixelMap[left - 1, top - 1])
assertEquals(Color.Red, pixelMap[right, top - 1])
assertEquals(Color.Red, pixelMap[left - 1, bottom])
}
@Test
fun testRotationCenterPivot() {
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
TestDrawScope().drawInto(Canvas(imageAsset), size) {
drawRect(color = Color.Red)
rotate(180.0f) {
drawRect(
topLeft = Offset(100.0f, 100.0f),
size = Size(100.0f, 100.0f),
color = Color.Blue
)
}
}
val pixelMap = imageAsset.toPixelMap()
assertEquals(Color.Blue, pixelMap[0, 0])
assertEquals(Color.Blue, pixelMap[99, 0])
assertEquals(Color.Blue, pixelMap[0, 99])
assertEquals(Color.Blue, pixelMap[99, 99])
assertEquals(Color.Red, pixelMap[0, 100])
assertEquals(Color.Red, pixelMap[100, 0])
assertEquals(Color.Red, pixelMap[100, 100])
assertEquals(Color.Red, pixelMap[100, 99])
assertEquals(Color.Red, pixelMap[99, 100])
}
@Test
fun testRotationTopLeftPivot() {
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset = ImageAsset(width, height)
TestDrawScope().drawInto(Canvas(imageAsset), size) {
drawRect(color = Color.Red)
rotate(-45.0f, 0.0f, 0.0f) {
drawRect(
size = Size(100.0f, 100.0f),
color = Color.Blue
)
}
}
val pixelMap = imageAsset.toPixelMap()
assertEquals(Color.Blue, pixelMap[2, 0])
assertEquals(Color.Blue, pixelMap[50, 49])
assertEquals(Color.Blue, pixelMap[70, 0])
assertEquals(Color.Blue, pixelMap[70, 68])
assertEquals(Color.Red, pixelMap[50, 51])
assertEquals(Color.Red, pixelMap[75, 76])
}
@Test
fun testBatchTransformEquivalent() {
val width = 200
val height = 200
val size = Size(width.toFloat(), height.toFloat())
val imageAsset1 = ImageAsset(width, height)
TestDrawScope().drawInto(Canvas(imageAsset1), size) {
drawRect(color = Color.Red)
inset(20.0f, 12.0f, 10.0f, 8.0f) {
scale(2.0f, 0.5f) {
rotate(-45.0f, 0.0f, 0.0f) {
translate(7.0f, 9.0f) {
drawRect(
size = Size(100.0f, 100.0f),
color = Color.Blue
)
}
}
}
}
}
val imageAsset2 = ImageAsset(width, height)
val saveCountCanvas = SaveCountCanvas(Canvas(imageAsset2))
TestDrawScope().drawInto(saveCountCanvas, size) {
drawRect(color = Color.Red)
withTransform({
inset(20.0f, 12.0f, 10.0f, 8.0f)
scale(2.0f, 0.5f)
rotate(-45.0f, 0.0f, 0.0f)
translate(7.0f, 9.0f)
}) {
// 2 saves at this point, the initial draw call does a save
// as well as the withTransform call
assertEquals(2, saveCountCanvas.saveCount)
drawRect(
size = Size(100.0f, 100.0f),
color = Color.Blue
)
}
// Restore to the save count of the initial CanvasScope.draw call
assertEquals(1, saveCountCanvas.saveCount)
}
val pixelMap1 = imageAsset1.toPixelMap()
val pixelMap2 = imageAsset2.toPixelMap()
assertEquals(pixelMap1.width, pixelMap2.width)
assertEquals(pixelMap1.height, pixelMap2.height)
assertEquals(pixelMap1.stride, pixelMap2.stride)
assertEquals(pixelMap1.bufferOffset, pixelMap2.bufferOffset)
for (x in 0 until pixelMap1.width) {
for (y in 0 until pixelMap1.height) {
assertEquals("coordinate: " + x + ", " + y + " expected: " +
pixelMap1[x, y] + " actual: " + pixelMap2[x, y],
pixelMap1[x, y],
pixelMap2[x, y]
)
}
}
}
@Test
fun testDrawLineStrokeParametersAreApplied() {
val width = 200
val height = 200
val start = Offset.Zero
val end = Offset(width.toFloat(), height.toFloat())
val strokeWidth = 10.0f
// Test that colors are rendered with the correct stroke parameters
testDrawScopeAndCanvasAreEquivalent(
width,
height,
{
drawLine(
Color.Cyan,
start,
end,
strokeWidth,
StrokeCap.round
)
},
{ canvas ->
canvas.drawLine(start, end, Paint().apply {
this.color = Color.Cyan
this.strokeWidth = strokeWidth
this.strokeCap = StrokeCap.round
})
}
)
// ... now test that Brush parameters are also rendered with the correct stroke parameters
testDrawScopeAndCanvasAreEquivalent(
width,
height,
{
drawLine(
SolidColor(Color.Cyan),
start,
end,
strokeWidth,
StrokeCap.round
)
},
{ canvas ->
canvas.drawLine(start, end, Paint().apply {
this.color = Color.Cyan
this.strokeWidth = strokeWidth
this.strokeCap = StrokeCap.round
})
}
)
}
@Test
fun testDrawPointStrokeParametersAreApplied() {
val width = 200
val height = 200
val points = listOf(
Offset.Zero,
Offset(10f, 10f),
Offset(25f, 25f),
Offset(40f, 40f),
Offset(50f, 50f),
Offset(75f, 75f),
Offset(150f, 150f)
)
// Test first that colors are rendered with the correct stroke parameters
testDrawScopeAndCanvasAreEquivalent(
width,
height,
{
drawPoints(
points,
PointMode.points,
Color.Magenta,
strokeWidth = 15.0f,
cap = StrokeCap.butt
)
},
{ canvas ->
canvas.drawPoints(
PointMode.points,
points,
Paint().apply {
this.color = Color.Magenta
this.strokeWidth = 15.0f
this.strokeCap = StrokeCap.butt
}
)
}
)
// ... now verify that Brush parameters are also rendered with the correct stroke parameters
testDrawScopeAndCanvasAreEquivalent(
width,
height,
{
drawPoints(
points,
PointMode.points,
SolidColor(Color.Magenta),
strokeWidth = 15.0f,
cap = StrokeCap.butt
)
},
{ canvas ->
canvas.drawPoints(
PointMode.points,
points,
Paint().apply {
this.color = Color.Magenta
this.strokeWidth = 15.0f
this.strokeCap = StrokeCap.butt
}
)
}
)
}
/**
* Helper method used to confirm both DrawScope rendered content and Canvas drawn
* content are identical
*/
private fun testDrawScopeAndCanvasAreEquivalent(
width: Int,
height: Int,
drawScopeBlock: DrawScope.() -> Unit,
canvasBlock: (Canvas) -> Unit
) {
val size = Size(width.toFloat(), height.toFloat())
val imageAsset1 = ImageAsset(width, height)
TestDrawScope().drawInto(Canvas(imageAsset1), size) {
drawScopeBlock()
}
val imageAsset2 = ImageAsset(width, height)
canvasBlock(Canvas(imageAsset2))
val pixelMap1 = imageAsset1.toPixelMap()
val pixelMap2 = imageAsset2.toPixelMap()
assertEquals(pixelMap1.width, pixelMap2.width)
assertEquals(pixelMap1.height, pixelMap2.height)
assertEquals(pixelMap1.stride, pixelMap2.stride)
assertEquals(pixelMap1.bufferOffset, pixelMap2.bufferOffset)
for (x in 0 until pixelMap1.width) {
for (y in 0 until pixelMap1.height) {
assertEquals("coordinate: " + x + ", " + y + " expected: " +
pixelMap1[x, y] + " actual: " + pixelMap2[x, y],
pixelMap1[x, y],
pixelMap2[x, y]
)
}
}
}
class SaveCountCanvas(val canvas: Canvas) : Canvas by canvas {
var saveCount: Int = 0
override fun save() {
saveCount++
}
override fun restore() {
saveCount--
}
}
}