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
17package android.media;
18
19import android.graphics.ImageFormat;
20import android.graphics.PixelFormat;
21import android.media.Image.Plane;
22import android.util.Size;
23
24import libcore.io.Memory;
25
26import java.nio.ByteBuffer;
27
28/**
29 * Package private utility class for hosting commonly used Image related methods.
30 */
31class 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     */
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     */
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     */
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
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
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