Skip to content

Commit

Permalink
feat: add support for image sdf and painter size (#197)
Browse files Browse the repository at this point in the history
  • Loading branch information
sargunv authored Dec 27, 2024
1 parent e1de5e5 commit 36568e2
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -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

/**
Expand All @@ -21,10 +21,10 @@ public interface ExpressionContext {
public val spScale: Expression<FloatValue>

/** @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 {
Expand All @@ -34,10 +34,10 @@ public interface ExpressionContext {
override val spScale: Expression<FloatValue>
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")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<StringValue, ImageBitmap> {
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)
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
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

/**
* 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<StringValue, Painter> {
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,8 +56,8 @@ public fun image(value: String): Expression<ImageValue> = 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<ImageValue> =
FunctionCall.of("image", BitmapLiteral.of(value)).cast()
public fun image(value: ImageBitmap, sdf: Boolean = false): Expression<ImageValue> =
FunctionCall.of("image", BitmapLiteral.of(value, sdf)).cast()

/**
* Returns an image type for use in `iconImage` (see
Expand All @@ -71,8 +72,11 @@ public fun image(value: ImageBitmap): Expression<ImageValue> =
* 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<ImageValue> =
FunctionCall.of("image", PainterLiteral.of(value)).cast()
public fun image(
value: Painter,
size: DpSize? = null,
sdf: Boolean = false,
): Expression<ImageValue> = FunctionCall.of("image", PainterLiteral.of(value, size, sdf)).cast()
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImageBitmap>("bitmap")
private val bitmapCounter = ReferenceCounter<ImageBitmap>()
private val bitmapIds = IncrementingIdMap<BitmapKey>("bitmap")
private val bitmapCounter = ReferenceCounter<BitmapKey>()

private val painterIds = IncrementingIdMap<Painter>("painter")
private val painterCounter = ReferenceCounter<Painter>()
private val painterBitmaps = mutableMapOf<Painter, ImageBitmap>()
private val painterIds = IncrementingIdMap<PainterKey>("painter")
private val painterCounter = ReferenceCounter<PainterKey>()
private val painterBitmaps = mutableMapOf<PainterKey, ImageBitmap>()

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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand All @@ -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 -> {}
}
}
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
)

0 comments on commit 36568e2

Please sign in to comment.