如何将从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时的对齐问题。
步骤:
- 创建
ImageWriter,指定输出的尺寸、格式和缓存数量。 - 从
ImageWriter中取出一个可写入的Image对象。 - 将Bitmap的ARGB像素转换为YUV_420_888的Y、U、V三分量字节数据。
- 把三分量数据分别写入
Image的三个Plane(通道)中。 - 提交
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
相关产品推荐
相关产品推荐

