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