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