blob: 3e3303cbb09a3da478fece618dd1b40fe6843f3c [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.hardware.camera2;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.location.Location;
import android.media.ExifInterface;
import android.media.Image;
import android.os.SystemClock;
import android.util.Size;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* The {@link DngCreator} class provides functions to write raw pixel data as a DNG file.
*
* <p>
* This class is designed to be used with the {@link android.graphics.ImageFormat#RAW_SENSOR}
* buffers available from {@link android.hardware.camera2.CameraDevice}, or with Bayer-type raw
* pixel data that is otherwise generated by an application. The DNG metadata tags will be
* generated from a {@link android.hardware.camera2.CaptureResult} object or set directly.
* </p>
*
* <p>
* The DNG file format is a cross-platform file format that is used to store pixel data from
* camera sensors with minimal pre-processing applied. DNG files allow for pixel data to be
* defined in a user-defined colorspace, and have associated metadata that allow for this
* pixel data to be converted to the standard CIE XYZ colorspace during post-processing.
* </p>
*
* <p>
* For more information on the DNG file format and associated metadata, please refer to the
* <a href=
* "https://wwwimages2.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf">
* Adobe DNG 1.4.0.0 specification</a>.
* </p>
*/
public final class DngCreator implements AutoCloseable {
private static final String TAG = "DngCreator";
/**
* Create a new DNG object.
*
* <p>
* It is not necessary to call any set methods to write a well-formatted DNG file.
* </p>
* <p>
* DNG metadata tags will be generated from the corresponding parameters in the
* {@link android.hardware.camera2.CaptureResult} object. This removes or overrides
* all previous tags set.
* </p>
*
* @param characteristics an object containing the static
* {@link android.hardware.camera2.CameraCharacteristics}.
* @param metadata a metadata object to generate tags from.
*/
public DngCreator(CameraCharacteristics characteristics, CaptureResult metadata) {
if (characteristics == null || metadata == null) {
throw new NullPointerException("Null argument to DngCreator constructor");
}
// Find current time
long currentTime = System.currentTimeMillis();
// Find boot time
long bootTimeMillis = currentTime - SystemClock.elapsedRealtime();
// Find capture time (nanos since boot)
Long timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP);
long captureTime = currentTime;
if (timestamp != null) {
captureTime = timestamp / 1000000 + bootTimeMillis;
}
// Format for metadata
String formattedCaptureTime = sDateTimeStampFormat.format(captureTime);
nativeInit(characteristics.getNativeCopy(), metadata.getNativeCopy(),
formattedCaptureTime);
}
/**
* Set the orientation value to write.
*
* <p>
* This will be written as the TIFF "Orientation" tag {@code (0x0112)}.
* Calling this will override any prior settings for this tag.
* </p>
*
* @param orientation the orientation value to set, one of:
* <ul>
* <li>{@link android.media.ExifInterface#ORIENTATION_NORMAL}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_HORIZONTAL}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_180}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_FLIP_VERTICAL}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_TRANSPOSE}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_90}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_TRANSVERSE}</li>
* <li>{@link android.media.ExifInterface#ORIENTATION_ROTATE_270}</li>
* </ul>
* @return this {@link #DngCreator} object.
* @hide
*/
public DngCreator setOrientation(int orientation) {
if (orientation < ExifInterface.ORIENTATION_UNDEFINED ||
orientation > ExifInterface.ORIENTATION_ROTATE_270) {
throw new IllegalArgumentException("Orientation " + orientation +
" is not a valid EXIF orientation value");
}
nativeSetOrientation(orientation);
return this;
}
/**
* Set the thumbnail image.
*
* <p>
* Pixel data will be converted to a Baseline TIFF RGB image, with 8 bits per color channel.
* The alpha channel will be discarded.
* </p>
*
* <p>
* The given bitmap should not be altered while this object is in use.
* </p>
*
* @param pixels a {@link android.graphics.Bitmap} of pixel data.
* @return this {@link #DngCreator} object.
* @hide
*/
public DngCreator setThumbnail(Bitmap pixels) {
if (pixels == null) {
throw new NullPointerException("Null argument to setThumbnail");
}
Bitmap.Config config = pixels.getConfig();
if (config != Bitmap.Config.ARGB_8888) {
pixels = pixels.copy(Bitmap.Config.ARGB_8888, false);
if (pixels == null) {
throw new IllegalArgumentException("Unsupported Bitmap format " + config);
}
nativeSetThumbnailBitmap(pixels);
}
return this;
}
/**
* Set the thumbnail image.
*
* <p>
* Pixel data is interpreted as a {@link android.graphics.ImageFormat#YUV_420_888} image.
* </p>
*
* <p>
* The given image should not be altered while this object is in use.
* </p>
*
* @param pixels an {@link android.media.Image} object with the format
* {@link android.graphics.ImageFormat#YUV_420_888}.
* @return this {@link #DngCreator} object.
* @hide
*/
public DngCreator setThumbnail(Image pixels) {
if (pixels == null) {
throw new NullPointerException("Null argument to setThumbnail");
}
int format = pixels.getFormat();
if (format != ImageFormat.YUV_420_888) {
throw new IllegalArgumentException("Unsupported image format " + format);
}
Image.Plane[] planes = pixels.getPlanes();
nativeSetThumbnailImage(pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
planes[0].getRowStride(), planes[0].getPixelStride(), planes[1].getBuffer(),
planes[1].getRowStride(), planes[1].getPixelStride(), planes[1].getBuffer(),
planes[1].getRowStride(), planes[1].getPixelStride());
return this;
}
/**
* Set image location metadata.
*
* <p>
* The given location object must contain at least a valid time, latitude, and longitude
* (equivalent to the values returned by {@link android.location.Location#getTime()},
* {@link android.location.Location#getLatitude()}, and
* {@link android.location.Location#getLongitude()} methods).
* </p>
*
* @param location an {@link android.location.Location} object to set.
* @return this {@link #DngCreator} object.
*
* @throws java.lang.IllegalArgumentException if the given location object doesn't
* contain enough information to set location metadata.
* @hide
*/
public DngCreator setLocation(Location location) {
/*TODO*/
return this;
}
/**
* Set the user description string to write.
*
* <p>
* This is equivalent to setting the TIFF "ImageDescription" tag {@code (0x010E)}.
* </p>
*
* @param description the user description string.
* @return this {@link #DngCreator} object.
* @hide
*/
public DngCreator setDescription(String description) {
/*TODO*/
return this;
}
/**
* Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
* the currently configured metadata.
*
* <p>
* Raw pixel data must have 16 bits per pixel, and the input must contain at least
* {@code offset + 2 * width * height)} bytes. The width and height of
* the input are taken from the width and height set in the {@link DngCreator} metadata tags,
* and will typically be equal to the width and height of
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
* The pixel layout in the input is determined from the reported color filter arrangement (CFA)
* set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient
* metadata is available to write a well-formatted DNG file, an
* {@link java.lang.IllegalStateException} will be thrown.
* </p>
*
* @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
* @param size the {@link Size} of the image to write, in pixels.
* @param pixels an {@link java.io.InputStream} of pixel data to write.
* @param offset the offset of the raw image in bytes. This indicates how many bytes will
* be skipped in the input before any pixel data is read.
*
* @throws IOException if an error was encountered in the input or output stream.
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
* @throws java.lang.IllegalArgumentException if the size passed in does not match the
* @hide
*/
public void writeInputStream(OutputStream dngOutput, Size size, InputStream pixels, long offset)
throws IOException {
if (dngOutput == null || pixels == null) {
throw new NullPointerException("Null argument to writeImage");
}
nativeWriteInputStream(dngOutput, pixels, offset);
}
/**
* Write the {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data to a DNG file with
* the currently configured metadata.
*
* <p>
* Raw pixel data must have 16 bits per pixel, and the input must contain at least
* {@code offset + 2 * width * height)} bytes. The width and height of
* the input are taken from the width and height set in the {@link DngCreator} metadata tags,
* and will typically be equal to the width and height of
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE}.
* The pixel layout in the input is determined from the reported color filter arrangement (CFA)
* set in {@link CameraCharacteristics#SENSOR_INFO_COLOR_FILTER_ARRANGEMENT}. If insufficient
* metadata is available to write a well-formatted DNG file, an
* {@link java.lang.IllegalStateException} will be thrown.
* </p>
*
* @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
* @param size the {@link Size} of the image to write, in pixels.
* @param pixels an {@link java.nio.ByteBuffer} of pixel data to write.
* @param offset the offset of the raw image in bytes. This indicates how many bytes will
* be skipped in the input before any pixel data is read.
*
* @throws IOException if an error was encountered in the input or output stream.
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
* @hide
*/
public void writeByteBuffer(OutputStream dngOutput, Size size, ByteBuffer pixels, long offset)
throws IOException {
if (dngOutput == null || pixels == null) {
throw new NullPointerException("Null argument to writeImage");
}
nativeWriteByteBuffer(dngOutput, pixels, offset);
}
/**
* Write the pixel data to a DNG file with the currently configured metadata.
*
* <p>
* For this method to succeed, the {@link android.media.Image} input must contain
* {@link android.graphics.ImageFormat#RAW_SENSOR} pixel data, otherwise an
* {@link java.lang.IllegalArgumentException} will be thrown.
* </p>
*
* @param dngOutput an {@link java.io.OutputStream} to write the DNG file to.
* @param pixels an {@link android.media.Image} to write.
*
* @throws java.io.IOException if an error was encountered in the output stream.
* @throws java.lang.IllegalArgumentException if an image with an unsupported format was used.
* @throws java.lang.IllegalStateException if not enough metadata information has been
* set to write a well-formatted DNG file.
*/
public void writeImage(OutputStream dngOutput, Image pixels) throws IOException {
if (dngOutput == null || pixels == null) {
throw new NullPointerException("Null argument to writeImage");
}
int format = pixels.getFormat();
if (format != ImageFormat.RAW_SENSOR) {
throw new IllegalArgumentException("Unsupported image format " + format);
}
Image.Plane[] planes = pixels.getPlanes();
nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
planes[0].getRowStride(), planes[0].getPixelStride());
}
@Override
public void close() {
nativeDestroy();
}
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
private static final DateFormat sDateTimeStampFormat =
new SimpleDateFormat(TIFF_DATETIME_FORMAT);
static {
sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
}
/**
* This field is used by native code, do not access or modify.
*/
private long mNativeContext;
private static native void nativeClassInit();
private synchronized native void nativeInit(CameraMetadataNative nativeCharacteristics,
CameraMetadataNative nativeResult,
String captureTime);
private synchronized native void nativeDestroy();
private synchronized native void nativeSetOrientation(int orientation);
private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
private synchronized native void nativeSetThumbnailImage(int width, int height,
ByteBuffer yBuffer, int yRowStride,
int yPixStride, ByteBuffer uBuffer,
int uRowStride, int uPixStride,
ByteBuffer vBuffer, int vRowStride,
int vPixStride);
private synchronized native void nativeWriteImage(OutputStream out, int width, int height,
ByteBuffer rawBuffer, int rowStride,
int pixStride) throws IOException;
private synchronized native void nativeWriteByteBuffer(OutputStream out, ByteBuffer rawBuffer,
long offset) throws IOException;
private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
long offset) throws IOException;
static {
nativeClassInit();
}
}