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