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