Home | History | Annotate | Download | only in util
      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