1 /*
2  * Copyright (C) 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.extensions.impl.advanced;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.graphics.ImageFormat;
22 import android.media.Image;
23 import android.media.Image.Plane;
24 import android.media.ImageWriter;
25 import android.util.Log;
26 
27 import java.nio.ByteBuffer;
28 
29 // Jpeg compress input YUV and queue back in the client target surface.
30 public class JpegEncoder {
31 
32     public final static int JPEG_DEFAULT_QUALITY = 100;
33     public final static int JPEG_DEFAULT_ROTATION = 0;
34     public static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
35 
36     /**
37      * Compresses a YCbCr image to jpeg, applying a crop and rotation.
38      * <p>
39      * The input is defined as a set of 3 planes of 8-bit samples, one plane for
40      * each channel of Y, Cb, Cr.<br>
41      * The Y plane is assumed to have the same width and height of the entire
42      * image.<br>
43      * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to
44      * have dimensions (floor(width / 2), floor(height / 2)).<br>
45      * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride,
46      * and a row-stride. So, the sample at coordinate (x, y) can be retrieved
47      * from byteBuffer[x * pixel_stride + y * row_stride].
48      * <p>
49      * The pre-compression transformation is applied as follows:
50      * <ol>
51      * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to
52      * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) -
53      * (width, height) is a no-op.</li>
54      * <li>The rotation is applied counter-clockwise relative to the coordinate
55      * space of the image, so a CCW rotation will appear CW when the image is
56      * rendered in scanline order. Only rotations which are multiples of
57      * 90-degrees are suppored, so the parameter 'rot90' specifies which
58      * multiple of 90 to rotate the image.</li>
59      * </ol>
60      *
61      * @param width          the width of the image to compress
62      * @param height         the height of the image to compress
63      * @param yBuf           the buffer containing the Y component of the image
64      * @param yPStride       the stride between adjacent pixels in the same row in
65      *                       yBuf
66      * @param yRStride       the stride between adjacent rows in yBuf
67      * @param cbBuf          the buffer containing the Cb component of the image
68      * @param cbPStride      the stride between adjacent pixels in the same row in
69      *                       cbBuf
70      * @param cbRStride      the stride between adjacent rows in cbBuf
71      * @param crBuf          the buffer containing the Cr component of the image
72      * @param crPStride      the stride between adjacent pixels in the same row in
73      *                       crBuf
74      * @param crRStride      the stride between adjacent rows in crBuf
75      * @param outBuf         a direct java.nio.ByteBuffer to hold the compressed jpeg.
76      *                       This must have enough capacity to store the result, or an
77      *                       error code will be returned.
78      * @param outBufCapacity the capacity of outBuf
79      * @param quality        the jpeg-quality (1-100) to use
80      * @param cropLeft       left-edge of the bounds of the image to crop to before
81      *                       rotation
82      * @param cropTop        top-edge of the bounds of the image to crop to before
83      *                       rotation
84      * @param cropRight      right-edge of the bounds of the image to crop to before
85      *                       rotation
86      * @param cropBottom     bottom-edge of the bounds of the image to crop to
87      *                       before rotation
88      * @param rot90          the multiple of 90 to rotate the image CCW (after cropping)
89      */
compressJpegFromYUV420pNative( int width, int height, ByteBuffer yBuf, int yPStride, int yRStride, ByteBuffer cbBuf, int cbPStride, int cbRStride, ByteBuffer crBuf, int crPStride, int crRStride, ByteBuffer outBuf, int outBufCapacity, int quality, int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90)90     public static native int compressJpegFromYUV420pNative(
91             int width, int height,
92             ByteBuffer yBuf, int yPStride, int yRStride,
93             ByteBuffer cbBuf, int cbPStride, int cbRStride,
94             ByteBuffer crBuf, int crPStride, int crRStride,
95             ByteBuffer outBuf, int outBufCapacity,
96             int quality,
97             int cropLeft, int cropTop, int cropRight, int cropBottom,
98             int rot90);
99 
encodeToJpeg(Image yuvImage, Image jpegImage, int jpegOrientation, int jpegQuality)100     public static void encodeToJpeg(Image yuvImage, Image jpegImage,
101             int jpegOrientation, int jpegQuality) {
102 
103         jpegOrientation =  (360 - (jpegOrientation % 360)) / 90;
104         ByteBuffer jpegBuffer = jpegImage.getPlanes()[0].getBuffer();
105 
106         jpegBuffer.clear();
107 
108         int jpegCapacity = jpegImage.getWidth();
109 
110         Plane lumaPlane = yuvImage.getPlanes()[0];
111 
112         Plane crPlane = yuvImage.getPlanes()[1];
113         Plane cbPlane = yuvImage.getPlanes()[2];
114 
115         JpegEncoder.compressJpegFromYUV420pNative(
116             yuvImage.getWidth(), yuvImage.getHeight(),
117             lumaPlane.getBuffer(), lumaPlane.getPixelStride(), lumaPlane.getRowStride(),
118             crPlane.getBuffer(), crPlane.getPixelStride(), crPlane.getRowStride(),
119             cbPlane.getBuffer(), cbPlane.getPixelStride(), cbPlane.getRowStride(),
120             jpegBuffer, jpegCapacity, jpegQuality,
121             0, 0, yuvImage.getWidth(), yuvImage.getHeight(),
122             jpegOrientation);
123     }
124 
imageFormatToPublic(int format)125     public static int imageFormatToPublic(int format) {
126         switch (format) {
127             case HAL_PIXEL_FORMAT_BLOB:
128                 return ImageFormat.JPEG;
129             case ImageFormat.JPEG:
130                 throw new IllegalArgumentException(
131                         "ImageFormat.JPEG is an unknown internal format");
132             default:
133                 return format;
134         }
135     }
136 }