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