1 /* 2 * Copyright 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.integration.extensions.utils 18 19 import android.graphics.ImageFormat 20 import android.graphics.Rect 21 import android.graphics.YuvImage 22 import android.media.Image 23 import androidx.annotation.IntRange 24 import androidx.camera.core.ImageProxy 25 import java.io.ByteArrayOutputStream 26 27 /** Image util functions */ 28 object ImageUtil { 29 30 /** Converts JPEG [Image] to [ByteArray] */ 31 @JvmStatic jpegImageToJpegByteArraynull32 fun jpegImageToJpegByteArray(image: Image): ByteArray { 33 require(image.format == ImageFormat.JPEG) { 34 "Incorrect image format of the input image proxy: ${image.format}" 35 } 36 val planes = image.planes 37 val buffer = planes[0].buffer 38 val data = ByteArray(buffer.capacity()) 39 buffer.rewind() 40 buffer[data] 41 return data 42 } 43 44 /** 45 * Converts YUV_420_888 [ImageProxy] to JPEG byte array. The input YUV_420_888 image will be 46 * cropped if a non-null crop rectangle is specified. The output JPEG byte array will be 47 * compressed by the specified quality value. 48 */ 49 @JvmStatic yuvImageToJpegByteArraynull50 fun yuvImageToJpegByteArray( 51 image: Image, 52 @IntRange(from = 1, to = 100) jpegQuality: Int 53 ): ByteArray { 54 require(image.format == ImageFormat.YUV_420_888) { 55 "Incorrect image format of the input image proxy: ${image.format}" 56 } 57 return nv21ToJpeg(yuv_420_888toNv21(image), image.width, image.height, jpegQuality) 58 } 59 60 /** Converts nv21 byte array to JPEG format. */ 61 @JvmStatic nv21ToJpegnull62 private fun nv21ToJpeg( 63 nv21: ByteArray, 64 width: Int, 65 height: Int, 66 @IntRange(from = 1, to = 100) jpegQuality: Int 67 ): ByteArray { 68 val out = ByteArrayOutputStream() 69 val yuv = YuvImage(nv21, ImageFormat.NV21, width, height, null) 70 val success = yuv.compressToJpeg(Rect(0, 0, width, height), jpegQuality, out) 71 72 if (!success) { 73 throw RuntimeException("YuvImage failed to encode jpeg.") 74 } 75 return out.toByteArray() 76 } 77 78 /** Converts a YUV [Image] to NV21 byte array. */ 79 @JvmStatic yuv_420_888toNv21null80 private fun yuv_420_888toNv21(image: Image): ByteArray { 81 require(image.format == ImageFormat.YUV_420_888) { 82 "Incorrect image format of the input image proxy: ${image.format}" 83 } 84 85 val yPlane = image.planes[0] 86 val uPlane = image.planes[1] 87 val vPlane = image.planes[2] 88 val yBuffer = yPlane.buffer 89 val uBuffer = uPlane.buffer 90 val vBuffer = vPlane.buffer 91 yBuffer.rewind() 92 uBuffer.rewind() 93 vBuffer.rewind() 94 val ySize = yBuffer.remaining() 95 var position = 0 96 // TODO(b/115743986): Pull these bytes from a pool instead of allocating for every image. 97 val nv21 = ByteArray(ySize + image.width * image.height / 2) 98 99 // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped. 100 for (row in 0 until image.height) { 101 yBuffer[nv21, position, image.width] 102 position += image.width 103 yBuffer.position(Math.min(ySize, yBuffer.position() - image.width + yPlane.rowStride)) 104 } 105 val chromaHeight = image.height / 2 106 val chromaWidth = image.width / 2 107 val vRowStride = vPlane.rowStride 108 val uRowStride = uPlane.rowStride 109 val vPixelStride = vPlane.pixelStride 110 val uPixelStride = uPlane.pixelStride 111 112 // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to 113 // perform faster bulk gets from the byte buffers. 114 val vLineBuffer = ByteArray(vRowStride) 115 val uLineBuffer = ByteArray(uRowStride) 116 for (row in 0 until chromaHeight) { 117 vBuffer[vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining())] 118 uBuffer[uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining())] 119 var vLineBufferPosition = 0 120 var uLineBufferPosition = 0 121 for (col in 0 until chromaWidth) { 122 nv21[position++] = vLineBuffer[vLineBufferPosition] 123 nv21[position++] = uLineBuffer[uLineBufferPosition] 124 vLineBufferPosition += vPixelStride 125 uLineBufferPosition += uPixelStride 126 } 127 } 128 return nv21 129 } 130 } 131