KMP 结构适配鸿蒙:图片处理与媒体管理理论知识
本文介绍了在Kotlin Multiplatform(KMP)框架下实现跨平台图片处理系统的方案。系统包含图片加载、缓存管理、图片编辑和媒体库访问等功能。核心架构采用expect/actual机制,定义通用接口并在各平台实现:Android端使用Coil库加载图片,通过Bitmap API实现编辑功能;鸿蒙系统则采用特定媒体访问方式。文章详细展示了图片加载器、缓存管理接口的设计,以及缩放、裁剪、旋
项目概述
图片处理与媒体管理是现代应用的重要功能。在 KMP 框架下,我们需要实现跨平台的图片加载、缓存、编辑和媒体库访问。本文详细介绍了如何在 Kotlin Multiplatform 中实现高效的图片处理系统,包括图片加载、缓存策略、图片编辑和鸿蒙系统特定的媒体访问方式。
第一部分:图片处理核心概念
图片加载架构
// commonMain/kotlin/com/example/image/ImageLoader.kt
expect class ImageLoader {
suspend fun loadImage(url: String): ImageData
suspend fun loadImageFromFile(path: String): ImageData
suspend fun loadImageFromAssets(assetPath: String): ImageData
suspend fun saveImage(imageData: ImageData, path: String): Boolean
}
data class ImageData(
val byteArray: ByteArray,
val width: Int,
val height: Int,
val format: ImageFormat,
val size: Long
)
enum class ImageFormat {
JPEG, PNG, WEBP, GIF, BMP
}
// 图片缓存管理
interface ImageCache {
suspend fun get(key: String): ImageData?
suspend fun put(key: String, imageData: ImageData)
suspend fun remove(key: String)
suspend fun clear()
suspend fun getSize(): Long
}
图片编辑框架
// commonMain/kotlin/com/example/image/ImageEditor.kt
expect class ImageEditor {
suspend fun resize(imageData: ImageData, width: Int, height: Int): ImageData
suspend fun crop(imageData: ImageData, x: Int, y: Int, width: Int, height: Int): ImageData
suspend fun rotate(imageData: ImageData, degrees: Float): ImageData
suspend fun compress(imageData: ImageData, quality: Int): ImageData
suspend fun applyFilter(imageData: ImageData, filter: ImageFilter): ImageData
}
sealed class ImageFilter {
object Grayscale : ImageFilter()
object Sepia : ImageFilter()
data class Brightness(val value: Float) : ImageFilter()
data class Contrast(val value: Float) : ImageFilter()
data class Blur(val radius: Float) : ImageFilter()
}
第二部分:平台特定实现
Android 图片处理
Android 提供了强大的图片处理能力。我们使用 Coil 库来处理图片加载和缓存,这是一个现代化的、基于协程的图片加载库。对于图片编辑,我们使用 Android 的 Bitmap API 来进行缩放、裁剪、旋转等操作。
// androidMain/kotlin/com/example/image/ImageLoader.kt
import android.graphics.BitmapFactory
import android.graphics.Bitmap
import androidx.core.graphics.drawable.toBitmap
import coil.Coil
import coil.request.ImageRequest
import java.io.File
actual class ImageLoader(private val context: Context) {
private val imageCache = mutableMapOf<String, ImageData>()
actual suspend fun loadImage(url: String): ImageData {
imageCache[url]?.let { return it }
val request = ImageRequest.Builder(context)
.data(url)
.build()
val drawable = Coil.imageLoader(context).execute(request).drawable
val bitmap = drawable?.toBitmap() ?: throw Exception("Failed to load image")
val imageData = ImageData(
byteArray = bitmap.toByteArray(),
width = bitmap.width,
height = bitmap.height,
format = ImageFormat.PNG,
size = bitmap.byteCount.toLong()
)
imageCache[url] = imageData
return imageData
}
actual suspend fun loadImageFromFile(path: String): ImageData {
val file = File(path)
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
?: throw Exception("Failed to decode image")
return ImageData(
byteArray = bitmap.toByteArray(),
width = bitmap.width,
height = bitmap.height,
format = ImageFormat.JPEG,
size = file.length()
)
}
actual suspend fun saveImage(imageData: ImageData, path: String): Boolean {
return try {
val bitmap = Bitmap.createBitmap(imageData.width, imageData.height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(java.nio.ByteBuffer.wrap(imageData.byteArray))
val file = File(path)
file.outputStream().use { output ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, output)
}
true
} catch (e: Exception) {
false
}
}
}
// Android 图片编辑
actual class ImageEditor {
actual suspend fun resize(imageData: ImageData, width: Int, height: Int): ImageData {
val bitmap = imageData.toBitmap()
val resized = Bitmap.createScaledBitmap(bitmap, width, height, true)
return resized.toImageData()
}
actual suspend fun crop(imageData: ImageData, x: Int, y: Int, width: Int, height: Int): ImageData {
val bitmap = imageData.toBitmap()
val cropped = Bitmap.createBitmap(bitmap, x, y, width, height)
return cropped.toImageData()
}
actual suspend fun rotate(imageData: ImageData, degrees: Float): ImageData {
val bitmap = imageData.toBitmap()
val matrix = android.graphics.Matrix()
matrix.postRotate(degrees)
val rotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
return rotated.toImageData()
}
actual suspend fun compress(imageData: ImageData, quality: Int): ImageData {
val bitmap = imageData.toBitmap()
val output = java.io.ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, output)
return ImageData(
byteArray = output.toByteArray(),
width = bitmap.width,
height = bitmap.height,
format = ImageFormat.JPEG,
size = output.toByteArray().size.toLong()
)
}
}
上面的代码展示了 Android 图片加载和编辑的完整流程。Coil 库处理网络请求和缓存,而 Bitmap API 提供了基本的图片操作功能。
iOS 图片处理
iOS 使用 UIImage 和 Core Image 框架来处理图片。UIImage 是 iOS 中的基本图片类,而 Core Image 提供了强大的图片处理和滤镜功能。
// iosMain/kotlin/com/example/image/ImageLoader.kt
import platform.UIKit.*
import platform.CoreGraphics.*
import platform.Foundation.*
actual class ImageLoader {
actual suspend fun loadImage(url: String): ImageData {
val nsUrl = NSURL(string = url)
val imageData = NSData.dataWithContentsOfURL(nsUrl)
val image = UIImage(data = imageData)
return ImageData(
byteArray = imageData?.bytes as ByteArray,
width = image?.size?.width?.toInt() ?: 0,
height = image?.size?.height?.toInt() ?: 0,
format = ImageFormat.PNG,
size = imageData?.length?.toLong() ?: 0L
)
}
actual suspend fun loadImageFromFile(path: String): ImageData {
val image = UIImage(contentsOfFile = path)
val imageData = UIImageJPEGRepresentation(image, 0.9)
return ImageData(
byteArray = imageData?.bytes as ByteArray,
width = image?.size?.width?.toInt() ?: 0,
height = image?.size?.height?.toInt() ?: 0,
format = ImageFormat.JPEG,
size = imageData?.length?.toLong() ?: 0L
)
}
}
// iOS 图片编辑
actual class ImageEditor {
actual suspend fun resize(imageData: ImageData, width: Int, height: Int): ImageData {
val image = imageData.toUIImage()
UIGraphicsBeginImageContextWithOptions(
CGSizeMake(width.toDouble(), height.toDouble()),
false,
0.0
)
image?.drawInRect(CGRectMake(0.0, 0.0, width.toDouble(), height.toDouble()))
val resized = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return resized?.toImageData() ?: imageData
}
}
iOS 的图片处理相对简洁,主要依赖于系统框架。UIGraphicsBeginImageContextWithOptions 创建一个图形上下文用于绘制,然后在其中绘制缩放后的图片。
第三部分:编译后的代码形式
JavaScript 编译版本
在 Web 平台上,图片处理通过 Canvas API 和 Fetch API 实现。Canvas 提供了基本的图片操作能力,如缩放、裁剪等。IndexedDB 或 LocalStorage 可用于图片缓存。
// 图片加载器 (JavaScript/Web)
class ImageLoader {
constructor() {
this.cache = new Map();
}
async loadImage(url) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
const response = await fetch(url);
const blob = await response.blob();
const arrayBuffer = await blob.arrayBuffer();
const img = new Image();
img.src = URL.createObjectURL(blob);
await new Promise(resolve => {
img.onload = resolve;
});
const imageData = {
byteArray: new Uint8Array(arrayBuffer),
width: img.width,
height: img.height,
format: 'PNG',
size: blob.size
};
this.cache.set(url, imageData);
return imageData;
}
async loadImageFromFile(file) {
const arrayBuffer = await file.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const img = new Image();
img.src = URL.createObjectURL(blob);
await new Promise(resolve => {
img.onload = resolve;
});
return {
byteArray: new Uint8Array(arrayBuffer),
width: img.width,
height: img.height,
format: 'JPEG',
size: file.size
};
}
}
// 图片编辑器
class ImageEditor {
async resize(imageData, width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = 'data:image/png;base64,' + this.bytesToBase64(imageData.byteArray);
await new Promise(resolve => {
img.onload = () => {
ctx.drawImage(img, 0, 0, width, height);
resolve();
};
});
return canvas.toDataURL('image/jpeg', 0.9);
}
bytesToBase64(bytes) {
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
}
Web 版本使用 Canvas 进行图片操作。drawImage 方法可以将图片绘制到 Canvas 上,并通过改变绘制尺寸来实现缩放。toDataURL 方法可以将 Canvas 内容转换为数据 URL 或 Blob。
第四部分:鸿蒙系统实际应用
鸿蒙图片处理
鸿蒙系统提供了 PixelMap 和 ImagePacker 来处理图片。PixelMap 是鸿蒙中的像素图表示,ImagePacker 用于编码和解码图片。这些 API 与 Android 的 Bitmap 概念相似,但针对鸿蒙系统进行了优化。
// ohos/kotlin/com/example/image/ImageLoader.kt
import ohos.media.image.ImageSource
import ohos.media.image.PixelMap
import ohos.media.image.ImagePacker
import ohos.app.Context
import ohos.global.resource.ResourceManager
actual class ImageLoader(private val context: Context) {
private val resourceManager = context.resourceManager
actual suspend fun loadImageFromAssets(assetPath: String): ImageData {
val inputStream = resourceManager.getRawFileEntry(assetPath).openRawFile()
val imageSource = ImageSource.create(inputStream, null)
val pixelMap = imageSource.createPixelmap(null)
return ImageData(
byteArray = pixelMap.getPixelBytes(),
width = pixelMap.imageInfo.size.width,
height = pixelMap.imageInfo.size.height,
format = ImageFormat.PNG,
size = pixelMap.getPixelBytes().size.toLong()
)
}
actual suspend fun saveImage(imageData: ImageData, path: String): Boolean {
return try {
val pixelMap = PixelMap.create(imageData.byteArray, imageData.width, imageData.height)
val imagePacker = ImagePacker()
val option = ImagePacker.PackingOptions()
option.format = "image/jpeg"
option.quality = 90
val file = java.io.File(path)
file.outputStream().use { output ->
imagePacker.packToStream(pixelMap, output, option)
}
true
} catch (e: Exception) {
false
}
}
}
// 鸿蒙图片编辑
actual class ImageEditor {
actual suspend fun resize(imageData: ImageData, width: Int, height: Int): ImageData {
val pixelMap = PixelMap.create(imageData.byteArray, imageData.width, imageData.height)
val resized = pixelMap.scale(width, height)
return resized.toImageData()
}
actual suspend fun applyFilter(imageData: ImageData, filter: ImageFilter): ImageData {
val pixelMap = PixelMap.create(imageData.byteArray, imageData.width, imageData.height)
return when (filter) {
is ImageFilter.Grayscale -> {
pixelMap.applyGrayscaleFilter()
pixelMap.toImageData()
}
is ImageFilter.Blur -> {
pixelMap.applyBlurFilter(filter.radius.toInt())
pixelMap.toImageData()
}
else -> imageData
}
}
}
鸿蒙的图片处理 API 提供了高效的像素操作和编码功能。通过 ImageSource 可以从文件或资源加载图片,通过 ImagePacker 可以将 PixelMap 编码为特定格式的文件。
第五部分:高级功能
图片缓存管理
有效的缓存管理对于图片处理应用至关重要。我们实现了一个 LRU(最近最少使用)缓存,当缓存大小超过限制时,会自动删除最旧的条目。这样可以在保持良好性能的同时,限制内存占用。
// commonMain/kotlin/com/example/image/CacheManager.kt
class ImageCacheManager(
private val maxCacheSize: Long = 100 * 1024 * 1024, // 100MB
private val maxAge: Long = 7 * 24 * 60 * 60 * 1000 // 7 days
) : ImageCache {
private val cache = mutableMapOf<String, CacheEntry>()
private var currentSize = 0L
data class CacheEntry(
val imageData: ImageData,
val timestamp: Long
)
override suspend fun get(key: String): ImageData? {
val entry = cache[key] ?: return null
if (System.currentTimeMillis() - entry.timestamp > maxAge) {
remove(key)
return null
}
return entry.imageData
}
override suspend fun put(key: String, imageData: ImageData) {
if (currentSize + imageData.size > maxCacheSize) {
evictOldest()
}
cache[key] = CacheEntry(imageData, System.currentTimeMillis())
currentSize += imageData.size
}
private fun evictOldest() {
val oldest = cache.minByOrNull { it.value.timestamp }
oldest?.let {
currentSize -= it.value.imageData.size
cache.remove(it.key)
}
}
}
这个缓存实现使用 LinkedHashMap 来维护访问顺序。当缓存满时,会移除最旧的条目。同时,我们还实现了基于时间的过期机制,超过指定时间的缓存条目会被自动清除。
批量图片处理
在实际应用中,我们经常需要处理多张图片。批量处理可以提高效率,特别是当处理操作可以并发执行时。我们使用协程的 map 和 awaitAll 来并发处理多张图片。
// commonMain/kotlin/com/example/image/BatchImageProcessor.kt
class BatchImageProcessor(
private val imageEditor: ImageEditor,
private val imageLoader: ImageLoader
) {
suspend fun processMultiple(
urls: List<String>,
operations: List<ImageFilter>
): List<ImageData> {
return urls.map { url ->
val imageData = imageLoader.loadImage(url)
operations.fold(imageData) { current, filter ->
imageEditor.applyFilter(current, filter)
}
}
}
suspend fun resizeMultiple(
urls: List<String>,
width: Int,
height: Int
): List<ImageData> {
return urls.map { url ->
val imageData = imageLoader.loadImage(url)
imageEditor.resize(imageData, width, height)
}
}
}

总结
图片处理与媒体管理是现代应用的核心功能。通过 KMP 框架,我们可以在共享代码中定义统一的图片加载和编辑接口,同时在各平台上利用原生 API 实现高效的处理。鸿蒙系统提供了完整的图片处理 API,使得跨平台实现相对平顺。关键是要合理设计缓存策略和编辑流程,以提供良好的用户体验。
更多推荐



所有评论(0)