diff options
author | Ruben Brunk <rubenbrunk@google.com> | 2014-05-22 12:33:54 -0700 |
---|---|---|
committer | Ruben Brunk <rubenbrunk@google.com> | 2014-05-22 22:57:11 +0000 |
commit | b6079005ed0631c3972ff427f56e12523ec214a7 (patch) | |
tree | 0ed9977a4c689a07702302c72dc1622ce7ae835f /core/jni | |
parent | 88aca033ff10d3298f06b4c950ef24ec091185b7 (diff) | |
download | frameworks_base-b6079005ed0631c3972ff427f56e12523ec214a7.zip frameworks_base-b6079005ed0631c3972ff427f56e12523ec214a7.tar.gz frameworks_base-b6079005ed0631c3972ff427f56e12523ec214a7.tar.bz2 |
camera2: Move DngCreator to hardware/camera2.
Bug: 15091253
Change-Id: Ibe2ee376436e5c796e33c0b782ea68278589f2e6
Diffstat (limited to 'core/jni')
-rw-r--r-- | core/jni/Android.mk | 1 | ||||
-rw-r--r-- | core/jni/AndroidRuntime.cpp | 2 | ||||
-rw-r--r-- | core/jni/android_hardware_camera2_DngCreator.cpp | 774 |
3 files changed, 777 insertions, 0 deletions
diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 835a648..a159715 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -138,6 +138,7 @@ LOCAL_SRC_FILES:= \ android_hardware_Camera.cpp \ android_hardware_camera2_CameraMetadata.cpp \ android_hardware_camera2_legacy_LegacyCameraDevice.cpp \ + android_hardware_camera2_DngCreator.cpp \ android_hardware_SensorManager.cpp \ android_hardware_SerialPort.cpp \ android_hardware_UsbDevice.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 2d350e0..0c7eefa 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -80,6 +80,7 @@ extern int register_android_opengl_jni_GLES31Ext(JNIEnv* env); extern int register_android_hardware_Camera(JNIEnv *env); extern int register_android_hardware_camera2_CameraMetadata(JNIEnv *env); extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env); +extern int register_android_hardware_camera2_DngCreator(JNIEnv *env); extern int register_android_hardware_SensorManager(JNIEnv *env); extern int register_android_hardware_SerialPort(JNIEnv *env); extern int register_android_hardware_UsbDevice(JNIEnv *env); @@ -1286,6 +1287,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_hardware_Camera), REG_JNI(register_android_hardware_camera2_CameraMetadata), REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice), + REG_JNI(register_android_hardware_camera2_DngCreator), REG_JNI(register_android_hardware_SensorManager), REG_JNI(register_android_hardware_SerialPort), REG_JNI(register_android_hardware_UsbDevice), diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp new file mode 100644 index 0000000..7b686e7 --- /dev/null +++ b/core/jni/android_hardware_camera2_DngCreator.cpp @@ -0,0 +1,774 @@ +/* + * 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "DngCreator_JNI" + +#include <system/camera_metadata.h> +#include <camera/CameraMetadata.h> +#include <img_utils/DngUtils.h> +#include <img_utils/TagDefinitions.h> +#include <img_utils/TiffIfd.h> +#include <img_utils/TiffWriter.h> +#include <img_utils/Output.h> + +#include <utils/Log.h> +#include <utils/Errors.h> +#include <utils/StrongPointer.h> +#include <utils/RefBase.h> +#include <cutils/properties.h> + +#include "android_runtime/AndroidRuntime.h" +#include "android_runtime/android_hardware_camera2_CameraMetadata.h" + +#include <jni.h> +#include <JNIHelp.h> + +using namespace android; +using namespace img_utils; + +#define BAIL_IF_INVALID(expr, jnienv, tagId) \ + if ((expr) != OK) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Invalid metadata for tag %x", tagId); \ + return; \ + } + +#define BAIL_IF_EMPTY(entry, jnienv, tagId) \ + if (entry.count == 0) { \ + jniThrowExceptionFmt(jnienv, "java/lang/IllegalArgumentException", \ + "Missing metadata fields for tag %x", tagId); \ + return; \ + } + +#define ANDROID_DNGCREATOR_CTX_JNI_ID "mNativeContext" + +static struct { + jfieldID mNativeContext; +} gDngCreatorClassInfo; + +static struct { + jmethodID mWriteMethod; +} gOutputStreamClassInfo; + +enum { + BITS_PER_SAMPLE = 16, + BYTES_PER_SAMPLE = 2, + TIFF_IFD_0 = 0 +}; + +// ---------------------------------------------------------------------------- + +// 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 + }; + jobject mOutputStream; + JNIEnv* mEnv; + jbyteArray mByteArray; +}; + +JniOutputStream::JniOutputStream(JNIEnv* env, jobject outStream) : mOutputStream(outStream), + mEnv(env) { + mByteArray = env->NewByteArray(BYTE_ARRAY_LENGTH); + if (mByteArray == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", "Could not allocate byte array."); + } +} + +JniOutputStream::~JniOutputStream() { + mEnv->DeleteLocalRef(mByteArray); +} + +status_t JniOutputStream::open() { + // Do nothing + return OK; +} + +status_t JniOutputStream::write(const uint8_t* buf, size_t offset, size_t count) { + while(count > 0) { + size_t len = BYTE_ARRAY_LENGTH; + len = (count > len) ? len : count; + mEnv->SetByteArrayRegion(mByteArray, 0, len, reinterpret_cast<const jbyte*>(buf + offset)); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + mEnv->CallVoidMethod(mOutputStream, gOutputStreamClassInfo.mWriteMethod, mByteArray, + 0, len); + + if (mEnv->ExceptionCheck()) { + return BAD_VALUE; + } + + count -= len; + offset += len; + } + return OK; +} + +status_t JniOutputStream::close() { + // Do nothing + return OK; +} + +// ---------------------------------------------------------------------------- + +extern "C" { + +static TiffWriter* DngCreator_getCreator(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + return reinterpret_cast<TiffWriter*>(env->GetLongField(thiz, + gDngCreatorClassInfo.mNativeContext)); +} + +static void DngCreator_setCreator(JNIEnv* env, jobject thiz, sp<TiffWriter> writer) { + ALOGV("%s:", __FUNCTION__); + TiffWriter* current = DngCreator_getCreator(env, thiz); + if (writer != NULL) { + writer->incStrong((void*) DngCreator_setCreator); + } + if (current) { + current->decStrong((void*) DngCreator_setCreator); + } + env->SetLongField(thiz, gDngCreatorClassInfo.mNativeContext, + reinterpret_cast<jlong>(writer.get())); +} + +static void DngCreator_nativeClassInit(JNIEnv* env, jclass clazz) { + ALOGV("%s:", __FUNCTION__); + + gDngCreatorClassInfo.mNativeContext = env->GetFieldID(clazz, + ANDROID_DNGCREATOR_CTX_JNI_ID, "J"); + LOG_ALWAYS_FATAL_IF(gDngCreatorClassInfo.mNativeContext == NULL, + "can't find android/hardware/camera2/DngCreator.%s", + ANDROID_DNGCREATOR_CTX_JNI_ID); + + jclass outputStreamClazz = env->FindClass("java/io/OutputStream"); + 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"); +} + +static void DngCreator_init(JNIEnv* env, jobject thiz, jobject characteristicsPtr, + jobject resultsPtr) { + ALOGV("%s:", __FUNCTION__); + CameraMetadata characteristics; + CameraMetadata results; + if (CameraMetadata_getNativeMetadata(env, characteristicsPtr, &characteristics) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for camera characteristics."); + return; + } + if (CameraMetadata_getNativeMetadata(env, resultsPtr, &results) != OK) { + jniThrowException(env, "java/lang/AssertionError", + "No native metadata defined for capture results."); + return; + } + + sp<TiffWriter> writer = new TiffWriter(); + + writer->addIfd(TIFF_IFD_0); + + status_t err = OK; + + const uint32_t samplesPerPixel = 1; + const uint32_t bitsPerSample = BITS_PER_SAMPLE; + const uint32_t bitsPerByte = BITS_PER_SAMPLE / BYTES_PER_SAMPLE; + uint32_t imageWidth = 0; + uint32_t imageHeight = 0; + + OpcodeListBuilder::CfaLayout opcodeCfaLayout = OpcodeListBuilder::CFA_RGGB; + + // TODO: Greensplit. + // TODO: UniqueCameraModel + // TODO: Add remaining non-essential tags + { + // Set orientation + uint16_t orientation = 1; // Normal + BAIL_IF_INVALID(writer->addEntry(TAG_ORIENTATION, 1, &orientation, TIFF_IFD_0), env, + TAG_ORIENTATION); + } + + { + // Set subfiletype + uint32_t subfileType = 0; // Main image + BAIL_IF_INVALID(writer->addEntry(TAG_NEWSUBFILETYPE, 1, &subfileType, TIFF_IFD_0), env, + TAG_NEWSUBFILETYPE); + } + + { + // 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); + } + + { + // Set compression + uint16_t compression = 1; // None + BAIL_IF_INVALID(writer->addEntry(TAG_COMPRESSION, 1, &compression, TIFF_IFD_0), env, + TAG_COMPRESSION); + } + + { + // Set dimensions + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE); + BAIL_IF_EMPTY(entry, env, TAG_IMAGEWIDTH); + 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); + BAIL_IF_INVALID(writer->addEntry(TAG_IMAGELENGTH, 1, &height, TIFF_IFD_0), env, + TAG_IMAGELENGTH); + imageWidth = width; + imageHeight = height; + } + + { + // Set photometric interpretation + uint16_t interpretation = 32803; + BAIL_IF_INVALID(writer->addEntry(TAG_PHOTOMETRICINTERPRETATION, 1, &interpretation, + TIFF_IFD_0), env, TAG_PHOTOMETRICINTERPRETATION); + } + + { + // Set blacklevel tags + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_BLACK_LEVEL_PATTERN); + BAIL_IF_EMPTY(entry, env, TAG_BLACKLEVEL); + 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); + + uint16_t repeatDim[2] = {2, 2}; + BAIL_IF_INVALID(writer->addEntry(TAG_BLACKLEVELREPEATDIM, 2, repeatDim, TIFF_IFD_0), env, + TAG_BLACKLEVELREPEATDIM); + } + + { + // 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); + } + + { + // Set planar configuration + uint16_t config = 1; // Chunky + BAIL_IF_INVALID(writer->addEntry(TAG_PLANARCONFIGURATION, 1, &config, TIFF_IFD_0), + env, TAG_PLANARCONFIGURATION); + } + + { + // 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); + } + + { + // Set CFA pattern + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); + BAIL_IF_EMPTY(entry, env, TAG_CFAPATTERN); + 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]); + switch(cfa) { + 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); + 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); + 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); + 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); + opcodeCfaLayout = OpcodeListBuilder::CFA_BGGR; + break; + } + default: { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", + "Invalid metadata for tag %d", TAG_CFAPATTERN); + return; + } + } + } + + { + // 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); + } + + { + // Set CFA layout + uint16_t cfaLayout = 1; + BAIL_IF_INVALID(writer->addEntry(TAG_CFALAYOUT, 1, &cfaLayout, TIFF_IFD_0), + env, TAG_CFALAYOUT); + } + + { + // 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); + + uint8_t backwardVersion[4] = {1, 1, 0, 0}; + BAIL_IF_INVALID(writer->addEntry(TAG_DNGBACKWARDVERSION, 4, backwardVersion, TIFF_IFD_0), + env, TAG_DNGBACKWARDVERSION); + } + + { + // Set whitelevel + camera_metadata_entry entry = + characteristics.find(ANDROID_SENSOR_INFO_WHITE_LEVEL); + BAIL_IF_EMPTY(entry, env, TAG_WHITELEVEL); + 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); + } + + { + // 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); + } + + bool singleIlluminant = false; + { + // Set calibration illuminants + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT1); + BAIL_IF_EMPTY(entry1, env, TAG_CALIBRATIONILLUMINANT1); + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_REFERENCE_ILLUMINANT2); + if (entry2.count == 0) { + singleIlluminant = true; + } + uint16_t ref1 = entry1.data.u8[0]; + + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT1, 1, &ref1, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT1); + + if (!singleIlluminant) { + uint16_t ref2 = entry2.data.u8[0]; + BAIL_IF_INVALID(writer->addEntry(TAG_CALIBRATIONILLUMINANT2, 1, &ref2, + TIFF_IFD_0), env, TAG_CALIBRATIONILLUMINANT2); + } + } + + { + // Set color transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_COLORMATRIX1); + + int32_t colorTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + colorTransform1[ctr++] = entry1.data.r[i].numerator; + colorTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX1, entry1.count, colorTransform1, TIFF_IFD_0), + env, TAG_COLORMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = characteristics.find(ANDROID_SENSOR_COLOR_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_COLORMATRIX2); + int32_t colorTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + colorTransform2[ctr++] = entry2.data.r[i].numerator; + colorTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_COLORMATRIX2, entry2.count, colorTransform2, TIFF_IFD_0), + env, TAG_COLORMATRIX2); + } + } + + { + // Set calibration transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM1); + BAIL_IF_EMPTY(entry1, env, TAG_CAMERACALIBRATION1); + + int32_t calibrationTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + calibrationTransform1[ctr++] = entry1.data.r[i].numerator; + calibrationTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION1, entry1.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_CALIBRATION_TRANSFORM2); + BAIL_IF_EMPTY(entry2, env, TAG_CAMERACALIBRATION2); + int32_t calibrationTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + calibrationTransform2[ctr++] = entry2.data.r[i].numerator; + calibrationTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_CAMERACALIBRATION2, entry2.count, calibrationTransform1, + TIFF_IFD_0), env, TAG_CAMERACALIBRATION2); + } + } + + { + // Set forward transforms + camera_metadata_entry entry1 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX1); + BAIL_IF_EMPTY(entry1, env, TAG_FORWARDMATRIX1); + + int32_t forwardTransform1[entry1.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry1.count; ++i) { + forwardTransform1[ctr++] = entry1.data.r[i].numerator; + forwardTransform1[ctr++] = entry1.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX1, entry1.count, forwardTransform1, + TIFF_IFD_0), env, TAG_FORWARDMATRIX1); + + if (!singleIlluminant) { + camera_metadata_entry entry2 = + characteristics.find(ANDROID_SENSOR_FORWARD_MATRIX2); + BAIL_IF_EMPTY(entry2, env, TAG_FORWARDMATRIX2); + int32_t forwardTransform2[entry2.count * 2]; + + ctr = 0; + for(size_t i = 0; i < entry2.count; ++i) { + forwardTransform2[ctr++] = entry2.data.r[i].numerator; + forwardTransform2[ctr++] = entry2.data.r[i].denominator; + } + + BAIL_IF_INVALID(writer->addEntry(TAG_FORWARDMATRIX2, entry2.count, forwardTransform2, + TIFF_IFD_0), env, TAG_FORWARDMATRIX2); + } + } + + { + // Set camera neutral + camera_metadata_entry entry = + results.find(ANDROID_SENSOR_NEUTRAL_COLOR_POINT); + BAIL_IF_EMPTY(entry, env, TAG_ASSHOTNEUTRAL); + uint32_t cameraNeutral[entry.count * 2]; + + size_t ctr = 0; + for(size_t i = 0; i < entry.count; ++i) { + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].numerator); + cameraNeutral[ctr++] = + static_cast<uint32_t>(entry.data.r[i].denominator); + } + + BAIL_IF_INVALID(writer->addEntry(TAG_ASSHOTNEUTRAL, entry.count, cameraNeutral, + TIFF_IFD_0), env, TAG_ASSHOTNEUTRAL); + } + + { + // 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); + } + + { + // Setup default crop + crop origin tags + uint32_t margin = 8; // Default margin recommended by Adobe for interpolation. + uint32_t dimensionLimit = 128; // Smallest image dimension crop margin from. + if (imageWidth >= dimensionLimit && imageHeight >= dimensionLimit) { + 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); + BAIL_IF_INVALID(writer->addEntry(TAG_DEFAULTCROPSIZE, 2, defaultCropSize, + TIFF_IFD_0), env, TAG_DEFAULTCROPSIZE); + } + } + + { + // Setup unique camera model tag + char model[PROPERTY_VALUE_MAX]; + property_get("ro.product.model", model, ""); + + char manufacturer[PROPERTY_VALUE_MAX]; + property_get("ro.product.manufacturer", manufacturer, ""); + + char brand[PROPERTY_VALUE_MAX]; + property_get("ro.product.brand", brand, ""); + + String8 cameraModel(model); + cameraModel += "-"; + cameraModel += manufacturer; + cameraModel += "-"; + cameraModel += brand; + + BAIL_IF_INVALID(writer->addEntry(TAG_UNIQUECAMERAMODEL, cameraModel.size() + 1, + reinterpret_cast<const uint8_t*>(cameraModel.string()), TIFF_IFD_0), env, + TAG_UNIQUECAMERAMODEL); + } + + { + // 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]); + + camera_metadata_entry entry2 = + results.find(ANDROID_STATISTICS_LENS_SHADING_MAP); + BAIL_IF_EMPTY(entry2, env, TAG_OPCODELIST2); + if (entry2.count == lsmWidth * lsmHeight * 4) { + + OpcodeListBuilder builder; + status_t err = builder.addGainMapsForMetadata(lsmWidth, + lsmHeight, + 0, + 0, + imageHeight, + imageWidth, + opcodeCfaLayout, + entry2.data.f); + if (err == OK) { + size_t listSize = builder.getSize(); + uint8_t opcodeListBuf[listSize]; + err = builder.buildOpList(opcodeListBuf); + if (err == OK) { + BAIL_IF_INVALID(writer->addEntry(TAG_OPCODELIST2, listSize, opcodeListBuf, + TIFF_IFD_0), env, TAG_OPCODELIST2); + } else { + ALOGE("%s: Could not build Lens shading map opcode.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to construct lens shading map opcode."); + } + } else { + ALOGE("%s: Could not add Lens shading map.", __FUNCTION__); + jniThrowRuntimeException(env, "failed to add lens shading map."); + } + } else { + ALOGW("%s: Lens shading map not present in results, skipping...", __FUNCTION__); + } + } + + DngCreator_setCreator(env, thiz, writer); +} + +static void DngCreator_destroy(JNIEnv* env, jobject thiz) { + ALOGV("%s:", __FUNCTION__); + DngCreator_setCreator(env, thiz, NULL); +} + +static void DngCreator_nativeSetOrientation(JNIEnv* env, jobject thiz) { + 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"); +} + +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) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeSetThumbnailImage is not implemented"); +} + +static void DngCreator_nativeWriteImage(JNIEnv* env, jobject thiz, jobject outStream, jint width, + jint height, jobject inBuffer, jint rowStride, jint pixStride) { + ALOGV("%s:", __FUNCTION__); + + sp<JniOutputStream> out = new JniOutputStream(env, outStream); + if(env->ExceptionCheck()) { + ALOGE("%s: Could not allocate buffers for output stream", __FUNCTION__); + 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"); + return; + } + + TiffWriter* writer = DngCreator_getCreator(env, thiz); + if (writer == NULL) { + ALOGE("%s: Failed to initialize DngCreator", __FUNCTION__); + jniThrowException(env, "java/lang/AssertionError", + "Write 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); + return; + } + + if (metadataHeight != height) { + jniThrowExceptionFmt(env, "java/lang/IllegalStateException", \ + "Metadata height %d doesn't match image height %d", metadataHeight, height); + return; + } + + uint32_t stripOffset = writer->getTotalSize(); + + BAIL_IF_INVALID(writer->addEntry(TAG_STRIPOFFSETS, 1, &stripOffset, TIFF_IFD_0), env, + TAG_STRIPOFFSETS); + + if (writer->write(out.get()) != OK) { + if (!env->ExceptionCheck()) { + jniThrowException(env, "java/io/IOException", "Failed to write metadata"); + } + 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); + 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"); + } + 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; + } + } + } 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; + } + } + } + } + +} + +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) { + ALOGV("%s:", __FUNCTION__); + jniThrowRuntimeException(env, "nativeWriteInputStream is not implemented."); +} + +} /*extern "C" */ + +static JNINativeMethod gDngCreatorMethods[] = { + {"nativeClassInit", "()V", (void*) DngCreator_nativeClassInit}, + {"nativeInit", "(Landroid/hardware/camera2/impl/CameraMetadataNative;" + "Landroid/hardware/camera2/impl/CameraMetadataNative;)V", (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", + (void*) DngCreator_nativeWriteImage}, + {"nativeWriteByteBuffer", "(Ljava/io/OutputStream;Ljava/nio/ByteBuffer;J)V", + (void*) DngCreator_nativeWriteByteBuffer}, + {"nativeWriteInputStream", "(Ljava/io/OutputStream;Ljava/io/InputStream;J)V", + (void*) DngCreator_nativeWriteInputStream}, +}; + +int register_android_hardware_camera2_DngCreator(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/hardware/camera2/DngCreator", gDngCreatorMethods, + NELEM(gDngCreatorMethods)); +} |