1 /* 2 * Copyright 2015 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 android.media; 18 19 import android.graphics.ImageFormat; 20 import android.graphics.PixelFormat; 21 import android.hardware.HardwareBuffer; 22 import android.media.Image.Plane; 23 import android.util.Size; 24 25 import libcore.io.Memory; 26 27 import java.nio.ByteBuffer; 28 29 /** 30 * Package private utility class for hosting commonly used Image related methods. 31 */ 32 class ImageUtils { 33 34 /** 35 * Only a subset of the formats defined in 36 * {@link android.graphics.ImageFormat ImageFormat} and 37 * {@link android.graphics.PixelFormat PixelFormat} are supported by 38 * ImageReader. When reading RGB data from a surface, the formats defined in 39 * {@link android.graphics.PixelFormat PixelFormat} can be used; when 40 * reading YUV, JPEG, HEIC or raw sensor data (for example, from the camera 41 * or video decoder), formats from {@link android.graphics.ImageFormat ImageFormat} 42 * are used. 43 */ getNumPlanesForFormat(int format)44 public static int getNumPlanesForFormat(int format) { 45 switch (format) { 46 case ImageFormat.YV12: 47 case ImageFormat.YUV_420_888: 48 case ImageFormat.NV21: 49 case ImageFormat.YCBCR_P010: 50 return 3; 51 case ImageFormat.NV16: 52 return 2; 53 case PixelFormat.RGB_565: 54 case PixelFormat.RGBA_8888: 55 case PixelFormat.RGBX_8888: 56 case PixelFormat.RGB_888: 57 case ImageFormat.JPEG: 58 case ImageFormat.YUY2: 59 case ImageFormat.Y8: 60 case ImageFormat.Y16: 61 case ImageFormat.RAW_SENSOR: 62 case ImageFormat.RAW_PRIVATE: 63 case ImageFormat.RAW10: 64 case ImageFormat.RAW12: 65 case ImageFormat.DEPTH16: 66 case ImageFormat.DEPTH_POINT_CLOUD: 67 case ImageFormat.RAW_DEPTH: 68 case ImageFormat.RAW_DEPTH10: 69 case ImageFormat.DEPTH_JPEG: 70 case ImageFormat.HEIC: 71 return 1; 72 case ImageFormat.PRIVATE: 73 return 0; 74 default: 75 throw new UnsupportedOperationException( 76 String.format("Invalid format specified %d", format)); 77 } 78 } 79 80 /** 81 * Only a subset of the formats defined in 82 * {@link android.graphics.HardwareBuffer.Format} constants are supported by ImageReader. 83 */ getNumPlanesForHardwareBufferFormat(int hardwareBufferFormat)84 public static int getNumPlanesForHardwareBufferFormat(int hardwareBufferFormat) { 85 switch(hardwareBufferFormat) { 86 case HardwareBuffer.YCBCR_420_888: 87 return 3; 88 case HardwareBuffer.RGBA_8888: 89 case HardwareBuffer.RGBX_8888: 90 case HardwareBuffer.RGB_888: 91 case HardwareBuffer.RGB_565: 92 case HardwareBuffer.RGBA_FP16: 93 case HardwareBuffer.RGBA_1010102: 94 case HardwareBuffer.BLOB: 95 case HardwareBuffer.D_16: 96 case HardwareBuffer.D_24: 97 case HardwareBuffer.DS_24UI8: 98 case HardwareBuffer.D_FP32: 99 case HardwareBuffer.DS_FP32UI8: 100 case HardwareBuffer.S_UI8: 101 return 1; 102 default: 103 throw new UnsupportedOperationException( 104 String.format("Invalid hardwareBuffer format specified %d", 105 hardwareBufferFormat)); 106 } 107 } 108 /** 109 * <p> 110 * Copy source image data to destination Image. 111 * </p> 112 * <p> 113 * Only support the copy between two non-{@link ImageFormat#PRIVATE PRIVATE} format 114 * images with same properties (format, size, etc.). The data from the 115 * source image will be copied to the byteBuffers from the destination Image 116 * starting from position zero, and the destination image will be rewound to 117 * zero after copy is done. 118 * </p> 119 * 120 * @param src The source image to be copied from. 121 * @param dst The destination image to be copied to. 122 * @throws IllegalArgumentException If the source and destination images 123 * have different format, or one of the images is not copyable. 124 */ imageCopy(Image src, Image dst)125 public static void imageCopy(Image src, Image dst) { 126 if (src == null || dst == null) { 127 throw new IllegalArgumentException("Images should be non-null"); 128 } 129 if (src.getFormat() != dst.getFormat()) { 130 throw new IllegalArgumentException("Src and dst images should have the same format"); 131 } 132 if (src.getFormat() == ImageFormat.PRIVATE || 133 dst.getFormat() == ImageFormat.PRIVATE) { 134 throw new IllegalArgumentException("PRIVATE format images are not copyable"); 135 } 136 if (src.getFormat() == ImageFormat.RAW_PRIVATE) { 137 throw new IllegalArgumentException( 138 "Copy of RAW_OPAQUE format has not been implemented"); 139 } 140 if (src.getFormat() == ImageFormat.RAW_DEPTH) { 141 throw new IllegalArgumentException( 142 "Copy of RAW_DEPTH format has not been implemented"); 143 } 144 if (src.getFormat() == ImageFormat.RAW_DEPTH10) { 145 throw new IllegalArgumentException( 146 "Copy of RAW_DEPTH10 format has not been implemented"); 147 } 148 if (!(dst.getOwner() instanceof ImageWriter)) { 149 throw new IllegalArgumentException("Destination image is not from ImageWriter. Only" 150 + " the images from ImageWriter are writable"); 151 } 152 Size srcSize = new Size(src.getWidth(), src.getHeight()); 153 Size dstSize = new Size(dst.getWidth(), dst.getHeight()); 154 if (!srcSize.equals(dstSize)) { 155 throw new IllegalArgumentException("source image size " + srcSize + " is different" 156 + " with " + "destination image size " + dstSize); 157 } 158 159 Plane[] srcPlanes = src.getPlanes(); 160 Plane[] dstPlanes = dst.getPlanes(); 161 ByteBuffer srcBuffer = null; 162 ByteBuffer dstBuffer = null; 163 for (int i = 0; i < srcPlanes.length; i++) { 164 int srcRowStride = srcPlanes[i].getRowStride(); 165 int dstRowStride = dstPlanes[i].getRowStride(); 166 srcBuffer = srcPlanes[i].getBuffer(); 167 dstBuffer = dstPlanes[i].getBuffer(); 168 if (!(srcBuffer.isDirect() && dstBuffer.isDirect())) { 169 throw new IllegalArgumentException("Source and destination ByteBuffers must be" 170 + " direct byteBuffer!"); 171 } 172 if (srcPlanes[i].getPixelStride() != dstPlanes[i].getPixelStride()) { 173 throw new IllegalArgumentException("Source plane image pixel stride " + 174 srcPlanes[i].getPixelStride() + 175 " must be same as destination image pixel stride " + 176 dstPlanes[i].getPixelStride()); 177 } 178 179 int srcPos = srcBuffer.position(); 180 srcBuffer.rewind(); 181 dstBuffer.rewind(); 182 if (srcRowStride == dstRowStride) { 183 // Fast path, just copy the content if the byteBuffer all together. 184 dstBuffer.put(srcBuffer); 185 } else { 186 // Source and destination images may have different alignment requirements, 187 // therefore may have different strides. Copy row by row for such case. 188 int srcOffset = srcBuffer.position(); 189 int dstOffset = dstBuffer.position(); 190 Size effectivePlaneSize = getEffectivePlaneSizeForImage(src, i); 191 int srcByteCount = effectivePlaneSize.getWidth() * srcPlanes[i].getPixelStride(); 192 for (int row = 0; row < effectivePlaneSize.getHeight(); row++) { 193 if (row == effectivePlaneSize.getHeight() - 1) { 194 // Special case for NV21 backed YUV420_888: need handle the last row 195 // carefully to avoid memory corruption. Check if we have enough bytes to 196 // copy. 197 int remainingBytes = srcBuffer.remaining() - srcOffset; 198 if (srcByteCount > remainingBytes) { 199 srcByteCount = remainingBytes; 200 } 201 } 202 directByteBufferCopy(srcBuffer, srcOffset, dstBuffer, dstOffset, srcByteCount); 203 srcOffset += srcRowStride; 204 dstOffset += dstRowStride; 205 } 206 } 207 208 srcBuffer.position(srcPos); 209 dstBuffer.rewind(); 210 } 211 } 212 213 /** 214 * Return the estimated native allocation size in bytes based on width, height, format, 215 * and number of images. 216 * 217 * <p>This is a very rough estimation and should only be used for native allocation 218 * registration in VM so it can be accounted for during GC.</p> 219 * 220 * @param width The width of the images. 221 * @param height The height of the images. 222 * @param format The format of the images. 223 * @param numImages The number of the images. 224 */ getEstimatedNativeAllocBytes(int width, int height, int format, int numImages)225 public static int getEstimatedNativeAllocBytes(int width, int height, int format, 226 int numImages) { 227 double estimatedBytePerPixel; 228 switch (format) { 229 // 10x compression from RGB_888 230 case ImageFormat.JPEG: 231 case ImageFormat.DEPTH_POINT_CLOUD: 232 case ImageFormat.DEPTH_JPEG: 233 case ImageFormat.HEIC: 234 estimatedBytePerPixel = 0.3; 235 break; 236 case ImageFormat.Y8: 237 estimatedBytePerPixel = 1.0; 238 break; 239 case ImageFormat.RAW10: 240 case ImageFormat.RAW_DEPTH10: 241 estimatedBytePerPixel = 1.25; 242 break; 243 case ImageFormat.YV12: 244 case ImageFormat.YUV_420_888: 245 case ImageFormat.NV21: 246 case ImageFormat.RAW12: 247 case ImageFormat.PRIVATE: // A rough estimate because the real size is unknown. 248 estimatedBytePerPixel = 1.5; 249 break; 250 case ImageFormat.NV16: 251 case PixelFormat.RGB_565: 252 case ImageFormat.YUY2: 253 case ImageFormat.Y16: 254 case ImageFormat.RAW_DEPTH: 255 case ImageFormat.RAW_SENSOR: 256 case ImageFormat.RAW_PRIVATE: // round estimate, real size is unknown 257 case ImageFormat.DEPTH16: 258 case ImageFormat.YCBCR_P010: 259 estimatedBytePerPixel = 2.0; 260 break; 261 case PixelFormat.RGB_888: 262 estimatedBytePerPixel = 3.0; 263 break; 264 case PixelFormat.RGBA_8888: 265 case PixelFormat.RGBX_8888: 266 estimatedBytePerPixel = 4.0; 267 break; 268 default: 269 throw new UnsupportedOperationException( 270 String.format("Invalid format specified %d", format)); 271 } 272 273 return (int)(width * height * estimatedBytePerPixel * numImages); 274 } 275 getEffectivePlaneSizeForImage(Image image, int planeIdx)276 private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) { 277 switch (image.getFormat()) { 278 case ImageFormat.YCBCR_P010: 279 case ImageFormat.YV12: 280 case ImageFormat.YUV_420_888: 281 case ImageFormat.NV21: 282 if (planeIdx == 0) { 283 return new Size(image.getWidth(), image.getHeight()); 284 } else { 285 return new Size(image.getWidth() / 2, image.getHeight() / 2); 286 } 287 case ImageFormat.NV16: 288 if (planeIdx == 0) { 289 return new Size(image.getWidth(), image.getHeight()); 290 } else { 291 return new Size(image.getWidth(), image.getHeight() / 2); 292 } 293 case PixelFormat.RGB_565: 294 case PixelFormat.RGBA_8888: 295 case PixelFormat.RGBX_8888: 296 case PixelFormat.RGB_888: 297 case ImageFormat.JPEG: 298 case ImageFormat.YUY2: 299 case ImageFormat.Y8: 300 case ImageFormat.Y16: 301 case ImageFormat.RAW_SENSOR: 302 case ImageFormat.RAW10: 303 case ImageFormat.RAW12: 304 case ImageFormat.RAW_DEPTH: 305 case ImageFormat.RAW_DEPTH10: 306 case ImageFormat.HEIC: 307 return new Size(image.getWidth(), image.getHeight()); 308 case ImageFormat.PRIVATE: 309 return new Size(0, 0); 310 default: 311 throw new UnsupportedOperationException( 312 String.format("Invalid image format %d", image.getFormat())); 313 } 314 } 315 directByteBufferCopy(ByteBuffer srcBuffer, int srcOffset, ByteBuffer dstBuffer, int dstOffset, int srcByteCount)316 private static void directByteBufferCopy(ByteBuffer srcBuffer, int srcOffset, 317 ByteBuffer dstBuffer, int dstOffset, int srcByteCount) { 318 Memory.memmove(dstBuffer, dstOffset, srcBuffer, srcOffset, srcByteCount); 319 } 320 } 321