Skip to content

Instantly share code, notes, and snippets.

@iTaysonLab
Created December 2, 2024 06:47
Show Gist options
  • Save iTaysonLab/2beaf93345d2c4ef195fa3ec09940484 to your computer and use it in GitHub Desktop.
Save iTaysonLab/2beaf93345d2c4ef195fa3ec09940484 to your computer and use it in GitHub Desktop.
Compose Multiplatform -> Skia Image to CG/UIImage
fun test() {
// Create Coil image request
val imageRequest = ImageRequest.Builder(platformContext).data(artworkUrl).build()
// Execute the image request
val bitmapImage = imageLoader.execute(imageRequest).image as? BitmapImage
// Convert Coil BitmapImage to UIImage
val uiImage = bitmapImage?.bitmap?.toUIImage()
// Now we can, for example, create MPMediaItemArtwork out of it and set to MPNowPlayingInfoCenter
// Create MPMediaItemArtwork
val mpArtwork = MPMediaItemArtwork(boundsSize = uiImage.size, requestHandler = uiImage::resizeTo)
// Set new data to MPNowPlayingInfoCenter
val artworkMetadata: MutableMap<Any?, Any?> = mutableMapOf(
MPMediaItemPropertyArtwork to mpArtwork
)
MPNowPlayingInfoCenter.defaultCenter().also { center ->
center.nowPlayingInfo = center.nowPlayingInfo?.let { it + artworkMetadata } ?: artworkMetadata
}
}
package bruhcollective.itaysonlab.playback
import kotlinx.cinterop.CValue
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.refTo
import kotlinx.cinterop.useContents
import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.ColorAlphaType
import org.jetbrains.skia.ColorType
import platform.CoreGraphics.CGBitmapContextCreate
import platform.CoreGraphics.CGBitmapContextCreateImage
import platform.CoreGraphics.CGBitmapInfo
import platform.CoreGraphics.CGColorSpaceCreateDeviceRGB
import platform.CoreGraphics.CGColorSpaceRelease
import platform.CoreGraphics.CGContextRelease
import platform.CoreGraphics.CGImageAlphaInfo
import platform.CoreGraphics.CGImageRef
import platform.CoreGraphics.CGImageRelease
import platform.CoreGraphics.CGRectMake
import platform.CoreGraphics.CGSize
import platform.CoreGraphics.kCGBitmapByteOrder32Big
import platform.CoreGraphics.kCGBitmapByteOrder32Little
import platform.UIKit.UIGraphicsImageRenderer
import platform.UIKit.UIImage
/**
* Converts a Skiko [Bitmap] to a [CGImageRef].
*
* Sources:
* - https://github.com/google/skia/blob/main/src/utils/mac/SkCreateCGImageRef.cpp
*
* @return the reference to CGImage that must be releasing this object by calling CGImageRelease after using.
*/
@OptIn(ExperimentalForeignApi::class)
fun Bitmap.toCGImage(): CGImageRef? {
// Skiko does not have SkWriteICCProfile, so we can't create color spaces.
if (colorSpace != null && colorSpace?.isSRGB == false) return null
val pixels = readPixels() ?: return null
val colorSpace = CGColorSpaceCreateDeviceRGB()
val context = CGBitmapContextCreate(
data = pixels.refTo(0),
width = width.toULong(),
height = height.toULong(),
bitsPerComponent = 8u,
bytesPerRow = 4u * width.toULong(),
space = colorSpace,
bitmapInfo = computeCGBitmapInfo()
)
return try {
CGBitmapContextCreateImage(context)
} finally {
CGColorSpaceRelease(colorSpace)
CGContextRelease(context)
}
}
/**
* Converts a Skiko [Bitmap] to a [UIImage].
*/
@OptIn(ExperimentalForeignApi::class)
fun Bitmap.toUIImage(): UIImage {
val imageRef = toCGImage()
val image = UIImage.imageWithCGImage(imageRef)
CGImageRelease(imageRef)
return image
}
/**
* Resizes a [UIImage] from a given [CGSize].
*
* Useful for MPMediaItemArtwork or other APIs that require resizing images.
*/
@OptIn(ExperimentalForeignApi::class)
fun UIImage.resizeTo(size: CValue<CGSize>): UIImage {
return UIGraphicsImageRenderer(size).imageWithActions { _ ->
size.useContents {
[email protected](CGRectMake(0.0, 0.0, width, height))
}
}.imageWithRenderingMode(renderingMode)
}
private fun Bitmap.computeCGBitmapInfo(): CGBitmapInfo {
return when (colorType) {
ColorType.RGBA_8888 -> {
alphaType.computeCGAlphaInfoRgba()
}
ColorType.BGRA_8888 -> {
alphaType.computeCGAlphaInfoBgra()
}
else -> {
// Unknown
CGImageAlphaInfo.kCGImageAlphaPremultipliedLast.value
}
}
}
private fun ColorAlphaType.computeCGAlphaInfoRgba(): CGBitmapInfo {
val info = kCGBitmapByteOrder32Big
return when (this) {
ColorAlphaType.UNKNOWN -> info
ColorAlphaType.OPAQUE -> info or CGImageAlphaInfo.kCGImageAlphaNoneSkipLast.value
ColorAlphaType.PREMUL -> info or CGImageAlphaInfo.kCGImageAlphaPremultipliedLast.value
ColorAlphaType.UNPREMUL -> info or CGImageAlphaInfo.kCGImageAlphaLast.value
}
}
private fun ColorAlphaType.computeCGAlphaInfoBgra(): CGBitmapInfo {
val info = kCGBitmapByteOrder32Little
return when (this) {
ColorAlphaType.UNKNOWN -> info
ColorAlphaType.OPAQUE -> info or CGImageAlphaInfo.kCGImageAlphaNoneSkipFirst.value
ColorAlphaType.PREMUL -> info or CGImageAlphaInfo.kCGImageAlphaPremultipliedFirst.value
ColorAlphaType.UNPREMUL -> info or CGImageAlphaInfo.kCGImageAlphaFirst.value
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment