And also the following things are included: - Remove mInputStream. - Update javadoc accordingly. Bug: 11224701 Change-Id: I30b4c29ac800ae396fca8f6b2c2c0f68028a44b3
427 lines
15 KiB
C++
427 lines
15 KiB
C++
/*
|
|
* Copyright 2016 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 "ExifInterface_JNI"
|
|
|
|
#include "android_media_Utils.h"
|
|
|
|
#include "android/graphics/CreateJavaOutputStreamAdaptor.h"
|
|
#include "src/piex_types.h"
|
|
#include "src/piex.h"
|
|
|
|
#include <jni.h>
|
|
#include <JNIHelp.h>
|
|
#include <androidfw/Asset.h>
|
|
#include <android_runtime/AndroidRuntime.h>
|
|
#include <android/graphics/Utils.h>
|
|
#include <nativehelper/ScopedLocalRef.h>
|
|
|
|
#include <utils/Log.h>
|
|
#include <utils/String8.h>
|
|
#include <utils/KeyedVector.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
using namespace android;
|
|
|
|
static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff};
|
|
static const int kJpegSignatureSize = 3;
|
|
|
|
#define FIND_CLASS(var, className) \
|
|
var = env->FindClass(className); \
|
|
LOG_FATAL_IF(! var, "Unable to find class " className);
|
|
|
|
#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
|
|
var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
|
|
LOG_FATAL_IF(! var, "Unable to find method " fieldName);
|
|
|
|
struct HashMapFields {
|
|
jmethodID init;
|
|
jmethodID put;
|
|
};
|
|
|
|
struct fields_t {
|
|
HashMapFields hashMap;
|
|
jclass hashMapClassId;
|
|
};
|
|
|
|
static fields_t gFields;
|
|
|
|
static jobject KeyedVectorToHashMap(JNIEnv *env, KeyedVector<String8, String8> const &map) {
|
|
jclass clazz = gFields.hashMapClassId;
|
|
jobject hashMap = env->NewObject(clazz, gFields.hashMap.init);
|
|
for (size_t i = 0; i < map.size(); ++i) {
|
|
jstring jkey = env->NewStringUTF(map.keyAt(i).string());
|
|
jstring jvalue = env->NewStringUTF(map.valueAt(i).string());
|
|
env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jvalue);
|
|
env->DeleteLocalRef(jkey);
|
|
env->DeleteLocalRef(jvalue);
|
|
}
|
|
return hashMap;
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
// -------------------------- ExifInterface methods ---------------------------
|
|
|
|
static void ExifInterface_initRaw(JNIEnv *env) {
|
|
jclass clazz;
|
|
FIND_CLASS(clazz, "java/util/HashMap");
|
|
gFields.hashMapClassId = static_cast<jclass>(env->NewGlobalRef(clazz));
|
|
|
|
GET_METHOD_ID(gFields.hashMap.init, clazz, "<init>", "()V");
|
|
GET_METHOD_ID(gFields.hashMap.put, clazz, "put",
|
|
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
}
|
|
|
|
static bool is_asset_stream(const SkStream& stream) {
|
|
return stream.hasLength() && stream.hasPosition();
|
|
}
|
|
|
|
static jobject ExifInterface_getThumbnailFromAsset(
|
|
JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset,
|
|
jint jthumbnailLength) {
|
|
Asset* asset = reinterpret_cast<Asset*>(jasset);
|
|
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
|
|
|
|
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]);
|
|
if (thumbnailData.get() == NULL) {
|
|
ALOGI("No memory to get thumbnail");
|
|
return NULL;
|
|
}
|
|
|
|
// Do not know the current offset. So rewind it.
|
|
stream->rewind();
|
|
|
|
// Read thumbnail.
|
|
stream->skip((int)jthumbnailOffset);
|
|
stream->read((void*)thumbnailData.get(), (int)jthumbnailLength);
|
|
|
|
// Copy to the byte array.
|
|
jbyteArray byteArray = env->NewByteArray(jthumbnailLength);
|
|
env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get());
|
|
return byteArray;
|
|
}
|
|
|
|
static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) {
|
|
std::unique_ptr<SkStream> streamDeleter(stream);
|
|
|
|
std::unique_ptr<::piex::StreamInterface> piexStream;
|
|
if (is_asset_stream(*stream)) {
|
|
piexStream.reset(new AssetStream(streamDeleter.release()));
|
|
} else {
|
|
piexStream.reset(new BufferedStream(streamDeleter.release()));
|
|
}
|
|
|
|
piex::PreviewImageData image_data;
|
|
|
|
if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) {
|
|
ALOGI("Raw image not detected");
|
|
return NULL;
|
|
}
|
|
|
|
KeyedVector<String8, String8> map;
|
|
|
|
if (image_data.thumbnail.length > 0) {
|
|
map.add(String8("hasThumbnail"), String8("true"));
|
|
map.add(String8("thumbnailOffset"), String8::format("%d", image_data.thumbnail.offset));
|
|
map.add(String8("thumbnailLength"), String8::format("%d", image_data.thumbnail.length));
|
|
} else {
|
|
map.add(String8("hasThumbnail"), String8("false"));
|
|
}
|
|
|
|
map.add(
|
|
String8("Orientation"),
|
|
String8::format("%u", image_data.exif_orientation));
|
|
map.add(
|
|
String8("ImageWidth"),
|
|
String8::format("%u", image_data.full_width));
|
|
map.add(
|
|
String8("ImageLength"),
|
|
String8::format("%u", image_data.full_height));
|
|
|
|
// Current PIEX does not have LightSource information while JPEG version of
|
|
// EXIFInterface always declares the light source field. For the
|
|
// compatibility, it provides the default value of the light source field.
|
|
map.add(String8("LightSource"), String8("0"));
|
|
|
|
if (!image_data.maker.empty()) {
|
|
map.add(String8("Make"), String8(image_data.maker.c_str()));
|
|
}
|
|
|
|
if (!image_data.model.empty()) {
|
|
map.add(String8("Model"), String8(image_data.model.c_str()));
|
|
}
|
|
|
|
if (!image_data.date_time.empty()) {
|
|
map.add(String8("DateTime"), String8(image_data.date_time.c_str()));
|
|
}
|
|
|
|
if (image_data.iso) {
|
|
map.add(
|
|
String8("ISOSpeedRatings"),
|
|
String8::format("%u", image_data.iso));
|
|
}
|
|
|
|
if (image_data.exposure_time.numerator != 0
|
|
&& image_data.exposure_time.denominator != 0) {
|
|
double exposureTime =
|
|
(double)image_data.exposure_time.numerator
|
|
/ image_data.exposure_time.denominator;
|
|
|
|
const char* format;
|
|
if (exposureTime < 0.01) {
|
|
format = "%6.4f";
|
|
} else {
|
|
format = "%5.3f";
|
|
}
|
|
map.add(String8("ExposureTime"), String8::format(format, exposureTime));
|
|
}
|
|
|
|
if (image_data.fnumber.numerator != 0
|
|
&& image_data.fnumber.denominator != 0) {
|
|
double fnumber =
|
|
(double)image_data.fnumber.numerator
|
|
/ image_data.fnumber.denominator;
|
|
map.add(String8("FNumber"), String8::format("%5.3f", fnumber));
|
|
}
|
|
|
|
if (image_data.focal_length.numerator != 0
|
|
&& image_data.focal_length.denominator != 0) {
|
|
map.add(
|
|
String8("FocalLength"),
|
|
String8::format(
|
|
"%u/%u",
|
|
image_data.focal_length.numerator,
|
|
image_data.focal_length.denominator));
|
|
}
|
|
|
|
if (image_data.gps.is_valid) {
|
|
if (image_data.gps.latitude[0].denominator != 0
|
|
&& image_data.gps.latitude[1].denominator != 0
|
|
&& image_data.gps.latitude[2].denominator != 0) {
|
|
map.add(
|
|
String8("GPSLatitude"),
|
|
String8::format(
|
|
"%u/%u,%u/%u,%u/%u",
|
|
image_data.gps.latitude[0].numerator,
|
|
image_data.gps.latitude[0].denominator,
|
|
image_data.gps.latitude[1].numerator,
|
|
image_data.gps.latitude[1].denominator,
|
|
image_data.gps.latitude[2].numerator,
|
|
image_data.gps.latitude[2].denominator));
|
|
}
|
|
|
|
if (image_data.gps.latitude_ref) {
|
|
char str[2];
|
|
str[0] = image_data.gps.latitude_ref;
|
|
str[1] = 0;
|
|
map.add(String8("GPSLatitudeRef"), String8(str));
|
|
}
|
|
|
|
if (image_data.gps.longitude[0].denominator != 0
|
|
&& image_data.gps.longitude[1].denominator != 0
|
|
&& image_data.gps.longitude[2].denominator != 0) {
|
|
map.add(
|
|
String8("GPSLongitude"),
|
|
String8::format(
|
|
"%u/%u,%u/%u,%u/%u",
|
|
image_data.gps.longitude[0].numerator,
|
|
image_data.gps.longitude[0].denominator,
|
|
image_data.gps.longitude[1].numerator,
|
|
image_data.gps.longitude[1].denominator,
|
|
image_data.gps.longitude[2].numerator,
|
|
image_data.gps.longitude[2].denominator));
|
|
}
|
|
|
|
if (image_data.gps.longitude_ref) {
|
|
char str[2];
|
|
str[0] = image_data.gps.longitude_ref;
|
|
str[1] = 0;
|
|
map.add(String8("GPSLongitudeRef"), String8(str));
|
|
}
|
|
|
|
if (image_data.gps.altitude.denominator != 0) {
|
|
map.add(
|
|
String8("GPSAltitude"),
|
|
String8::format("%u/%u",
|
|
image_data.gps.altitude.numerator,
|
|
image_data.gps.altitude.denominator));
|
|
|
|
map.add(
|
|
String8("GPSAltitudeRef"),
|
|
String8(image_data.gps.altitude_ref ? "1" : "0"));
|
|
}
|
|
|
|
if (image_data.gps.time_stamp[0].denominator != 0
|
|
&& image_data.gps.time_stamp[1].denominator != 0
|
|
&& image_data.gps.time_stamp[2].denominator != 0) {
|
|
map.add(
|
|
String8("GPSTimeStamp"),
|
|
String8::format(
|
|
"%02u:%02u:%02u",
|
|
image_data.gps.time_stamp[0].numerator
|
|
/ image_data.gps.time_stamp[0].denominator,
|
|
image_data.gps.time_stamp[1].numerator
|
|
/ image_data.gps.time_stamp[1].denominator,
|
|
image_data.gps.time_stamp[2].numerator
|
|
/ image_data.gps.time_stamp[2].denominator));
|
|
}
|
|
|
|
if (!image_data.gps.date_stamp.empty()) {
|
|
map.add(
|
|
String8("GPSDateStamp"),
|
|
String8(image_data.gps.date_stamp.c_str()));
|
|
}
|
|
}
|
|
|
|
jobject hashMap = KeyedVectorToHashMap(env, map);
|
|
|
|
if (returnThumbnail) {
|
|
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]);
|
|
if (thumbnailData.get() == NULL) {
|
|
ALOGE("No memory to parse a thumbnail");
|
|
return NULL;
|
|
}
|
|
jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length);
|
|
if (jthumbnailByteArray == NULL) {
|
|
ALOGE("No memory to parse a thumbnail");
|
|
return NULL;
|
|
}
|
|
piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length,
|
|
(uint8_t*)thumbnailData.get());
|
|
env->SetByteArrayRegion(
|
|
jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get());
|
|
jstring jkey = env->NewStringUTF(String8("thumbnailData"));
|
|
env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray);
|
|
env->DeleteLocalRef(jkey);
|
|
env->DeleteLocalRef(jthumbnailByteArray);
|
|
}
|
|
return hashMap;
|
|
}
|
|
|
|
static jobject ExifInterface_getRawAttributesFromAsset(
|
|
JNIEnv* env, jclass /* clazz */, jlong jasset) {
|
|
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
|
|
if (jpegSignature.get() == NULL) {
|
|
ALOGE("No enough memory to parse");
|
|
return NULL;
|
|
}
|
|
|
|
Asset* asset = reinterpret_cast<Asset*>(jasset);
|
|
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
|
|
|
|
if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) {
|
|
// Rewind the stream.
|
|
stream.get()->rewind();
|
|
|
|
ALOGI("Corrupted image.");
|
|
return NULL;
|
|
}
|
|
|
|
// Rewind the stream.
|
|
stream.get()->rewind();
|
|
|
|
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
|
|
ALOGI("Should be a JPEG stream.");
|
|
return NULL;
|
|
}
|
|
|
|
// Try to parse from the given stream.
|
|
jobject result = getRawAttributes(env, stream.get(), false);
|
|
|
|
// Rewind the stream for the chance to read JPEG.
|
|
if (result == NULL) {
|
|
stream.get()->rewind();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static jobject ExifInterface_getRawAttributesFromFileDescriptor(
|
|
JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
|
|
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
|
|
if (jpegSignature.get() == NULL) {
|
|
ALOGE("No enough memory to parse");
|
|
return NULL;
|
|
}
|
|
|
|
int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
|
|
if (fd < 0) {
|
|
ALOGI("Invalid file descriptor");
|
|
return NULL;
|
|
}
|
|
|
|
// Restore the file descriptor's offset on exiting this function.
|
|
AutoFDSeek autoRestore(fd);
|
|
|
|
int dupFd = dup(fd);
|
|
|
|
FILE* file = fdopen(dupFd, "r");
|
|
if (file == NULL) {
|
|
ALOGI("Failed to open the file descriptor");
|
|
return NULL;
|
|
}
|
|
|
|
if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) {
|
|
ALOGI("Corrupted image.");
|
|
return NULL;
|
|
}
|
|
|
|
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
|
|
ALOGI("Should be a JPEG stream.");
|
|
return NULL;
|
|
}
|
|
|
|
// Rewind the file descriptor.
|
|
fseek(file, 0L, SEEK_SET);
|
|
|
|
std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file,
|
|
SkFILEStream::kCallerPasses_Ownership));
|
|
return getRawAttributes(env, fileStream.get(), false);
|
|
}
|
|
|
|
static jobject ExifInterface_getRawAttributesFromInputStream(
|
|
JNIEnv* env, jclass /* clazz */, jobject jinputStream) {
|
|
jbyteArray byteArray = env->NewByteArray(8*1024);
|
|
ScopedLocalRef<jbyteArray> scoper(env, byteArray);
|
|
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get()));
|
|
return getRawAttributes(env, stream.get(), true);
|
|
}
|
|
|
|
} // extern "C"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
static JNINativeMethod gMethods[] = {
|
|
{ "nativeInitRaw", "()V", (void *)ExifInterface_initRaw },
|
|
{ "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset },
|
|
{ "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;",
|
|
(void*)ExifInterface_getRawAttributesFromAsset },
|
|
{ "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
|
|
(void*)ExifInterface_getRawAttributesFromFileDescriptor },
|
|
{ "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;",
|
|
(void*)ExifInterface_getRawAttributesFromInputStream },
|
|
};
|
|
|
|
int register_android_media_ExifInterface(JNIEnv *env) {
|
|
return AndroidRuntime::registerNativeMethods(
|
|
env,
|
|
"android/media/ExifInterface",
|
|
gMethods,
|
|
NELEM(gMethods));
|
|
}
|