[go: nahoru, domu]

blob: 8dafd6e5f6610da2b7fe8824f88dc249fa1a777a [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.compose.foundation
import android.os.Build
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.preferredHeightIn
import androidx.compose.foundation.layout.preferredSize
import androidx.compose.foundation.layout.preferredSizeIn
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.test.R
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Canvas
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
import androidx.compose.ui.graphics.painter.ImagePainter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.DensityAmbient
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.loadVectorResource
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.testutils.assertPixels
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@MediumTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
@RunWith(AndroidJUnit4::class)
class ImageTest {
val contentTag = "ImageTest"
val imageWidth = 100
val imageHeight = 100
val containerSize = imageWidth
val bgColor = Color.Blue
val pathColor = Color.Red
@get:Rule
val rule = createComposeRule()
private fun createImageBitmap(): ImageBitmap {
val image = ImageBitmap(imageWidth, imageHeight)
val path = Path().apply {
lineTo(imageWidth.toFloat(), imageHeight.toFloat())
lineTo(0.0f, imageHeight.toFloat())
close()
}
val paint = Paint()
Canvas(image).apply {
paint.color = bgColor
drawRect(
Rect(Offset.Zero, Size(imageWidth.toFloat(), imageHeight.toFloat())),
paint
)
paint.color = pathColor
drawPath(path, paint)
}
return image
}
@Test
fun testImage() {
rule.setContent {
val size = (containerSize / DensityAmbient.current.density).dp
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
Image(modifier = Modifier.testTag(contentTag), bitmap = createImageBitmap())
}
}
val bgColorArgb = bgColor.toArgb()
val pathArgb = pathColor.toArgb()
rule.onNodeWithTag(contentTag).captureToImage().asAndroidBitmap().apply {
val imageStartX = width / 2 - imageWidth / 2
val imageStartY = height / 2 - imageHeight / 2
Assert.assertEquals(bgColorArgb, getPixel(imageStartX + 2, imageStartY))
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + 1))
Assert.assertEquals(
pathArgb,
getPixel(
imageStartX + (imageWidth / 2) - 1,
imageStartY + (imageHeight / 2) + 1
)
)
Assert.assertEquals(
bgColorArgb,
getPixel(
imageStartX + (imageWidth / 2) - 2,
imageStartY + (imageHeight / 2) - 5
)
)
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + imageHeight - 1))
}
}
@Test
fun testImageSubsection() {
val subsectionWidth = imageWidth / 2
val subsectionHeight = imageHeight / 2
rule.setContent {
val size = (containerSize / DensityAmbient.current.density).dp
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
Image(
ImagePainter(
createImageBitmap(),
IntOffset(
imageWidth / 2 - subsectionWidth / 2,
imageHeight / 2 - subsectionHeight / 2
),
IntSize(subsectionWidth, subsectionHeight)
)
)
}
}
val boxBgArgb = Color.White.toArgb()
val bgColorArgb = bgColor.toArgb()
val pathArgb = pathColor.toArgb()
rule.onRoot().captureToImage().asAndroidBitmap().apply {
val imageStartX = width / 2 - subsectionWidth / 2
val imageStartY = height / 2 - subsectionHeight / 2
Assert.assertEquals(bgColorArgb, getPixel(imageStartX + 2, imageStartY))
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + 1))
Assert.assertEquals(
pathArgb,
getPixel(
imageStartX + (subsectionWidth / 2) - 1,
imageStartY + (subsectionHeight / 2) + 1
)
)
Assert.assertEquals(
bgColorArgb,
getPixel(
imageStartX + (subsectionWidth / 2) - 2,
imageStartY + (subsectionHeight / 2) - 5
)
)
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + subsectionHeight - 1))
// Verify top left region outside the subsection has a white background
Assert.assertEquals(boxBgArgb, getPixel(imageStartX - 1, imageStartY - 1))
Assert.assertEquals(boxBgArgb, getPixel(imageStartX - 1, imageStartY))
Assert.assertEquals(boxBgArgb, getPixel(imageStartX, imageStartY - 1))
// Verify top right region outside the subsection has a white background
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth - 1, imageStartY - 1)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth, imageStartY - 1)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth, imageStartY)
)
// Verify bottom left region outside the subsection has a white background
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX - 1, imageStartY + subsectionHeight - 1)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX - 1, imageStartY + subsectionHeight)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX, imageStartY + subsectionHeight)
)
// Verify bottom right region outside the subsection has a white background
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth - 1, imageStartY + subsectionHeight)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth, imageStartY + subsectionHeight)
)
Assert.assertEquals(
boxBgArgb,
getPixel(imageStartX + subsectionWidth, imageStartY + subsectionHeight - 1)
)
}
}
@Test
fun testImageFixedSizeIsStretched() {
val imageComposableWidth = imageWidth * 2
val imageComposableHeight = imageHeight * 2
rule.setContent {
val density = DensityAmbient.current.density
val size = (containerSize * 2 / density).dp
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
// The resultant Image composable should be twice the size of the underlying
// ImageBitmap that is to be drawn and will stretch the content to fit
// the bounds
Image(
bitmap = createImageBitmap(),
modifier = Modifier
.testTag(contentTag)
.preferredSize(
(imageComposableWidth / density).dp,
(imageComposableHeight / density).dp
)
)
}
}
val bgColorArgb = bgColor.toArgb()
val pathArgb = pathColor.toArgb()
rule.onNodeWithTag(contentTag).captureToImage().asAndroidBitmap().apply {
val imageStartX = width / 2 - imageComposableWidth / 2
val imageStartY = height / 2 - imageComposableHeight / 2
Assert.assertEquals(bgColorArgb, getPixel(imageStartX + 5, imageStartY))
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + 5))
Assert.assertEquals(
pathArgb,
getPixel(
imageStartX + (imageComposableWidth / 2) - 5,
imageStartY + (imageComposableHeight / 2) + 5
)
)
Assert.assertEquals(
bgColorArgb,
getPixel(
imageStartX + (imageComposableWidth / 2),
imageStartY + (imageComposableHeight / 2) - 10
)
)
Assert.assertEquals(
pathArgb,
getPixel(
imageStartX,
imageStartY +
imageComposableHeight - 1
)
)
}
}
@Test
fun testImageScalesNonuniformly() {
val imageComposableWidth = imageWidth * 3
val imageComposableHeight = imageHeight * 7
rule.setContent {
val density = DensityAmbient.current
val size = (containerSize * 2 / density.density).dp
val ImageBitmap = ImageBitmap(imageWidth, imageHeight)
CanvasDrawScope().draw(
density,
LayoutDirection.Ltr,
Canvas(ImageBitmap),
Size(imageWidth.toFloat(), imageHeight.toFloat())
) {
drawRect(color = Color.Blue)
}
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
Image(
bitmap = ImageBitmap,
modifier = Modifier
.testTag(contentTag)
.preferredSize(
(imageComposableWidth / density.density).dp,
(imageComposableHeight / density.density).dp
),
// Scale the image non-uniformly within the bounds of the composable
contentScale = ContentScale.FillBounds,
alignment = Alignment.BottomEnd
)
}
}
rule.onNodeWithTag(contentTag).captureToImage().assertPixels { Color.Blue }
}
@Test
fun testImageFixedSizeAlignedBottomEnd() {
val imageComposableWidth = imageWidth * 2
val imageComposableHeight = imageHeight * 2
rule.setContent {
val density = DensityAmbient.current.density
val size = (containerSize * 2 / density).dp
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
// The resultant Image composable should be twice the size of the underlying
// ImageBitmap that is to be drawn in the bottom end section of the composable
Image(
bitmap = createImageBitmap(),
modifier = Modifier
.testTag(contentTag)
.preferredSize(
(imageComposableWidth / density).dp,
(imageComposableHeight / density).dp
),
// Intentionally do not scale up the contents of the ImageBitmap
contentScale = ContentScale.Inside,
alignment = Alignment.BottomEnd
)
}
}
val bgColorArgb = bgColor.toArgb()
val pathArgb = pathColor.toArgb()
rule.onNodeWithTag(contentTag).captureToImage().asAndroidBitmap().apply {
val composableEndX = width / 2 + imageComposableWidth / 2
val composableEndY = height / 2 + imageComposableHeight / 2
val imageStartX = composableEndX - imageWidth
val imageStartY = composableEndY - imageHeight
Assert.assertEquals(bgColorArgb, getPixel(imageStartX + 2, imageStartY))
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + 1))
Assert.assertEquals(
pathArgb,
getPixel(
imageStartX + (imageWidth / 2) - 1,
imageStartY + (imageHeight / 2) + 1
)
)
Assert.assertEquals(
bgColorArgb,
getPixel(
imageStartX + (imageWidth / 2) - 2,
imageStartY + (imageHeight / 2) - 5
)
)
Assert.assertEquals(pathArgb, getPixel(imageStartX, imageStartY + imageHeight - 1))
}
}
@Test
fun testVectorScaledCentered() {
val boxWidth = 240
val boxHeight = 240
// latch used to wait until vector resource is loaded asynchronously
val vectorLatch = CountDownLatch(1)
rule.setContent {
val density = DensityAmbient.current.density
val size = (boxWidth * 2 / density).dp
val minWidth = (boxWidth / density).dp
val minHeight = (boxHeight / density).dp
Box(
Modifier.preferredSize(size)
.background(color = Color.White)
.wrapContentSize(Alignment.Center)
) {
// This is an async call to parse the VectorDrawable xml asset into
// a ImageVector, update the latch once we receive this callback
// and draw the Image composable
loadVectorResource(R.drawable.ic_vector_asset_test).resource.resource?.let {
Image(
it,
modifier = Modifier.preferredSizeIn(
minWidth = minWidth,
minHeight = minHeight
)
.drawBehind { vectorLatch.countDown() }
)
}
}
}
Assert.assertTrue(vectorLatch.await(5, TimeUnit.SECONDS))
val imageColor = Color.Red.toArgb()
val containerBgColor = Color.White.toArgb()
rule.onRoot().captureToImage().asAndroidBitmap().apply {
val imageStartX = width / 2 - boxWidth / 2
val imageStartY = height / 2 - boxHeight / 2
Assert.assertEquals(containerBgColor, getPixel(imageStartX - 1, imageStartY - 1))
Assert.assertEquals(
containerBgColor,
getPixel(
imageStartX + boxWidth + 1,
imageStartY - 1
)
)
Assert.assertEquals(
containerBgColor,
getPixel(
imageStartX + boxWidth + 1,
imageStartY + boxHeight + 1
)
)
Assert.assertEquals(
containerBgColor,
getPixel(
imageStartX - 1,
imageStartY +
boxHeight + 1
)
)
Assert.assertEquals(imageColor, getPixel(imageStartX, imageStartY + 15))
Assert.assertEquals(
containerBgColor,
getPixel(
imageStartX + boxWidth - 2,
imageStartY - 1
)
)
Assert.assertEquals(
imageColor,
getPixel(
imageStartX + boxWidth - 10,
imageStartY + boxHeight - 2
)
)
Assert.assertEquals(
imageColor,
getPixel(
imageStartX,
imageStartY +
boxHeight - 2
)
)
}
}
@Test
fun testContentScaleCropRespectsMaxDimension() {
val testTag = "testTag"
rule.setContent {
val asset = with(ImageBitmap(100, 100)) {
with(Canvas(this)) {
val paint = Paint().apply { this.color = Color.Blue }
drawRect(0f, 0f, 100f, 100f, paint)
drawRect(
25f, 25f, 75f, 75f,
paint.apply { this.color = Color.Red }
)
}
this
}
val heightDp = asset.height / DensityAmbient.current.density
Image(
asset,
modifier = Modifier
.testTag(testTag)
.background(Color.Green)
.preferredHeightIn(max = (heightDp / 2f).dp),
contentScale = ContentScale.Crop
)
}
rule.onNodeWithTag(testTag).captureToImage().asAndroidBitmap().apply {
Assert.assertEquals(100, width)
Assert.assertEquals(50, height)
Assert.assertEquals(Color.Blue.toArgb(), getPixel(24, height / 2))
Assert.assertEquals(Color.Blue.toArgb(), getPixel(75, height / 2))
Assert.assertEquals(Color.Red.toArgb(), getPixel(50, 0))
Assert.assertEquals(Color.Red.toArgb(), getPixel(50, height - 1))
}
}
}