diff --git a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt index 0e411ce719..fa8d7c8824 100644 --- a/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt +++ b/components/resources/library/src/androidMain/kotlin/org/jetbrains/compose/resources/ImageResources.android.kt @@ -6,8 +6,17 @@ import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density -internal actual fun ByteArray.toImageBitmap(): ImageBitmap = - BitmapFactory.decodeByteArray(this, 0, size).asImageBitmap() +internal actual fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap { + val options = BitmapFactory.Options().apply { + //https://youtrack.jetbrains.com/issue/CMP-5657 + //android only downscales drawables. If there is only low dpi resource then use it as is (not upscale) + if (resourceDensity > targetDensity) { + inDensity = resourceDensity + inTargetDensity = targetDensity + } + } + return BitmapFactory.decodeByteArray(this, 0, size, options).asImageBitmap() +} internal actual class SvgElement diff --git a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt index 30892cf43f..9a2de24183 100644 --- a/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt +++ b/components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/ImageResources.kt @@ -56,10 +56,17 @@ private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) } @Composable fun imageResource(resource: DrawableResource): ImageBitmap { val resourceReader = LocalResourceReader.currentOrPreview - val imageBitmap by rememberResourceState(resource, resourceReader, { emptyImageBitmap }) { env -> - val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { - ImageCache.Bitmap(it.toImageBitmap()) + val resourceEnvironment = rememberResourceEnvironment() + val imageBitmap by rememberResourceState( + resource, resourceReader, resourceEnvironment, { emptyImageBitmap } + ) { env -> + val item = resource.getResourceItemByEnvironment(env) + val resourceDensityQualifier = item.qualifiers.firstOrNull { it is DensityQualifier } as? DensityQualifier + val resourceDensity = resourceDensityQualifier?.dpi ?: DensityQualifier.MDPI.dpi + val screenDensity = resourceEnvironment.density.dpi + val path = item.path + val cached = loadImage(path, "$path-${screenDensity}dpi", resourceReader) { + ImageCache.Bitmap(it.toImageBitmap(resourceDensity, screenDensity)) } as ImageCache.Bitmap cached.bitmap } @@ -82,7 +89,7 @@ fun vectorResource(resource: DrawableResource): ImageVector { val density = LocalDensity.current val imageVector by rememberResourceState(resource, resourceReader, density, { emptyImageVector }) { env -> val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { + val cached = loadImage(path, path, resourceReader) { ImageCache.Vector(it.toXmlElement().toImageVector(density)) } as ImageCache.Vector cached.vector @@ -102,7 +109,7 @@ private fun svgPainter(resource: DrawableResource): Painter { val density = LocalDensity.current val svgPainter by rememberResourceState(resource, resourceReader, density, { emptySvgPainter }) { env -> val path = resource.getResourceItemByEnvironment(env).path - val cached = loadImage(path, resourceReader) { + val cached = loadImage(path, path, resourceReader) { ImageCache.Svg(it.toSvgElement().toSvgPainter(density)) } as ImageCache.Svg cached.painter @@ -126,7 +133,7 @@ suspend fun getDrawableResourceBytes( return DefaultResourceReader.read(resourceItem.path) } -internal expect fun ByteArray.toImageBitmap(): ImageBitmap +internal expect fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap internal expect fun ByteArray.toXmlElement(): Element internal expect fun ByteArray.toSvgElement(): SvgElement @@ -145,6 +152,7 @@ internal fun dropImageCache() { private suspend fun loadImage( path: String, + cacheKey: String, resourceReader: ResourceReader, decode: (ByteArray) -> ImageCache -): ImageCache = imageCache.getOrLoad(path) { decode(resourceReader.read(path)) } +): ImageCache = imageCache.getOrLoad(cacheKey) { decode(resourceReader.read(path)) } diff --git a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt index a046534554..3ac9eefc10 100644 --- a/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt +++ b/components/resources/library/src/skikoMain/kotlin/org/jetbrains/compose/resources/ImageResources.skiko.kt @@ -6,10 +6,37 @@ import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.unit.Density import org.jetbrains.skia.Data import org.jetbrains.skia.Image +import org.jetbrains.skia.Paint +import org.jetbrains.skia.Rect +import org.jetbrains.skia.SamplingMode +import org.jetbrains.skia.Surface import org.jetbrains.skia.svg.SVGDOM -internal actual fun ByteArray.toImageBitmap(): ImageBitmap = - Image.makeFromEncoded(this).toComposeImageBitmap() +internal actual fun ByteArray.toImageBitmap(resourceDensity: Int, targetDensity: Int): ImageBitmap { + val image = Image.makeFromEncoded(this) + + val targetImage: Image + //https://youtrack.jetbrains.com/issue/CMP-5657 + //android only downscales drawables. If there is only low dpi resource then use it as is (not upscale) + //we need a consistent behavior on all platforms + if (resourceDensity > targetDensity) { + val scale = targetDensity.toFloat() / resourceDensity.toFloat() + val targetH = image.height * scale + val targetW = image.width * scale + val srcRect = Rect.Companion.makeWH(image.width.toFloat(), image.height.toFloat()) + val dstRect = Rect.Companion.makeWH(targetW, targetH) + + targetImage = Surface.makeRasterN32Premul(targetW.toInt(), targetH.toInt()).run { + val paint = Paint().apply { isAntiAlias = true } + canvas.drawImageRect(image, srcRect, dstRect, SamplingMode.LINEAR, paint, true) + makeImageSnapshot() + } + } else { + targetImage = image + } + + return targetImage.toComposeImageBitmap() +} internal actual class SvgElement(val svgdom: SVGDOM)