diff --git a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ExpressionContext.kt b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ExpressionContext.kt index 970b5326..90984cea 100644 --- a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ExpressionContext.kt +++ b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ExpressionContext.kt @@ -1,9 +1,9 @@ package dev.sargunv.maplibrecompose.expressions -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.painter.Painter +import dev.sargunv.maplibrecompose.expressions.ast.BitmapLiteral import dev.sargunv.maplibrecompose.expressions.ast.CompiledExpression import dev.sargunv.maplibrecompose.expressions.ast.Expression +import dev.sargunv.maplibrecompose.expressions.ast.PainterLiteral import dev.sargunv.maplibrecompose.expressions.value.FloatValue /** @@ -21,10 +21,10 @@ public interface ExpressionContext { public val spScale: Expression /** @return the resolved identifier for the [bitmap]. */ - public fun resolveBitmap(bitmap: ImageBitmap): String + public fun resolveBitmap(bitmap: BitmapLiteral): String /** @return the resolved identifier for the [painter]. */ - public fun resolvePainter(painter: Painter): String + public fun resolvePainter(painter: PainterLiteral): String /** A context where no complex types can be resolved. */ public object None : ExpressionContext { @@ -34,10 +34,10 @@ public interface ExpressionContext { override val spScale: Expression get() = error("TextUnit not allowed in this context") - override fun resolveBitmap(bitmap: ImageBitmap): String = + override fun resolveBitmap(bitmap: BitmapLiteral): String = error("Bitmap not allowed in this context") - override fun resolvePainter(painter: Painter): String = + override fun resolvePainter(painter: PainterLiteral): String = error("Painter not allowed in this context") } } diff --git a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/BitmapLiteral.kt b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/BitmapLiteral.kt index 4c9b6cec..941d07e1 100644 --- a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/BitmapLiteral.kt +++ b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/BitmapLiteral.kt @@ -8,14 +8,15 @@ import dev.sargunv.maplibrecompose.expressions.value.StringValue * A [Literal] representing an [ImageBitmap] value, which will be loaded as an image into the style * upon compilation. */ -public data class BitmapLiteral private constructor(override val value: ImageBitmap) : +public data class BitmapLiteral +private constructor(override val value: ImageBitmap, val sdf: Boolean) : Literal { override fun compile(context: ExpressionContext): StringLiteral = - StringLiteral.of(context.resolveBitmap(value)) + StringLiteral.of(context.resolveBitmap(this)) override fun visit(block: (Expression<*>) -> Unit): Unit = block(this) public companion object { - public fun of(value: ImageBitmap): BitmapLiteral = BitmapLiteral(value) + public fun of(value: ImageBitmap, sdf: Boolean): BitmapLiteral = BitmapLiteral(value, sdf) } } diff --git a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/PainterLiteral.kt b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/PainterLiteral.kt index 77fe364c..26fb5236 100644 --- a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/PainterLiteral.kt +++ b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/ast/PainterLiteral.kt @@ -1,6 +1,7 @@ package dev.sargunv.maplibrecompose.expressions.ast import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.DpSize import dev.sargunv.maplibrecompose.expressions.ExpressionContext import dev.sargunv.maplibrecompose.expressions.value.StringValue @@ -8,14 +9,16 @@ import dev.sargunv.maplibrecompose.expressions.value.StringValue * A [Literal] representing a [Painter] value, which will be drawn to a bitmap and loaded as an * image into the style upon compilation. */ -public data class PainterLiteral private constructor(override val value: Painter) : +public data class PainterLiteral +private constructor(override val value: Painter, val size: DpSize?, val sdf: Boolean) : Literal { override fun compile(context: ExpressionContext): StringLiteral = - StringLiteral.of(context.resolvePainter(value)) + StringLiteral.of(context.resolvePainter(this)) override fun visit(block: (Expression<*>) -> Unit): Unit = block(this) public companion object { - public fun of(value: Painter): PainterLiteral = PainterLiteral(value) + public fun of(value: Painter, size: DpSize?, sdf: Boolean): PainterLiteral = + PainterLiteral(value, size, sdf) } } diff --git a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/dsl/image.kt b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/dsl/image.kt index 88253c9f..948e2f39 100644 --- a/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/dsl/image.kt +++ b/lib/maplibre-compose-expressions/src/commonMain/kotlin/dev/sargunv/maplibrecompose/expressions/dsl/image.kt @@ -2,6 +2,7 @@ package dev.sargunv.maplibrecompose.expressions.dsl import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.unit.DpSize import dev.sargunv.maplibrecompose.expressions.ast.BitmapLiteral import dev.sargunv.maplibrecompose.expressions.ast.Expression import dev.sargunv.maplibrecompose.expressions.ast.FunctionCall @@ -55,8 +56,8 @@ public fun image(value: String): Expression = image(const(value)) * unregistered from the style if it's no longer referenced by any layer. An ID referencing the * bitmap will be generated automatically and inserted into the expression. */ -public fun image(value: ImageBitmap): Expression = - FunctionCall.of("image", BitmapLiteral.of(value)).cast() +public fun image(value: ImageBitmap, sdf: Boolean = false): Expression = + FunctionCall.of("image", BitmapLiteral.of(value, sdf)).cast() /** * Returns an image type for use in `iconImage` (see @@ -71,8 +72,11 @@ public fun image(value: ImageBitmap): Expression = * referenced by a layer, and unregistered from the style if it's no longer referenced by any layer. * An ID referencing the bitmap will be generated automatically and inserted into the expression. * - * The bitmap will be created with the intrinsic size of the painter, or 16x16 DP if the painter - * does not have an intrinsic size. + * The bitmap will be created with the provided [size], or the intrinsic size of the painter if not + * provided, or 16x16 DP if the painter has no intrinsic size. */ -public fun image(value: Painter): Expression = - FunctionCall.of("image", PainterLiteral.of(value)).cast() +public fun image( + value: Painter, + size: DpSize? = null, + sdf: Boolean = false, +): Expression = FunctionCall.of("image", PainterLiteral.of(value, size, sdf)).cast() diff --git a/lib/maplibre-compose/src/androidMain/kotlin/dev/sargunv/maplibrecompose/core/AndroidStyle.kt b/lib/maplibre-compose/src/androidMain/kotlin/dev/sargunv/maplibrecompose/core/AndroidStyle.kt index a85f128e..17436df5 100644 --- a/lib/maplibre-compose/src/androidMain/kotlin/dev/sargunv/maplibrecompose/core/AndroidStyle.kt +++ b/lib/maplibre-compose/src/androidMain/kotlin/dev/sargunv/maplibrecompose/core/AndroidStyle.kt @@ -11,8 +11,8 @@ import org.maplibre.android.maps.Style as MLNStyle internal class AndroidStyle(style: MLNStyle) : Style { private var impl: MLNStyle = style - override fun addImage(id: String, image: ImageBitmap) { - impl.addImage(id, image.asAndroidBitmap()) + override fun addImage(id: String, image: ImageBitmap, sdf: Boolean) { + impl.addImage(id, image.asAndroidBitmap(), sdf) } override fun removeImage(id: String) { diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/engine/ImageManager.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/engine/ImageManager.kt index 01a501eb..dcc82814 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/engine/ImageManager.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/engine/ImageManager.kt @@ -7,65 +7,76 @@ import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.drawscope.CanvasDrawScope import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp internal class ImageManager(private val node: StyleNode) { - private val bitmapIds = IncrementingIdMap("bitmap") - private val bitmapCounter = ReferenceCounter() + private val bitmapIds = IncrementingIdMap("bitmap") + private val bitmapCounter = ReferenceCounter() - private val painterIds = IncrementingIdMap("painter") - private val painterCounter = ReferenceCounter() - private val painterBitmaps = mutableMapOf() + private val painterIds = IncrementingIdMap("painter") + private val painterCounter = ReferenceCounter() + private val painterBitmaps = mutableMapOf() - internal fun acquireBitmap(bitmap: ImageBitmap): String { - bitmapCounter.increment(bitmap) { - val id = bitmapIds.addId(bitmap) + internal fun acquireBitmap(key: BitmapKey): String { + bitmapCounter.increment(key) { + val id = bitmapIds.addId(key) node.logger?.i { "Adding bitmap $id" } - node.style.addImage(id, bitmap) + node.style.addImage(id, key.bitmap, key.sdf) } - return bitmapIds.getId(bitmap) + return bitmapIds.getId(key) } - internal fun releaseBitmap(bitmap: ImageBitmap) { - bitmapCounter.decrement(bitmap) { - val id = bitmapIds.removeId(bitmap) + internal fun releaseBitmap(key: BitmapKey) { + bitmapCounter.decrement(key) { + val id = bitmapIds.removeId(key) node.logger?.i { "Removing bitmap $id" } node.style.removeImage(id) } } - private fun Painter.drawToBitmap( - density: Density, - layoutDirection: LayoutDirection, - ): ImageBitmap { - val size = intrinsicSize.takeOrElse { Size(16f, 16f) } - return ImageBitmap(size.width.toInt(), size.height.toInt()).also { bitmap -> - CanvasDrawScope().draw(density, layoutDirection, Canvas(bitmap), size) { draw(size) } + private fun PainterKey.drawToBitmap(): ImageBitmap { + val size = + with(density) { + size?.let { Size(it.width.toPx(), it.height.toPx()) } + ?: painter.intrinsicSize.takeOrElse { Size(16.dp.toPx(), 16.dp.toPx()) } + } + val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) + CanvasDrawScope().draw(density, layoutDirection, Canvas(bitmap), size) { + with(painter) { draw(size) } } + return bitmap } - internal fun acquirePainter( - painter: Painter, - density: Density, - layoutDirection: LayoutDirection, - ): String { - painterCounter.increment(painter) { - val id = painterIds.addId(painter) + internal fun acquirePainter(key: PainterKey): String { + painterCounter.increment(key) { + val id = painterIds.addId(key) node.logger?.i { "Adding painter $id" } - painter.drawToBitmap(density, layoutDirection).let { bitmap -> - painterBitmaps[painter] = bitmap - node.style.addImage(id, bitmap) + key.drawToBitmap().let { bitmap -> + painterBitmaps[key] = bitmap + node.style.addImage(id, bitmap, key.sdf) } } - return painterIds.getId(painter) + return painterIds.getId(key) } - internal fun releasePainter(painter: Painter) { - painterCounter.decrement(painter) { - val id = painterIds.removeId(painter) + internal fun releasePainter(key: PainterKey) { + painterCounter.decrement(key) { + val id = painterIds.removeId(key) node.logger?.i { "Removing painter $id" } - painterBitmaps.remove(painter) + painterBitmaps.remove(key) node.style.removeImage(id) } } + + internal data class BitmapKey(val bitmap: ImageBitmap, val sdf: Boolean) + + internal data class PainterKey( + val painter: Painter, + val density: Density, + val layoutDirection: LayoutDirection, + val size: DpSize?, + val sdf: Boolean, + ) } diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/layer/LayerPropertyCompiler.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/layer/LayerPropertyCompiler.kt index 1b84ad70..f52028fb 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/layer/LayerPropertyCompiler.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/compose/layer/LayerPropertyCompiler.kt @@ -3,13 +3,12 @@ package dev.sargunv.maplibrecompose.compose.layer import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.ImageBitmap -import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.TextUnitType +import dev.sargunv.maplibrecompose.compose.engine.ImageManager import dev.sargunv.maplibrecompose.compose.engine.LocalStyleNode import dev.sargunv.maplibrecompose.compose.engine.StyleNode import dev.sargunv.maplibrecompose.expressions.ExpressionContext @@ -60,12 +59,12 @@ internal class LayerPropertyCompiler( } } - override fun resolveBitmap(bitmap: ImageBitmap): String { - return styleNode.imageManager.acquireBitmap(bitmap) + override fun resolveBitmap(bitmap: BitmapLiteral): String { + return styleNode.imageManager.acquireBitmap(bitmap.key()) } - override fun resolvePainter(painter: Painter): String { - return styleNode.imageManager.acquirePainter(painter, density, layoutDirection) + override fun resolvePainter(painter: PainterLiteral): String { + return styleNode.imageManager.acquirePainter(painter.key(density, layoutDirection)) } fun reset() { @@ -79,8 +78,10 @@ internal class LayerPropertyCompiler( onDispose { expression.visit { when (it) { - is BitmapLiteral -> styleNode.imageManager.releaseBitmap(it.value) - is PainterLiteral -> styleNode.imageManager.releasePainter(it.value) + is BitmapLiteral -> styleNode.imageManager.releaseBitmap(it.key()) + is PainterLiteral -> + styleNode.imageManager.releasePainter(it.key(density, layoutDirection)) + else -> {} } } @@ -91,6 +92,13 @@ internal class LayerPropertyCompiler( expression.compile(context) } } + + private fun BitmapLiteral.key() = ImageManager.BitmapKey(value, sdf) + + private fun PainterLiteral.key( + density: Density, + layoutDirection: LayoutDirection, + ): ImageManager.PainterKey = ImageManager.PainterKey(value, density, layoutDirection, size, sdf) } @Composable diff --git a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/Style.kt b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/Style.kt index db404452..05cf63eb 100644 --- a/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/Style.kt +++ b/lib/maplibre-compose/src/commonMain/kotlin/dev/sargunv/maplibrecompose/core/Style.kt @@ -5,7 +5,7 @@ import dev.sargunv.maplibrecompose.core.layer.Layer import dev.sargunv.maplibrecompose.core.source.Source internal interface Style { - fun addImage(id: String, image: ImageBitmap) + fun addImage(id: String, image: ImageBitmap, sdf: Boolean) fun removeImage(id: String) @@ -32,7 +32,7 @@ internal interface Style { fun removeLayer(layer: Layer) object Null : Style { - override fun addImage(id: String, image: ImageBitmap) {} + override fun addImage(id: String, image: ImageBitmap, sdf: Boolean) {} override fun removeImage(id: String) {} diff --git a/lib/maplibre-compose/src/commonTest/kotlin/dev/sargunv/maplibrecompose/compose/FakeStyle.kt b/lib/maplibre-compose/src/commonTest/kotlin/dev/sargunv/maplibrecompose/compose/FakeStyle.kt index 27b6ee08..1baea0ea 100644 --- a/lib/maplibre-compose/src/commonTest/kotlin/dev/sargunv/maplibrecompose/compose/FakeStyle.kt +++ b/lib/maplibre-compose/src/commonTest/kotlin/dev/sargunv/maplibrecompose/compose/FakeStyle.kt @@ -15,7 +15,7 @@ internal class FakeStyle( private val layerList = layers.toMutableList() private val layerMap = layers.associateBy { it.id }.toMutableMap() - override fun addImage(id: String, image: ImageBitmap) { + override fun addImage(id: String, image: ImageBitmap, sdf: Boolean) { if (id in imageMap) error("Image ID '${id}' already exists in style") imageMap[id] = image } diff --git a/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/IosStyle.kt b/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/IosStyle.kt index 155dc0d6..3e1dff70 100644 --- a/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/IosStyle.kt +++ b/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/IosStyle.kt @@ -13,8 +13,8 @@ import dev.sargunv.maplibrecompose.core.util.toUIImage internal class IosStyle(style: MLNStyle, private val getScale: () -> Float) : Style { private var impl: MLNStyle = style - override fun addImage(id: String, image: ImageBitmap) { - impl.setImage(image.toUIImage(getScale()), forName = id) + override fun addImage(id: String, image: ImageBitmap, sdf: Boolean) { + impl.setImage(image.toUIImage(getScale(), sdf), forName = id) } override fun removeImage(id: String) { diff --git a/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/util/util.kt b/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/util/util.kt index 3f409d9f..2718ebf4 100644 --- a/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/util/util.kt +++ b/lib/maplibre-compose/src/iosMain/kotlin/dev/sargunv/maplibrecompose/core/util/util.kt @@ -62,6 +62,7 @@ import platform.Foundation.dataWithBytes import platform.UIKit.UIColor import platform.UIKit.UIEdgeInsetsMake import platform.UIKit.UIImage +import platform.UIKit.UIImageRenderingMode import platform.UIKit.valueWithCGVector import platform.UIKit.valueWithUIEdgeInsets @@ -201,9 +202,12 @@ internal fun Alignment.toMLNOrnamentPosition(layoutDir: LayoutDirection): MLNOrn } } -internal fun ImageBitmap.toUIImage(scale: Float): UIImage { - return UIImage( - data = Image.makeFromBitmap(this.asSkiaBitmap()).encodeToData()!!.bytes.toNSData(), - scale = scale.toDouble(), - ) -} +internal fun ImageBitmap.toUIImage(scale: Float, sdf: Boolean) = + UIImage( + data = Image.makeFromBitmap(this.asSkiaBitmap()).encodeToData()!!.bytes.toNSData(), + scale = scale.toDouble(), + ) + .imageWithRenderingMode( + if (sdf) UIImageRenderingMode.UIImageRenderingModeAlwaysTemplate + else UIImageRenderingMode.UIImageRenderingModeAutomatic + )