Home | History | Annotate | Download | only in camera2
      1 /*
      2  * Copyright 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 android.hardware.camera2;
     18 
     19 import android.annotation.IntRange;
     20 import android.annotation.NonNull;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Color;
     23 import android.graphics.ImageFormat;
     24 import android.hardware.camera2.impl.CameraMetadataNative;
     25 import android.location.Location;
     26 import android.media.ExifInterface;
     27 import android.media.Image;
     28 import android.os.SystemClock;
     29 import android.util.Log;
     30 import android.util.Size;
     31 
     32 import java.io.IOException;
     33 import java.io.InputStream;
     34 import java.io.OutputStream;
     35 import java.nio.ByteBuffer;
     36 import java.text.DateFormat;
     37 import java.text.SimpleDateFormat;
     38 import java.util.Calendar;
     39 import java.util.Locale;
     40 import java.util.TimeZone;
     41 
     42 /**
     43  * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
     44  *
     45  * <p>
     46  * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
     47  * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
     48  * pixel data that is otherwise generated by an application.  The DNG metadata tags will be
     49  * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
     50  * </p>
     51  *
     52  * <p>
     53  * The DNG file format is a cross-platform file format that is used to store pixel data from
     54  * camera sensors with minimal pre-processing applied.  DNG files allow for pixel data to be
     55  * defined in a user-defined colorspace, and have associated metadata that allow for this
     56  * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
     57  * </p>
     58  *
     59  * <p>
     60  * For more information on the DNG file format and associated metadata, please refer to the
     61  * <a href=
     62  * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
     63  * Adobe DNG 1.4.0.0 specification</a>.
     64  * </p>
     65  */
     66 public final class DngCreator implements AutoCloseable {
     67 
     68     private static final String TAG = "DngCreator";
     69     /**
     70      * Create a new DNG object.
     71      *
     72      * <p>
     73      * It is not necessary to call any set methods to write a well-formatted DNG file.
     74      * </p>
     75      * <p>
     76      * DNG metadata tags will be generated from the corresponding parameters in the
     77      * {@link android.hardware.camera2.CaptureResult} object.
     78      * </p>
     79      * <p>
     80      * For best quality DNG files, it is strongly recommended that lens shading map output is
     81      * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}.
     82      * </p>
     83      * @param characteristics an object containing the static
     84      *          {@link android.hardware.camera2.CameraCharacteristics}.
     85      * @param metadata a metadata object to generate tags from.
     86      */
     87     public DngCreator(@NonNull CameraCharacteristics characteristics,
     88             @NonNull CaptureResult metadata) {
     89         if (characteristics == null || metadata == null) {
     90             throw new IllegalArgumentException("Null argument to DngCreator constructor");
     91         }
     92 
     93         // Find current time in milliseconds since 1970
     94         long currentTime = System.currentTimeMillis();
     95         // Assume that sensor timestamp has that timebase to start
     96         long timeOffset = 0;
     97 
     98         int timestampSource = characteristics.get(
     99                 CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
    100 
    101         if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME) {
    102             // This means the same timebase as SystemClock.elapsedRealtime(),
    103             // which is CLOCK_BOOTTIME
    104             timeOffset = currentTime - SystemClock.elapsedRealtime();
    105         } else if (timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN) {
    106             // This means the same timebase as System.currentTimeMillis(),
    107             // which is CLOCK_MONOTONIC
    108             timeOffset = currentTime - SystemClock.uptimeMillis();
    109         } else {
    110             // Unexpected time source - treat as CLOCK_MONOTONIC
    111             Log.w(TAG, "Sensor timestamp source is unexpected: " + timestampSource);
    112             timeOffset = currentTime - SystemClock.uptimeMillis();
    113         }
    114 
    115         // Find capture time (nanos since boot)
    116         Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
    117         long captureTime = currentTime;
    118         if (timestamp != null) {
    119             captureTime = timestamp / 1000000 + timeOffset;
    120         }
    121 
    122         // Create this fresh each time since the time zone may change while a long-running application
    123         // is active.
    124         final DateFormat dateTimeStampFormat =
    125                 new SimpleDateFormat(TIFF_DATETIME_FORMAT, Locale.US);
    126         dateTimeStampFormat.setTimeZone(TimeZone.getDefault());
    127 
    128         // Format for metadata
    129         String formattedCaptureTime = dateTimeStampFormat.format(captureTime);
    130 
    131         nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
    132                 formattedCaptureTime);
    133     }
    134 
    135     /**
    136      * Set the orientation value to write.
    137      *
    138      * <p>
    139      * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
    140      * Calling this will override any prior settings for this tag.
    141      * </p>
    142      *
    143      * @param orientation the orientation value to set, one of:
    144      *                    <ul>
    145      *                      <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
    146      *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
    147      *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
    148      *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
    149      *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
    150      *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
    151      *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
    152      *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
    153      *                    </ul>
    154      * @return this {@link #DngCreator} object.
    155      */
    156     @NonNull
    157     public DngCreator setOrientation(int orientation) {
    158         if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
    159                 orientation > ExifInterface.ORIENTATION_ROTATE_270) {
    160             throw new IllegalArgumentException("Orientation " + orientation +
    161                     " is not a valid EXIF orientation value");
    162         }
    163         // ExifInterface and TIFF/EP spec differ on definition of
    164         // "Unknown" orientation; other values map directly
    165         if (orientation == ExifInterface.ORIENTATION_UNDEFINED) {
    166             orientation = TAG_ORIENTATION_UNKNOWN;
    167         }
    168         nativeSetOrientation(orientation);
    169         return this;
    170     }
    171 
    172     /**
    173      * Set the thumbnail image.
    174      *
    175      * <p>
    176      * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
    177      * The alpha channel will be discarded.  Thumbnail images with a dimension larger than
    178      * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected.
    179      * </p>
    180      *
    181      * @param pixels a {@link android.graphics.Bitmap} of pixel data.
    182      * @return this {@link #DngCreator} object.
    183      * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
    184      *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
    185      */
    186     @NonNull
    187     public DngCreator setThumbnail(@NonNull Bitmap pixels) {
    188         if (pixels == null) {
    189             throw new IllegalArgumentException("Null argument to setThumbnail");
    190         }
    191 
    192         int width = pixels.getWidth();
    193         int height = pixels.getHeight();
    194 
    195         if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
    196             throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
    197                     "," + height + ") too large, dimensions must be smaller than " +
    198                     MAX_THUMBNAIL_DIMENSION);
    199         }
    200 
    201         ByteBuffer rgbBuffer = convertToRGB(pixels);
    202         nativeSetThumbnail(rgbBuffer, width, height);
    203 
    204         return this;
    205     }
    206 
    207     /**
    208      * Set the thumbnail image.
    209      *
    210      * <p>
    211      * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
    212      * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be
    213      * rejected.
    214      * </p>
    215      *
    216      * @param pixels an {@link android.media.Image} object with the format
    217      *               {@link android.graphics.ImageFormat#YUV_420_888}.
    218      * @return this {@link #DngCreator} object.
    219      * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
    220      *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
    221      */
    222     @NonNull
    223     public DngCreator setThumbnail(@NonNull Image pixels) {
    224         if (pixels == null) {
    225             throw new IllegalArgumentException("Null argument to setThumbnail");
    226         }
    227 
    228         int format = pixels.getFormat();
    229         if (format != ImageFormat.YUV_420_888) {
    230             throw new IllegalArgumentException("Unsupported Image format " + format);
    231         }
    232 
    233         int width = pixels.getWidth();
    234         int height = pixels.getHeight();
    235 
    236         if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
    237             throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
    238                     "," + height + ") too large, dimensions must be smaller than " +
    239                     MAX_THUMBNAIL_DIMENSION);
    240         }
    241 
    242         ByteBuffer rgbBuffer = convertToRGB(pixels);
    243         nativeSetThumbnail(rgbBuffer, width, height);
    244 
    245         return this;
    246     }
    247 
    248     /**
    249      * Set image location metadata.
    250      *
    251      * <p>
    252      * The given location object must contain at least a valid time, latitude, and longitude
    253      * (equivalent to the values returned by {@link android.location.Location#getTime()},
    254      * {@link android.location.Location#getLatitude()}, and
    255      * {@link android.location.Location#getLongitude()} methods).
    256      * </p>
    257      *
    258      * @param location an {@link android.location.Location} object to set.
    259      * @return this {@link #DngCreator} object.
    260      *
    261      * @throws java.lang.IllegalArgumentException if the given location object doesn't
    262      *          contain enough information to set location metadata.
    263      */
    264     @NonNull
    265     public DngCreator setLocation(@NonNull Location location) {
    266         if (location == null) {
    267             throw new IllegalArgumentException("Null location passed to setLocation");
    268         }
    269         double latitude = location.getLatitude();
    270         double longitude = location.getLongitude();
    271         long time = location.getTime();
    272 
    273         int[] latTag = toExifLatLong(latitude);
    274         int[] longTag = toExifLatLong(longitude);
    275         String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH;
    276         String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST;
    277 
    278         String dateTag = sExifGPSDateStamp.format(time);
    279         mGPSTimeStampCalendar.setTimeInMillis(time);
    280         int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1,
    281                 mGPSTimeStampCalendar.get(Calendar.MINUTE), 1,
    282                 mGPSTimeStampCalendar.get(Calendar.SECOND), 1 };
    283         nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag);
    284         return this;
    285     }
    286 
    287     /**
    288      * Set the user description string to write.
    289      *
    290      * <p>
    291      * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
    292      * </p>
    293      *
    294      * @param description the user description string.
    295      * @return this {@link #DngCreator} object.
    296      */
    297     @NonNull
    298     public DngCreator setDescription(@NonNull String description) {
    299         if (description == null) {
    300             throw new IllegalArgumentException("Null description passed to setDescription.");
    301         }
    302         nativeSetDescription(description);
    303         return this;
    304     }
    305 
    306     /**
    307      * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
    308      * the currently configured metadata.
    309      *
    310      * <p>
    311      * Raw pixel data must have 16 bits per pixel, and the input must contain at least
    312      * {@code offset + 2 * width * height)} bytes.  The width and height of
    313      * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
    314      * and will typically be equal to the width and height of
    315      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE}.  Prior to
    316      * API level 23, this was always the same as
    317      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
    318      * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
    319      * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
    320      * metadata is available to write a well-formatted DNG file, an
    321      * {@link java.lang.IllegalStateException} will be thrown.
    322      * </p>
    323      *
    324      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
    325      * @param size the {@link Size} of the image to write, in pixels.
    326      * @param pixels an {@link java.io.InputStream} of pixel data to write.
    327      * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
    328      *               be skipped in the input before any pixel data is read.
    329      *
    330      * @throws IOException if an error was encountered in the input or output stream.
    331      * @throws java.lang.IllegalStateException if not enough metadata information has been
    332      *          set to write a well-formatted DNG file.
    333      * @throws java.lang.IllegalArgumentException if the size passed in does not match the
    334      */
    335     public void writeInputStream(@NonNull OutputStream dngOutput, @NonNull Size size,
    336             @NonNull InputStream pixels, @IntRange(from=0) long offset) throws IOException {
    337         if (dngOutput == null) {
    338             throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
    339         } else if (size == null) {
    340             throw new IllegalArgumentException("Null size passed to writeInputStream");
    341         } else if (pixels == null) {
    342             throw new IllegalArgumentException("Null pixels passed to writeInputStream");
    343         } else if (offset < 0) {
    344             throw new IllegalArgumentException("Negative offset passed to writeInputStream");
    345         }
    346 
    347         int width = size.getWidth();
    348         int height = size.getHeight();
    349         if (width <= 0 || height <= 0) {
    350             throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," +
    351                     height + ") passed to writeInputStream");
    352         }
    353         nativeWriteInputStream(dngOutput, pixels, width, height, offset);
    354     }
    355 
    356     /**
    357      * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
    358      * the currently configured metadata.
    359      *
    360      * <p>
    361      * Raw pixel data must have 16 bits per pixel, and the input must contain at least
    362      * {@code offset + 2 * width * height)} bytes.  The width and height of
    363      * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
    364      * and will typically be equal to the width and height of
    365      * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE}.  Prior to
    366      * API level 23, this was always the same as
    367      * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
    368      * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
    369      * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
    370      * metadata is available to write a well-formatted DNG file, an
    371      * {@link java.lang.IllegalStateException} will be thrown.
    372      * </p>
    373      *
    374      * <p>
    375      * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this
    376      * method.
    377      * </p>
    378      *
    379      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
    380      * @param size the {@link Size} of the image to write, in pixels.
    381      * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
    382      * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
    383      *               be skipped in the input before any pixel data is read.
    384      *
    385      * @throws IOException if an error was encountered in the input or output stream.
    386      * @throws java.lang.IllegalStateException if not enough metadata information has been
    387      *          set to write a well-formatted DNG file.
    388      */
    389     public void writeByteBuffer(@NonNull OutputStream dngOutput, @NonNull Size size,
    390             @NonNull ByteBuffer pixels, @IntRange(from=0) long offset)
    391             throws IOException {
    392         if (dngOutput == null) {
    393             throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
    394         } else if (size == null) {
    395             throw new IllegalArgumentException("Null size passed to writeByteBuffer");
    396         } else if (pixels == null) {
    397             throw new IllegalArgumentException("Null pixels passed to writeByteBuffer");
    398         } else if (offset < 0) {
    399             throw new IllegalArgumentException("Negative offset passed to writeByteBuffer");
    400         }
    401 
    402         int width = size.getWidth();
    403         int height = size.getHeight();
    404 
    405         writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE,
    406                 width * DEFAULT_PIXEL_STRIDE, offset);
    407     }
    408 
    409     /**
    410      * Write the pixel data to a DNG file with the currently configured metadata.
    411      *
    412      * <p>
    413      * For this method to succeed, the {@link android.media.Image} input must contain
    414      * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
    415      * {@link java.lang.IllegalArgumentException} will be thrown.
    416      * </p>
    417      *
    418      * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
    419      * @param pixels an {@link android.media.Image} to write.
    420      *
    421      * @throws java.io.IOException if an error was encountered in the output stream.
    422      * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
    423      * @throws java.lang.IllegalStateException if not enough metadata information has been
    424      *          set to write a well-formatted DNG file.
    425      */
    426     public void writeImage(@NonNull OutputStream dngOutput, @NonNull Image pixels)
    427             throws IOException {
    428         if (dngOutput == null) {
    429             throw new IllegalArgumentException("Null dngOutput to writeImage");
    430         } else if (pixels == null) {
    431             throw new IllegalArgumentException("Null pixels to writeImage");
    432         }
    433 
    434         int format = pixels.getFormat();
    435         if (format != ImageFormat.RAW_SENSOR) {
    436             throw new IllegalArgumentException("Unsupported image format " + format);
    437         }
    438 
    439         Image.Plane[] planes = pixels.getPlanes();
    440         if (planes == null || planes.length <= 0) {
    441             throw new IllegalArgumentException("Image with no planes passed to writeImage");
    442         }
    443 
    444         ByteBuffer buf = planes[0].getBuffer();
    445         writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput,
    446                 planes[0].getPixelStride(), planes[0].getRowStride(), 0);
    447     }
    448 
    449     @Override
    450     public void close() {
    451         nativeDestroy();
    452     }
    453 
    454     /**
    455      * Max width or height dimension for thumbnails.
    456      */
    457     public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP
    458 
    459     @Override
    460     protected void finalize() throws Throwable {
    461         try {
    462             close();
    463         } finally {
    464             super.finalize();
    465         }
    466     }
    467 
    468     private static final String GPS_LAT_REF_NORTH = "N";
    469     private static final String GPS_LAT_REF_SOUTH = "S";
    470     private static final String GPS_LONG_REF_EAST = "E";
    471     private static final String GPS_LONG_REF_WEST = "W";
    472 
    473     private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
    474     private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd HH:mm:ss";
    475     private static final DateFormat sExifGPSDateStamp =
    476             new SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.US);
    477     private final Calendar mGPSTimeStampCalendar = Calendar
    478             .getInstance(TimeZone.getTimeZone("UTC"));
    479 
    480     static {
    481         sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
    482     }
    483 
    484     private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
    485     private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
    486 
    487     // TIFF tag values needed to map between public API and TIFF spec
    488     private static final int TAG_ORIENTATION_UNKNOWN = 9;
    489 
    490     /**
    491      * Offset, rowStride, and pixelStride are given in bytes.  Height and width are given in pixels.
    492      */
    493     private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput,
    494                                  int pixelStride, int rowStride, long offset)  throws IOException {
    495         if (width <= 0 || height <= 0) {
    496             throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," +
    497                     height + ") passed to write");
    498         }
    499         long capacity = pixels.capacity();
    500         long totalSize = ((long) rowStride) * height + offset;
    501         if (capacity < totalSize) {
    502             throw new IllegalArgumentException("Image size " + capacity +
    503                     " is too small (must be larger than " + totalSize + ")");
    504         }
    505         int minRowStride = pixelStride * width;
    506         if (minRowStride > rowStride) {
    507             throw new IllegalArgumentException("Invalid image pixel stride, row byte width " +
    508                     minRowStride + " is too large, expecting " + rowStride);
    509         }
    510         pixels.clear(); // Reset mark and limit
    511         nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset,
    512                 pixels.isDirect());
    513         pixels.clear();
    514     }
    515 
    516     /**
    517      * Convert a single YUV pixel to RGB.
    518      */
    519     private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) {
    520         final int COLOR_MAX = 255;
    521 
    522         float y = yuvData[0] & 0xFF;  // Y channel
    523         float cb = yuvData[1] & 0xFF; // U channel
    524         float cr = yuvData[2] & 0xFF; // V channel
    525 
    526         // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
    527         float r = y + 1.402f * (cr - 128);
    528         float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
    529         float b = y + 1.772f * (cb - 128);
    530 
    531         // clamp to [0,255]
    532         rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r));
    533         rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g));
    534         rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b));
    535     }
    536 
    537     /**
    538      * Convert a single {@link Color} pixel to RGB.
    539      */
    540     private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) {
    541         rgbOut[outOffset] = (byte) Color.red(color);
    542         rgbOut[outOffset + 1] = (byte) Color.green(color);
    543         rgbOut[outOffset + 2] = (byte) Color.blue(color);
    544         // Discards Alpha
    545     }
    546 
    547     /**
    548      * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}.
    549      */
    550     private static ByteBuffer convertToRGB(Image yuvImage) {
    551         // TODO: Optimize this with renderscript intrinsic.
    552         int width = yuvImage.getWidth();
    553         int height = yuvImage.getHeight();
    554         ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
    555 
    556         Image.Plane yPlane = yuvImage.getPlanes()[0];
    557         Image.Plane uPlane = yuvImage.getPlanes()[1];
    558         Image.Plane vPlane = yuvImage.getPlanes()[2];
    559 
    560         ByteBuffer yBuf = yPlane.getBuffer();
    561         ByteBuffer uBuf = uPlane.getBuffer();
    562         ByteBuffer vBuf = vPlane.getBuffer();
    563 
    564         yBuf.rewind();
    565         uBuf.rewind();
    566         vBuf.rewind();
    567 
    568         int yRowStride = yPlane.getRowStride();
    569         int vRowStride = vPlane.getRowStride();
    570         int uRowStride = uPlane.getRowStride();
    571 
    572         int yPixStride = yPlane.getPixelStride();
    573         int vPixStride = vPlane.getPixelStride();
    574         int uPixStride = uPlane.getPixelStride();
    575 
    576         byte[] yuvPixel = { 0, 0, 0 };
    577         byte[] yFullRow = new byte[yPixStride * (width - 1) + 1];
    578         byte[] uFullRow = new byte[uPixStride * (width / 2 - 1) + 1];
    579         byte[] vFullRow = new byte[vPixStride * (width / 2 - 1) + 1];
    580         byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
    581         for (int i = 0; i < height; i++) {
    582             int halfH = i / 2;
    583             yBuf.position(yRowStride * i);
    584             yBuf.get(yFullRow);
    585             uBuf.position(uRowStride * halfH);
    586             uBuf.get(uFullRow);
    587             vBuf.position(vRowStride * halfH);
    588             vBuf.get(vFullRow);
    589             for (int j = 0; j < width; j++) {
    590                 int halfW = j / 2;
    591                 yuvPixel[0] = yFullRow[yPixStride * j];
    592                 yuvPixel[1] = uFullRow[uPixStride * halfW];
    593                 yuvPixel[2] = vFullRow[vPixStride * halfW];
    594                 yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow);
    595             }
    596             buf.put(finalRow);
    597         }
    598 
    599         yBuf.rewind();
    600         uBuf.rewind();
    601         vBuf.rewind();
    602         buf.rewind();
    603         return buf;
    604     }
    605 
    606     /**
    607      * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
    608      */
    609     private static ByteBuffer convertToRGB(Bitmap argbBitmap) {
    610         // TODO: Optimize this.
    611         int width = argbBitmap.getWidth();
    612         int height = argbBitmap.getHeight();
    613         ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
    614 
    615         int[] pixelRow = new int[width];
    616         byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
    617         for (int i = 0; i < height; i++) {
    618             argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i,
    619                     /*width*/width, /*height*/1);
    620             for (int j = 0; j < width; j++) {
    621                 colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow);
    622             }
    623             buf.put(finalRow);
    624         }
    625 
    626         buf.rewind();
    627         return buf;
    628     }
    629 
    630     /**
    631      * Convert coordinate to EXIF GPS tag format.
    632      */
    633     private static int[] toExifLatLong(double value) {
    634         // convert to the format dd/1 mm/1 ssss/100
    635         value = Math.abs(value);
    636         int degrees = (int) value;
    637         value = (value - degrees) * 60;
    638         int minutes = (int) value;
    639         value = (value - minutes) * 6000;
    640         int seconds = (int) value;
    641         return new int[] { degrees, 1, minutes, 1, seconds, 100 };
    642     }
    643 
    644     /**
    645      * This field is used by native code, do not access or modify.
    646      */
    647     private long mNativeContext;
    648 
    649     private static native void nativeClassInit();
    650 
    651     private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
    652                                                 CameraMetadataNative nativeResult,
    653                                                 String captureTime);
    654 
    655     private synchronized native void nativeDestroy();
    656 
    657     private synchronized native void nativeSetOrientation(int orientation);
    658 
    659     private synchronized native void nativeSetDescription(String description);
    660 
    661     private synchronized native void nativeSetGpsTags(int[] latTag, String latRef, int[] longTag,
    662                                                       String longRef, String dateTag,
    663                                                       int[] timeTag);
    664 
    665     private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height);
    666 
    667     private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
    668                                                       ByteBuffer rawBuffer, int rowStride,
    669                                                       int pixStride, long offset, boolean isDirect)
    670                                                       throws IOException;
    671 
    672     private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
    673                                                             int width, int height, long offset)
    674                                                             throws IOException;
    675 
    676     static {
    677         nativeClassInit();
    678     }
    679 }
    680