diff options
author | Puneet Lall <puneetl@google.com> | 2015-02-09 17:45:44 -0800 |
---|---|---|
committer | Puneet Lall <puneetl@google.com> | 2015-03-05 11:21:43 -0800 |
commit | 7e744b14ab0657d5e766d6313255eed6dd6eab2a (patch) | |
tree | 3cc51f9dec257f765bb196bf7f00064b2f416dcd /src/com/android/camera/util | |
parent | a73b87109097e0557e3b9e7a03a12ae04b610245 (diff) | |
download | android_packages_apps_Camera2-7e744b14ab0657d5e766d6313255eed6dd6eab2a.tar.gz android_packages_apps_Camera2-7e744b14ab0657d5e766d6313255eed6dd6eab2a.tar.bz2 android_packages_apps_Camera2-7e744b14ab0657d5e766d6313255eed6dd6eab2a.zip |
Add on-the-fly cropping and rotation to jpegutil
Bug: 19323062
* Adds another JpegUtilNative method for compression with a crop
rectangle.
* Improves speed of Jpeg compression with a non-zero rotation.
* Fixes small native memory leak caused by not calling
jpeg_destroy_compress() to free the libjpeg context.
* Adds gunit tests for native code.
Change-Id: Id0aa5fd525fb232c4241a6b24970923f4f2c20df
Diffstat (limited to 'src/com/android/camera/util')
-rw-r--r-- | src/com/android/camera/util/JpegUtilNative.java | 245 |
1 files changed, 124 insertions, 121 deletions
diff --git a/src/com/android/camera/util/JpegUtilNative.java b/src/com/android/camera/util/JpegUtilNative.java index 3cd5a422d..339b3217b 100644 --- a/src/com/android/camera/util/JpegUtilNative.java +++ b/src/com/android/camera/util/JpegUtilNative.java @@ -16,12 +16,13 @@ package com.android.camera.util; -import com.android.camera.one.v2.camera2proxy.ImageProxy; - import android.graphics.Bitmap; import android.graphics.ImageFormat; -import android.media.Image; -import android.media.Image.Plane; +import android.graphics.Rect; + +import com.android.camera.debug.Log; +import com.android.camera.one.v2.camera2proxy.ImageProxy; +import com.google.common.base.Preconditions; import java.nio.ByteBuffer; import java.util.List; @@ -35,10 +36,35 @@ public class JpegUtilNative { } public static final int ERROR_OUT_BUF_TOO_SMALL = -1; + private static final Log.Tag TAG = new Log.Tag("JpegUtilNative"); /** - * Compresses an image from YUV422 format to jpeg. + * Compresses a YCbCr image to jpeg, applying a crop and rotation. + * <p> + * The input is defined as a set of 3 planes of 8-bit samples, one plane for + * each channel of Y, Cb, Cr.<br> + * The Y plane is assumed to have the same width and height of the entire + * image.<br> + * The Cb and Cr planes are assumed to be downsampled by a factor of 2, to + * have dimensions (floor(width / 2), floor(height / 2)).<br> + * Each plane is specified by a direct java.nio.ByteBuffer, a pixel-stride, + * and a row-stride. So, the sample at coordinate (x, y) can be retrieved + * from byteBuffer[x * pixel_stride + y * row_stride]. + * <p> + * The pre-compression transformation is applied as follows: + * <ol> + * <li>The image is cropped to the rectangle from (cropLeft, cropTop) to + * (cropRight - 1, cropBottom - 1). So, a cropping-rectangle of (0, 0) - + * (width, height) is a no-op.</li> + * <li>The rotation is applied counter-clockwise relative to the coordinate + * space of the image, so a CCW rotation will appear CW when the image is + * rendered in scanline order. Only rotations which are multiples of + * 90-degrees are suppored, so the parameter 'rot90' specifies which + * multiple of 90 to rotate the image.</li> + * </ol> * + * @param width the width of the image to compress + * @param height the height of the image to compress * @param yBuf the buffer containing the Y component of the image * @param yPStride the stride between adjacent pixels in the same row in * yBuf @@ -51,16 +77,30 @@ public class JpegUtilNative { * @param crPStride the stride between adjacent pixels in the same row in * crBuf * @param crRStride the stride between adjacent rows in crBuf - * @param quality the quality level (0 to 100) to use - * @return The number of bytes written, or a negative value indicating an - * error + * @param outBuf a direct java.nio.ByteBuffer to hold the compressed jpeg. + * This must have enough capacity to store the result, or an + * error code will be returned. + * @param outBufCapacity the capacity of outBuf + * @param quality the jpeg-quality (1-100) to use + * @param cropLeft left-edge of the bounds of the image to crop to before + * rotation + * @param cropTop top-edge of the bounds of the image to crop to before + * rotation + * @param cropRight right-edge of the bounds of the image to crop to before + * rotation + * @param cropBottom bottom-edge of the bounds of the image to crop to + * before rotation + * @param rot90 the multiple of 90 to rotate the image CCW (after cropping) */ private static native int 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); + Object outBuf, int outBufCapacity, + int quality, + int cropLeft, int cropTop, int cropRight, int cropBottom, + int rot90); /** * Copies the Image.Plane specified by planeBuf, pStride, and rStride to the @@ -73,7 +113,8 @@ public class JpegUtilNative { * planeBuf * @param rStride the stride between adjacent rows in planeBuf * @param outBitmap the output bitmap object - * @param rot90 the multiple of 90 degrees to rotate counterclockwise, one of {0, 1, 2, 3}. + * @param rot90 the multiple of 90 degrees to rotate counterclockwise, one + * of {0, 1, 2, 3}. */ private static native void copyImagePlaneToBitmap(int width, int height, Object planeBuf, int pStride, int rStride, Object outBitmap, int rot90); @@ -91,19 +132,32 @@ public class JpegUtilNative { } /** - * @see JpegUtilNative#compressJpegFromYUV420pNative(int, int, - * java.lang.Object, int, int, java.lang.Object, int, int, - * java.lang.Object, int, int, java.lang.Object, int, int) + * @see JpegUtilNative#compressJpegFromYUV420pNative(int, int, Object, int, + * int, Object, int, int, Object, int, int, Object, int, int, int, int, + * int, int, int) */ public static int 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) { + ByteBuffer outBuf, int quality, + int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90) { + Log.i(TAG, String.format( + "Compressing jpeg with size = (%d, %d); " + + "y-channel pixel stride = %d; " + + "y-channel row stride = %d; " + + "cb-channel pixel stride = %d; " + + "cb-channel row stride = %d; " + + "cr-channel pixel stride = %d; " + + "cr-channel row stride = %d; " + + "crop = [(%d, %d) - (%d, %d)]; " + + "rotation = %d * 90 deg. ", + width, height, yPStride, yRStride, cbPStride, cbRStride, crPStride, crRStride, + cropLeft, cropTop, cropRight, cropBottom, rot90)); return compressJpegFromYUV420pNative(width, height, yBuf, yPStride, yRStride, cbBuf, cbPStride, cbRStride, crBuf, crPStride, crRStride, outBuf, outBuf.capacity(), - quality); + quality, cropLeft, cropTop, cropRight, cropBottom, rot90); } /** @@ -117,56 +171,13 @@ public class JpegUtilNative { * @return The number of bytes written to outBuf */ public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality) { - if (img.getFormat() != ImageFormat.YUV_420_888) { - throw new RuntimeException("Unsupported Image Format."); - } - - final int NUM_PLANES = 3; - final List<ImageProxy.Plane> planeList = img.getPlanes(); - - if (planeList.size() != NUM_PLANES) { - throw new RuntimeException("Output buffer must be direct."); - } - - if (!outBuf.isDirect()) { - throw new RuntimeException("Output buffer must be direct."); - } - - ByteBuffer[] planeBuf = new ByteBuffer[NUM_PLANES]; - int[] pixelStride = new int[NUM_PLANES]; - int[] rowStride = new int[NUM_PLANES]; - - for (int i = 0; i < NUM_PLANES; i++) { - ImageProxy.Plane plane = planeList.get(i); - - if (!plane.getBuffer().isDirect()) { - return -1; - } - - planeBuf[i] = plane.getBuffer(); - pixelStride[i] = plane.getPixelStride(); - rowStride[i] = plane.getRowStride(); - } - - outBuf.clear(); - - int numBytesWritten = compressJpegFromYUV420p( - img.getWidth(), img.getHeight(), - planeBuf[0], pixelStride[0], rowStride[0], - planeBuf[1], pixelStride[1], rowStride[1], - planeBuf[2], pixelStride[2], rowStride[2], - outBuf, quality); - - outBuf.limit(numBytesWritten); - - return numBytesWritten; + return compressJpegFromYUV420Image(img, outBuf, quality, 0); } /** * Compresses the given image to jpeg. Note that only * ImageFormat.YUV_420_888 is currently supported. Furthermore, all planes * must use direct byte buffers.<br> - * FIXME TODO OPTIMIZE This method is *incredibly* inefficient. * * @param img the image to compress * @param outBuf a direct byte buffer to hold the output jpeg. @@ -176,29 +187,38 @@ public class JpegUtilNative { */ public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality, int degrees) { - final List<ImageProxy.Plane> planeList = img.getPlanes(); - - if (degrees != 0 && degrees != 90 && degrees != 180 && degrees != 270) { - throw new RuntimeException("Unsupported rotation angle"); - } - - if (degrees == 0) { - return compressJpegFromYUV420Image(img, outBuf, quality); - } - - if (img.getFormat() != ImageFormat.YUV_420_888) { - throw new RuntimeException("Unsupported Image Format."); - } + return compressJpegFromYUV420Image(img, outBuf, quality, new Rect(0, 0, img.getWidth(), + img.getHeight()), degrees); + } + /** + * Compresses the given image to jpeg. Note that only + * ImageFormat.YUV_420_888 is currently supported. Furthermore, all planes + * must use direct byte buffers. + * + * @param img the image to compress + * @param outBuf a direct byte buffer to hold the output jpeg. + * @param quality the jpeg encoder quality (0 to 100) + * @param crop The crop rectangle to apply *before* rotation. + * @param degrees The number of degrees to rotate the image *after* + * cropping. This must be a multiple of 90. Note that this + * represents a clockwise rotation in the space of the image + * plane, which appears as a counter-clockwise rotation when the + * image is displayed in raster-order. + * @return The number of bytes written to outBuf + */ + public static int compressJpegFromYUV420Image(ImageProxy img, ByteBuffer outBuf, int quality, + Rect crop, int degrees) { + Preconditions.checkState(degrees > 0, "Rotation must be positive"); + Preconditions.checkState((degrees % 90) == 0, "Rotation must be a multiple of 90 degrees"); + Preconditions.checkState(outBuf.isDirect(), "Output buffer must be direct"); + Preconditions.checkState(crop.left < crop.right, "Invalid crop rectangle"); + Preconditions.checkState(crop.top < crop.bottom, "Invalid crop rectangle"); final int NUM_PLANES = 3; - - if (img.getPlanes().size() != NUM_PLANES) { - throw new RuntimeException("Output buffer must be direct."); - } - - if (!outBuf.isDirect()) { - throw new RuntimeException("Output buffer must be direct."); - } + Preconditions.checkState(img.getFormat() == ImageFormat.YUV_420_888, "Only " + + "ImageFormat.YUV_420_888 is supported"); + final List<ImageProxy.Plane> planeList = img.getPlanes(); + Preconditions.checkState(planeList.size() == NUM_PLANES); ByteBuffer[] planeBuf = new ByteBuffer[NUM_PLANES]; int[] pixelStride = new int[NUM_PLANES]; @@ -207,59 +227,42 @@ public class JpegUtilNative { for (int i = 0; i < NUM_PLANES; i++) { ImageProxy.Plane plane = planeList.get(i); - if (!plane.getBuffer().isDirect()) { - return -1; - } - - int width = img.getWidth(); - int height = img.getHeight(); - - if (i > 0) { - // The image plane for the Cb and Cr channels is downsampled. - width /= 2; - height /= 2; - } - - if (degrees == 90 || degrees == 270) { - int tmp = width; - width = height; - height = tmp; - } - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8); + Preconditions.checkState(plane.getBuffer().isDirect()); - // TODO: Make copyImagePlaneToBitmap take clockwise angle to rotate the bitmap. - int counterClockwiseDegrees = (360 - degrees) % 360; - copyImagePlaneToBitmap(plane, bitmap, counterClockwiseDegrees / 90); + planeBuf[i] = plane.getBuffer(); + pixelStride[i] = plane.getPixelStride(); + rowStride[i] = plane.getRowStride(); + } - Bitmap rotatedBitmap = bitmap; + outBuf.clear(); - ByteBuffer rotatedBitmapBuffer = ByteBuffer.allocateDirect( - rotatedBitmap.getWidth() * rotatedBitmap.getHeight()); + int cropLeft = crop.left; + cropLeft = Math.max(cropLeft, 0); + cropLeft = Math.min(cropLeft, img.getWidth() - 1); - rotatedBitmap.copyPixelsToBuffer(rotatedBitmapBuffer); + int cropRight = crop.right; + cropRight = Math.max(cropRight, 0); + cropRight = Math.min(cropRight, img.getWidth()); - planeBuf[i] = rotatedBitmapBuffer; - pixelStride[i] = 1; - rowStride[i] = rotatedBitmap.getWidth(); - } + int cropTop = crop.top; + cropTop = Math.max(cropTop, 0); + cropTop = Math.min(cropTop, img.getHeight() - 1); - outBuf.clear(); + int cropBot = crop.bottom; + cropBot = Math.max(cropBot, 0); + cropBot = Math.min(cropBot, img.getHeight()); - int width = img.getWidth(); - int height = img.getHeight(); - if (degrees == 90 || degrees == 270) { - int tmp = width; - width = height; - height = tmp; - } + degrees = degrees % 360; + // Convert from clockwise to counter-clockwise. + int rot90 = (360 - degrees) / 90; int numBytesWritten = compressJpegFromYUV420p( - width, height, + img.getWidth(), img.getHeight(), planeBuf[0], pixelStride[0], rowStride[0], planeBuf[1], pixelStride[1], rowStride[1], planeBuf[2], pixelStride[2], rowStride[2], - outBuf, quality); + outBuf, quality, cropLeft, cropTop, cropRight, cropBot, + rot90); outBuf.limit(numBytesWritten); |