Мне нужно правильно получить растровое изображение из ImageProxy, и я попробовал несколько методов. Я пробовал следующий метод преобразования ImageProxy в растровое изображение:
class CustomBitmapConverter(context: Context) {
private val rs = RenderScript.create(context)
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private val scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs))
private var pixelCount: Int = -1
private lateinit var yuvBuffer: ByteBuffer
private lateinit var inputAllocation: Allocation
private lateinit var outputAllocation: Allocation
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@Synchronized
fun convertYuvToBitmap(image: Image): Bitmap {
val conf = Bitmap.Config.ARGB_8888
val output = Bitmap.createBitmap(image.width, image.height, conf)
// Ensure that the intermediate output byte buffer is allocated
if (!::yuvBuffer.isInitialized) {
pixelCount = image.cropRect.width() * image.cropRect.height()
yuvBuffer = ByteBuffer.allocateDirect(
pixelCount * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8)
}
// Get the YUV data in byte array form
imageToByteBuffer(image, yuvBuffer)
// Ensure that the RenderScript inputs and outputs are allocated
if (!::inputAllocation.isInitialized) {
inputAllocation = Allocation.createSized(rs, Element.U8(rs), yuvBuffer.array().size)
}
if (!::outputAllocation.isInitialized) {
outputAllocation = Allocation.createFromBitmap(rs, output)
}
// Convert YUV to RGB
inputAllocation.copyFrom(yuvBuffer.array())
scriptYuvToRgb.setInput(inputAllocation)
scriptYuvToRgb.forEach(outputAllocation)
outputAllocation.copyTo(output)
return output
}
@RequiresApi(Build.VERSION_CODES.KITKAT)
private fun imageToByteBuffer(image: Image, outputBuffer: ByteBuffer) {
assert(image.format == ImageFormat.YUV_420_888)
val imageCrop = image.cropRect
val imagePlanes = image.planes
val rowData = ByteArray(imagePlanes.first().rowStride)
imagePlanes.forEachIndexed { planeIndex, plane ->
val outputStride: Int
var outputOffset: Int
when (planeIndex) {
0 -> {
outputStride = 1
outputOffset = 0
}
1 -> {
outputStride = 2
outputOffset = pixelCount + 1
}
2 -> {
outputStride = 2
outputOffset = pixelCount
}
else -> {
return@forEachIndexed
}
}
val buffer = plane.buffer
val rowStride = plane.rowStride
val pixelStride = plane.pixelStride
// We have to divide the width and height by two if it's not the Y plane
val planeCrop = if (planeIndex == 0) {
imageCrop
} else {
Rect(
imageCrop.left / 2,
imageCrop.top / 2,
imageCrop.right / 2,
imageCrop.bottom / 2
)
}
val planeWidth = planeCrop.width()
val planeHeight = planeCrop.height()
buffer.position(rowStride * planeCrop.top + pixelStride * planeCrop.left)
for (row in 0 until planeHeight) {
val length: Int
if (pixelStride == 1 && outputStride == 1) {
// When there is a single stride value for pixel and output, we can just copy
// the entire row in a single step
length = planeWidth
buffer.get(outputBuffer.array(), outputOffset, length)
outputOffset += length
} else {
// When either pixel or output have a stride > 1 we must copy pixel by pixel
length = (planeWidth - 1) * pixelStride + 1
buffer.get(rowData, 0, length)
for (col in 0 until planeWidth) {
outputBuffer.array()[outputOffset] = rowData[col * pixelStride]
outputOffset += outputStride
}
}
if (row < planeHeight - 1) {
buffer.position(buffer.position() + rowStride - length)
}
}
}
}
Однако это значительно снижает качество, и вы изображение теряет резкость . Затем я попробовал следующее преобразование:
// Image → JPEG
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static byte[] imageToByteArray(Image image) {
byte[] data = null;
if (image.getFormat() == ImageFormat.JPEG) {
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
data = new byte[buffer.capacity()];
buffer.get(data);
return data;
} else if (image.getFormat() == ImageFormat.YUV_420_888) {
data = NV21toJPEG(YUV_420_888toNV21(image),
image.getWidth(), image.getHeight());
}
return data;
}
// YUV_420_888 → NV21
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private static byte[] YUV_420_888toNV21(Image image) {
byte[] nv21;
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
nv21 = new byte[ySize + uSize + vSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
return nv21;
}
// NV21 → JPEG
private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
return out.toByteArray();
}
Это сохраняет все пропорции, но на таком устройстве, как Xioami Mi A2, изображение имеет красный / синий цвета по всей поверхности , поэтому вы даже не можете видеть что-нибудь там ... В ImageProxy определенно есть какая-то ошибка.
Тогда я сделал что-то более простое. Я получаю растровое изображение прямо из PreviewView . Я использую этот код:
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@SuppressLint("UnsafeExperimentalUsageError")
@Override
public void analyze(@NonNull ImageProxy image) {
final Bitmap bitmap = previewView.getBitmap();
if(bitmap==null){
return;
}
int rotation = CameraUtils.INSTANCE.getRotationDegrees(previewView.getDisplay().getRotation());
}
Тогда мне даже не нужно обрабатывать поворот, потому что возвращаемое растровое изображение имеет точно такое же вращение, как я установил его с помощью конструктора предварительного просмотра:
internal fun buildCameraXPreview(screenAspectRatio: Int, cameraRotation: Int): Preview {
return Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(cameraRotation)
.build()
}
Работает хорошо во всех случаях. Качество, размер такие же, как при предварительном просмотре. Никаких зависаний и эт c.
Теперь мне нужен ваш отзыв. Это жизнеспособное решение? Есть ли альтернативы? Возможно, кому-то удалось исправить преобразование из Proxy в растровое изображение ...
EDIT Я смотрел "рекомендуемый" подход в одном из репозиториев от Google здесь
Однако это даже не кажется рекомендуемым подходом, потому что они буквально пометили это как экспериментальное и только для демонстрации ...