Alan Viverette | 3da604b | 2020-06-10 18:34:39 +0000 | [diff] [blame] | 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 | */ |
| 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 | */ |
| 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 | */ |
| 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 | |
| 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 | |
| 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 | } |