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