summaryrefslogtreecommitdiffstats
path: root/src/com/android/camera/util
diff options
context:
space:
mode:
authorPuneet Lall <puneetl@google.com>2015-02-09 17:45:44 -0800
committerPuneet Lall <puneetl@google.com>2015-03-05 11:21:43 -0800
commit7e744b14ab0657d5e766d6313255eed6dd6eab2a (patch)
tree3cc51f9dec257f765bb196bf7f00064b2f416dcd /src/com/android/camera/util
parenta73b87109097e0557e3b9e7a03a12ae04b610245 (diff)
downloadandroid_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.java245
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);