Skip to content

Commit

Permalink
Add support and a test for image transparency (#2255)
Browse files Browse the repository at this point in the history
  • Loading branch information
yschimke authored Jun 7, 2024
1 parent a907949 commit 0badef1
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 14 deletions.
3 changes: 3 additions & 0 deletions tiles/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ plugins {
id("org.jetbrains.dokka")
id("me.tylerbwong.gradle.metalava")
kotlin("android")
alias(libs.plugins.roborazzi)
}

android {
Expand All @@ -38,6 +39,7 @@ android {
}

buildFeatures {
compose = true
buildConfig = false
}

Expand Down Expand Up @@ -109,6 +111,7 @@ dependencies {
implementation(libs.androidx.complications.datasource.ktx)
api(libs.wearcompose.material)

testImplementation(projects.roboscreenshots)
testImplementation(libs.androidx.wear.tiles.testing)
testImplementation(libs.androidx.test.espressocore)
testImplementation(libs.junit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import androidx.wear.protolayout.ResourceBuilders
import androidx.wear.protolayout.ResourceBuilders.IMAGE_FORMAT_ARGB_8888
import androidx.wear.protolayout.ResourceBuilders.IMAGE_FORMAT_RGB_565
import androidx.wear.protolayout.ResourceBuilders.IMAGE_FORMAT_UNDEFINED
import androidx.wear.protolayout.ResourceBuilders.ImageResource
import coil.ImageLoader
import coil.request.ImageRequest
Expand All @@ -40,7 +43,7 @@ public suspend fun ImageLoader.loadImage(
val request = ImageRequest.Builder(context)
.data(data)
.apply(configurer)
.allowRgb565(true)
.allowRgb565(false)
.allowHardware(false)
.build()
val response = execute(request)
Expand All @@ -63,26 +66,26 @@ public suspend fun ImageLoader.loadImageResource(
/**
* Convert a bitmap to a ImageResource.
*
* Ensures it uses RGB_565 encoding, then generates an ImageResource
* with the correct width and height.
* Format will be one of IMAGE_FORMAT_ARGB_8888, IMAGE_FORMAT_RGB_565 or IMAGE_FORMAT_UNDEFINED,
* based on the bitmap.
*/
public fun Bitmap.toImageResource(): ImageResource {
val rgb565Bitmap = if (config == Bitmap.Config.RGB_565) {
this
} else {
copy(Bitmap.Config.RGB_565, false)
val format = when (this.config) {
Bitmap.Config.ARGB_8888 -> IMAGE_FORMAT_ARGB_8888
Bitmap.Config.RGB_565 -> IMAGE_FORMAT_RGB_565
else -> IMAGE_FORMAT_UNDEFINED
}

val byteBuffer = ByteBuffer.allocate(rgb565Bitmap.byteCount)
rgb565Bitmap.copyPixelsToBuffer(byteBuffer)
val byteBuffer = ByteBuffer.allocate(byteCount)
copyPixelsToBuffer(byteBuffer)
val bytes: ByteArray = byteBuffer.array()

return ImageResource.Builder().setInlineResource(
ResourceBuilders.InlineImageResource.Builder()
.setData(bytes)
.setWidthPx(rgb565Bitmap.width)
.setHeightPx(rgb565Bitmap.height)
.setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
.setWidthPx(width)
.setHeightPx(height)
.setFormat(format)
.build(),
)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ImagesTest {
val imageResource = imageLoader.loadImageResource(context, android.R.drawable.ic_delete)

val inlineResource = imageResource!!.inlineResource!!
assertThat(inlineResource.format).isEqualTo(ResourceBuilders.IMAGE_FORMAT_RGB_565)
assertThat(inlineResource.format).isEqualTo(ResourceBuilders.IMAGE_FORMAT_ARGB_8888)
assertThat(inlineResource.widthPx).isEqualTo(64)
assertThat(inlineResource.heightPx).isEqualTo(64)
}
Expand All @@ -78,7 +78,7 @@ class ImagesTest {
}

val inlineResource = imageResource!!.inlineResource!!
assertThat(inlineResource.format).isEqualTo(ResourceBuilders.IMAGE_FORMAT_RGB_565)
assertThat(inlineResource.format).isEqualTo(ResourceBuilders.IMAGE_FORMAT_ARGB_8888)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2024 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
*
* https://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.
*/

@file:Suppress("UnstableApiUsage", "DEPRECATION")

package com.google.android.horologist.tiles

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.wear.protolayout.ColorBuilders.argb
import androidx.wear.protolayout.DeviceParametersBuilders.DeviceParameters
import androidx.wear.protolayout.DimensionBuilders.dp
import androidx.wear.protolayout.DimensionBuilders.expand
import androidx.wear.protolayout.LayoutElementBuilders
import androidx.wear.protolayout.LayoutElementBuilders.Box
import androidx.wear.protolayout.LayoutElementBuilders.CONTENT_SCALE_MODE_FIT
import androidx.wear.protolayout.LayoutElementBuilders.Image
import androidx.wear.protolayout.ModifiersBuilders.Background
import androidx.wear.protolayout.ModifiersBuilders.Modifiers
import androidx.wear.protolayout.ResourceBuilders
import androidx.wear.protolayout.material.Text
import androidx.wear.protolayout.material.Typography
import androidx.wear.protolayout.material.layouts.PrimaryLayout
import com.google.android.horologist.compose.tools.TileLayoutPreview
import com.google.android.horologist.screenshots.rng.WearDevice
import com.google.android.horologist.screenshots.rng.WearScreenshotTest
import com.google.android.horologist.tiles.images.toImageResource
import com.google.android.horologist.tiles.render.SingleTileLayoutRenderer
import org.junit.Test

class TileScreenshotTest : WearScreenshotTest() {

override fun testName(suffix: String): String =
"src/test/screenshots/" +
"${javaClass.simpleName}_" +
"${testInfo.methodName}_" +
"${super.device?.id ?: WearDevice.GenericLargeRound.id}" +
"$suffix.png"

@Composable
override fun TestScaffold(content: @Composable () -> Unit) {
content()
}

@Test
fun imageArgb8888() {
// https://en.wikipedia.org/wiki/File:PNG_transparency_demonstration_1.png
val bitmap =
BitmapFactory.decodeFile("src/test/resources/PNG_transparency_demonstration_1.png")

runTest {
val context = LocalContext.current

TileLayoutPreview(
state = Unit,
resourceState = Unit,
renderer = TestImageTileRenderer(
context = context,
bitmap = bitmap,
),
)
}
}

@Test
fun imageRgb565() {
// https://en.wikipedia.org/wiki/File:PNG_transparency_demonstration_1.png
val bitmap =
BitmapFactory.decodeFile("src/test/resources/PNG_transparency_demonstration_1.png")
.copy(Bitmap.Config.RGB_565, false)

runTest {
val context = LocalContext.current

TileLayoutPreview(
state = Unit,
resourceState = Unit,
renderer = TestImageTileRenderer(
context = context,
bitmap = bitmap,
),
)
}
}

class TestImageTileRenderer(
context: Context,
val bitmap: Bitmap,
) : SingleTileLayoutRenderer<Unit, Unit>(context) {
override fun renderTile(
state: Unit,
deviceParameters: DeviceParameters,
): LayoutElementBuilders.LayoutElement {
return Box.Builder()
.setHeight(expand())
.setWidth(expand())
.setModifiers(
Modifiers.Builder()
.setBackground(
Background.Builder()
.setColor(argb(Color.DKGRAY))
.build(),
)
.build(),
)
.addContent(
PrimaryLayout.Builder(deviceParameters)
.setContent(
Image.Builder()
.setContentScaleMode(CONTENT_SCALE_MODE_FIT)
.setResourceId("dice")
.setWidth(expand())
.setHeight(dp(130f))
.build(),
)
.setPrimaryLabelTextContent(
Text.Builder(
context,
when (bitmap.config) {
Bitmap.Config.ARGB_8888 -> "ARGB_8888"
Bitmap.Config.RGB_565 -> "RGB_565"
else -> "UNDEFINED"
},
)
.setColor(argb(Color.WHITE))
.setTypography(Typography.TYPOGRAPHY_BODY2)
.build(),
).build(),
)
.build()
}

override fun ResourceBuilders.Resources.Builder.produceRequestedResources(
resourceState: Unit,
deviceParameters: DeviceParameters,
resourceIds: List<String>,
) {
addIdToImageMapping("dice", bitmap.toImageResource())
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0badef1

Please sign in to comment.