Created
December 2, 2024 06:47
-
-
Save iTaysonLab/2beaf93345d2c4ef195fa3ec09940484 to your computer and use it in GitHub Desktop.
Compose Multiplatform -> Skia Image to CG/UIImage
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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