blob: d8a0bb334c539d6a71311e02f16f98e04a71455c [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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, 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}