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