1 /*
2  * Copyright (C) 2014 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 com.android.camera.util;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.ImageFormat;
21 import android.graphics.Rect;
22 
23 import com.android.camera.debug.Log;
24 import com.android.camera.one.v2.camera2proxy.ImageProxy;
25 import com.google.common.base.Preconditions;
26 
27 import java.nio.ByteBuffer;
28 import java.util.List;
29 
30 /**
31  * Provides direct access to libjpeg-turbo via the NDK.
32  */
33 public class JpegUtilNative {
34     static {
35         System.loadLibrary("jni_jpegutil");
36     }
37 
38     public static final int ERROR_OUT_BUF_TOO_SMALL = -1;
39     private static final Log.Tag TAG = new Log.Tag("JpegUtilNative");
40 
41     /**
42      * Compresses a YCbCr image to jpeg, applying a crop and rotation.
43      * <p>
44      * The input is defined as a set of 3 planes of 8-bit samples, one plane for
45      * each channel of Y, Cb, Cr.<br>
46      * The Y plane is assumed to have the same width and height of the entire
47      * image.<br>
48      * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to
49      * have dimensions (floor(width / 2), floor(height / 2)).<br>
50      * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride,
51      * and a row-stride. So, the sample at coordinate (x, y) can be retrieved
52      * from byteBuffer[x * pixel_stride + y * row_stride].
53      * <p>
54      * The pre-compression transformation is applied as follows:
55      * <ol>
56      * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to
57      * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) -
58      * (width, height) is a no-op.</li>
59      * <li>The rotation is applied counter-clockwise relative to the coordinate
60      * space of the image, so a CCW rotation will appear CW when the image is
61      * rendered in scanline order. Only rotations which are multiples of
62      * 90-degrees are suppored, so the parameter 'rot90' specifies which
63      * multiple of 90 to rotate the image.</li>
64      * </ol>
65      *
66      * @param width the width of the image to compress
67      * @param height the height of the image to compress
68      * @param yBuf the buffer containing the Y component of the image
69      * @param yPStride the stride between adjacent pixels in the same row in
70      *            yBuf
71      * @param yRStride the stride between adjacent rows in yBuf
72      * @param cbBuf the buffer containing the Cb component of the image
73      * @param cbPStride the stride between adjacent pixels in the same row in
74      *            cbBuf
75      * @param cbRStride the stride between adjacent rows in cbBuf
76      * @param crBuf the buffer containing the Cr component of the image
77      * @param crPStride the stride between adjacent pixels in the same row in
78      *            crBuf
79      * @param crRStride the stride between adjacent rows in crBuf
80      * @param outBuf a direct java.nio.ByteBuffer to hold the compressed jpeg.
81      *            This must have enough capacity to store the result, or an
82      *            error code will be returned.
83      * @param outBufCapacity the capacity of outBuf
84      * @param quality the jpeg-quality (1-100) to use
85      * @param cropLeft left-edge of the bounds of the image to crop to before
86      *            rotation
87      * @param cropTop top-edge of the bounds of the image to crop to before
88      *            rotation
89      * @param cropRight right-edge of the bounds of the image to crop to before
90      *            rotation
91      * @param cropBottom bottom-edge of the bounds of the image to crop to
92      *            before rotation
93      * @param rot90 the multiple of 90 to rotate the image CCW (after cropping)
94      */
compressJpegFromYUV420pNative( int width, int height, Object yBuf, int yPStride, int yRStride, Object cbBuf, int cbPStride, int cbRStride, Object crBuf, int crPStride, int crRStride, Object outBuf, int outBufCapacity, int quality, int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90)95     private static native int compressJpegFromYUV420pNative(
96             int width, int height,
97             Object yBuf, int yPStride, int yRStride,
98             Object cbBuf, int cbPStride, int cbRStride,
99             Object crBuf, int crPStride, int crRStride,
100             Object outBuf, int outBufCapacity,
101             int quality,
102             int cropLeft, int cropTop, int cropRight, int cropBottom,
103             int rot90);
104 
105     /**
106      * Copies the Image.Plane specified by planeBuf, pStride, and rStride to the
107      * Bitmap.
108      *
109      * @param width the width of the image
110      * @param height the height of the image
111      * @param planeBuf the native ByteBuffer containing the image plane data
112      * @param pStride the stride between adjacent pixels in the same row of
113      *            planeBuf
114      * @param rStride the stride between adjacent rows in planeBuf
115      * @param outBitmap the output bitmap object
116      * @param rot90 the multiple of 90 degrees to rotate counterclockwise, one
117      *            of {0, 1, 2, 3}.
118      */
copyImagePlaneToBitmap(int width, int height, Object planeBuf, int pStride, int rStride, Object outBitmap, int rot90)119     private static native void copyImagePlaneToBitmap(int width, int height, Object planeBuf,
120             int pStride, int rStride, Object outBitmap, int rot90);
121 
copyImagePlaneToBitmap(ImageProxy.Plane plane, Bitmap bitmap, int rot90)122     public static void copyImagePlaneToBitmap(ImageProxy.Plane plane, Bitmap bitmap, int rot90) {
123         if (bitmap.getConfig() != Bitmap.Config.ALPHA_8) {
124             throw new RuntimeException("Unsupported bitmap format");
125         }
126 
127         int width = bitmap.getWidth();
128         int height = bitmap.getHeight();
129 
130         copyImagePlaneToBitmap(width, height, plane.getBuffer(), plane.getPixelStride(),
131                 plane.getRowStride(), bitmap, rot90);
132     }
133 
134     /**
135      * @see JpegUtilNative#compressJpegFromYUV420pNative(int, int, Object, int,
136      *      int, Object, int, int, Object, int, int, Object, int, int, int, int,
137      *      int, int, int)
138      */
compressJpegFromYUV420p( 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 quality, int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90)139     public static int compressJpegFromYUV420p(
140             int width, int height,
141             ByteBuffer yBuf, int yPStride, int yRStride,
142             ByteBuffer cbBuf, int cbPStride, int cbRStride,
143             ByteBuffer crBuf, int crPStride, int crRStride,
144             ByteBuffer outBuf, int quality,
145             int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90) {
146         Log.i(TAG, String.format(
147                 "Compressing jpeg with size = (%d, %d); " +
148                         "y-channel pixel stride = %d; " +
149                         "y-channel row stride =  %d; " +
150                         "cb-channel pixel stride = %d; " +
151                         "cb-channel row stride =  %d; " +
152                         "cr-channel pixel stride = %d; " +
153                         "cr-channel row stride =  %d; " +
154                         "crop = [(%d, %d) - (%d, %d)]; " +
155                         "rotation = %d * 90 deg. ",
156                 width, height, yPStride, yRStride, cbPStride, cbRStride, crPStride, crRStride,
157                 cropLeft, cropTop, cropRight, cropBottom, rot90));
158         return compressJpegFromYUV420pNative(width, height, yBuf, yPStride, yRStride, cbBuf,
159                 cbPStride, cbRStride, crBuf, crPStride, crRStride, outBuf, outBuf.capacity(),
160                 quality, cropLeft, cropTop, cropRight, cropBottom, rot90);
161     }
162 
163     /**
164      * Compresses the given image to jpeg. Note that only
165      * ImageFormat.YUV_420_888 is currently supported. Furthermore, all planes
166      * must use direct byte buffers.
167      *
168      * @param img the image to compress
169      * @param outBuf a direct byte buffer to hold the output jpeg.
170      * @param quality the jpeg encoder quality (0 to 100)
171      * @return The number of bytes written to outBuf
172      */
compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality)173     public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality) {
174         return compressJpegFromYUV420Image(img, outBuf, quality, 0);
175     }
176 
177     /**
178      * Compresses the given image to jpeg. Note that only
179      * ImageFormat.YUV_420_888 is currently supported. Furthermore, all planes
180      * must use direct byte buffers.<br>
181      *
182      * @param img the image to compress
183      * @param outBuf a direct byte buffer to hold the output jpeg.
184      * @param quality the jpeg encoder quality (0 to 100)
185      * @param degrees the amount to rotate the image clockwise, in degrees.
186      * @return The number of bytes written to outBuf
187      */
compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality, int degrees)188     public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality,
189             int degrees) {
190         return compressJpegFromYUV420Image(img, outBuf, quality, new Rect(0, 0, img.getWidth(),
191                 img.getHeight()), degrees);
192     }
193 
194     /**
195      * Compresses the given image to jpeg. Note that only
196      * ImageFormat.YUV_420_888 is currently supported. Furthermore, all planes
197      * must use direct byte buffers.
198      *
199      * @param img the image to compress
200      * @param outBuf a direct byte buffer to hold the output jpeg.
201      * @param quality the jpeg encoder quality (0 to 100)
202      * @param crop The crop rectangle to apply *before* rotation.
203      * @param degrees The number of degrees to rotate the image *after*
204      *            cropping. This must be a multiple of 90. Note that this
205      *            represents a clockwise rotation in the space of the image
206      *            plane, which appears as a counter-clockwise rotation when the
207      *            image is displayed in raster-order.
208      * @return The number of bytes written to outBuf
209      */
compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality, Rect crop, int degrees)210     public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality,
211             Rect crop, int degrees) {
212         Preconditions.checkState((degrees % 90) == 0, "Rotation must be a multiple of 90 degrees," +
213                 " was " + degrees);
214         // Handle negative angles by converting to positive.
215         degrees = ((degrees % 360) + (360 * 2)) % 360;
216         Preconditions.checkState(outBuf.isDirect(), "Output buffer must be direct");
217         Preconditions.checkState(crop.left < crop.right, "Invalid crop rectangle: " +
218                 crop.toString());
219         Preconditions.checkState(crop.top < crop.bottom, "Invalid crop rectangle: " +
220                 crop.toString());
221         final int NUM_PLANES = 3;
222         Preconditions.checkState(img.getFormat() == ImageFormat.YUV_420_888, "Only " +
223                 "ImageFormat.YUV_420_888 is supported, found " + img.getFormat());
224         final List<ImageProxy.Plane> planeList = img.getPlanes();
225         Preconditions.checkState(planeList.size() == NUM_PLANES);
226 
227         ByteBuffer[] planeBuf = new ByteBuffer[NUM_PLANES];
228         int[] pixelStride = new int[NUM_PLANES];
229         int[] rowStride = new int[NUM_PLANES];
230 
231         for (int i = 0; i < NUM_PLANES; i++) {
232             ImageProxy.Plane plane = planeList.get(i);
233 
234             Preconditions.checkState(plane.getBuffer().isDirect());
235 
236             planeBuf[i] = plane.getBuffer();
237             pixelStride[i] = plane.getPixelStride();
238             rowStride[i] = plane.getRowStride();
239         }
240 
241         outBuf.clear();
242 
243         int cropLeft = crop.left;
244         cropLeft = Math.max(cropLeft, 0);
245         cropLeft = Math.min(cropLeft, img.getWidth() - 1);
246 
247         int cropRight = crop.right;
248         cropRight = Math.max(cropRight, 0);
249         cropRight = Math.min(cropRight, img.getWidth());
250 
251         int cropTop = crop.top;
252         cropTop = Math.max(cropTop, 0);
253         cropTop = Math.min(cropTop, img.getHeight() - 1);
254 
255         int cropBot = crop.bottom;
256         cropBot = Math.max(cropBot, 0);
257         cropBot = Math.min(cropBot, img.getHeight());
258 
259         degrees = degrees % 360;
260         // Convert from clockwise to counter-clockwise.
261         int rot90 = (360 - degrees) / 90;
262 
263         int numBytesWritten = compressJpegFromYUV420p(
264                 img.getWidth(), img.getHeight(),
265                 planeBuf[0], pixelStride[0], rowStride[0],
266                 planeBuf[1], pixelStride[1], rowStride[1],
267                 planeBuf[2], pixelStride[2], rowStride[2],
268                 outBuf, quality, cropLeft, cropTop, cropRight, cropBot,
269                 rot90);
270 
271         outBuf.limit(numBytesWritten);
272 
273         return numBytesWritten;
274     }
275 }
276