1 /*
2  * Copyright 2021 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 androidx.camera.core;
18 
19 import static androidx.camera.core.ImageProcessingUtil.Result.ERROR_CONVERSION;
20 import static androidx.camera.core.ImageProcessingUtil.Result.SUCCESS;
21 
22 import android.graphics.Bitmap;
23 import android.graphics.ImageFormat;
24 import android.media.Image;
25 import android.media.ImageWriter;
26 import android.os.Build;
27 import android.util.Log;
28 import android.view.Surface;
29 
30 import androidx.annotation.IntRange;
31 import androidx.annotation.RequiresApi;
32 import androidx.annotation.RestrictTo;
33 import androidx.camera.core.impl.ImageOutputConfig;
34 import androidx.camera.core.impl.ImageReaderProxy;
35 import androidx.camera.core.internal.compat.ImageWriterCompat;
36 import androidx.camera.core.internal.utils.ImageUtil;
37 import androidx.core.util.Preconditions;
38 
39 import org.jspecify.annotations.NonNull;
40 import org.jspecify.annotations.Nullable;
41 
42 import java.nio.ByteBuffer;
43 import java.util.Locale;
44 
45 /**
46  * Utility class to convert an {@link Image} from YUV to RGB.
47  *
48  */
49 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
50 public final class ImageProcessingUtil {
51 
52     private static final String TAG = "ImageProcessingUtil";
53     public static final String JNI_LIB_NAME = "image_processing_util_jni";
54     private static int sImageCount = 0;
55 
56     static {
57         System.loadLibrary(JNI_LIB_NAME);
58     }
59 
60     enum Result {
61         UNKNOWN,
62         SUCCESS,
63         ERROR_CONVERSION,  // Native conversion error.
64     }
65 
ImageProcessingUtil()66     private ImageProcessingUtil() {
67     }
68 
69     /**
70      * Wraps a JPEG byte array with an {@link Image}.
71      *
72      * <p>This methods wraps the given byte array with an {@link Image} via the help of the
73      * given ImageReader. The image format of the ImageReader has to be JPEG, and the JPEG image
74      * size has to match the size of the ImageReader.
75      */
convertJpegBytesToImage( @onNull ImageReaderProxy jpegImageReaderProxy, byte @NonNull [] jpegBytes)76     public static @Nullable ImageProxy convertJpegBytesToImage(
77             @NonNull ImageReaderProxy jpegImageReaderProxy,
78             byte @NonNull [] jpegBytes) {
79         Preconditions.checkArgument(jpegImageReaderProxy.getImageFormat() == ImageFormat.JPEG);
80         Preconditions.checkNotNull(jpegBytes);
81 
82         Surface surface = jpegImageReaderProxy.getSurface();
83         Preconditions.checkNotNull(surface);
84 
85         if (nativeWriteJpegToSurface(jpegBytes, surface) != 0) {
86             Logger.e(TAG, "Failed to enqueue JPEG image.");
87             return null;
88         }
89 
90         final ImageProxy imageProxy = jpegImageReaderProxy.acquireLatestImage();
91         if (imageProxy == null) {
92             Logger.e(TAG, "Failed to get acquire JPEG image.");
93         }
94         return imageProxy;
95     }
96 
97 
98     /**
99      * Copies information from a given Bitmap to the address of the ByteBuffer
100      *
101      * @param bitmap            source bitmap
102      * @param byteBuffer        destination ByteBuffer
103      * @param bufferStride      the stride of the ByteBuffer
104      */
copyBitmapToByteBuffer(@onNull Bitmap bitmap, @NonNull ByteBuffer byteBuffer, int bufferStride)105     public static void copyBitmapToByteBuffer(@NonNull Bitmap bitmap,
106             @NonNull ByteBuffer byteBuffer, int bufferStride) {
107         int bitmapStride = bitmap.getRowBytes();
108         int width = bitmap.getWidth();
109         int height = bitmap.getHeight();
110         nativeCopyBetweenByteBufferAndBitmap(bitmap, byteBuffer, bitmapStride, bufferStride, width,
111                 height, false);
112     }
113 
114     /**
115      * Copies information from a ByteBuffer to the address of the Bitmap
116      *
117      * @param bitmap            destination Bitmap
118      * @param byteBuffer        source ByteBuffer
119      * @param bufferStride      the stride of the ByteBuffer
120      *
121      */
copyByteBufferToBitmap(@onNull Bitmap bitmap, @NonNull ByteBuffer byteBuffer, int bufferStride)122     public static void copyByteBufferToBitmap(@NonNull Bitmap bitmap,
123             @NonNull ByteBuffer byteBuffer, int bufferStride) {
124         int bitmapStride = bitmap.getRowBytes();
125         int width = bitmap.getWidth();
126         int height = bitmap.getHeight();
127         nativeCopyBetweenByteBufferAndBitmap(bitmap, byteBuffer, bufferStride, bitmapStride, width,
128                 height, true);
129     }
130 
131     /**
132      * Writes a JPEG bytes data as an Image into the Surface. Returns true if it succeeds and false
133      * otherwise.
134      */
writeJpegBytesToSurface( @onNull Surface surface, byte @NonNull [] jpegBytes)135     public static boolean writeJpegBytesToSurface(
136             @NonNull Surface surface,
137             byte @NonNull [] jpegBytes) {
138         Preconditions.checkNotNull(jpegBytes);
139         Preconditions.checkNotNull(surface);
140 
141         if (nativeWriteJpegToSurface(jpegBytes, surface) != 0) {
142             Logger.e(TAG, "Failed to enqueue JPEG image.");
143             return false;
144         }
145         return true;
146     }
147 
148     /**
149      * Convert a YUV_420_888 Image to a JPEG bytes data as an Image into the Surface.
150      *
151      * <p>Returns true if it succeeds and false otherwise.
152      */
convertYuvToJpegBytesIntoSurface( @onNull Image image, @IntRange(from = 1, to = 100) int jpegQuality, @ImageOutputConfig.RotationDegreesValue int rotationDegrees, @NonNull Surface outputSurface)153     public static boolean convertYuvToJpegBytesIntoSurface(
154             @NonNull Image image,
155             @IntRange(from = 1, to = 100) int jpegQuality,
156             @ImageOutputConfig.RotationDegreesValue int rotationDegrees,
157             @NonNull Surface outputSurface) {
158         return convertYuvToJpegBytesIntoSurface(new AndroidImageProxy(image), jpegQuality,
159                 rotationDegrees, outputSurface);
160     }
161 
162         /**
163          * Convert a YUV_420_888 ImageProxy to a JPEG bytes data as an Image into the Surface.
164          *
165          * <p>Returns true if it succeeds and false otherwise.
166          */
convertYuvToJpegBytesIntoSurface( @onNull ImageProxy imageProxy, @IntRange(from = 1, to = 100) int jpegQuality, @ImageOutputConfig.RotationDegreesValue int rotationDegrees, @NonNull Surface outputSurface)167     public static boolean convertYuvToJpegBytesIntoSurface(
168             @NonNull ImageProxy imageProxy,
169             @IntRange(from = 1, to = 100) int jpegQuality,
170             @ImageOutputConfig.RotationDegreesValue int rotationDegrees,
171             @NonNull Surface outputSurface) {
172         try {
173             byte[] jpegBytes =
174                     ImageUtil.yuvImageToJpegByteArray(
175                             imageProxy, null, jpegQuality, rotationDegrees);
176             return writeJpegBytesToSurface(outputSurface,
177                     jpegBytes);
178         } catch (ImageUtil.CodecFailedException e) {
179             Logger.e(TAG, "Failed to encode YUV to JPEG", e);
180             return false;
181         }
182     }
183 
184     /**
185      * Converts image proxy in YUV to RGB.
186      *
187      * Currently this config supports the devices which generated NV21, NV12, I420 YUV layout,
188      * otherwise the input YUV layout will be converted to NV12 first and then to RGBA_8888 as a
189      * fallback.
190      *
191      * @param imageProxy           input image proxy in YUV.
192      * @param rgbImageReaderProxy  output image reader proxy in RGB.
193      * @param rgbConvertedBuffer   intermediate image buffer for format conversion.
194      * @param rotationDegrees      output image rotation degrees.
195      * @param onePixelShiftEnabled true if one pixel shift should be applied, otherwise false.
196      * @return output image proxy in RGB.
197      */
convertYUVToRGB( @onNull ImageProxy imageProxy, @NonNull ImageReaderProxy rgbImageReaderProxy, @Nullable ByteBuffer rgbConvertedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees, boolean onePixelShiftEnabled)198     public static @Nullable ImageProxy convertYUVToRGB(
199             @NonNull ImageProxy imageProxy,
200             @NonNull ImageReaderProxy rgbImageReaderProxy,
201             @Nullable ByteBuffer rgbConvertedBuffer,
202             @IntRange(from = 0, to = 359) int rotationDegrees,
203             boolean onePixelShiftEnabled) {
204         if (!isSupportedYUVFormat(imageProxy)) {
205             Logger.e(TAG, "Unsupported format for YUV to RGB");
206             return null;
207         }
208         long startTimeMillis = System.currentTimeMillis();
209 
210         if (!isSupportedRotationDegrees(rotationDegrees)) {
211             Logger.e(TAG, "Unsupported rotation degrees for rotate RGB");
212             return null;
213         }
214 
215         // Convert YUV To RGB and write data to surface
216         Result result = convertYUVToRGBInternal(
217                 imageProxy,
218                 rgbImageReaderProxy.getSurface(),
219                 rgbConvertedBuffer,
220                 rotationDegrees,
221                 onePixelShiftEnabled);
222 
223         if (result == ERROR_CONVERSION) {
224             Logger.e(TAG, "YUV to RGB conversion failure");
225             return null;
226         }
227         if (Log.isLoggable("MH", Log.DEBUG)) {
228             // The log is used to profile the ImageProcessing performance and only shows in the
229             // mobile harness tests.
230             Logger.d(TAG, String.format(Locale.US,
231                     "Image processing performance profiling, duration: [%d], image count: %d",
232                     (System.currentTimeMillis() - startTimeMillis), sImageCount));
233             sImageCount++;
234         }
235 
236         // Retrieve ImageProxy in RGB
237         final ImageProxy rgbImageProxy = rgbImageReaderProxy.acquireLatestImage();
238         if (rgbImageProxy == null) {
239             Logger.e(TAG, "YUV to RGB acquireLatestImage failure");
240             return null;
241         }
242 
243         // Close ImageProxy for the next image
244         SingleCloseImageProxy wrappedRgbImageProxy = new SingleCloseImageProxy(rgbImageProxy);
245         wrappedRgbImageProxy.addOnImageCloseListener(image -> {
246             // Close YUV image proxy when RGB image proxy is closed by app.
247             if (rgbImageProxy != null && imageProxy != null) {
248                 imageProxy.close();
249             }
250         });
251         return wrappedRgbImageProxy;
252     }
253 
254     /**
255      * Converts image proxy in YUV to {@link Bitmap}.
256      *
257      * <p> Different from {@link ImageProcessingUtil#convertYUVToRGB(
258      * ImageProxy, ImageReaderProxy, ByteBuffer, int, boolean)}, this function converts to
259      * {@link Bitmap} in RGBA directly. If input format is invalid,
260      * {@link IllegalArgumentException} will be thrown. If the conversion to bitmap failed,
261      * {@link UnsupportedOperationException} will be thrown.
262      *
263      * @param imageProxy input image proxy in YUV.
264      * @return bitmap output bitmap in RGBA.
265      */
convertYUVToBitmap(@onNull ImageProxy imageProxy)266     public static @NonNull Bitmap convertYUVToBitmap(@NonNull ImageProxy imageProxy) {
267         if (imageProxy.getFormat() != ImageFormat.YUV_420_888) {
268             throw new IllegalArgumentException("Input image format must be YUV_420_888");
269         }
270 
271         int imageWidth = imageProxy.getWidth();
272         int imageHeight = imageProxy.getHeight();
273         int srcStrideY = imageProxy.getPlanes()[0].getRowStride();
274         int srcStrideU = imageProxy.getPlanes()[1].getRowStride();
275         int srcStrideV = imageProxy.getPlanes()[2].getRowStride();
276         int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride();
277         int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride();
278 
279         Bitmap bitmap = Bitmap.createBitmap(imageProxy.getWidth(),
280                 imageProxy.getHeight(), Bitmap.Config.ARGB_8888);
281         int bitmapStride = bitmap.getRowBytes();
282 
283         int result = nativeConvertAndroid420ToBitmap(
284                 imageProxy.getPlanes()[0].getBuffer(),
285                 srcStrideY,
286                 imageProxy.getPlanes()[1].getBuffer(),
287                 srcStrideU,
288                 imageProxy.getPlanes()[2].getBuffer(),
289                 srcStrideV,
290                 srcPixelStrideY,
291                 srcPixelStrideUV,
292                 bitmap,
293                 bitmapStride,
294                 imageWidth,
295                 imageHeight);
296         if (result != 0) {
297             throw new UnsupportedOperationException("YUV to RGB conversion failed");
298         }
299         return bitmap;
300     }
301 
302     /**
303      * Applies one pixel shift workaround for YUV image
304      *
305      * @param imageProxy input image proxy in YUV.
306      * @return true if one pixel shift is applied successfully, otherwise false.
307      */
applyPixelShiftForYUV(@onNull ImageProxy imageProxy)308     public static boolean applyPixelShiftForYUV(@NonNull ImageProxy imageProxy) {
309         if (!isSupportedYUVFormat(imageProxy)) {
310             Logger.e(TAG, "Unsupported format for YUV to RGB");
311             return false;
312         }
313 
314         Result result = applyPixelShiftInternal(imageProxy);
315 
316         if (result == ERROR_CONVERSION) {
317             Logger.e(TAG, "One pixel shift for YUV failure");
318             return false;
319         }
320         return true;
321     }
322 
323     /**
324      * Rotates YUV image proxy.
325      *
326      * @param imageProxy              input image proxy.
327      * @param rotatedImageReaderProxy input image reader proxy.
328      * @param rotatedImageWriter      output image writer.
329      * @param yRotatedBuffer          intermediate image buffer for y plane rotation.
330      * @param uRotatedBuffer          intermediate image buffer for u plane rotation.
331      * @param vRotatedBuffer          intermediate image buffer for v plane rotation.
332      * @param rotationDegrees         output image rotation degrees.
333      * @return rotated image proxy or null if rotation fails or format is not supported.
334      */
rotateYUV( @onNull ImageProxy imageProxy, @NonNull ImageReaderProxy rotatedImageReaderProxy, @NonNull ImageWriter rotatedImageWriter, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees)335     public static @Nullable ImageProxy rotateYUV(
336             @NonNull ImageProxy imageProxy,
337             @NonNull ImageReaderProxy rotatedImageReaderProxy,
338             @NonNull ImageWriter rotatedImageWriter,
339             @NonNull ByteBuffer yRotatedBuffer,
340             @NonNull ByteBuffer uRotatedBuffer,
341             @NonNull ByteBuffer vRotatedBuffer,
342             @IntRange(from = 0, to = 359) int rotationDegrees) {
343         if (!isSupportedYUVFormat(imageProxy)) {
344             Logger.e(TAG, "Unsupported format for rotate YUV");
345             return null;
346         }
347 
348         if (!isSupportedRotationDegrees(rotationDegrees)) {
349             Logger.e(TAG, "Unsupported rotation degrees for rotate YUV");
350             return null;
351         }
352 
353         Result result = ERROR_CONVERSION;
354 
355         // YUV rotation is checking non-zero rotation degrees in java layer to avoid unnecessary
356         // overhead, while RGB rotation is checking in c++ layer.
357         if (Build.VERSION.SDK_INT >= 23 && rotationDegrees > 0) {
358             result = rotateYUVInternal(
359                     imageProxy,
360                     rotatedImageWriter,
361                     yRotatedBuffer,
362                     uRotatedBuffer,
363                     vRotatedBuffer,
364                     rotationDegrees);
365         }
366 
367         if (result == ERROR_CONVERSION) {
368             Logger.e(TAG, "rotate YUV failure");
369             return null;
370         }
371 
372         // Retrieve ImageProxy in rotated YUV
373         ImageProxy rotatedImageProxy = rotatedImageReaderProxy.acquireLatestImage();
374         if (rotatedImageProxy == null) {
375             Logger.e(TAG, "YUV rotation acquireLatestImage failure");
376             return null;
377         }
378 
379         SingleCloseImageProxy wrappedRotatedImageProxy = new SingleCloseImageProxy(
380                 rotatedImageProxy);
381         wrappedRotatedImageProxy.addOnImageCloseListener(image -> {
382             // Close original YUV image proxy when rotated YUV image is closed by app.
383             if (rotatedImageProxy != null && imageProxy != null) {
384                 imageProxy.close();
385             }
386         });
387 
388         return wrappedRotatedImageProxy;
389     }
390 
391 
392     /**
393      * Rotates YUV image proxy and output the NV21 format image proxy with the delegated byte
394      * buffers.
395      *
396      * @param imageProxy              input image proxy.
397      * @param yRotatedBuffer          intermediate image buffer for y plane rotation.
398      * @param uRotatedBuffer          intermediate image buffer for u plane rotation.
399      * @param vRotatedBuffer          intermediate image buffer for v plane rotation.
400      * @param nv21YDelegatedBuffer    delegated image buffer for y plane.
401      * @param nv21UVDelegatedBuffer   delegated image buffer for u/v plane.
402      * @param rotationDegrees         output image rotation degrees.
403      * @return rotated image proxy or null if rotation fails or format is not supported.
404      */
rotateYUVAndConvertToNV21( @onNull ImageProxy imageProxy, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @NonNull ByteBuffer nv21YDelegatedBuffer, @NonNull ByteBuffer nv21UVDelegatedBuffer, @IntRange(from = 0, to = 359) int rotationDegrees)405     public static @Nullable ImageProxy rotateYUVAndConvertToNV21(
406             @NonNull ImageProxy imageProxy,
407             @NonNull ByteBuffer yRotatedBuffer,
408             @NonNull ByteBuffer uRotatedBuffer,
409             @NonNull ByteBuffer vRotatedBuffer,
410             @NonNull ByteBuffer nv21YDelegatedBuffer,
411             @NonNull ByteBuffer nv21UVDelegatedBuffer,
412             @IntRange(from = 0, to = 359) int rotationDegrees) {
413         if (!isSupportedYUVFormat(imageProxy)) {
414             Logger.e(TAG, "Unsupported format for rotate YUV");
415             return null;
416         }
417 
418         if (!isSupportedRotationDegrees(rotationDegrees)) {
419             Logger.e(TAG, "Unsupported rotation degrees for rotate YUV");
420             return null;
421         }
422 
423         // If both rotation and format conversion processing are unnecessary, directly return here.
424         if (rotationDegrees == 0 && isNV21FormatImage(imageProxy)) {
425             return null;
426         }
427 
428         int rotatedWidth =
429                 (rotationDegrees % 180 == 0) ? imageProxy.getWidth() : imageProxy.getHeight();
430         int rotatedHeight =
431                 (rotationDegrees % 180 == 0) ? imageProxy.getHeight() : imageProxy.getWidth();
432 
433         ByteBuffer position1ChildByteBuffer = nativeNewDirectByteBuffer(
434                 nv21UVDelegatedBuffer, 1, nv21UVDelegatedBuffer.capacity());
435 
436         int result = nativeRotateYUV(
437                 imageProxy.getPlanes()[0].getBuffer(),
438                 imageProxy.getPlanes()[0].getRowStride(),
439                 imageProxy.getPlanes()[1].getBuffer(),
440                 imageProxy.getPlanes()[1].getRowStride(),
441                 imageProxy.getPlanes()[2].getBuffer(),
442                 imageProxy.getPlanes()[2].getRowStride(),
443                 imageProxy.getPlanes()[2].getPixelStride(),
444                 nv21YDelegatedBuffer,
445                 rotatedWidth,
446                 1,
447                 position1ChildByteBuffer,
448                 rotatedWidth,
449                 2,
450                 nv21UVDelegatedBuffer,
451                 rotatedWidth,
452                 2,
453                 yRotatedBuffer,
454                 uRotatedBuffer,
455                 vRotatedBuffer,
456                 imageProxy.getWidth(),
457                 imageProxy.getHeight(),
458                 rotationDegrees);
459 
460         if (result != 0) {
461             Logger.e(TAG, "rotate YUV failure");
462             return null;
463         }
464 
465         // Wraps to NV21ImageProxy to make sure that the returned v plane position is in front of u
466         // plane position.
467         return new SingleCloseImageProxy(
468                 new NV21ImageProxy(imageProxy,
469                         nv21YDelegatedBuffer,
470                         position1ChildByteBuffer,
471                         nv21UVDelegatedBuffer,
472                         rotatedWidth,
473                         rotatedHeight,
474                         rotationDegrees));
475     }
476 
isSupportedYUVFormat(@onNull ImageProxy imageProxy)477     private static boolean isSupportedYUVFormat(@NonNull ImageProxy imageProxy) {
478         return imageProxy.getFormat() == ImageFormat.YUV_420_888
479                 && imageProxy.getPlanes().length == 3;
480     }
481 
isSupportedRotationDegrees( @ntRangefrom = 0, to = 359) int rotationDegrees)482     private static boolean isSupportedRotationDegrees(
483             @IntRange(from = 0, to = 359) int rotationDegrees) {
484         return rotationDegrees == 0
485                 || rotationDegrees == 90
486                 || rotationDegrees == 180
487                 || rotationDegrees == 270;
488     }
489 
convertYUVToRGBInternal( @onNull ImageProxy imageProxy, @NonNull Surface surface, @Nullable ByteBuffer rgbConvertedBuffer, @ImageOutputConfig.RotationDegreesValue int rotation, boolean onePixelShiftEnabled)490     private static @NonNull Result convertYUVToRGBInternal(
491             @NonNull ImageProxy imageProxy,
492             @NonNull Surface surface,
493             @Nullable ByteBuffer rgbConvertedBuffer,
494             @ImageOutputConfig.RotationDegreesValue int rotation,
495             boolean onePixelShiftEnabled) {
496         int imageWidth = imageProxy.getWidth();
497         int imageHeight = imageProxy.getHeight();
498         int srcStrideY = imageProxy.getPlanes()[0].getRowStride();
499         int srcStrideU = imageProxy.getPlanes()[1].getRowStride();
500         int srcStrideV = imageProxy.getPlanes()[2].getRowStride();
501         int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride();
502         int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride();
503 
504         int startOffsetY = onePixelShiftEnabled ? srcPixelStrideY : 0;
505         int startOffsetU = onePixelShiftEnabled ? srcPixelStrideUV : 0;
506         int startOffsetV = onePixelShiftEnabled ? srcPixelStrideUV : 0;
507 
508         int result = nativeConvertAndroid420ToABGR(
509                 imageProxy.getPlanes()[0].getBuffer(),
510                 srcStrideY,
511                 imageProxy.getPlanes()[1].getBuffer(),
512                 srcStrideU,
513                 imageProxy.getPlanes()[2].getBuffer(),
514                 srcStrideV,
515                 srcPixelStrideY,
516                 srcPixelStrideUV,
517                 surface,
518                 rgbConvertedBuffer,
519                 imageWidth,
520                 imageHeight,
521                 startOffsetY,
522                 startOffsetU,
523                 startOffsetV,
524                 rotation);
525         if (result != 0) {
526             return ERROR_CONVERSION;
527         }
528         return SUCCESS;
529     }
530 
applyPixelShiftInternal(@onNull ImageProxy imageProxy)531     private static @NonNull Result applyPixelShiftInternal(@NonNull ImageProxy imageProxy) {
532         int imageWidth = imageProxy.getWidth();
533         int imageHeight = imageProxy.getHeight();
534         int srcStrideY = imageProxy.getPlanes()[0].getRowStride();
535         int srcStrideU = imageProxy.getPlanes()[1].getRowStride();
536         int srcStrideV = imageProxy.getPlanes()[2].getRowStride();
537         int srcPixelStrideY = imageProxy.getPlanes()[0].getPixelStride();
538         int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride();
539 
540         int startOffsetY = srcPixelStrideY;
541         int startOffsetU = srcPixelStrideUV;
542         int startOffsetV = srcPixelStrideUV;
543 
544         int result = nativeShiftPixel(
545                 imageProxy.getPlanes()[0].getBuffer(),
546                 srcStrideY,
547                 imageProxy.getPlanes()[1].getBuffer(),
548                 srcStrideU,
549                 imageProxy.getPlanes()[2].getBuffer(),
550                 srcStrideV,
551                 srcPixelStrideY,
552                 srcPixelStrideUV,
553                 imageWidth,
554                 imageHeight,
555                 startOffsetY,
556                 startOffsetU,
557                 startOffsetV);
558         if (result != 0) {
559             return ERROR_CONVERSION;
560         }
561         return SUCCESS;
562     }
563 
564     @RequiresApi(23)
rotateYUVInternal( @onNull ImageProxy imageProxy, @NonNull ImageWriter rotatedImageWriter, @NonNull ByteBuffer yRotatedBuffer, @NonNull ByteBuffer uRotatedBuffer, @NonNull ByteBuffer vRotatedBuffer, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)565     private static @Nullable Result rotateYUVInternal(
566             @NonNull ImageProxy imageProxy,
567             @NonNull ImageWriter rotatedImageWriter,
568             @NonNull ByteBuffer yRotatedBuffer,
569             @NonNull ByteBuffer uRotatedBuffer,
570             @NonNull ByteBuffer vRotatedBuffer,
571             @ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
572         int imageWidth = imageProxy.getWidth();
573         int imageHeight = imageProxy.getHeight();
574         int srcStrideY = imageProxy.getPlanes()[0].getRowStride();
575         int srcStrideU = imageProxy.getPlanes()[1].getRowStride();
576         int srcStrideV = imageProxy.getPlanes()[2].getRowStride();
577         int srcPixelStrideUV = imageProxy.getPlanes()[1].getPixelStride();
578 
579         Image rotatedImage = ImageWriterCompat.dequeueInputImage(rotatedImageWriter);
580         if (rotatedImage == null) {
581             return ERROR_CONVERSION;
582         }
583 
584         int result = nativeRotateYUV(
585                 imageProxy.getPlanes()[0].getBuffer(),
586                 srcStrideY,
587                 imageProxy.getPlanes()[1].getBuffer(),
588                 srcStrideU,
589                 imageProxy.getPlanes()[2].getBuffer(),
590                 srcStrideV,
591                 srcPixelStrideUV,
592                 rotatedImage.getPlanes()[0].getBuffer(),
593                 rotatedImage.getPlanes()[0].getRowStride(),
594                 rotatedImage.getPlanes()[0].getPixelStride(),
595                 rotatedImage.getPlanes()[1].getBuffer(),
596                 rotatedImage.getPlanes()[1].getRowStride(),
597                 rotatedImage.getPlanes()[1].getPixelStride(),
598                 rotatedImage.getPlanes()[2].getBuffer(),
599                 rotatedImage.getPlanes()[2].getRowStride(),
600                 rotatedImage.getPlanes()[2].getPixelStride(),
601                 yRotatedBuffer,
602                 uRotatedBuffer,
603                 vRotatedBuffer,
604                 imageWidth,
605                 imageHeight,
606                 rotationDegrees);
607 
608         if (result != 0) {
609             return ERROR_CONVERSION;
610         }
611 
612         ImageWriterCompat.queueInputImage(rotatedImageWriter, rotatedImage);
613         return SUCCESS;
614     }
615 
616     /**
617      * Checks whether the image proxy data is formatted in NV21.
618      */
isNV21FormatImage(@onNull ImageProxy imageProxy)619     public static boolean isNV21FormatImage(@NonNull ImageProxy imageProxy) {
620         if (imageProxy.getPlanes().length != 3) {
621             return false;
622         }
623         if (imageProxy.getPlanes()[1].getPixelStride() != 2) {
624             return false;
625         }
626         return nativeGetYUVImageVUOff(
627                 imageProxy.getPlanes()[2].getBuffer(),
628                 imageProxy.getPlanes()[1].getBuffer()) == -1;
629     }
630 
631     /**
632      * A wrapper to make sure that the returned v plane position (getPlanes()[2]) is in front of
633      * u plane position (getPlanes()[1]). So that the following operations can correctly check
634      * whether the format is NV12 or NV21 by the plane buffers' pointer positions.
635      *
636      * <p>The callers need to ensure that the v data is put in the plane with former position and v
637      * data is put in the plane with the later position in the associated image proxy.
638      */
639     private static class NV21ImageProxy extends ForwardingImageProxy {
640         private final ImageProxy.PlaneProxy[] mPlanes;
641         private final int mWidth;
642         private final int mHeight;
643 
NV21ImageProxy(@onNull ImageProxy imageProxy, @NonNull ByteBuffer delegateBufferY, @NonNull ByteBuffer delegateBufferU, @NonNull ByteBuffer delegateBufferV, int width, int height, @IntRange(from = 0, to = 359) int rotatedRotationDegrees)644         NV21ImageProxy(@NonNull ImageProxy imageProxy,
645                 @NonNull ByteBuffer delegateBufferY,
646                 @NonNull ByteBuffer delegateBufferU,
647                 @NonNull ByteBuffer delegateBufferV,
648                 int width, int height,
649                 @IntRange(from = 0, to = 359) int rotatedRotationDegrees) {
650             super(imageProxy);
651             mPlanes = createPlanes(delegateBufferY, delegateBufferU, delegateBufferV, width);
652             mWidth = width;
653             mHeight = height;
654         }
655 
656         @Override
getHeight()657         public int getHeight() {
658             return mHeight;
659         }
660 
661         @Override
getWidth()662         public int getWidth() {
663             return mWidth;
664         }
665 
666         @Override
getPlanes()667         public ImageProxy.PlaneProxy @NonNull [] getPlanes() {
668             return mPlanes;
669         }
670 
createPlanes( @onNull ByteBuffer delegateBufferY, @NonNull ByteBuffer delegateBufferU, @NonNull ByteBuffer delegateBufferV, int rowStride )671         private ImageProxy.PlaneProxy @NonNull [] createPlanes(
672                 @NonNull ByteBuffer delegateBufferY,
673                 @NonNull ByteBuffer delegateBufferU,
674                 @NonNull ByteBuffer delegateBufferV,
675                 int rowStride
676         ) {
677             ImageProxy.PlaneProxy[] planes = new ImageProxy.PlaneProxy[3];
678             planes[0] = new ImageProxy.PlaneProxy() {
679                 @Override
680                 public int getRowStride() {
681                     return rowStride;
682                 }
683 
684                 @Override
685                 public int getPixelStride() {
686                     return 1;
687                 }
688 
689                 @Override
690                 public @NonNull ByteBuffer getBuffer() {
691                     return delegateBufferY;
692                 }
693             };
694             planes[1] = new NV21PlaneProxy(delegateBufferU, rowStride);
695             planes[2] = new NV21PlaneProxy(delegateBufferV, rowStride);
696 
697             return planes;
698         }
699     }
700 
701     private static class NV21PlaneProxy implements ImageProxy.PlaneProxy {
702         private final ByteBuffer mByteBuffer;
703         private final int mRowStride;
704 
NV21PlaneProxy(@onNull ByteBuffer byteBuffer, int rowStride)705         NV21PlaneProxy(@NonNull ByteBuffer byteBuffer, int rowStride) {
706             mByteBuffer = byteBuffer;
707             mRowStride = rowStride;
708         }
709 
710         @Override
getRowStride()711         public int getRowStride() {
712             return mRowStride;
713         }
714 
715         @Override
getPixelStride()716         public int getPixelStride() {
717             // Force return pixel stride value 2
718             return 2;
719         }
720 
721         @Override
getBuffer()722         public @NonNull ByteBuffer getBuffer() {
723             return mByteBuffer;
724         }
725     }
726 
nativeCopyBetweenByteBufferAndBitmap(Bitmap bitmap, ByteBuffer byteBuffer, int sourceStride, int destinationStride, int width, int height, boolean isCopyBufferToBitmap)727     private static native int nativeCopyBetweenByteBufferAndBitmap(Bitmap bitmap,
728             ByteBuffer byteBuffer,
729             int sourceStride, int destinationStride, int width, int height,
730             boolean isCopyBufferToBitmap);
731 
732 
nativeWriteJpegToSurface(byte @NonNull [] jpegArray, @NonNull Surface surface)733     private static native int nativeWriteJpegToSurface(byte @NonNull [] jpegArray,
734             @NonNull Surface surface);
735 
nativeConvertAndroid420ToABGR( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, @Nullable Surface surface, @Nullable ByteBuffer convertedByteBufferRGB, int width, int height, int startOffsetY, int startOffsetU, int startOffsetV, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)736     private static native int nativeConvertAndroid420ToABGR(
737             @NonNull ByteBuffer srcByteBufferY,
738             int srcStrideY,
739             @NonNull ByteBuffer srcByteBufferU,
740             int srcStrideU,
741             @NonNull ByteBuffer srcByteBufferV,
742             int srcStrideV,
743             int srcPixelStrideY,
744             int srcPixelStrideUV,
745             @Nullable Surface surface,
746             @Nullable ByteBuffer convertedByteBufferRGB,
747             int width,
748             int height,
749             int startOffsetY,
750             int startOffsetU,
751             int startOffsetV,
752             @ImageOutputConfig.RotationDegreesValue int rotationDegrees);
753 
nativeConvertAndroid420ToBitmap( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, @NonNull Bitmap bitmap, int bitmapStride, int width, int height)754     private static native int nativeConvertAndroid420ToBitmap(
755             @NonNull ByteBuffer srcByteBufferY,
756             int srcStrideY,
757             @NonNull ByteBuffer srcByteBufferU,
758             int srcStrideU,
759             @NonNull ByteBuffer srcByteBufferV,
760             int srcStrideV,
761             int srcPixelStrideY,
762             int srcPixelStrideUV,
763             @NonNull Bitmap bitmap,
764             int bitmapStride,
765             int width,
766             int height);
767 
nativeShiftPixel( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideY, int srcPixelStrideUV, int width, int height, int startOffsetY, int startOffsetU, int startOffsetV)768     private static native int nativeShiftPixel(
769             @NonNull ByteBuffer srcByteBufferY,
770             int srcStrideY,
771             @NonNull ByteBuffer srcByteBufferU,
772             int srcStrideU,
773             @NonNull ByteBuffer srcByteBufferV,
774             int srcStrideV,
775             int srcPixelStrideY,
776             int srcPixelStrideUV,
777             int width,
778             int height,
779             int startOffsetY,
780             int startOffsetU,
781             int startOffsetV);
782 
nativeRotateYUV( @onNull ByteBuffer srcByteBufferY, int srcStrideY, @NonNull ByteBuffer srcByteBufferU, int srcStrideU, @NonNull ByteBuffer srcByteBufferV, int srcStrideV, int srcPixelStrideUV, @NonNull ByteBuffer dstByteBufferY, int dstStrideY, int dstPixelStrideY, @NonNull ByteBuffer dstByteBufferU, int dstStrideU, int dstPixelStrideU, @NonNull ByteBuffer dstByteBufferV, int dstStrideV, int dstPixelStrideV, @NonNull ByteBuffer rotatedByteBufferY, @NonNull ByteBuffer rotatedByteBufferU, @NonNull ByteBuffer rotatedByteBufferV, int width, int height, @ImageOutputConfig.RotationDegreesValue int rotationDegrees)783     private static native int nativeRotateYUV(
784             @NonNull ByteBuffer srcByteBufferY,
785             int srcStrideY,
786             @NonNull ByteBuffer srcByteBufferU,
787             int srcStrideU,
788             @NonNull ByteBuffer srcByteBufferV,
789             int srcStrideV,
790             int srcPixelStrideUV,
791             @NonNull ByteBuffer dstByteBufferY,
792             int dstStrideY,
793             int dstPixelStrideY,
794             @NonNull ByteBuffer dstByteBufferU,
795             int dstStrideU,
796             int dstPixelStrideU,
797             @NonNull ByteBuffer dstByteBufferV,
798             int dstStrideV,
799             int dstPixelStrideV,
800             @NonNull ByteBuffer rotatedByteBufferY,
801             @NonNull ByteBuffer rotatedByteBufferU,
802             @NonNull ByteBuffer rotatedByteBufferV,
803             int width,
804             int height,
805             @ImageOutputConfig.RotationDegreesValue int rotationDegrees);
806 
807     /**
808      * Checks the V and U planes' ByteBuffer start position pointer distance.
809      *
810      * <p>This can be used to determine whether the YUV image is NV12 or NV21 data type.
811      */
nativeGetYUVImageVUOff( @onNull ByteBuffer srcByteBufferV, @NonNull ByteBuffer srcByteBufferU )812     public static native int nativeGetYUVImageVUOff(
813             @NonNull ByteBuffer srcByteBufferV,
814             @NonNull ByteBuffer srcByteBufferU
815     );
816 
817     /**
818      * Creates ByteBuffer from the offset of the input byte buffer.
819      */
nativeNewDirectByteBuffer( @onNull ByteBuffer byteBuffer, int offset, int capacity )820     public static native @NonNull ByteBuffer nativeNewDirectByteBuffer(
821             @NonNull ByteBuffer byteBuffer,
822             int offset,
823             int capacity
824     );
825 }
826