1ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk/*
2ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * Copyright 2014 The Android Open Source Project
3ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
4ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * Licensed under the Apache License, Version 2.0 (the "License");
5ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * you may not use this file except in compliance with the License.
6ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * You may obtain a copy of the License at
7ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
8ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *      http://www.apache.org/licenses/LICENSE-2.0
9ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
10ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * Unless required by applicable law or agreed to in writing, software
11ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * distributed under the License is distributed on an "AS IS" BASIS,
12ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * See the License for the specific language governing permissions and
14ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * limitations under the License.
15ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk */
16ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
17b6079005ed0631c3972ff427f56e12523ec214a7Ruben Brunkpackage android.hardware.camera2;
18ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
19ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport android.graphics.Bitmap;
2047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunkimport android.graphics.Color;
21f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunkimport android.graphics.ImageFormat;
22f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunkimport android.hardware.camera2.impl.CameraMetadataNative;
23ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport android.location.Location;
24b6079005ed0631c3972ff427f56e12523ec214a7Ruben Brunkimport android.media.ExifInterface;
25b6079005ed0631c3972ff427f56e12523ec214a7Ruben Brunkimport android.media.Image;
26b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunkimport android.os.SystemClock;
27f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunkimport android.util.Size;
28ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
29ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport java.io.IOException;
30ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport java.io.InputStream;
31ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport java.io.OutputStream;
32ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunkimport java.nio.ByteBuffer;
33b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunkimport java.text.DateFormat;
34b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunkimport java.text.SimpleDateFormat;
3547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunkimport java.util.Calendar;
36b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunkimport java.util.TimeZone;
37ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
38ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk/**
39ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
40ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
41ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * <p>
42ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
43ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
44ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * pixel data that is otherwise generated by an application.  The DNG metadata tags will be
45ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
46ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * </p>
47ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
48ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * <p>
49ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * The DNG file format is a cross-platform file format that is used to store pixel data from
50ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * camera sensors with minimal pre-processing applied.  DNG files allow for pixel data to be
51ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * defined in a user-defined colorspace, and have associated metadata that allow for this
52ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
53ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * </p>
54ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk *
55ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * <p>
56ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * For more information on the DNG file format and associated metadata, please refer to the
57ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * <a href=
58ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
59ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * Adobe DNG 1.4.0.0 specification</a>.
60ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk * </p>
61ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk */
62f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunkpublic final class DngCreator implements AutoCloseable {
63ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
64b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk    private static final String TAG = "DngCreator";
65ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
66ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Create a new DNG object.
67ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
68ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
69ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * It is not necessary to call any set methods to write a well-formatted DNG file.
70ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
71ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
72ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * DNG metadata tags will be generated from the corresponding parameters in the
7347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * {@link android.hardware.camera2.CaptureResult} object.
7447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * </p>
7547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * <p>
7647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * For best quality DNG files, it is strongly recommended that lens shading map output is
7747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}.
78ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
79ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param characteristics an object containing the static
80ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *          {@link android.hardware.camera2.CameraCharacteristics}.
81ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param metadata a metadata object to generate tags from.
82ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
83f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
84f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (characteristics == null || metadata == null) {
8547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null argument to DngCreator constructor");
86f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
87b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
88b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        // Find current time
89b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        long currentTime = System.currentTimeMillis();
90b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
91b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        // Find boot time
92b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
93b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
94b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        // Find capture time (nanos since boot)
95b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
96b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        long captureTime = currentTime;
97b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        if (timestamp != null) {
98b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk            captureTime = timestamp / 1000000 + bootTimeMillis;
99b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        }
100b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
101b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        // Format for metadata
102b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        String formattedCaptureTime = sDateTimeStampFormat.format(captureTime);
103b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
104b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
105b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk                formattedCaptureTime);
106f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
107ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
108ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
109ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Set the orientation value to write.
110ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
111ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
112ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
113ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Calling this will override any prior settings for this tag.
114ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
115ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
116ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param orientation the orientation value to set, one of:
117ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                    <ul>
118ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
119ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
120ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
121ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
122ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
123ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
124ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
125ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                      <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
126ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *                    </ul>
127ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @return this {@link #DngCreator} object.
128ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
129ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    public DngCreator setOrientation(int orientation) {
130f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
131f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk                orientation > ExifInterface.ORIENTATION_ROTATE_270) {
132f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            throw new IllegalArgumentException("Orientation " + orientation +
133f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk                    " is not a valid EXIF orientation value");
134f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
135f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        nativeSetOrientation(orientation);
136ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk        return this;
137ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    }
138ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
139ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
140ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Set the thumbnail image.
141ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
142ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
143ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
14447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * The alpha channel will be discarded.  Thumbnail images with a dimension larger than
14547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected.
146ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
147ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
148ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param pixels a {@link android.graphics.Bitmap} of pixel data.
149ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @return this {@link #DngCreator} object.
15047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
15147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
152ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
153ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    public DngCreator setThumbnail(Bitmap pixels) {
154f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (pixels == null) {
15547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null argument to setThumbnail");
156f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
157f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
15847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = pixels.getWidth();
15947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = pixels.getHeight();
160f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
16147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
16247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
16347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    "," + height + ") too large, dimensions must be smaller than " +
16447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    MAX_THUMBNAIL_DIMENSION);
165f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
166f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
16747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer rgbBuffer = convertToRGB(pixels);
16847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeSetThumbnail(rgbBuffer, width, height);
16947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
170ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk        return this;
171ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    }
172ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
173ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
174ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Set the thumbnail image.
175ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
176ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
177ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
17847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be
17947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * rejected.
180ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
181ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
182ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param pixels an {@link android.media.Image} object with the format
183ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *               {@link android.graphics.ImageFormat#YUV_420_888}.
184ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @return this {@link #DngCreator} object.
18547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
18647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     *      larger than {@link #MAX_THUMBNAIL_DIMENSION}.
187ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
188ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    public DngCreator setThumbnail(Image pixels) {
189f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (pixels == null) {
19047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null argument to setThumbnail");
191f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
192f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
193f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        int format = pixels.getFormat();
194f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (format != ImageFormat.YUV_420_888) {
19547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Unsupported Image format " + format);
196f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
197f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
19847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = pixels.getWidth();
19947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = pixels.getHeight();
20047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
20147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
20247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
20347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    "," + height + ") too large, dimensions must be smaller than " +
20447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    MAX_THUMBNAIL_DIMENSION);
20547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
20647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
20747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer rgbBuffer = convertToRGB(pixels);
20847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeSetThumbnail(rgbBuffer, width, height);
209f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
210ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk        return this;
211ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    }
212ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
213ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
214ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Set image location metadata.
215ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
216ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
217ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * The given location object must contain at least a valid time, latitude, and longitude
218ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * (equivalent to the values returned by {@link android.location.Location#getTime()},
219ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link android.location.Location#getLatitude()}, and
220ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link android.location.Location#getLongitude()} methods).
221ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
222ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
223ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param location an {@link android.location.Location} object to set.
224ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @return this {@link #DngCreator} object.
225ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
226ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.lang.IllegalArgumentException if the given location object doesn't
227ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *          contain enough information to set location metadata.
228ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
229f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public DngCreator setLocation(Location location) {
23047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (location == null) {
23147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null location passed to setLocation");
23247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
23347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        double latitude = location.getLatitude();
23447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        double longitude = location.getLongitude();
23547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        long time = location.getTime();
23647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
23747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int[] latTag = toExifLatLong(latitude);
23847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int[] longTag = toExifLatLong(longitude);
23947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH;
24047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST;
24147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
24247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        String dateTag = sExifGPSDateStamp.format(time);
24347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        mGPSTimeStampCalendar.setTimeInMillis(time);
24447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1,
24547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                mGPSTimeStampCalendar.get(Calendar.MINUTE), 1,
24647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                mGPSTimeStampCalendar.get(Calendar.SECOND), 1 };
24747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag);
248f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        return this;
249f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
250ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
251ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
252ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Set the user description string to write.
253ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
254ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
255ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
256ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
257ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
258ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param description the user description string.
259ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @return this {@link #DngCreator} object.
260ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
261ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    public DngCreator setDescription(String description) {
26247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (description == null) {
26347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null description passed to setDescription.");
26447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
26547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeSetDescription(description);
266ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk        return this;
267ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    }
268ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
269ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
270ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
271ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * the currently configured metadata.
272ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
273ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
274ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Raw pixel data must have 16 bits per pixel, and the input must contain at least
275f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * {@code offset + 2 * width * height)} bytes.  The width and height of
276ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
277ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * and will typically be equal to the width and height of
278f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
279f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
280f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
281f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * metadata is available to write a well-formatted DNG file, an
282ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link java.lang.IllegalStateException} will be thrown.
283ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
284ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
285ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
286f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * @param size the {@link Size} of the image to write, in pixels.
287ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param pixels an {@link java.io.InputStream} of pixel data to write.
288ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
289ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *               be skipped in the input before any pixel data is read.
290ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
291ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws IOException if an error was encountered in the input or output stream.
292ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.lang.IllegalStateException if not enough metadata information has been
293ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *          set to write a well-formatted DNG file.
294f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * @throws java.lang.IllegalArgumentException if the size passed in does not match the
295ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
296f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
297f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            throws IOException {
29847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (dngOutput == null) {
29947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
30047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (size == null) {
30147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null size passed to writeInputStream");
30247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (pixels == null) {
30347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null pixels passed to writeInputStream");
30447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (offset < 0) {
30547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Negative offset passed to writeInputStream");
306f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
30747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
30847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = size.getWidth();
30947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = size.getHeight();
31047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (width <= 0 || height <= 0) {
31147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," +
31247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    height + ") passed to writeInputStream");
31347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
31447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeWriteInputStream(dngOutput, pixels, width, height, offset);
315ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    }
316ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
317ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
318ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
319ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * the currently configured metadata.
320ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
321ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
322ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Raw pixel data must have 16 bits per pixel, and the input must contain at least
323f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * {@code offset + 2 * width * height)} bytes.  The width and height of
324ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * the input are taken from the width and height set in the {@link DngCreator} metadata tags,
325ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * and will typically be equal to the width and height of
326f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
327f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * The pixel layout in the input is determined from the reported color filter arrangement (CFA)
328f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}.  If insufficient
329f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * metadata is available to write a well-formatted DNG file, an
330ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link java.lang.IllegalStateException} will be thrown.
331ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
332ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
33347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * <p>
33447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this
33547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * method.
33647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * </p>
33747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     *
338ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
339b6079005ed0631c3972ff427f56e12523ec214a7Ruben Brunk     * @param size the {@link Size} of the image to write, in pixels.
340ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
341ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param offset the offset of the raw image in bytes.  This indicates how many bytes will
342ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *               be skipped in the input before any pixel data is read.
343ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
344ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws IOException if an error was encountered in the input or output stream.
345ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.lang.IllegalStateException if not enough metadata information has been
346ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *          set to write a well-formatted DNG file.
347ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
348f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
349f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            throws IOException {
35047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (dngOutput == null) {
35147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
35247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (size == null) {
35347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null size passed to writeByteBuffer");
35447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (pixels == null) {
35547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null pixels passed to writeByteBuffer");
35647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (offset < 0) {
35747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Negative offset passed to writeByteBuffer");
358f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
35947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
36047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = size.getWidth();
36147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = size.getHeight();
36247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
36347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE,
36447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                width * DEFAULT_PIXEL_STRIDE, offset);
365f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
366ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
367ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk    /**
368ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * Write the pixel data to a DNG file with the currently configured metadata.
369ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
370ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * <p>
371ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * For this method to succeed, the {@link android.media.Image} input must contain
372ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
373ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * {@link java.lang.IllegalArgumentException} will be thrown.
374ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * </p>
375ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
376ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
377ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @param pixels an {@link android.media.Image} to write.
378ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *
379ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.io.IOException if an error was encountered in the output stream.
380ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
381ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     * @throws java.lang.IllegalStateException if not enough metadata information has been
382ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     *          set to write a well-formatted DNG file.
383ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk     */
384f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
38547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (dngOutput == null) {
38647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null dngOutput to writeImage");
38747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        } else if (pixels == null) {
38847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Null pixels to writeImage");
389f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
390ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk
391f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        int format = pixels.getFormat();
392f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        if (format != ImageFormat.RAW_SENSOR) {
393f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            throw new IllegalArgumentException("Unsupported image format " + format);
394f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
395f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
396f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        Image.Plane[] planes = pixels.getPlanes();
39747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (planes == null || planes.length <= 0) {
39847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Image with no planes passed to writeImage");
39947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
40047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
40147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer buf = planes[0].getBuffer();
40247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput,
40347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                planes[0].getPixelStride(), planes[0].getRowStride(), 0);
404f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
405f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
406f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    @Override
407f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    public void close() {
408f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        nativeDestroy();
409f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
410f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
41147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
41247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Max width or height dimension for thumbnails.
41347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
41447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP
41547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
416f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    @Override
417f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    protected void finalize() throws Throwable {
418f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        try {
419f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            close();
420f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        } finally {
421f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk            super.finalize();
422f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        }
423f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
424f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
42547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final String GPS_LAT_REF_NORTH = "N";
42647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final String GPS_LAT_REF_SOUTH = "S";
42747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final String GPS_LONG_REF_EAST = "E";
42847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final String GPS_LONG_REF_WEST = "W";
42947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
43047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
431b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk    private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
43247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
433b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk    private static final DateFormat sDateTimeStampFormat =
434b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk            new SimpleDateFormat(TIFF_DATETIME_FORMAT);
43547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private final Calendar mGPSTimeStampCalendar = Calendar
43647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            .getInstance(TimeZone.getTimeZone("UTC"));
437b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk
438b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk    static {
439b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk        sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
44047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
44147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
44247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
44347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
44447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
44547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
44647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
44747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Offset, rowStride, and pixelStride are given in bytes.  Height and width are given in pixels.
44847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
44947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput,
45047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                 int pixelStride, int rowStride, long offset)  throws IOException {
45147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (width <= 0 || height <= 0) {
45247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," +
45347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    height + ") passed to write");
45447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
45547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        long capacity = pixels.capacity();
45647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        long totalSize = rowStride * height + offset;
45747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (capacity < totalSize) {
45847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Image size " + capacity +
45947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    " is too small (must be larger than " + totalSize + ")");
46047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
46147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int minRowStride = pixelStride * width;
46247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        if (minRowStride > rowStride) {
46347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            throw new IllegalArgumentException("Invalid image pixel stride, row byte width " +
46447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    minRowStride + " is too large, expecting " + rowStride);
46547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
46647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        pixels.clear(); // Reset mark and limit
46747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset,
46847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                pixels.isDirect());
46947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        pixels.clear();
47047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
47147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
47247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
47347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Convert a single YUV pixel to RGB.
47447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
47547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) {
47647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        final int COLOR_MAX = 255;
47747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
47847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float y = yuvData[0] & 0xFF;  // Y channel
47947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float cb = yuvData[1] & 0xFF; // U channel
48047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float cr = yuvData[2] & 0xFF; // V channel
48147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
48247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
48347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float r = y + 1.402f * (cr - 128);
48447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
48547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        float b = y + 1.772f * (cb - 128);
48647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
48747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // clamp to [0,255]
48847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r));
48947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g));
49047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b));
491b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk    }
49247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
49347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
49447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Convert a single {@link Color} pixel to RGB.
49547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
49647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) {
49747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset] = (byte) Color.red(color);
49847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset + 1] = (byte) Color.green(color);
49947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        rgbOut[outOffset + 2] = (byte) Color.blue(color);
50047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // Discards Alpha
50147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
50247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
50347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
50447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}.
50547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
50647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static ByteBuffer convertToRGB(Image yuvImage) {
50747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // TODO: Optimize this with renderscript intrinsic.
50847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = yuvImage.getWidth();
50947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = yuvImage.getHeight();
51047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
51147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
51247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        Image.Plane yPlane = yuvImage.getPlanes()[0];
51347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        Image.Plane uPlane = yuvImage.getPlanes()[1];
51447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        Image.Plane vPlane = yuvImage.getPlanes()[2];
51547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
51647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer yBuf = yPlane.getBuffer();
51747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer uBuf = uPlane.getBuffer();
51847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer vBuf = vPlane.getBuffer();
51947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
52047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        yBuf.rewind();
52147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        uBuf.rewind();
52247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        vBuf.rewind();
52347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
52447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int yRowStride = yPlane.getRowStride();
52547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int vRowStride = vPlane.getRowStride();
52647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int uRowStride = uPlane.getRowStride();
52747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
52847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int yPixStride = yPlane.getPixelStride();
52947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int vPixStride = vPlane.getPixelStride();
53047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int uPixStride = uPlane.getPixelStride();
53147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
53247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        byte[] yuvPixel = { 0, 0, 0 };
533fdb2112bbc140aab869aac047454cd2f67809df1Lajos Molnar        byte[] yFullRow = new byte[yPixStride * (width - 1) + 1];
534fdb2112bbc140aab869aac047454cd2f67809df1Lajos Molnar        byte[] uFullRow = new byte[uPixStride * (width / 2 - 1) + 1];
535fdb2112bbc140aab869aac047454cd2f67809df1Lajos Molnar        byte[] vFullRow = new byte[vPixStride * (width / 2 - 1) + 1];
53647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
53747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        for (int i = 0; i < height; i++) {
53847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            int halfH = i / 2;
53947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            yBuf.position(yRowStride * i);
54047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            yBuf.get(yFullRow);
54147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            uBuf.position(uRowStride * halfH);
54247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            uBuf.get(uFullRow);
54347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            vBuf.position(vRowStride * halfH);
54447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            vBuf.get(vFullRow);
54547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            for (int j = 0; j < width; j++) {
54647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                int halfW = j / 2;
54747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                yuvPixel[0] = yFullRow[yPixStride * j];
54847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                yuvPixel[1] = uFullRow[uPixStride * halfW];
54947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                yuvPixel[2] = vFullRow[vPixStride * halfW];
55047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow);
55147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            }
55247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            buf.put(finalRow);
55347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
55447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
55547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        yBuf.rewind();
55647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        uBuf.rewind();
55747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        vBuf.rewind();
55847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        buf.rewind();
55947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        return buf;
56047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
56147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
56247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
56347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
56447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
56547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static ByteBuffer convertToRGB(Bitmap argbBitmap) {
56647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // TODO: Optimize this.
56747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int width = argbBitmap.getWidth();
56847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int height = argbBitmap.getHeight();
56947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
57047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
57147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int[] pixelRow = new int[width];
57247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
57347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        for (int i = 0; i < height; i++) {
57447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i,
57547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                    /*width*/width, /*height*/1);
57647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            for (int j = 0; j < width; j++) {
57747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow);
57847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            }
57947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk            buf.put(finalRow);
58047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        }
58147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
58247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        buf.rewind();
58347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        return buf;
58447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
58547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
58647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    /**
58747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     * Convert coordinate to EXIF GPS tag format.
58847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk     */
58947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private static int[] toExifLatLong(double value) {
59047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        // convert to the format dd/1 mm/1 ssss/100
59147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        value = Math.abs(value);
59247e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int degrees = (int) value;
59347e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        value = (value - degrees) * 60;
59447e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int minutes = (int) value;
59547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        value = (value - minutes) * 6000;
59647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        int seconds = (int) value;
59747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk        return new int[] { degrees, 1, minutes, 1, seconds, 100 };
59847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    }
59947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
600f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    /**
601f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     * This field is used by native code, do not access or modify.
602f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk     */
603f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private long mNativeContext;
604f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
605f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private static native void nativeClassInit();
606f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
607f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
608b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk                                                CameraMetadataNative nativeResult,
609b8df8e07d6fc530c82d21ca3199411e2e60975b1Ruben Brunk                                                String captureTime);
610f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
611f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private synchronized native void nativeDestroy();
612f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
613f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private synchronized native void nativeSetOrientation(int orientation);
614f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
61547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private synchronized native void nativeSetDescription(String description);
616f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
61747e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private synchronized native void nativeSetGpsTags(int[] latTag, String latRef, int[] longTag,
61847e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                      String longRef, String dateTag,
61947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                      int[] timeTag);
62047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk
62147e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk    private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height);
622f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
623f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
624f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk                                                      ByteBuffer rawBuffer, int rowStride,
62547e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                      int pixStride, long offset, boolean isDirect)
62647e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                      throws IOException;
627f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
628f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
62947e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                            int width, int height, long offset)
63047e91f20952e5eb2290146ba6e33a694dd2e45e8Ruben Brunk                                                            throws IOException;
631f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk
632f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    static {
633f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk        nativeClassInit();
634f967a5486a78db244624fde4c105aa5e6fa914b9Ruben Brunk    }
635ab6c9f75ff4299cd5a6a3017972b670e45e406b1Ruben Brunk}
636