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 */ 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 */ 119 private static native void copyImagePlaneToBitmap(int width, int height, Object planeBuf, 120 int pStride, int rStride, Object outBitmap, int rot90); 121 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 */ 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 */ 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 */ 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 */ 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