package android.hardware.camera2;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.ImageFormat;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.location.Location;
import java.nio.ByteBuffer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.TimeZone;
/**
* </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.
+ * {@link android.hardware.camera2.CaptureResult} object.
+ * </p>
+ * <p>
+ * For best quality DNG files, it is strongly recommended that lens shading map output is
+ * enabled if supported. See {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE}.
* </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");
+ throw new IllegalArgumentException("Null argument to DngCreator constructor");
}
// Find current time
* <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 +
*
* <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.
+ * The alpha channel will be discarded. Thumbnail images with a dimension larger than
+ * {@link #MAX_THUMBNAIL_DIMENSION} will be rejected.
* </p>
*
* @param pixels a {@link android.graphics.Bitmap} of pixel data.
* @return this {@link #DngCreator} object.
- * @hide
+ * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+ * larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
public DngCreator setThumbnail(Bitmap pixels) {
if (pixels == null) {
- throw new NullPointerException("Null argument to setThumbnail");
+ throw new IllegalArgumentException("Null argument to setThumbnail");
}
- Bitmap.Config config = pixels.getConfig();
+ int width = pixels.getWidth();
+ int height = pixels.getHeight();
- 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);
+ if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+ throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+ "," + height + ") too large, dimensions must be smaller than " +
+ MAX_THUMBNAIL_DIMENSION);
}
+ ByteBuffer rgbBuffer = convertToRGB(pixels);
+ nativeSetThumbnail(rgbBuffer, width, height);
+
return this;
}
*
* <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.
+ * Thumbnail images with a dimension larger than {@link #MAX_THUMBNAIL_DIMENSION} will be
+ * rejected.
* </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
+ * @throws java.lang.IllegalArgumentException if the given thumbnail image has a dimension
+ * larger than {@link #MAX_THUMBNAIL_DIMENSION}.
*/
public DngCreator setThumbnail(Image pixels) {
if (pixels == null) {
- throw new NullPointerException("Null argument to setThumbnail");
+ throw new IllegalArgumentException("Null argument to setThumbnail");
}
int format = pixels.getFormat();
if (format != ImageFormat.YUV_420_888) {
- throw new IllegalArgumentException("Unsupported image format " + format);
+ 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());
+ int width = pixels.getWidth();
+ int height = pixels.getHeight();
+
+ if (width > MAX_THUMBNAIL_DIMENSION || height > MAX_THUMBNAIL_DIMENSION) {
+ throw new IllegalArgumentException("Thumbnail dimensions width,height (" + width +
+ "," + height + ") too large, dimensions must be smaller than " +
+ MAX_THUMBNAIL_DIMENSION);
+ }
+
+ ByteBuffer rgbBuffer = convertToRGB(pixels);
+ nativeSetThumbnail(rgbBuffer, width, height);
return this;
}
-
/**
* Set image location metadata.
*
*
* @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*/
+ if (location == null) {
+ throw new IllegalArgumentException("Null location passed to setLocation");
+ }
+ double latitude = location.getLatitude();
+ double longitude = location.getLongitude();
+ long time = location.getTime();
+
+ int[] latTag = toExifLatLong(latitude);
+ int[] longTag = toExifLatLong(longitude);
+ String latRef = latitude >= 0 ? GPS_LAT_REF_NORTH : GPS_LAT_REF_SOUTH;
+ String longRef = longitude >= 0 ? GPS_LONG_REF_EAST : GPS_LONG_REF_WEST;
+
+ String dateTag = sExifGPSDateStamp.format(time);
+ mGPSTimeStampCalendar.setTimeInMillis(time);
+ int[] timeTag = new int[] { mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1,
+ mGPSTimeStampCalendar.get(Calendar.MINUTE), 1,
+ mGPSTimeStampCalendar.get(Calendar.SECOND), 1 };
+ nativeSetGpsTags(latTag, latRef, longTag, longRef, dateTag, timeTag);
return this;
}
*
* @param description the user description string.
* @return this {@link #DngCreator} object.
- * @hide
*/
public DngCreator setDescription(String description) {
- /*TODO*/
+ if (description == null) {
+ throw new IllegalArgumentException("Null description passed to setDescription.");
+ }
+ nativeSetDescription(description);
return this;
}
* @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");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput passed to writeInputStream");
+ } else if (size == null) {
+ throw new IllegalArgumentException("Null size passed to writeInputStream");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels passed to writeInputStream");
+ } else if (offset < 0) {
+ throw new IllegalArgumentException("Negative offset passed to writeInputStream");
}
- nativeWriteInputStream(dngOutput, pixels, offset);
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Size with invalid width, height: (" + width + "," +
+ height + ") passed to writeInputStream");
+ }
+ nativeWriteInputStream(dngOutput, pixels, width, height, offset);
}
/**
* {@link java.lang.IllegalStateException} will be thrown.
* </p>
*
+ * <p>
+ * Any mark or limit set on this {@link ByteBuffer} is ignored, and will be cleared by this
+ * method.
+ * </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.
* @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");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput passed to writeByteBuffer");
+ } else if (size == null) {
+ throw new IllegalArgumentException("Null size passed to writeByteBuffer");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels passed to writeByteBuffer");
+ } else if (offset < 0) {
+ throw new IllegalArgumentException("Negative offset passed to writeByteBuffer");
}
- nativeWriteByteBuffer(dngOutput, pixels, offset);
+
+ int width = size.getWidth();
+ int height = size.getHeight();
+
+ writeByteBuffer(width, height, pixels, dngOutput, DEFAULT_PIXEL_STRIDE,
+ width * DEFAULT_PIXEL_STRIDE, offset);
}
/**
* 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");
+ if (dngOutput == null) {
+ throw new IllegalArgumentException("Null dngOutput to writeImage");
+ } else if (pixels == null) {
+ throw new IllegalArgumentException("Null pixels to writeImage");
}
int format = pixels.getFormat();
}
Image.Plane[] planes = pixels.getPlanes();
- nativeWriteImage(dngOutput, pixels.getWidth(), pixels.getHeight(), planes[0].getBuffer(),
- planes[0].getRowStride(), planes[0].getPixelStride());
+ if (planes == null || planes.length <= 0) {
+ throw new IllegalArgumentException("Image with no planes passed to writeImage");
+ }
+
+ ByteBuffer buf = planes[0].getBuffer();
+ writeByteBuffer(pixels.getWidth(), pixels.getHeight(), buf, dngOutput,
+ planes[0].getPixelStride(), planes[0].getRowStride(), 0);
}
@Override
nativeDestroy();
}
+ /**
+ * Max width or height dimension for thumbnails.
+ */
+ public static final int MAX_THUMBNAIL_DIMENSION = 256; // max pixel dimension for TIFF/EP
+
@Override
protected void finalize() throws Throwable {
try {
}
}
+ private static final String GPS_LAT_REF_NORTH = "N";
+ private static final String GPS_LAT_REF_SOUTH = "S";
+ private static final String GPS_LONG_REF_EAST = "E";
+ private static final String GPS_LONG_REF_WEST = "W";
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
private static final String TIFF_DATETIME_FORMAT = "yyyy:MM:dd kk:mm:ss";
+ private static final DateFormat sExifGPSDateStamp = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
private static final DateFormat sDateTimeStampFormat =
new SimpleDateFormat(TIFF_DATETIME_FORMAT);
+ private final Calendar mGPSTimeStampCalendar = Calendar
+ .getInstance(TimeZone.getTimeZone("UTC"));
static {
sDateTimeStampFormat.setTimeZone(TimeZone.getDefault());
+ sExifGPSDateStamp.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample
+ private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel
+
+ /**
+ * Offset, rowStride, and pixelStride are given in bytes. Height and width are given in pixels.
+ */
+ private void writeByteBuffer(int width, int height, ByteBuffer pixels, OutputStream dngOutput,
+ int pixelStride, int rowStride, long offset) throws IOException {
+ if (width <= 0 || height <= 0) {
+ throw new IllegalArgumentException("Image with invalid width, height: (" + width + "," +
+ height + ") passed to write");
+ }
+ long capacity = pixels.capacity();
+ long totalSize = rowStride * height + offset;
+ if (capacity < totalSize) {
+ throw new IllegalArgumentException("Image size " + capacity +
+ " is too small (must be larger than " + totalSize + ")");
+ }
+ int minRowStride = pixelStride * width;
+ if (minRowStride > rowStride) {
+ throw new IllegalArgumentException("Invalid image pixel stride, row byte width " +
+ minRowStride + " is too large, expecting " + rowStride);
+ }
+ pixels.clear(); // Reset mark and limit
+ nativeWriteImage(dngOutput, width, height, pixels, rowStride, pixelStride, offset,
+ pixels.isDirect());
+ pixels.clear();
+ }
+
+ /**
+ * Convert a single YUV pixel to RGB.
+ */
+ private static void yuvToRgb(byte[] yuvData, int outOffset, /*out*/byte[] rgbOut) {
+ final int COLOR_MAX = 255;
+
+ float y = yuvData[0] & 0xFF; // Y channel
+ float cb = yuvData[1] & 0xFF; // U channel
+ float cr = yuvData[2] & 0xFF; // V channel
+
+ // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+ float r = y + 1.402f * (cr - 128);
+ float g = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128);
+ float b = y + 1.772f * (cb - 128);
+
+ // clamp to [0,255]
+ rgbOut[outOffset] = (byte) Math.max(0, Math.min(COLOR_MAX, r));
+ rgbOut[outOffset + 1] = (byte) Math.max(0, Math.min(COLOR_MAX, g));
+ rgbOut[outOffset + 2] = (byte) Math.max(0, Math.min(COLOR_MAX, b));
}
+
+ /**
+ * Convert a single {@link Color} pixel to RGB.
+ */
+ private static void colorToRgb(int color, int outOffset, /*out*/byte[] rgbOut) {
+ rgbOut[outOffset] = (byte) Color.red(color);
+ rgbOut[outOffset + 1] = (byte) Color.green(color);
+ rgbOut[outOffset + 2] = (byte) Color.blue(color);
+ // Discards Alpha
+ }
+
+ /**
+ * Generate a direct RGB {@link ByteBuffer} from a YUV420_888 {@link Image}.
+ */
+ private static ByteBuffer convertToRGB(Image yuvImage) {
+ // TODO: Optimize this with renderscript intrinsic.
+ int width = yuvImage.getWidth();
+ int height = yuvImage.getHeight();
+ ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+ Image.Plane yPlane = yuvImage.getPlanes()[0];
+ Image.Plane uPlane = yuvImage.getPlanes()[1];
+ Image.Plane vPlane = yuvImage.getPlanes()[2];
+
+ ByteBuffer yBuf = yPlane.getBuffer();
+ ByteBuffer uBuf = uPlane.getBuffer();
+ ByteBuffer vBuf = vPlane.getBuffer();
+
+ yBuf.rewind();
+ uBuf.rewind();
+ vBuf.rewind();
+
+ int yRowStride = yPlane.getRowStride();
+ int vRowStride = vPlane.getRowStride();
+ int uRowStride = uPlane.getRowStride();
+
+ int yPixStride = yPlane.getPixelStride();
+ int vPixStride = vPlane.getPixelStride();
+ int uPixStride = uPlane.getPixelStride();
+
+ byte[] yuvPixel = { 0, 0, 0 };
+ byte[] yFullRow = new byte[yPixStride * width];
+ byte[] uFullRow = new byte[uPixStride * width / 2];
+ byte[] vFullRow = new byte[vPixStride * width / 2];
+ byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+ for (int i = 0; i < height; i++) {
+ int halfH = i / 2;
+ yBuf.position(yRowStride * i);
+ yBuf.get(yFullRow);
+ uBuf.position(uRowStride * halfH);
+ uBuf.get(uFullRow);
+ vBuf.position(vRowStride * halfH);
+ vBuf.get(vFullRow);
+ for (int j = 0; j < width; j++) {
+ int halfW = j / 2;
+ yuvPixel[0] = yFullRow[yPixStride * j];
+ yuvPixel[1] = uFullRow[uPixStride * halfW];
+ yuvPixel[2] = vFullRow[vPixStride * halfW];
+ yuvToRgb(yuvPixel, j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+ }
+ buf.put(finalRow);
+ }
+
+ yBuf.rewind();
+ uBuf.rewind();
+ vBuf.rewind();
+ buf.rewind();
+ return buf;
+ }
+
+ /**
+ * Generate a direct RGB {@link ByteBuffer} from a {@link Bitmap}.
+ */
+ private static ByteBuffer convertToRGB(Bitmap argbBitmap) {
+ // TODO: Optimize this.
+ int width = argbBitmap.getWidth();
+ int height = argbBitmap.getHeight();
+ ByteBuffer buf = ByteBuffer.allocateDirect(BYTES_PER_RGB_PIX * width * height);
+
+ int[] pixelRow = new int[width];
+ byte[] finalRow = new byte[BYTES_PER_RGB_PIX * width];
+ for (int i = 0; i < height; i++) {
+ argbBitmap.getPixels(pixelRow, /*offset*/0, /*stride*/width, /*x*/0, /*y*/i,
+ /*width*/width, /*height*/1);
+ for (int j = 0; j < width; j++) {
+ colorToRgb(pixelRow[j], j * BYTES_PER_RGB_PIX, /*out*/finalRow);
+ }
+ buf.put(finalRow);
+ }
+
+ buf.rewind();
+ return buf;
+ }
+
+ /**
+ * Convert coordinate to EXIF GPS tag format.
+ */
+ private static int[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new int[] { degrees, 1, minutes, 1, seconds, 100 };
+ }
+
/**
* This field is used by native code, do not access or modify.
*/
private synchronized native void nativeSetOrientation(int orientation);
- private synchronized native void nativeSetThumbnailBitmap(Bitmap bitmap);
+ private synchronized native void nativeSetDescription(String description);
- 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 nativeSetGpsTags(int[] latTag, String latRef, int[] longTag,
+ String longRef, String dateTag,
+ int[] timeTag);
+
+ private synchronized native void nativeSetThumbnail(ByteBuffer buffer, int width, int height);
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;
+ int pixStride, long offset, boolean isDirect)
+ throws IOException;
private synchronized native void nativeWriteInputStream(OutputStream out, InputStream rawStream,
- long offset) throws IOException;
+ int width, int height, long offset)
+ throws IOException;
static {
nativeClassInit();
#include <img_utils/TiffIfd.h>
#include <img_utils/TiffWriter.h>
#include <img_utils/Output.h>
+#include <img_utils/Input.h>
+#include <img_utils/StripSource.h>
#include <utils/Log.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <utils/RefBase.h>
+#include <utils/Vector.h>
#include <cutils/properties.h>
#include <string.h>
using namespace android;
using namespace img_utils;
-#define BAIL_IF_INVALID(expr, jnienv, tagId) \
+#define BAIL_IF_INVALID(expr, jnienv, tagId, writer) \
if ((expr) != OK) { \
jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
- "Invalid metadata for tag %x", tagId); \
+ "Invalid metadata for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
return; \
}
-#define BAIL_IF_EMPTY(entry, jnienv, tagId) \
+#define BAIL_IF_EMPTY(entry, jnienv, tagId, writer) \
if (entry.count == 0) { \
jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \
- "Missing metadata fields for tag %x", tagId); \
+ "Missing metadata fields for tag %s (%x)", (writer)->getTagName(tagId), (tagId)); \
return; \
}
jmethodID mWriteMethod;
} gOutputStreamClassInfo;
+static struct {
+ jmethodID mReadMethod;
+ jmethodID mSkipMethod;
+} gInputStreamClassInfo;
+
+static struct {
+ jmethodID mGetMethod;
+} gInputByteBufferClassInfo;
+
enum {
BITS_PER_SAMPLE = 16,
BYTES_PER_SAMPLE = 2,
- TIFF_IFD_0 = 0
+ BYTES_PER_RGB_PIXEL = 3,
+ BITS_PER_RGB_SAMPLE = 8,
+ BYTES_PER_RGB_SAMPLE = 1,
+ SAMPLES_PER_RGB_PIXEL = 3,
+ SAMPLES_PER_RAW_PIXEL = 1,
+ TIFF_IFD_0 = 0,
+ TIFF_IFD_SUB1 = 1,
+ TIFF_IFD_GPSINFO = 2,
};
// ----------------------------------------------------------------------------
-// This class is not intended to be used across JNI calls.
+/**
+ * Container class for the persistent native context.
+ */
+
+class NativeContext : public LightRefBase<NativeContext> {
+
+public:
+ NativeContext();
+ virtual ~NativeContext();
+
+ TiffWriter* getWriter();
+
+ uint32_t getThumbnailWidth();
+ uint32_t getThumbnailHeight();
+ const uint8_t* getThumbnail();
+
+ bool setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height);
+
+private:
+ Vector<uint8_t> mCurrentThumbnail;
+ TiffWriter mWriter;
+ uint32_t mThumbnailWidth;
+ uint32_t mThumbnailHeight;
+};
+
+NativeContext::NativeContext() : mThumbnailWidth(0), mThumbnailHeight(0) {}
+
+NativeContext::~NativeContext() {}
+
+TiffWriter* NativeContext::getWriter() {
+ return &mWriter;
+}
+
+uint32_t NativeContext::getThumbnailWidth() {
+ return mThumbnailWidth;
+}
+
+uint32_t NativeContext::getThumbnailHeight() {
+ return mThumbnailHeight;
+}
+
+const uint8_t* NativeContext::getThumbnail() {
+ return mCurrentThumbnail.array();
+}
+
+bool NativeContext::setThumbnail(const uint8_t* buffer, uint32_t width, uint32_t height) {
+ mThumbnailWidth = width;
+ mThumbnailHeight = height;
+
+ size_t size = BYTES_PER_RGB_PIXEL * width * height;
+ if (mCurrentThumbnail.resize(size) < 0) {
+ ALOGE("%s: Could not resize thumbnail buffer.", __FUNCTION__);
+ return false;
+ }
+
+ uint8_t* thumb = mCurrentThumbnail.editArray();
+ memcpy(thumb, buffer, size);
+ return true;
+}
+
+// End of NativeContext
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a Java OutputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
class JniOutputStream : public Output, public LightRefBase<JniOutputStream> {
public:
JniOutputStream(JNIEnv* env, jobject outStream);
virtual ~JniOutputStream();
status_t open();
+
status_t write(const uint8_t* buf, size_t offset, size_t count);
+
status_t close();
private:
enum {
- BYTE_ARRAY_LENGTH = 1024
+ BYTE_ARRAY_LENGTH = 4096
};
jobject mOutputStream;
JNIEnv* mEnv;
return OK;
}
+// End of JniOutputStream
// ----------------------------------------------------------------------------
+/**
+ * Wrapper class for a Java InputStream.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputStream : public Input, public LightRefBase<JniInputStream> {
+public:
+ JniInputStream(JNIEnv* env, jobject inStream);
+
+ status_t open();
+
+ status_t close();
+
+ ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+ ssize_t skip(size_t count);
+
+ virtual ~JniInputStream();
+private:
+ enum {
+ BYTE_ARRAY_LENGTH = 4096
+ };
+ jobject mInStream;
+ JNIEnv* mEnv;
+ jbyteArray mByteArray;
+
+};
+
+JniInputStream::JniInputStream(JNIEnv* env, jobject inStream) : mInStream(inStream), mEnv(env) {
+ mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+ if (mByteArray == NULL) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+ }
+}
+
+JniInputStream::~JniInputStream() {
+ mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputStream::read(uint8_t* buf, size_t offset, size_t count) {
+
+ jint realCount = BYTE_ARRAY_LENGTH;
+ if (count < BYTE_ARRAY_LENGTH) {
+ realCount = count;
+ }
+ jint actual = mEnv->CallIntMethod(mInStream, gInputStreamClassInfo.mReadMethod, mByteArray, 0,
+ realCount);
+
+ if (actual < 0) {
+ return NOT_ENOUGH_DATA;
+ }
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+
+ mEnv->GetByteArrayRegion(mByteArray, 0, actual, reinterpret_cast<jbyte*>(buf + offset));
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ return actual;
+}
+
+ssize_t JniInputStream::skip(size_t count) {
+ jlong actual = mEnv->CallLongMethod(mInStream, gInputStreamClassInfo.mSkipMethod,
+ static_cast<jlong>(count));
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ if (actual < 0) {
+ return NOT_ENOUGH_DATA;
+ }
+ return actual;
+}
+
+status_t JniInputStream::open() {
+ // Do nothing
+ return OK;
+}
+
+status_t JniInputStream::close() {
+ // Do nothing
+ return OK;
+}
+
+// End of JniInputStream
+// ----------------------------------------------------------------------------
+
+/**
+ * Wrapper class for a non-direct Java ByteBuffer.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+class JniInputByteBuffer : public Input, public LightRefBase<JniInputByteBuffer> {
+public:
+ JniInputByteBuffer(JNIEnv* env, jobject inBuf);
+
+ status_t open();
+
+ status_t close();
+
+ ssize_t read(uint8_t* buf, size_t offset, size_t count);
+
+ virtual ~JniInputByteBuffer();
+private:
+ enum {
+ BYTE_ARRAY_LENGTH = 4096
+ };
+ jobject mInBuf;
+ JNIEnv* mEnv;
+ jbyteArray mByteArray;
+};
+
+JniInputByteBuffer::JniInputByteBuffer(JNIEnv* env, jobject inBuf) : mInBuf(inBuf), mEnv(env) {
+ mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH);
+ if (mByteArray == NULL) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array.");
+ }
+}
+
+JniInputByteBuffer::~JniInputByteBuffer() {
+ mEnv->DeleteLocalRef(mByteArray);
+}
+
+ssize_t JniInputByteBuffer::read(uint8_t* buf, size_t offset, size_t count) {
+ jint realCount = BYTE_ARRAY_LENGTH;
+ if (count < BYTE_ARRAY_LENGTH) {
+ realCount = count;
+ }
+
+ mEnv->CallObjectMethod(mInBuf, gInputByteBufferClassInfo.mGetMethod, mByteArray, 0,
+ realCount);
+
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+
+ mEnv->GetByteArrayRegion(mByteArray, 0, realCount, reinterpret_cast<jbyte*>(buf + offset));
+ if (mEnv->ExceptionCheck()) {
+ return BAD_VALUE;
+ }
+ return realCount;
+}
+
+status_t JniInputByteBuffer::open() {
+ // Do nothing
+ return OK;
+}
+
+status_t JniInputByteBuffer::close() {
+ // Do nothing
+ return OK;
+}
+
+// End of JniInputByteBuffer
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for Input types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class InputStripSource : public StripSource, public LightRefBase<InputStripSource> {
+public:
+ InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width, uint32_t height,
+ uint32_t pixStride, uint32_t rowStride, uint64_t offset, uint32_t bytesPerSample,
+ uint32_t samplesPerPixel);
+
+ virtual ~InputStripSource();
+
+ virtual status_t writeToStream(Output& stream, uint32_t count);
+
+ virtual uint32_t getIfd() const;
+protected:
+ uint32_t mIfd;
+ Input* mInput;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mPixStride;
+ uint32_t mRowStride;
+ uint64_t mOffset;
+ JNIEnv* mEnv;
+ uint32_t mBytesPerSample;
+ uint32_t mSamplesPerPixel;
+};
+
+InputStripSource::InputStripSource(JNIEnv* env, Input& input, uint32_t ifd, uint32_t width,
+ uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+ uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd), mInput(&input),
+ mWidth(width), mHeight(height), mPixStride(pixStride), mRowStride(rowStride),
+ mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+ mSamplesPerPixel(samplesPerPixel) {}
+
+InputStripSource::~InputStripSource() {}
+
+status_t InputStripSource::writeToStream(Output& stream, uint32_t count) {
+ status_t err = OK;
+ uint32_t fullSize = mRowStride * mHeight;
+ jlong offset = mOffset;
+
+ if (fullSize != count) {
+ ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+ fullSize);
+ jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+ return BAD_VALUE;
+ }
+
+ // Skip offset
+ while (offset > 0) {
+ ssize_t skipped = mInput->skip(offset);
+ if (skipped <= 0) {
+ if (skipped == NOT_ENOUGH_DATA || skipped == 0) {
+ jniThrowExceptionFmt(mEnv, "java/io/IOException",
+ "Early EOF encountered in skip, not enough pixel data for image of size %u",
+ fullSize);
+ skipped = NOT_ENOUGH_DATA;
+ } else {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException",
+ "Error encountered while skip bytes in input stream.");
+ }
+ }
+
+ return skipped;
+ }
+ offset -= skipped;
+ }
+
+ Vector<uint8_t> row;
+ if (row.resize(mRowStride) < 0) {
+ jniThrowException(mEnv, "java/lang/OutOfMemoryError", "Could not allocate row vector.");
+ return BAD_VALUE;
+ }
+
+ uint8_t* rowBytes = row.editArray();
+
+ for (uint32_t i = 0; i < mHeight; ++i) {
+ size_t rowFillAmt = 0;
+ size_t rowSize = mPixStride;
+
+ while (rowFillAmt < mRowStride) {
+ ssize_t bytesRead = mInput->read(rowBytes, rowFillAmt, rowSize);
+ if (bytesRead <= 0) {
+ if (bytesRead == NOT_ENOUGH_DATA || bytesRead == 0) {
+ jniThrowExceptionFmt(mEnv, "java/io/IOException",
+ "Early EOF encountered, not enough pixel data for image of size %u",
+ fullSize);
+ bytesRead = NOT_ENOUGH_DATA;
+ } else {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException",
+ "Error encountered while reading");
+ }
+ }
+ return bytesRead;
+ }
+ rowFillAmt += bytesRead;
+ rowSize -= bytesRead;
+ }
+
+ if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using stream per-row write for strip.", __FUNCTION__);
+
+ if (stream.write(rowBytes, 0, mBytesPerSample * mSamplesPerPixel * mWidth) != OK ||
+ mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ } else {
+ ALOGV("%s: Using stream per-pixel write for strip.", __FUNCTION__);
+ jniThrowException(mEnv, "java/lang/IllegalStateException",
+ "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+ return BAD_VALUE;
+
+ // TODO: Add support for non-contiguous pixels if needed.
+ }
+ }
+ return OK;
+}
+
+uint32_t InputStripSource::getIfd() const {
+ return mIfd;
+}
+
+// End of InputStripSource
+// ----------------------------------------------------------------------------
+
+/**
+ * StripSource subclass for direct buffer types.
+ *
+ * This class is not intended to be used across JNI calls.
+ */
+
+class DirectStripSource : public StripSource, public LightRefBase<DirectStripSource> {
+public:
+ DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd, uint32_t width,
+ uint32_t height, uint32_t pixStride, uint32_t rowStride, uint64_t offset,
+ uint32_t bytesPerSample, uint32_t samplesPerPixel);
+
+ virtual ~DirectStripSource();
+
+ virtual status_t writeToStream(Output& stream, uint32_t count);
+
+ virtual uint32_t getIfd() const;
+protected:
+ uint32_t mIfd;
+ const uint8_t* mPixelBytes;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint32_t mPixStride;
+ uint32_t mRowStride;
+ uint16_t mOffset;
+ JNIEnv* mEnv;
+ uint32_t mBytesPerSample;
+ uint32_t mSamplesPerPixel;
+};
+
+DirectStripSource::DirectStripSource(JNIEnv* env, const uint8_t* pixelBytes, uint32_t ifd,
+ uint32_t width, uint32_t height, uint32_t pixStride, uint32_t rowStride,
+ uint64_t offset, uint32_t bytesPerSample, uint32_t samplesPerPixel) : mIfd(ifd),
+ mPixelBytes(pixelBytes), mWidth(width), mHeight(height), mPixStride(pixStride),
+ mRowStride(rowStride), mOffset(offset), mEnv(env), mBytesPerSample(bytesPerSample),
+ mSamplesPerPixel(samplesPerPixel) {}
+
+DirectStripSource::~DirectStripSource() {}
+
+status_t DirectStripSource::writeToStream(Output& stream, uint32_t count) {
+ uint32_t fullSize = mRowStride * mHeight;
+
+ if (fullSize != count) {
+ ALOGE("%s: Amount to write %u doesn't match image size %u", __FUNCTION__, count,
+ fullSize);
+ jniThrowException(mEnv, "java/lang/IllegalStateException", "Not enough data to write");
+ return BAD_VALUE;
+ }
+
+ if (mPixStride == mBytesPerSample * mSamplesPerPixel
+ && mRowStride == mWidth * mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using direct single-pass write for strip.", __FUNCTION__);
+
+ if (stream.write(mPixelBytes, mOffset, fullSize) != OK || mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ } else if (mPixStride == mBytesPerSample * mSamplesPerPixel) {
+ ALOGV("%s: Using direct per-row write for strip.", __FUNCTION__);
+
+ for (size_t i = 0; i < mHeight; ++i) {
+ if (stream.write(mPixelBytes, mOffset + i * mRowStride, mPixStride * mWidth) != OK ||
+ mEnv->ExceptionCheck()) {
+ if (!mEnv->ExceptionCheck()) {
+ jniThrowException(mEnv, "java/io/IOException", "Failed to write pixel data");
+ }
+ return BAD_VALUE;
+ }
+ }
+ } else {
+ ALOGV("%s: Using direct per-pixel write for strip.", __FUNCTION__);
+
+ jniThrowException(mEnv, "java/lang/IllegalStateException",
+ "Per-pixel strides are not supported for RAW16 -- pixels must be contiguous");
+ return BAD_VALUE;
+
+ // TODO: Add support for non-contiguous pixels if needed.
+ }
+ return OK;
+
+}
+
+uint32_t DirectStripSource::getIfd() const {
+ return mIfd;
+}
+
+// End of DirectStripSource
+// ----------------------------------------------------------------------------
+
+static bool validateDngHeader(JNIEnv* env, TiffWriter* writer, jint width, jint height) {
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+ // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
+ uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+ uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, (hasThumbnail) ? TIFF_IFD_SUB1 : TIFF_IFD_0)->getData<uint32_t>());
+
+ if (width < 0 || metadataWidth != static_cast<uint32_t>(width)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+ "Metadata width %d doesn't match image width %d", metadataWidth, width);
+ return false;
+ }
+
+ if (height < 0 || metadataHeight != static_cast<uint32_t>(height)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", \
+ "Metadata height %d doesn't match image height %d", metadataHeight, height);
+ return false;
+ }
+
+ return true;
+}
+
+static status_t moveEntries(TiffWriter* writer, uint32_t ifdFrom, uint32_t ifdTo,
+ const Vector<uint16_t>& entries) {
+ for (size_t i = 0; i < entries.size(); ++i) {
+ uint16_t tagId = entries[i];
+ sp<TiffEntry> entry = writer->getEntry(tagId, ifdFrom);
+ if (entry == NULL) {
+ ALOGE("%s: moveEntries failed, entry %u not found in IFD %u", __FUNCTION__, tagId,
+ ifdFrom);
+ return BAD_VALUE;
+ }
+ if (writer->addEntry(entry, ifdTo) != OK) {
+ ALOGE("%s: moveEntries failed, could not add entry %u to IFD %u", __FUNCTION__, tagId,
+ ifdFrom);
+ return BAD_VALUE;
+ }
+ writer->removeEntry(tagId, ifdFrom);
+ }
+ return OK;
+}
+
+// ----------------------------------------------------------------------------
extern "C" {
-static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+static NativeContext* DngCreator_getNativeContext(JNIEnv* env, jobject thiz) {
ALOGV("%s:", __FUNCTION__);
- return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz,
+ return reinterpret_cast<NativeContext*>(env->GetLongField(thiz,
gDngCreatorClassInfo.mNativeContext));
}
-static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) {
+static void DngCreator_setNativeContext(JNIEnv* env, jobject thiz, sp<NativeContext> context) {
ALOGV("%s:", __FUNCTION__);
- TiffWriter* current = DngCreator_getCreator(env, thiz);
- if (writer != NULL) {
- writer->incStrong((void*) DngCreator_setCreator);
+ NativeContext* current = DngCreator_getNativeContext(env, thiz);
+
+ if (context != NULL) {
+ context->incStrong((void*) DngCreator_setNativeContext);
}
+
if (current) {
- current->decStrong((void*) DngCreator_setCreator);
+ current->decStrong((void*) DngCreator_setNativeContext);
}
+
env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext,
- reinterpret_cast<jlong>(writer.get()));
+ reinterpret_cast<jlong>(context.get()));
+}
+
+static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) {
+ ALOGV("%s:", __FUNCTION__);
+ NativeContext* current = DngCreator_getNativeContext(env, thiz);
+ if (current) {
+ return current->getWriter();
+ }
+ return NULL;
}
static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) {
LOG_ALWAYS_FATAL_IF(outputStreamClazz == NULL, "Can't find java/io/OutputStream class");
gOutputStreamClassInfo.mWriteMethod = env->GetMethodID(outputStreamClazz, "write", "([BII)V");
LOG_ALWAYS_FATAL_IF(gOutputStreamClassInfo.mWriteMethod == NULL, "Can't find write method");
+
+ jclass inputStreamClazz = env->FindClass("java/io/InputStream");
+ LOG_ALWAYS_FATAL_IF(inputStreamClazz == NULL, "Can't find java/io/InputStream class");
+ gInputStreamClassInfo.mReadMethod = env->GetMethodID(inputStreamClazz, "read", "([BII)I");
+ LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mReadMethod == NULL, "Can't find read method");
+ gInputStreamClassInfo.mSkipMethod = env->GetMethodID(inputStreamClazz, "skip", "(J)J");
+ LOG_ALWAYS_FATAL_IF(gInputStreamClassInfo.mSkipMethod == NULL, "Can't find skip method");
+
+ jclass inputBufferClazz = env->FindClass("java/nio/ByteBuffer");
+ LOG_ALWAYS_FATAL_IF(inputBufferClazz == NULL, "Can't find java/nio/ByteBuffer class");
+ gInputByteBufferClassInfo.mGetMethod = env->GetMethodID(inputBufferClazz, "get",
+ "([BII)Ljava/nio/ByteBuffer;");
+ LOG_ALWAYS_FATAL_IF(gInputByteBufferClassInfo.mGetMethod == NULL, "Can't find get method");
}
static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr,
return;
}
- sp<TiffWriter> writer = new TiffWriter();
+ sp<NativeContext> nativeContext = new NativeContext();
+ TiffWriter* writer = nativeContext->getWriter();
writer->addIfd(TIFF_IFD_0);
// TODO: Greensplit.
// TODO: Add remaining non-essential tags
+
+ // Setup main image tags
+
{
// Set orientation
uint16_t orientation = 1; // Normal
BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
- TAG_ORIENTATION);
+ TAG_ORIENTATION, writer);
}
{
// Set subfiletype
uint32_t subfileType = 0; // Main image
BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
- TAG_NEWSUBFILETYPE);
+ TAG_NEWSUBFILETYPE, writer);
}
{
// Set bits per sample
uint16_t bits = static_cast<uint16_t>(bitsPerSample);
BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
- TAG_BITSPERSAMPLE);
+ TAG_BITSPERSAMPLE, writer);
}
{
// Set compression
uint16_t compression = 1; // None
BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
- TAG_COMPRESSION);
+ TAG_COMPRESSION, writer);
}
{
// Set dimensions
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH);
+ BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH, writer);
uint32_t width = static_cast<uint32_t>(entry.data.i32[2]);
uint32_t height = static_cast<uint32_t>(entry.data.i32[3]);
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &width, TIFF_IFD_0), env,
- TAG_IMAGEWIDTH);
+ TAG_IMAGEWIDTH, writer);
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env,
- TAG_IMAGELENGTH);
+ TAG_IMAGELENGTH, writer);
imageWidth = width;
imageHeight = height;
}
{
// Set photometric interpretation
- uint16_t interpretation = 32803;
+ uint16_t interpretation = 32803; // CFA
BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
- TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION);
+ TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
}
{
// Set blacklevel tags
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN);
- BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL);
+ BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL, writer);
const uint32_t* blackLevel = reinterpret_cast<const uint32_t*>(entry.data.i32);
BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVEL, entry.count, blackLevel, TIFF_IFD_0), env,
- TAG_BLACKLEVEL);
+ TAG_BLACKLEVEL, writer);
uint16_t repeatDim[2] = {2, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env,
- TAG_BLACKLEVELREPEATDIM);
+ TAG_BLACKLEVELREPEATDIM, writer);
}
{
// Set samples per pixel
uint16_t samples = static_cast<uint16_t>(samplesPerPixel);
BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
- env, TAG_SAMPLESPERPIXEL);
+ env, TAG_SAMPLESPERPIXEL, writer);
}
{
// Set planar configuration
uint16_t config = 1; // Chunky
BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
- env, TAG_PLANARCONFIGURATION);
+ env, TAG_PLANARCONFIGURATION, writer);
}
{
// Set CFA pattern dimensions
uint16_t repeatDim[2] = {2, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAREPEATPATTERNDIM, 2, repeatDim, TIFF_IFD_0),
- env, TAG_CFAREPEATPATTERNDIM);
+ env, TAG_CFAREPEATPATTERNDIM, writer);
}
{
// Set CFA pattern
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
- BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN);
+ BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN, writer);
camera_metadata_enum_android_sensor_info_color_filter_arrangement_t cfa =
static_cast<camera_metadata_enum_android_sensor_info_color_filter_arrangement_t>(
entry.data.u8[0]);
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_RGGB: {
uint8_t cfa[4] = {0, 1, 1, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GRBG: {
uint8_t cfa[4] = {1, 0, 2, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_GRBG;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_GBRG: {
uint8_t cfa[4] = {1, 2, 0, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_GBRG;
break;
}
case ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT_BGGR: {
uint8_t cfa[4] = {2, 1, 1, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPATTERN, 4, cfa, TIFF_IFD_0),
- env, TAG_CFAPATTERN);
+ env, TAG_CFAPATTERN, writer);
opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR;
break;
}
// Set CFA plane color
uint8_t cfaPlaneColor[3] = {0, 1, 2};
BAIL_IF_INVALID(writer->addEntry(TAG_CFAPLANECOLOR, 3, cfaPlaneColor, TIFF_IFD_0),
- env, TAG_CFAPLANECOLOR);
+ env, TAG_CFAPLANECOLOR, writer);
}
{
// Set CFA layout
uint16_t cfaLayout = 1;
BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0),
- env, TAG_CFALAYOUT);
+ env, TAG_CFALAYOUT, writer);
}
{
// image description
uint8_t imageDescription = '\0'; // empty
BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEDESCRIPTION, 1, &imageDescription, TIFF_IFD_0),
- env, TAG_IMAGEDESCRIPTION);
+ env, TAG_IMAGEDESCRIPTION, writer);
}
{
uint32_t count = static_cast<uint32_t>(strlen(manufacturer)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_MAKE, count, reinterpret_cast<uint8_t*>(manufacturer),
- TIFF_IFD_0), env, TAG_MAKE);
+ TIFF_IFD_0), env, TAG_MAKE, writer);
}
{
uint32_t count = static_cast<uint32_t>(strlen(model)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_MODEL, count, reinterpret_cast<uint8_t*>(model),
- TIFF_IFD_0), env, TAG_MODEL);
+ TIFF_IFD_0), env, TAG_MODEL, writer);
}
{
// x resolution
uint32_t xres[] = { 72, 1 }; // default 72 ppi
BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
- env, TAG_XRESOLUTION);
+ env, TAG_XRESOLUTION, writer);
// y resolution
uint32_t yres[] = { 72, 1 }; // default 72 ppi
BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
- env, TAG_YRESOLUTION);
+ env, TAG_YRESOLUTION, writer);
uint16_t unit = 2; // inches
BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
- env, TAG_RESOLUTIONUNIT);
+ env, TAG_RESOLUTIONUNIT, writer);
}
{
property_get("ro.build.fingerprint", software, "");
uint32_t count = static_cast<uint32_t>(strlen(software)) + 1;
BAIL_IF_INVALID(writer->addEntry(TAG_SOFTWARE, count, reinterpret_cast<uint8_t*>(software),
- TIFF_IFD_0), env, TAG_SOFTWARE);
+ TIFF_IFD_0), env, TAG_SOFTWARE, writer);
}
{
return;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+ if (writer->addEntry(TAG_DATETIME, DATETIME_COUNT,
+ reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+ env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_DATETIME);
+ return;
+ }
// datetime original
- BAIL_IF_INVALID(writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
- reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0), env, TAG_DATETIMEORIGINAL);
+ if (writer->addEntry(TAG_DATETIMEORIGINAL, DATETIME_COUNT,
+ reinterpret_cast<const uint8_t*>(captureTime), TIFF_IFD_0) != OK) {
+ env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_DATETIMEORIGINAL);
+ return;
+ }
env->ReleaseStringUTFChars(formattedCaptureTime, captureTime);
}
// TIFF/EP standard id
uint8_t standardId[] = { 1, 0, 0, 0 };
BAIL_IF_INVALID(writer->addEntry(TAG_TIFFEPSTANDARDID, 4, standardId,
- TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID);
+ TIFF_IFD_0), env, TAG_TIFFEPSTANDARDID, writer);
}
{
// copyright
uint8_t copyright = '\0'; // empty
BAIL_IF_INVALID(writer->addEntry(TAG_COPYRIGHT, 1, ©right,
- TIFF_IFD_0), env, TAG_COPYRIGHT);
+ TIFF_IFD_0), env, TAG_COPYRIGHT, writer);
}
{
// exposure time
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_EXPOSURE_TIME);
- BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME);
+ BAIL_IF_EMPTY(entry, env, TAG_EXPOSURETIME, writer);
int64_t exposureTime = *(entry.data.i64);
uint32_t exposure[] = { static_cast<uint32_t>(exposureTime), denominator };
BAIL_IF_INVALID(writer->addEntry(TAG_EXPOSURETIME, 1, exposure,
- TIFF_IFD_0), env, TAG_EXPOSURETIME);
+ TIFF_IFD_0), env, TAG_EXPOSURETIME, writer);
}
// ISO speed ratings
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_SENSITIVITY);
- BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS);
+ BAIL_IF_EMPTY(entry, env, TAG_ISOSPEEDRATINGS, writer);
int32_t tempIso = *(entry.data.i32);
if (tempIso < 0) {
uint16_t iso = static_cast<uint16_t>(tempIso);
BAIL_IF_INVALID(writer->addEntry(TAG_ISOSPEEDRATINGS, 1, &iso,
- TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS);
+ TIFF_IFD_0), env, TAG_ISOSPEEDRATINGS, writer);
}
{
// focal length
camera_metadata_entry entry =
results.find(ANDROID_LENS_FOCAL_LENGTH);
- BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH);
+ BAIL_IF_EMPTY(entry, env, TAG_FOCALLENGTH, writer);
uint32_t focalLength[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
BAIL_IF_INVALID(writer->addEntry(TAG_FOCALLENGTH, 1, focalLength,
- TIFF_IFD_0), env, TAG_FOCALLENGTH);
+ TIFF_IFD_0), env, TAG_FOCALLENGTH, writer);
}
{
// f number
camera_metadata_entry entry =
results.find(ANDROID_LENS_APERTURE);
- BAIL_IF_EMPTY(entry, env, TAG_FNUMBER);
+ BAIL_IF_EMPTY(entry, env, TAG_FNUMBER, writer);
uint32_t fnum[] = { static_cast<uint32_t>(*(entry.data.f) * 100), 100 };
BAIL_IF_INVALID(writer->addEntry(TAG_FNUMBER, 1, fnum,
- TIFF_IFD_0), env, TAG_FNUMBER);
+ TIFF_IFD_0), env, TAG_FNUMBER, writer);
}
{
// Set DNG version information
uint8_t version[4] = {1, 4, 0, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_DNGVERSION, 4, version, TIFF_IFD_0),
- env, TAG_DNGVERSION);
+ env, TAG_DNGVERSION, writer);
uint8_t backwardVersion[4] = {1, 1, 0, 0};
BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0),
- env, TAG_DNGBACKWARDVERSION);
+ env, TAG_DNGBACKWARDVERSION, writer);
}
{
// Set whitelevel
camera_metadata_entry entry =
characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL);
- BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL);
+ BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL, writer);
uint32_t whiteLevel = static_cast<uint32_t>(entry.data.i32[0]);
BAIL_IF_INVALID(writer->addEntry(TAG_WHITELEVEL, 1, &whiteLevel, TIFF_IFD_0), env,
- TAG_WHITELEVEL);
+ TAG_WHITELEVEL, writer);
}
{
// Set default scale
uint32_t defaultScale[4] = {1, 1, 1, 1};
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTSCALE, 2, defaultScale, TIFF_IFD_0),
- env, TAG_DEFAULTSCALE);
+ env, TAG_DEFAULTSCALE, writer);
}
bool singleIlluminant = false;
// Set calibration illuminants
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1);
- BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1);
+ BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1, writer);
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2);
if (entry2.count == 0) {
uint16_t ref1 = entry1.data.u8[0];
BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1,
- TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1);
+ TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1, writer);
if (!singleIlluminant) {
uint16_t ref2 = entry2.data.u8[0];
BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2,
- TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2);
+ TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2, writer);
}
}
// Set color transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1);
- BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1);
+ BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1, writer);
int32_t colorTransform1[entry1.count * 2];
colorTransform1[ctr++] = entry1.data.r[i].denominator;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0),
- env, TAG_COLORMATRIX1);
+ BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1,
+ TIFF_IFD_0), env, TAG_COLORMATRIX1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2);
- BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2);
+ BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2, writer);
int32_t colorTransform2[entry2.count * 2];
ctr = 0;
colorTransform2[ctr++] = entry2.data.r[i].denominator;
}
- BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0),
- env, TAG_COLORMATRIX2);
+ BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2,
+ TIFF_IFD_0), env, TAG_COLORMATRIX2, writer);
}
}
// Set calibration transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1);
- BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1);
+ BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1, writer);
int32_t calibrationTransform1[entry1.count * 2];
}
BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1,
- TIFF_IFD_0), env, TAG_CAMERACALIBRATION1);
+ TIFF_IFD_0), env, TAG_CAMERACALIBRATION1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2);
- BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2);
+ BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2, writer);
int32_t calibrationTransform2[entry2.count * 2];
ctr = 0;
}
BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1,
- TIFF_IFD_0), env, TAG_CAMERACALIBRATION2);
+ TIFF_IFD_0), env, TAG_CAMERACALIBRATION2, writer);
}
}
// Set forward transforms
camera_metadata_entry entry1 =
characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1);
- BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1);
+ BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1, writer);
int32_t forwardTransform1[entry1.count * 2];
}
BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1,
- TIFF_IFD_0), env, TAG_FORWARDMATRIX1);
+ TIFF_IFD_0), env, TAG_FORWARDMATRIX1, writer);
if (!singleIlluminant) {
camera_metadata_entry entry2 =
characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2);
- BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2);
+ BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2, writer);
int32_t forwardTransform2[entry2.count * 2];
ctr = 0;
}
BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2,
- TIFF_IFD_0), env, TAG_FORWARDMATRIX2);
+ TIFF_IFD_0), env, TAG_FORWARDMATRIX2, writer);
}
}
// Set camera neutral
camera_metadata_entry entry =
results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT);
- BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL);
+ BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL, writer);
uint32_t cameraNeutral[entry.count * 2];
size_t ctr = 0;
}
BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral,
- TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL);
+ TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL, writer);
}
{
// Setup data strips
// TODO: Switch to tiled implementation.
- uint32_t offset = 0;
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &offset, TIFF_IFD_0), env,
- TAG_STRIPOFFSETS);
-
- BAIL_IF_INVALID(writer->addEntry(TAG_ROWSPERSTRIP, 1, &imageHeight, TIFF_IFD_0), env,
- TAG_ROWSPERSTRIP);
-
- uint32_t byteCount = imageWidth * imageHeight * bitsPerSample * samplesPerPixel /
- bitsPerByte;
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPBYTECOUNTS, 1, &byteCount, TIFF_IFD_0), env,
- TAG_STRIPBYTECOUNTS);
+ if (writer->addStrip(TIFF_IFD_0) != OK) {
+ ALOGE("%s: Could not setup strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup strip tags.");
+ return;
+ }
}
{
uint32_t defaultCropOrigin[] = {margin, margin};
uint32_t defaultCropSize[] = {imageWidth - margin, imageHeight - margin};
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPORIGIN, 2, defaultCropOrigin,
- TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN);
+ TIFF_IFD_0), env, TAG_DEFAULTCROPORIGIN, writer);
BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize,
- TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE);
+ TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE, writer);
}
}
BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1,
reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env,
- TAG_UNIQUECAMERAMODEL);
+ TAG_UNIQUECAMERAMODEL, writer);
}
{
// Setup opcode List 2
camera_metadata_entry entry1 =
characteristics.find(ANDROID_LENS_INFO_SHADING_MAP_SIZE);
- BAIL_IF_EMPTY(entry1, env, TAG_OPCODELIST2);
- uint32_t lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
- uint32_t lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+
+ uint32_t lsmWidth = 0;
+ uint32_t lsmHeight = 0;
+
+ if (entry1.count != 0) {
+ lsmWidth = static_cast<uint32_t>(entry1.data.i32[0]);
+ lsmHeight = static_cast<uint32_t>(entry1.data.i32[1]);
+ }
camera_metadata_entry entry2 =
results.find(ANDROID_STATISTICS_LENS_SHADING_MAP);
- BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2);
- if (entry2.count == lsmWidth * lsmHeight * 4) {
+
+ if (entry2.count > 0 && entry2.count == lsmWidth * lsmHeight * 4) {
OpcodeListBuilder builder;
status_t err = builder.addGainMapsForMetadata(lsmWidth,
err = builder.buildOpList(opcodeListBuf);
if (err == OK) {
BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf,
- TIFF_IFD_0), env, TAG_OPCODELIST2);
+ TIFF_IFD_0), env, TAG_OPCODELIST2, writer);
} else {
ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__);
jniThrowRuntimeException(env, "failed to construct lens shading map opcode.");
jniThrowRuntimeException(env, "failed to add lens shading map.");
}
} else {
- ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__);
+ ALOGW("%s: No lens shading map found in result metadata. Image quality may be reduced.",
+ __FUNCTION__);
}
}
- DngCreator_setCreator(env, thiz, writer);
+ DngCreator_setNativeContext(env, thiz, nativeContext);
}
static void DngCreator_destroy(JNIEnv* env, jobject thiz) {
ALOGV("%s:", __FUNCTION__);
- DngCreator_setCreator(env, thiz, NULL);
+ DngCreator_setNativeContext(env, thiz, NULL);
}
-static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) {
+static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz, jint orient) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetOrientation is not implemented");
-}
-static void DngCreator_nativeSetThumbnailBitmap(JNIEnv* env, jobject thiz, jobject bitmap) {
- ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetThumbnailBitmap is not implemented");
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setOrientation called with uninitialized DngCreator");
+ return;
+ }
+
+ uint16_t orientation = static_cast<uint16_t>(orient);
+ BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env,
+ TAG_ORIENTATION, writer);
+
+ // Set main image orientation also if in a separate IFD
+ if (writer->hasIfd(TIFF_IFD_SUB1)) {
+ BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_SUB1), env,
+ TAG_ORIENTATION, writer);
+ }
}
-static void DngCreator_nativeSetThumbnailImage(JNIEnv* env, jobject thiz, jint width, jint height,
- jobject yBuffer, jint yRowStride, jint yPixStride, jobject uBuffer, jint uRowStride,
- jint uPixStride, jobject vBuffer, jint vRowStride, jint vPixStride) {
+static void DngCreator_nativeSetDescription(JNIEnv* env, jobject thiz, jstring description) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented");
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setDescription called with uninitialized DngCreator");
+ return;
+ }
+
+ const char* desc = env->GetStringUTFChars(description, NULL);
+ size_t len = strlen(desc) + 1;
+
+ if (writer->addEntry(TAG_IMAGEDESCRIPTION, len,
+ reinterpret_cast<const uint8_t*>(desc), TIFF_IFD_0) != OK) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+ "Invalid metadata for tag %x", TAG_IMAGEDESCRIPTION);
+ }
+
+ env->ReleaseStringUTFChars(description, desc);
}
-static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width,
- jint height, jobject inBuffer, jint rowStride, jint pixStride) {
+static void DngCreator_nativeSetGpsTags(JNIEnv* env, jobject thiz, jintArray latTag, jstring latRef,
+ jintArray longTag, jstring longRef, jstring dateTag, jintArray timeTag) {
ALOGV("%s:", __FUNCTION__);
- sp<JniOutputStream> out = new JniOutputStream(env, outStream);
- if(env->ExceptionCheck()) {
- ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ if (writer == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "setGpsTags called with uninitialized DngCreator");
return;
}
- uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
- if (pixelBytes == NULL) {
- ALOGE("%s: Could not get native byte buffer", __FUNCTION__);
- jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid bytebuffer");
+ if (!writer->hasIfd(TIFF_IFD_GPSINFO)) {
+ if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_GPSINFO, TiffWriter::GPSINFO) != OK) {
+ ALOGE("%s: Failed to add GpsInfo IFD %u to IFD %u", __FUNCTION__, TIFF_IFD_GPSINFO,
+ TIFF_IFD_0);
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to add GPSINFO");
+ return;
+ }
+ }
+
+ const jsize GPS_VALUE_LENGTH = 6;
+ jsize latLen = env->GetArrayLength(latTag);
+ jsize longLen = env->GetArrayLength(longTag);
+ jsize timeLen = env->GetArrayLength(timeTag);
+ if (latLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid latitude tag length");
+ return;
+ } else if (longLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid longitude tag length");
return;
+ } else if (timeLen != GPS_VALUE_LENGTH) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "invalid time tag length");
+ return;
+ }
+
+ uint32_t latitude[GPS_VALUE_LENGTH];
+ uint32_t longitude[GPS_VALUE_LENGTH];
+ uint32_t timestamp[GPS_VALUE_LENGTH];
+
+ env->GetIntArrayRegion(latTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(&latitude));
+ env->GetIntArrayRegion(longTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(&longitude));
+ env->GetIntArrayRegion(timeTag, 0, static_cast<jsize>(GPS_VALUE_LENGTH),
+ reinterpret_cast<jint*>(×tamp));
+
+ const jsize GPS_REF_LENGTH = 2;
+ const jsize GPS_DATE_LENGTH = 11;
+ uint8_t latitudeRef[GPS_REF_LENGTH];
+ uint8_t longitudeRef[GPS_REF_LENGTH];
+ uint8_t date[GPS_DATE_LENGTH];
+
+ env->GetStringUTFRegion(latRef, 0, 1, reinterpret_cast<char*>(&latitudeRef));
+ latitudeRef[GPS_REF_LENGTH - 1] = '\0';
+ env->GetStringUTFRegion(longRef, 0, 1, reinterpret_cast<char*>(&longitudeRef));
+ longitudeRef[GPS_REF_LENGTH - 1] = '\0';
+
+ env->GetStringUTFRegion(dateTag, 0, GPS_DATE_LENGTH - 1, reinterpret_cast<char*>(&date));
+ date[GPS_DATE_LENGTH - 1] = '\0';
+
+ {
+ uint8_t version[] = {2, 3, 0, 0};
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSVERSIONID, 4, version,
+ TIFF_IFD_GPSINFO), env, TAG_GPSVERSIONID, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDEREF, GPS_REF_LENGTH, latitudeRef,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDEREF, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDEREF, GPS_REF_LENGTH, longitudeRef,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDEREF, writer);
}
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLATITUDE, 3, latitude,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLATITUDE, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSLONGITUDE, 3, longitude,
+ TIFF_IFD_GPSINFO), env, TAG_GPSLONGITUDE, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSTIMESTAMP, 3, timestamp,
+ TIFF_IFD_GPSINFO), env, TAG_GPSTIMESTAMP, writer);
+ }
+
+ {
+ BAIL_IF_INVALID(writer->addEntry(TAG_GPSDATESTAMP, GPS_DATE_LENGTH, date,
+ TIFF_IFD_GPSINFO), env, TAG_GPSDATESTAMP, writer);
+ }
+}
+
+static void DngCreator_nativeSetThumbnail(JNIEnv* env, jobject thiz, jobject buffer, jint width,
+ jint height) {
+ ALOGV("%s:", __FUNCTION__);
+
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
TiffWriter* writer = DngCreator_getCreator(env, thiz);
- if (writer == NULL) {
+ if (writer == NULL || context == NULL) {
ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
jniThrowException(env, "java/lang/AssertionError",
- "Write called with uninitialized DngCreator");
+ "setThumbnail called with uninitialized DngCreator");
return;
}
- // TODO: handle lens shading map, etc. conversions for other raw buffer sizes.
- uint32_t metadataWidth = *(writer->getEntry(TAG_IMAGEWIDTH, TIFF_IFD_0)->getData<uint32_t>());
- uint32_t metadataHeight = *(writer->getEntry(TAG_IMAGELENGTH, TIFF_IFD_0)->getData<uint32_t>());
- if (metadataWidth != width) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
- "Metadata width %d doesn't match image width %d", metadataWidth, width);
+
+ size_t fullSize = width * height * BYTES_PER_RGB_PIXEL;
+ jlong capacity = env->GetDirectBufferCapacity(buffer);
+ if (capacity != fullSize) {
+ jniThrowExceptionFmt(env, "java/lang/AssertionError",
+ "Invalid size %d for thumbnail, expected size was %d",
+ capacity, fullSize);
return;
}
- if (metadataHeight != height) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \
- "Metadata height %d doesn't match image height %d", metadataHeight, height);
+ uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
+ if (pixelBytes == NULL) {
+ ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
return;
}
- uint32_t stripOffset = writer->getTotalSize();
+ if (!writer->hasIfd(TIFF_IFD_SUB1)) {
+ if (writer->addSubIfd(TIFF_IFD_0, TIFF_IFD_SUB1) != OK) {
+ ALOGE("%s: Failed to add SubIFD %u to IFD %u", __FUNCTION__, TIFF_IFD_SUB1,
+ TIFF_IFD_0);
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to add SubIFD");
+ return;
+ }
- BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env,
- TAG_STRIPOFFSETS);
+ Vector<uint16_t> tagsToMove;
+ tagsToMove.add(TAG_ORIENTATION);
+ tagsToMove.add(TAG_NEWSUBFILETYPE);
+ tagsToMove.add(TAG_BITSPERSAMPLE);
+ tagsToMove.add(TAG_COMPRESSION);
+ tagsToMove.add(TAG_IMAGEWIDTH);
+ tagsToMove.add(TAG_IMAGELENGTH);
+ tagsToMove.add(TAG_PHOTOMETRICINTERPRETATION);
+ tagsToMove.add(TAG_BLACKLEVEL);
+ tagsToMove.add(TAG_BLACKLEVELREPEATDIM);
+ tagsToMove.add(TAG_SAMPLESPERPIXEL);
+ tagsToMove.add(TAG_PLANARCONFIGURATION);
+ tagsToMove.add(TAG_CFAREPEATPATTERNDIM);
+ tagsToMove.add(TAG_CFAPATTERN);
+ tagsToMove.add(TAG_CFAPLANECOLOR);
+ tagsToMove.add(TAG_CFALAYOUT);
+ tagsToMove.add(TAG_XRESOLUTION);
+ tagsToMove.add(TAG_YRESOLUTION);
+ tagsToMove.add(TAG_RESOLUTIONUNIT);
+ tagsToMove.add(TAG_WHITELEVEL);
+ tagsToMove.add(TAG_DEFAULTSCALE);
+ tagsToMove.add(TAG_ROWSPERSTRIP);
+ tagsToMove.add(TAG_STRIPBYTECOUNTS);
+ tagsToMove.add(TAG_STRIPOFFSETS);
+ tagsToMove.add(TAG_DEFAULTCROPORIGIN);
+ tagsToMove.add(TAG_DEFAULTCROPSIZE);
+ tagsToMove.add(TAG_OPCODELIST2);
+
+ if (moveEntries(writer, TIFF_IFD_0, TIFF_IFD_SUB1, tagsToMove) != OK) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Failed to move entries");
+ return;
+ }
- if (writer->write(out.get()) != OK) {
- if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write metadata");
+ // Make sure both IFDs get the same orientation tag
+ sp<TiffEntry> orientEntry = writer->getEntry(TAG_ORIENTATION, TIFF_IFD_SUB1);
+ if (orientEntry != NULL) {
+ writer->addEntry(orientEntry, TIFF_IFD_0);
}
+ }
+
+ // Setup thumbnail tags
+
+ {
+ // Set photometric interpretation
+ uint16_t interpretation = 2; // RGB
+ BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation,
+ TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION, writer);
+ }
+
+ {
+ // Set planar configuration
+ uint16_t config = 1; // Chunky
+ BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0),
+ env, TAG_PLANARCONFIGURATION, writer);
+ }
+
+ {
+ // Set samples per pixel
+ uint16_t samples = SAMPLES_PER_RGB_PIXEL;
+ BAIL_IF_INVALID(writer->addEntry(TAG_SAMPLESPERPIXEL, 1, &samples, TIFF_IFD_0),
+ env, TAG_SAMPLESPERPIXEL, writer);
+ }
+
+ {
+ // Set bits per sample
+ uint16_t bits = BITS_PER_RGB_SAMPLE;
+ BAIL_IF_INVALID(writer->addEntry(TAG_BITSPERSAMPLE, 1, &bits, TIFF_IFD_0), env,
+ TAG_BITSPERSAMPLE, writer);
+ }
+
+ {
+ // Set subfiletype
+ uint32_t subfileType = 1; // Thumbnail image
+ BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env,
+ TAG_NEWSUBFILETYPE, writer);
+ }
+
+ {
+ // Set compression
+ uint16_t compression = 1; // None
+ BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env,
+ TAG_COMPRESSION, writer);
+ }
+
+ {
+ // Set dimensions
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ BAIL_IF_INVALID(writer->addEntry(TAG_IMAGEWIDTH, 1, &uWidth, TIFF_IFD_0), env,
+ TAG_IMAGEWIDTH, writer);
+ BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &uHeight, TIFF_IFD_0), env,
+ TAG_IMAGELENGTH, writer);
+ }
+
+ {
+ // x resolution
+ uint32_t xres[] = { 72, 1 }; // default 72 ppi
+ BAIL_IF_INVALID(writer->addEntry(TAG_XRESOLUTION, 1, xres, TIFF_IFD_0),
+ env, TAG_XRESOLUTION, writer);
+
+ // y resolution
+ uint32_t yres[] = { 72, 1 }; // default 72 ppi
+ BAIL_IF_INVALID(writer->addEntry(TAG_YRESOLUTION, 1, yres, TIFF_IFD_0),
+ env, TAG_YRESOLUTION, writer);
+
+ uint16_t unit = 2; // inches
+ BAIL_IF_INVALID(writer->addEntry(TAG_RESOLUTIONUNIT, 1, &unit, TIFF_IFD_0),
+ env, TAG_RESOLUTIONUNIT, writer);
+ }
+
+ {
+ // Setup data strips
+ if (writer->addStrip(TIFF_IFD_0) != OK) {
+ ALOGE("%s: Could not setup thumbnail strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup thumbnail strip tags.");
+ return;
+ }
+ if (writer->addStrip(TIFF_IFD_SUB1) != OK) {
+ ALOGE("%s: Could not main image strip tags.", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to setup main image strip tags.");
+ return;
+ }
+ }
+
+ if (!context->setThumbnail(pixelBytes, width, height)) {
+ jniThrowException(env, "java/lang/IllegalStateException",
+ "Failed to set thumbnail.");
return;
}
+}
- size_t fullSize = rowStride * height;
- jlong capacity = env->GetDirectBufferCapacity(inBuffer);
- if (capacity < 0 || fullSize > capacity) {
- jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
- "Invalid size %d for Image, size given in metadata is %d at current stride",
- capacity, fullSize);
+// TODO: Refactor out common preamble for the two nativeWrite methods.
+static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width,
+ jint height, jobject inBuffer, jint rowStride, jint pixStride, jlong offset,
+ jboolean isDirect) {
+ ALOGV("%s:", __FUNCTION__);
+ ALOGV("%s: nativeWriteImage called with: width=%d, height=%d, rowStride=%d, pixStride=%d,"
+ " offset=%lld", __FUNCTION__, width, height, rowStride, pixStride, offset);
+ uint32_t rStride = static_cast<uint32_t>(rowStride);
+ uint32_t pStride = static_cast<uint32_t>(pixStride);
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ uint64_t uOffset = static_cast<uint64_t>(offset);
+
+ sp<JniOutputStream> out = new JniOutputStream(env, outStream);
+ if(env->ExceptionCheck()) {
+ ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
return;
}
- if (pixStride == BYTES_PER_SAMPLE && rowStride == width * BYTES_PER_SAMPLE) {
- if (out->write(pixelBytes, 0, fullSize) != OK || env->ExceptionCheck()) {
- if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
- }
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
+ if (writer == NULL || context == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "Write called with uninitialized DngCreator");
+ return;
+ }
+
+ // Validate DNG header
+ if (!validateDngHeader(env, writer, width, height)) {
+ return;
+ }
+
+ sp<JniInputByteBuffer> inBuf;
+ Vector<StripSource*> sources;
+ sp<DirectStripSource> thumbnailSource;
+ uint32_t targetIfd = TIFF_IFD_0;
+
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+
+ if (hasThumbnail) {
+ ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+ uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+ uint32_t thumbWidth = context->getThumbnailWidth();
+ thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+ thumbWidth, context->getThumbnailHeight(), bytesPerPixel,
+ bytesPerPixel * thumbWidth, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+ SAMPLES_PER_RGB_PIXEL);
+ sources.add(thumbnailSource.get());
+ targetIfd = TIFF_IFD_SUB1;
+ }
+
+ if (isDirect) {
+ size_t fullSize = rStride * uHeight;
+ jlong capacity = env->GetDirectBufferCapacity(inBuffer);
+ if (capacity < 0 || fullSize + uOffset > static_cast<uint64_t>(capacity)) {
+ jniThrowExceptionFmt(env, "java/lang/IllegalStateException",
+ "Invalid size %d for Image, size given in metadata is %d at current stride",
+ capacity, fullSize);
return;
}
- } else if (pixStride == BYTES_PER_SAMPLE) {
- for (size_t i = 0; i < height; ++i) {
- if (out->write(pixelBytes, i * rowStride, pixStride * width) != OK ||
- env->ExceptionCheck()) {
- if (!env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
- }
- return;
+
+ uint8_t* pixelBytes = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(inBuffer));
+ if (pixelBytes == NULL) {
+ ALOGE("%s: Could not get native ByteBuffer", __FUNCTION__);
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid ByteBuffer");
+ return;
+ }
+
+ ALOGV("%s: Using direct-type strip source.", __FUNCTION__);
+ DirectStripSource stripSource(env, pixelBytes, targetIfd, uWidth, uHeight, pStride,
+ rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+ if (!env->ExceptionCheck()) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
}
+ return;
}
} else {
- for (size_t i = 0; i < height; ++i) {
- for (size_t j = 0; j < width; ++j) {
- if (out->write(pixelBytes, i * rowStride + j * pixStride,
- BYTES_PER_SAMPLE) != OK || !env->ExceptionCheck()) {
- if (env->ExceptionCheck()) {
- jniThrowException(env, "java/io/IOException", "Failed to write pixel data");
- }
- return;
- }
+ inBuf = new JniInputByteBuffer(env, inBuffer);
+
+ ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+ InputStripSource stripSource(env, *inBuf, targetIfd, uWidth, uHeight, pStride,
+ rStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+ if (!env->ExceptionCheck()) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
}
+ return;
}
}
-
-}
-
-static void DngCreator_nativeWriteByteBuffer(JNIEnv* env, jobject thiz, jobject outStream,
- jobject rawBuffer, jlong offset) {
- ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeWriteByteBuffer is not implemented.");
}
static void DngCreator_nativeWriteInputStream(JNIEnv* env, jobject thiz, jobject outStream,
- jobject inStream, jlong offset) {
+ jobject inStream, jint width, jint height, jlong offset) {
ALOGV("%s:", __FUNCTION__);
- jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented.");
+
+ uint32_t rowStride = width * BYTES_PER_SAMPLE;
+ uint32_t pixStride = BYTES_PER_SAMPLE;
+ uint32_t uWidth = static_cast<uint32_t>(width);
+ uint32_t uHeight = static_cast<uint32_t>(height);
+ uint64_t uOffset = static_cast<uint32_t>(offset);
+
+ ALOGV("%s: nativeWriteInputStream called with: width=%d, height=%d, rowStride=%u,"
+ "pixStride=%u, offset=%lld", __FUNCTION__, width, height, rowStride, pixStride,
+ offset);
+
+ sp<JniOutputStream> out = new JniOutputStream(env, outStream);
+ if(env->ExceptionCheck()) {
+ ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__);
+ return;
+ }
+
+ TiffWriter* writer = DngCreator_getCreator(env, thiz);
+ NativeContext* context = DngCreator_getNativeContext(env, thiz);
+ if (writer == NULL || context == NULL) {
+ ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__);
+ jniThrowException(env, "java/lang/AssertionError",
+ "Write called with uninitialized DngCreator");
+ return;
+ }
+
+ // Validate DNG header
+ if (!validateDngHeader(env, writer, width, height)) {
+ return;
+ }
+
+ sp<DirectStripSource> thumbnailSource;
+ uint32_t targetIfd = TIFF_IFD_0;
+ bool hasThumbnail = writer->hasIfd(TIFF_IFD_SUB1);
+ Vector<StripSource*> sources;
+
+ if (hasThumbnail) {
+ ALOGV("%s: Adding thumbnail strip sources.", __FUNCTION__);
+ uint32_t bytesPerPixel = SAMPLES_PER_RGB_PIXEL * BYTES_PER_RGB_SAMPLE;
+ uint32_t width = context->getThumbnailWidth();
+ thumbnailSource = new DirectStripSource(env, context->getThumbnail(), TIFF_IFD_0,
+ width, context->getThumbnailHeight(), bytesPerPixel,
+ bytesPerPixel * width, /*offset*/0, BYTES_PER_RGB_SAMPLE,
+ SAMPLES_PER_RGB_PIXEL);
+ sources.add(thumbnailSource.get());
+ targetIfd = TIFF_IFD_SUB1;
+ }
+
+ sp<JniInputStream> in = new JniInputStream(env, inStream);
+
+ ALOGV("%s: Using input-type strip source.", __FUNCTION__);
+ InputStripSource stripSource(env, *in, targetIfd, uWidth, uHeight, pixStride,
+ rowStride, uOffset, BYTES_PER_SAMPLE, SAMPLES_PER_RAW_PIXEL);
+ sources.add(&stripSource);
+
+ status_t ret = OK;
+ if ((ret = writer->write(out.get(), sources.editArray(), sources.size())) != OK) {
+ ALOGE("%s: write failed with error %d.", __FUNCTION__, ret);
+ if (!env->ExceptionCheck()) {
+ jniThrowExceptionFmt(env, "java/io/IOException",
+ "Encountered error %d while writing file.", ret);
+ }
+ return;
+ }
}
} /*extern "C" */
(void*) DngCreator_init},
{"nativeDestroy", "()V", (void*) DngCreator_destroy},
{"nativeSetOrientation", "(I)V", (void*) DngCreator_nativeSetOrientation},
- {"nativeSetThumbnailBitmap","(Landroid/graphics/Bitmap;)V",
- (void*) DngCreator_nativeSetThumbnailBitmap},
- {"nativeSetThumbnailImage",
- "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)V",
- (void*) DngCreator_nativeSetThumbnailImage},
- {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;II)V",
+ {"nativeSetDescription", "(Ljava/lang/String;)V", (void*) DngCreator_nativeSetDescription},
+ {"nativeSetGpsTags", "([ILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;[I)V",
+ (void*) DngCreator_nativeSetGpsTags},
+ {"nativeSetThumbnail","(Ljava/nio/ByteBuffer;II)V", (void*) DngCreator_nativeSetThumbnail},
+ {"nativeWriteImage", "(Ljava/io/OutputStream;IILjava/nio/ByteBuffer;IIJZ)V",
(void*) DngCreator_nativeWriteImage},
- {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V",
- (void*) DngCreator_nativeWriteByteBuffer},
- {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V",
+ {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;IIJ)V",
(void*) DngCreator_nativeWriteInputStream},
};