DngCreator.java revision b8df8e07d6fc530c82d21ca3199411e2e60975b1
1/* 2 * Copyright 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.hardware.camera2; 18 19import android.graphics.Bitmap; 20import android.graphics.ImageFormat; 21import android.hardware.camera2.impl.CameraMetadataNative; 22import android.location.Location; 23import android.media.ExifInterface; 24import android.media.Image; 25import android.os.SystemClock; 26import android.util.Size; 27 28import java.io.IOException; 29import java.io.InputStream; 30import java.io.OutputStream; 31import java.nio.ByteBuffer; 32import java.text.DateFormat; 33import java.text.SimpleDateFormat; 34import java.util.TimeZone; 35 36/** 37 * The {@link DngCreator} class provides functions to write raw pixel data as a DNG file. 38 * 39 * <p> 40 * This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR} 41 * buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw 42 * pixel data that is otherwise generated by an application. The DNG metadata tags will be 43 * generated from a {@link android.hardware.camera2.CaptureResult} object or set directly. 44 * </p> 45 * 46 * <p> 47 * The DNG file format is a cross-platform file format that is used to store pixel data from 48 * camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be 49 * defined in a user-defined colorspace, and have associated metadata that allow for this 50 * pixel data to be converted to the standard CIE XYZ colorspace during post-processing. 51 * </p> 52 * 53 * <p> 54 * For more information on the DNG file format and associated metadata, please refer to the 55 * <a href= 56 * "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf"> 57 * Adobe DNG 1.4.0.0 specification</a>. 58 * </p> 59 */ 60public final class DngCreator implements AutoCloseable { 61 62 private static final String TAG = "DngCreator"; 63 /** 64 * Create a new DNG object. 65 * 66 * <p> 67 * It is not necessary to call any set methods to write a well-formatted DNG file. 68 * </p> 69 * <p> 70 * DNG metadata tags will be generated from the corresponding parameters in the 71 * {@link android.hardware.camera2.CaptureResult} object. This removes or overrides 72 * all previous tags set. 73 * </p> 74 * 75 * @param characteristics an object containing the static 76 * {@link android.hardware.camera2.CameraCharacteristics}. 77 * @param metadata a metadata object to generate tags from. 78 */ 79 public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) { 80 if (characteristics == null || metadata == null) { 81 throw new NullPointerException("Null argument to DngCreator constructor"); 82 } 83 84 // Find current time 85 long currentTime = System.currentTimeMillis(); 86 87 // Find boot time 88 long bootTimeMillis = currentTime - SystemClock.elapsedRealtime(); 89 90 // Find capture time (nanos since boot) 91 Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP); 92 long captureTime = currentTime; 93 if (timestamp != null) { 94 captureTime = timestamp / 1000000 + bootTimeMillis; 95 } 96 97 // Format for metadata 98 String formattedCaptureTime = sDateTimeStampFormat.format(captureTime); 99 100 nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(), 101 formattedCaptureTime); 102 } 103 104 /** 105 * Set the orientation value to write. 106 * 107 * <p> 108 * This will be written as the TIFF "Orientation" tag {@code (0x0112)}. 109 * Calling this will override any prior settings for this tag. 110 * </p> 111 * 112 * @param orientation the orientation value to set, one of: 113 * <ul> 114 * <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li> 115 * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li> 116 * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li> 117 * <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li> 118 * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li> 119 * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li> 120 * <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li> 121 * <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li> 122 * </ul> 123 * @return this {@link #DngCreator} object. 124 */ 125 public DngCreator setOrientation(int orientation) { 126 127 if (orientation < ExifInterface.ORIENTATION_UNDEFINED || 128 orientation > ExifInterface.ORIENTATION_ROTATE_270) { 129 throw new IllegalArgumentException("Orientation " + orientation + 130 " is not a valid EXIF orientation value"); 131 } 132 nativeSetOrientation(orientation); 133 return this; 134 } 135 136 /** 137 * Set the thumbnail image. 138 * 139 * <p> 140 * Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel. 141 * The alpha channel will be discarded. 142 * </p> 143 * 144 * <p> 145 * The given bitmap should not be altered while this object is in use. 146 * </p> 147 * 148 * @param pixels a {@link android.graphics.Bitmap} of pixel data. 149 * @return this {@link #DngCreator} object. 150 */ 151 public DngCreator setThumbnail(Bitmap pixels) { 152 if (pixels == null) { 153 throw new NullPointerException("Null argument to setThumbnail"); 154 } 155 156 Bitmap.Config config = pixels.getConfig(); 157 158 if (config != Bitmap.Config.ARGB_8888) { 159 pixels = pixels.copy(Bitmap.Config.ARGB_8888, false); 160 if (pixels == null) { 161 throw new IllegalArgumentException("Unsupported Bitmap format " + config); 162 } 163 nativeSetThumbnailBitmap(pixels); 164 } 165 166 return this; 167 } 168 169 /** 170 * Set the thumbnail image. 171 * 172 * <p> 173 * Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image. 174 * </p> 175 * 176 * <p> 177 * The given image should not be altered while this object is in use. 178 * </p> 179 * 180 * @param pixels an {@link android.media.Image} object with the format 181 * {@link android.graphics.ImageFormat#YUV_420_888}. 182 * @return this {@link #DngCreator} object. 183 */ 184 public DngCreator setThumbnail(Image pixels) { 185 if (pixels == null) { 186 throw new NullPointerException("Null argument to setThumbnail"); 187 } 188 189 int format = pixels.getFormat(); 190 if (format != ImageFormat.YUV_420_888) { 191 throw new IllegalArgumentException("Unsupported image format " + format); 192 } 193 194 Image.Plane[] planes = pixels.getPlanes(); 195 nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), 196 planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(), 197 planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(), 198 planes[1].getRowStride(), planes[1].getPixelStride()); 199 200 return this; 201 } 202 203 204 /** 205 * Set image location metadata. 206 * 207 * <p> 208 * The given location object must contain at least a valid time, latitude, and longitude 209 * (equivalent to the values returned by {@link android.location.Location#getTime()}, 210 * {@link android.location.Location#getLatitude()}, and 211 * {@link android.location.Location#getLongitude()} methods). 212 * </p> 213 * 214 * @param location an {@link android.location.Location} object to set. 215 * @return this {@link #DngCreator} object. 216 * 217 * @throws java.lang.IllegalArgumentException if the given location object doesn't 218 * contain enough information to set location metadata. 219 */ 220 public DngCreator setLocation(Location location) { 221 /*TODO*/ 222 return this; 223 } 224 225 /** 226 * Set the user description string to write. 227 * 228 * <p> 229 * This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}. 230 * </p> 231 * 232 * @param description the user description string. 233 * @return this {@link #DngCreator} object. 234 */ 235 public DngCreator setDescription(String description) { 236 /*TODO*/ 237 return this; 238 } 239 240 /** 241 * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with 242 * the currently configured metadata. 243 * 244 * <p> 245 * Raw pixel data must have 16 bits per pixel, and the input must contain at least 246 * {@code offset + 2 * width * height)} bytes. The width and height of 247 * the input are taken from the width and height set in the {@link DngCreator} metadata tags, 248 * and will typically be equal to the width and height of 249 * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. 250 * The pixel layout in the input is determined from the reported color filter arrangement (CFA) 251 * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient 252 * metadata is available to write a well-formatted DNG file, an 253 * {@link java.lang.IllegalStateException} will be thrown. 254 * </p> 255 * 256 * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. 257 * @param size the {@link Size} of the image to write, in pixels. 258 * @param pixels an {@link java.io.InputStream} of pixel data to write. 259 * @param offset the offset of the raw image in bytes. This indicates how many bytes will 260 * be skipped in the input before any pixel data is read. 261 * 262 * @throws IOException if an error was encountered in the input or output stream. 263 * @throws java.lang.IllegalStateException if not enough metadata information has been 264 * set to write a well-formatted DNG file. 265 * @throws java.lang.IllegalArgumentException if the size passed in does not match the 266 */ 267 public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset) 268 throws IOException { 269 if (dngOutput == null || pixels == null) { 270 throw new NullPointerException("Null argument to writeImage"); 271 } 272 nativeWriteInputStream(dngOutput, pixels, offset); 273 } 274 275 /** 276 * Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with 277 * the currently configured metadata. 278 * 279 * <p> 280 * Raw pixel data must have 16 bits per pixel, and the input must contain at least 281 * {@code offset + 2 * width * height)} bytes. The width and height of 282 * the input are taken from the width and height set in the {@link DngCreator} metadata tags, 283 * and will typically be equal to the width and height of 284 * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}. 285 * The pixel layout in the input is determined from the reported color filter arrangement (CFA) 286 * set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient 287 * metadata is available to write a well-formatted DNG file, an 288 * {@link java.lang.IllegalStateException} will be thrown. 289 * </p> 290 * 291 * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. 292 * @param size the {@link Size} of the image to write, in pixels. 293 * @param pixels an {@link java.nio.ByteBuffer} of pixel data to write. 294 * @param offset the offset of the raw image in bytes. This indicates how many bytes will 295 * be skipped in the input before any pixel data is read. 296 * 297 * @throws IOException if an error was encountered in the input or output stream. 298 * @throws java.lang.IllegalStateException if not enough metadata information has been 299 * set to write a well-formatted DNG file. 300 */ 301 public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset) 302 throws IOException { 303 if (dngOutput == null || pixels == null) { 304 throw new NullPointerException("Null argument to writeImage"); 305 } 306 nativeWriteByteBuffer(dngOutput, pixels, offset); 307 } 308 309 /** 310 * Write the pixel data to a DNG file with the currently configured metadata. 311 * 312 * <p> 313 * For this method to succeed, the {@link android.media.Image} input must contain 314 * {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an 315 * {@link java.lang.IllegalArgumentException} will be thrown. 316 * </p> 317 * 318 * @param dngOutput an {@link java.io.OutputStream} to write the DNG file to. 319 * @param pixels an {@link android.media.Image} to write. 320 * 321 * @throws java.io.IOException if an error was encountered in the output stream. 322 * @throws java.lang.IllegalArgumentException if an image with an unsupported format was used. 323 * @throws java.lang.IllegalStateException if not enough metadata information has been 324 * set to write a well-formatted DNG file. 325 */ 326 public void writeImage(OutputStream dngOutput, Image pixels) throws IOException { 327 if (dngOutput == null || pixels == null) { 328 throw new NullPointerException("Null argument to writeImage"); 329 } 330 331 int format = pixels.getFormat(); 332 if (format != ImageFormat.RAW_SENSOR) { 333 throw new IllegalArgumentException("Unsupported image format " + format); 334 } 335 336 Image.Plane[] planes = pixels.getPlanes(); 337 nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(), 338 planes[0].getRowStride(), planes[0].getPixelStride()); 339 } 340 341 @Override 342 public void close() { 343 nativeDestroy(); 344 } 345 346 @Override 347 protected void finalize() throws Throwable { 348 try { 349 close(); 350 } finally { 351 super.finalize(); 352 } 353 } 354 355 private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss"; 356 private static final DateFormat sDateTimeStampFormat = 357 new SimpleDateFormat(TIFF_DATETIME_FORMAT); 358 359 static { 360 sDateTimeStampFormat.setTimeZone(TimeZone.getDefault()); 361 } 362 /** 363 * This field is used by native code, do not access or modify. 364 */ 365 private long mNativeContext; 366 367 private static native void nativeClassInit(); 368 369 private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics, 370 CameraMetadataNative nativeResult, 371 String captureTime); 372 373 private synchronized native void nativeDestroy(); 374 375 private synchronized native void nativeSetOrientation(int orientation); 376 377 private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap); 378 379 private synchronized native void nativeSetThumbnailImage(int width, int height, 380 ByteBuffer yBuffer, int yRowStride, 381 int yPixStride, ByteBuffer uBuffer, 382 int uRowStride, int uPixStride, 383 ByteBuffer vBuffer, int vRowStride, 384 int vPixStride); 385 386 private synchronized native void nativeWriteImage(OutputStream out, int width, int height, 387 ByteBuffer rawBuffer, int rowStride, 388 int pixStride) throws IOException; 389 390 private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer, 391 long offset) throws IOException; 392 393 private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream, 394 long offset) throws IOException; 395 396 static { 397 nativeClassInit(); 398 } 399} 400