You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将从Drawable获取的Bitmap对象转换为android.media.Image对象?

嘿,这个问题挺常见的——毕竟android.media.Image和Bitmap的底层存储结构差异不小,没有现成的API能一步到位转换,但我们可以通过手动处理像素数据来实现,下面是具体的步骤和代码示例:

将Bitmap转换为android.media.Image的方法

首先要明确:android.media.Image最常用的是YUV_420_888格式(Camera2、ImageReader等框架API都偏好这个格式),所以我们的核心思路是先把Bitmap转成YUV_420_888格式的字节数据,再填充到Image对象的对应通道中。

方法一:使用ImageWriter(推荐,符合Android框架规范)

这个方法利用系统提供的ImageWriter来获取可写入的Image实例,流程更规范,也能避免手动处理buffer时的对齐问题。

步骤:

  1. 创建ImageWriter,指定输出的尺寸、格式和缓存数量。
  2. 从ImageWriter中取出一个可写入的Image对象。
  3. 将Bitmap的ARGB像素转换为YUV_420_888的Y、U、V三分量字节数据。
  4. 把三分量数据分别写入Image的三个Plane(通道)中。
  5. 提交Image完成写入。

代码示例(Kotlin):

import android.media.Image
import android.media.ImageWriter
import android.graphics.Bitmap
import android.graphics.ImageFormat
import android.graphics.Rect

fun bitmapToImage(bitmap: Bitmap): Image? {
    val width = bitmap.width
    val height = bitmap.height

    // 创建ImageWriter,指定YUV_420_888格式,缓存1张图
    val imageWriter = ImageWriter.newInstance(
        width,
        height,
        ImageFormat.YUV_420_888,
        1
    )

    // 获取可写入的Image实例,失败则返回null
    val image = imageWriter.dequeueInputImage() ?: return null

    // 将Bitmap转换为YUV_420_888的三分量字节数组
    val (yBytes, uBytes, vBytes) = bitmapToYuv420(bitmap, width, height)

    // 获取Image的三个Plane(Y、U、V通道)
    val planes = image.planes
    val yPlane = planes[0]
    val uPlane = planes[1]
    val vPlane = planes[2]

    // 写入Y通道数据
    yPlane.buffer.put(yBytes)
    // 写入U通道数据
    uPlane.buffer.put(uBytes)
    // 写入V通道数据
    vPlane.buffer.put(vBytes)

    // 提交Image,完成写入
    imageWriter.queueInputImage(image)

    return image
}

// 辅助方法:将Bitmap的ARGB像素转换为YUV_420_888的三分量数据
private fun bitmapToYuv420(bitmap: Bitmap, width: Int, height: Int): Triple<ByteArray, ByteArray, ByteArray> {
    // 先把Bitmap像素读取到ARGB数组中
    val argbPixels = IntArray(width * height)
    bitmap.getPixels(argbPixels, 0, width, 0, 0, width, height)

    // 计算各分量的字节数组大小
    val ySize = width * height
    val uvSize = ySize / 4 // YUV_420是4:2:0采样,U/V分量是Y的1/4
    val yBytes = ByteArray(ySize)
    val uBytes = ByteArray(uvSize)
    val vBytes = ByteArray(uvSize)

    var uvIndex = 0
    for (i in argbPixels.indices) {
        val argb = argbPixels[i]
        val r = (argb shr 16) and 0xff
        val g = (argb shr 8) and 0xff
        val b = argb and 0xff

        // 计算Y分量(亮度)
        val y = (0.299 * r + 0.587 * g + 0.114 * b).toInt()
        yBytes[i] = y.coerceIn(0, 255).toByte()

        // 每2x2个像素计算一次U/V分量(色度),符合4:2:0采样规则
        if (i % 2 == 0 && i / width % 2 == 0) {
            val u = (-0.147 * r - 0.289 * g + 0.436 * b + 128).toInt()
            val v = (0.615 * r - 0.515 * g - 0.100 * b + 128).toInt()
            uBytes[uvIndex] = u.coerceIn(0, 255).toByte()
            vBytes[uvIndex] = v.coerceIn(0, 255).toByte()
            uvIndex++
        }
    }

    return Triple(yBytes, uBytes, vBytes)
}

方法二:手动创建Image(API 23+)

如果不想依赖ImageWriter,也可以用Image.createImage手动构建对象,但需要自己处理buffer的对齐要求,相对繁琐一些。

代码示例(简化版):

import android.media.Image
import android.graphics.Bitmap
import android.graphics.ImageFormat
import java.nio.ByteBuffer

fun bitmapToImageManual(bitmap: Bitmap): Image? {
    val width = bitmap.width
    val height = bitmap.height
    val format = ImageFormat.YUV_420_888

    // 计算各Plane的步长(对齐到4字节,符合硬件要求)
    val yStride = (width + 3) / 4 * 4
    val uvStride = (width / 2 + 3) / 4 * 4
    val ySize = yStride * height
    val uvSize = uvStride * (height / 2)

    // 分配DirectByteBuffer(避免JNI拷贝)
    val yBuffer = ByteBuffer.allocateDirect(ySize)
    val uBuffer = ByteBuffer.allocateDirect(uvSize)
    val vBuffer = ByteBuffer.allocateDirect(uvSize)

    // 转换Bitmap到YUV数据并写入buffer
    val (yBytes, uBytes, vBytes) = bitmapToYuv420(bitmap, width, height)
    yBuffer.put(yBytes)
    uBuffer.put(uBytes)
    vBuffer.put(vBytes)

    // 创建Plane数组
    val planes = arrayOf(
        Image.Plane(yBuffer, yStride, 1),
        Image.Plane(uBuffer, uvStride, 2),
        Image.Plane(vBuffer, uvStride, 2)
    )

    // 创建Image对象
    return Image.createImage(width, height, format, planes, 0)
}

关键注意事项:

  • android.media.Image是有生命周期的,使用完毕后一定要调用image.close()释放资源,避免内存泄漏。
  • 如果你的Bitmap不是ARGB_8888格式,建议先转换:bitmap.copy(Bitmap.Config.ARGB_8888, false),这样转换效率更高。
  • YUV转换公式可以根据需求调整,不同的公式可能会导致图像颜色略有差异。

内容的提问来源于stack exchange,提问作者Surya Tej

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:19:48