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