Hello, Is there a way to convert an Compose ImageV...
# compose-ios
m
Hello, Is there a way to convert an Compose ImageVector into an UIImage? I would like to use my Compose Vectors in some iOS Native Views (eg. UITabBarItem).
d
Does your workflow start with SVG file source?
m
not only SVG files, but I would like to use the Compose Material Icons (SVGs) also (if possible)
d
Then here's what is recommend; those built in Compose Material icons are already in
ImageVector
format, right? That is to say, they're actually compiled source already, that performs the vector drawing. This is highly efficient as no file or format parsing is needed. You can verify this by navigating into the symbol and seeing the drawing code.
You can draw those with
Image(imageVector = someIcon, ...)
So now what about your SVG files?
There a nice plugin called
compose-vector-plugin
by 'Irgaly' on Github
That will convert any SVGs you have into similar
ImageVector
source at build time. You can then access and draw them in the same way as the Material Icons.
m
ok, I understand that, but I already have the ImageVector instance built, what I want is to apply this to an UIImage iOS component
d
Since you're posting in #C0346LWVBJ4 I thought you are using Compose UI.
In which case no need to use
UIImage
, the above
Image
composable is multiplatform and works on iOS.
Sorry, I see you specified native views above.
I have used SVG rendering at runtime on iOS natively before, there are such libraries though I forget the name.
a
You can use SVG images from resources added as xcasset. Unfortunately, there is no default runtime support for SVG on iOS. For this case, the most efficient way is to process raw data on the Swift side, using raw bytes or file path. There are lots of libraries for different needs - some can draw directly to view canvas, skipping UIImage conversion, some can renter it to UIImage. Just google and you can easily find what you want.
a
Hey @magnumrocha have you checked out skiko? https://github.com/JetBrains/skiko?tab=readme-ov-file may help with this (sorry am too busy with deadlines over next few days to investigate further)
👍 1
m
I'm testing a implementation with Skiko APIs, let's see if it works. if it works, then I share here.
🙌 1
done.
Copy code
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toPixelMap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.asSkiaPath
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.graphics.vector.VectorGroup
import androidx.compose.ui.graphics.vector.VectorNode
import androidx.compose.ui.graphics.vector.VectorPath
import androidx.compose.ui.graphics.vector.toPath
import platform.UIKit.UIImage
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.convert
import kotlinx.cinterop.refTo
import org.jetbrains.skia.Color
import org.jetbrains.skia.Surface
import platform.CoreGraphics.CGBitmapContextCreate
import platform.CoreGraphics.CGBitmapContextCreateImage
import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
import platform.CoreGraphics.CGColorSpaceRelease
import platform.CoreGraphics.CGContextRelease
import platform.CoreGraphics.CGImageAlphaInfo
import platform.CoreGraphics.CGImageRelease

@OptIn(ExperimentalForeignApi::class)
fun ImageVector.toUIImage(width: Int, height: Int): UIImage? =
    this.toImageBitmap(width, height).toUIImage()

private fun ImageVector.toImageBitmap(width: Int, height: Int): ImageBitmap {
    val surface = Surface.makeRasterN32Premul(width, height)
    val canvas = surface.canvas

    canvas.clear(Color.TRANSPARENT) // clear the canvas
    with(canvas) { // draw the ImageVector
        val paint = org.jetbrains.skia.Paint().apply {
            color = Color.BLACK // Change color as needed
        }
        drawPath(toPath().asSkiaPath(), paint)
    }

    return surface.makeImageSnapshot().toComposeImageBitmap()
}

@OptIn(ExperimentalForeignApi::class)
private fun ImageBitmap.toUIImage(): UIImage? {
    val pixelMap = toPixelMap()
    val width = pixelMap.width
    val height = pixelMap.height

    val colorSpace = CGColorSpaceCreateDeviceRGB()
    val data = pixelMap.buffer.toUIntArray()

    val rowBytes = 4 * width

    val context = CGBitmapContextCreate(
        data = data.refTo(0),
        width = width.convert(),
        height = height.convert(),
        bitsPerComponent = 8.convert(),
        bytesPerRow = rowBytes.convert(),
        space = colorSpace,
        bitmapInfo = CGImageAlphaInfo.kCGImageAlphaPremultipliedLast.value
    )

    val cgImage = CGBitmapContextCreateImage(context)
    val image = UIImage.imageWithCGImage(cgImage!!)

    CGContextRelease(context)
    CGColorSpaceRelease(colorSpace)
    CGImageRelease(cgImage)

    return image
}

private fun ImageVector.toPath(): Path = Path().apply {
    traverseNodes(root, this)
}

private fun traverseNodes(node: VectorNode, path: Path) {
    when (node) {
        is VectorGroup -> {
            node.iterator().forEach { childNode ->
                traverseNodes(childNode, path)
            }
        }
        is VectorPath -> {
            path.addPath(node.pathData.toPath())
        }
    }
}
🏆 2