Merge "ExifInterface: add support for reading metadata from RAW images"
This commit is contained in:
committed by
Android (Google) Code Review
commit
1c78b60a49
@@ -17,7 +17,7 @@
|
||||
package android.media;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.regex.Pattern;
|
||||
import java.text.ParsePosition;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -27,7 +27,9 @@ import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
* This is a class for reading and writing Exif tags in a JPEG file.
|
||||
* This is a class for reading and writing Exif tags in a JPEG file or a RAW image file.
|
||||
* <p>
|
||||
* Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF.
|
||||
*/
|
||||
public class ExifInterface {
|
||||
// The Exif tag names
|
||||
@@ -68,8 +70,6 @@ public class ExifInterface {
|
||||
/** Type is int. */
|
||||
public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@@ -98,15 +98,22 @@ public class ExifInterface {
|
||||
/** Type is String. Name of GPS processing method used for location finding. */
|
||||
public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
|
||||
|
||||
// Private tags used for thumbnail information.
|
||||
private static final String TAG_HAS_THUMBNAIL = "hasThumbnail";
|
||||
private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset";
|
||||
private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength";
|
||||
|
||||
// Constants used for the Orientation Exif tag.
|
||||
public static final int ORIENTATION_UNDEFINED = 0;
|
||||
public static final int ORIENTATION_NORMAL = 1;
|
||||
public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror
|
||||
public static final int ORIENTATION_ROTATE_180 = 3;
|
||||
public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror
|
||||
public static final int ORIENTATION_TRANSPOSE = 5; // flipped about top-left <--> bottom-right axis
|
||||
// flipped about top-left <--> bottom-right axis
|
||||
public static final int ORIENTATION_TRANSPOSE = 5;
|
||||
public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it
|
||||
public static final int ORIENTATION_TRANSVERSE = 7; // flipped about top-right <--> bottom-left axis
|
||||
// flipped about top-right <--> bottom-left axis
|
||||
public static final int ORIENTATION_TRANSVERSE = 7;
|
||||
public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it
|
||||
|
||||
// Constants used for white balance
|
||||
@@ -116,13 +123,20 @@ public class ExifInterface {
|
||||
|
||||
static {
|
||||
System.loadLibrary("jhead_jni");
|
||||
System.loadLibrary("media_jni");
|
||||
initRawNative();
|
||||
|
||||
sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
|
||||
sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
private String mFilename;
|
||||
private HashMap<String, String> mAttributes;
|
||||
private final String mFilename;
|
||||
private final HashMap<String, String> mAttributes = new HashMap<>();
|
||||
private boolean mIsRaw;
|
||||
private boolean mHasThumbnail;
|
||||
// The following values used for indicating a thumbnail position.
|
||||
private int mThumbnailOffset;
|
||||
private int mThumbnailLength;
|
||||
|
||||
// Because the underlying implementation (jhead) uses static variables,
|
||||
// there can only be one user at a time for the native functions (and
|
||||
@@ -134,19 +148,20 @@ public class ExifInterface {
|
||||
private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*");
|
||||
|
||||
/**
|
||||
* Reads Exif tags from the specified JPEG file.
|
||||
* Reads Exif tags from the specified image file.
|
||||
*/
|
||||
public ExifInterface(String filename) throws IOException {
|
||||
if (filename == null) {
|
||||
throw new IllegalArgumentException("filename cannot be null");
|
||||
}
|
||||
mFilename = filename;
|
||||
// First test whether a given file is a one of RAW format or not.
|
||||
loadAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified tag or {@code null} if there
|
||||
* is no such tag in the JPEG file.
|
||||
* is no such tag in the image file.
|
||||
*
|
||||
* @param tag the name of the tag.
|
||||
*/
|
||||
@@ -156,7 +171,7 @@ public class ExifInterface {
|
||||
|
||||
/**
|
||||
* Returns the integer value of the specified tag. If there is no such tag
|
||||
* in the JPEG file or the value cannot be parsed as integer, return
|
||||
* in the image file or the value cannot be parsed as integer, return
|
||||
* <var>defaultValue</var>.
|
||||
*
|
||||
* @param tag the name of the tag.
|
||||
@@ -174,7 +189,7 @@ public class ExifInterface {
|
||||
|
||||
/**
|
||||
* Returns the double value of the specified rational tag. If there is no
|
||||
* such tag in the JPEG file or the value cannot be parsed as double, return
|
||||
* such tag in the image file or the value cannot be parsed as double, return
|
||||
* <var>defaultValue</var>.
|
||||
*
|
||||
* @param tag the name of the tag.
|
||||
@@ -210,17 +225,42 @@ public class ExifInterface {
|
||||
*
|
||||
* mAttributes is a HashMap which stores the Exif attributes of the file.
|
||||
* The key is the standard tag name and the value is the tag's value: e.g.
|
||||
* Model -> Nikon. Numeric values are stored as strings.
|
||||
* Model -> Nikon. Numeric values are stored as strings.
|
||||
*
|
||||
* This function also initialize mHasThumbnail to indicate whether the
|
||||
* file has a thumbnail inside.
|
||||
*/
|
||||
private void loadAttributes() throws IOException {
|
||||
HashMap map = getRawAttributesNative(mFilename);
|
||||
mIsRaw = map != null;
|
||||
if (mIsRaw) {
|
||||
for (Object o : map.entrySet()) {
|
||||
Map.Entry entry = (Map.Entry) o;
|
||||
String attrName = (String) entry.getKey();
|
||||
String attrValue = (String) entry.getValue();
|
||||
|
||||
switch (attrName) {
|
||||
case TAG_HAS_THUMBNAIL:
|
||||
mHasThumbnail = attrValue.equalsIgnoreCase("true");
|
||||
break;
|
||||
case TAG_THUMBNAIL_OFFSET:
|
||||
mThumbnailOffset = Integer.parseInt(attrValue);
|
||||
break;
|
||||
case TAG_THUMBNAIL_LENGTH:
|
||||
mThumbnailLength = Integer.parseInt(attrValue);
|
||||
break;
|
||||
default:
|
||||
mAttributes.put(attrName, attrValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// format of string passed from native C code:
|
||||
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
|
||||
// example:
|
||||
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
|
||||
mAttributes = new HashMap<String, String>();
|
||||
|
||||
String attrStr;
|
||||
synchronized (sLock) {
|
||||
@@ -248,7 +288,7 @@ public class ExifInterface {
|
||||
String attrValue = attrStr.substring(ptr, ptr + attrLen);
|
||||
ptr += attrLen;
|
||||
|
||||
if (attrName.equals("hasThumbnail")) {
|
||||
if (attrName.equals(TAG_HAS_THUMBNAIL)) {
|
||||
mHasThumbnail = attrValue.equalsIgnoreCase("true");
|
||||
} else {
|
||||
mAttributes.put(attrName, attrValue);
|
||||
@@ -257,32 +297,36 @@ public class ExifInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the tag data into the JPEG file. This is expensive because it involves
|
||||
* copying all the JPG data from one file to another and deleting the old file
|
||||
* and renaming the other. It's best to use {@link #setAttribute(String,String)}
|
||||
* to set all attributes to write and make a single call rather than multiple
|
||||
* calls for each attribute.
|
||||
* Save the tag data into the original image file. This is expensive because it involves
|
||||
* copying all the data from one file to another and deleting the old file and renaming the
|
||||
* other. It's best to use{@link #setAttribute(String,String)} to set all attributes to write
|
||||
* and make a single call rather than multiple calls for each attribute.
|
||||
*/
|
||||
public void saveAttributes() throws IOException {
|
||||
if (mIsRaw) {
|
||||
throw new UnsupportedOperationException(
|
||||
"ExifInterface does not support saving attributes on RAW formats.");
|
||||
}
|
||||
|
||||
// format of string passed to native C code:
|
||||
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
|
||||
// example:
|
||||
// "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int size = mAttributes.size();
|
||||
if (mAttributes.containsKey("hasThumbnail")) {
|
||||
if (mAttributes.containsKey(TAG_HAS_THUMBNAIL)) {
|
||||
--size;
|
||||
}
|
||||
sb.append(size + " ");
|
||||
for (Map.Entry<String, String> iter : mAttributes.entrySet()) {
|
||||
String key = iter.getKey();
|
||||
if (key.equals("hasThumbnail")) {
|
||||
sb.append(size).append(" ");
|
||||
for (Map.Entry<String, String> entry : mAttributes.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (key.equals(TAG_HAS_THUMBNAIL)) {
|
||||
// this is a fake attribute not saved as an exif tag
|
||||
continue;
|
||||
}
|
||||
String val = iter.getValue();
|
||||
sb.append(key + "=");
|
||||
sb.append(val.length() + " ");
|
||||
String val = entry.getValue();
|
||||
sb.append(key).append("=");
|
||||
sb.append(val.length()).append(" ");
|
||||
sb.append(val);
|
||||
}
|
||||
String s = sb.toString();
|
||||
@@ -293,25 +337,43 @@ public class ExifInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the JPEG file has a thumbnail.
|
||||
* Returns true if the image file has a thumbnail.
|
||||
*/
|
||||
public boolean hasThumbnail() {
|
||||
return mHasThumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the thumbnail inside the JPEG file, or {@code null} if there is no thumbnail.
|
||||
* Returns the thumbnail inside the image file, or {@code null} if there is no thumbnail.
|
||||
* The returned data is in JPEG format and can be decoded using
|
||||
* {@link android.graphics.BitmapFactory#decodeByteArray(byte[],int,int)}
|
||||
*/
|
||||
public byte[] getThumbnail() {
|
||||
if (mIsRaw) {
|
||||
if (mHasThumbnail) {
|
||||
try (RandomAccessFile file = new RandomAccessFile(mFilename, "r")) {
|
||||
if (file.length() < mThumbnailLength + mThumbnailOffset) {
|
||||
throw new IOException("Corrupted image.");
|
||||
}
|
||||
file.seek(mThumbnailOffset);
|
||||
|
||||
byte[] buffer = new byte[mThumbnailLength];
|
||||
file.readFully(buffer);
|
||||
return buffer;
|
||||
} catch (IOException e) {
|
||||
// Couldn't get a thumbnail image.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
return getThumbnailNative(mFilename);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the offset and length of thumbnail inside the JPEG file, or
|
||||
* Returns the offset and length of thumbnail inside the image file, or
|
||||
* {@code null} if there is no thumbnail.
|
||||
*
|
||||
* @return two-element array, the offset in the first value, and length in
|
||||
@@ -319,6 +381,13 @@ public class ExifInterface {
|
||||
* @hide
|
||||
*/
|
||||
public long[] getThumbnailRange() {
|
||||
if (mIsRaw) {
|
||||
long[] range = new long[2];
|
||||
range[0] = mThumbnailOffset;
|
||||
range[1] = mThumbnailLength;
|
||||
return range;
|
||||
}
|
||||
|
||||
synchronized (sLock) {
|
||||
return getThumbnailRangeNative(mFilename);
|
||||
}
|
||||
@@ -447,26 +516,28 @@ public class ExifInterface {
|
||||
return (float) -result;
|
||||
}
|
||||
return (float) result;
|
||||
} catch (NumberFormatException e) {
|
||||
// Some of the nubmers are not valid
|
||||
throw new IllegalArgumentException();
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Some of the rational does not follow the correct format
|
||||
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
|
||||
// Not valid
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private native boolean appendThumbnailNative(String fileName,
|
||||
// JNI methods for JPEG.
|
||||
private static native boolean appendThumbnailNative(String fileName,
|
||||
String thumbnailFileName);
|
||||
|
||||
private native void saveAttributesNative(String fileName,
|
||||
private static native void saveAttributesNative(String fileName,
|
||||
String compressedAttributes);
|
||||
|
||||
private native String getAttributesNative(String fileName);
|
||||
private static native String getAttributesNative(String fileName);
|
||||
|
||||
private native void commitChangesNative(String fileName);
|
||||
private static native void commitChangesNative(String fileName);
|
||||
|
||||
private native byte[] getThumbnailNative(String fileName);
|
||||
private static native byte[] getThumbnailNative(String fileName);
|
||||
|
||||
private native long[] getThumbnailRangeNative(String fileName);
|
||||
private static native long[] getThumbnailRangeNative(String fileName);
|
||||
|
||||
// JNI methods for RAW formats.
|
||||
private static native void initRawNative();
|
||||
private static native HashMap getRawAttributesNative(String filename);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
android_media_AmrInputStream.cpp \
|
||||
android_media_ExifInterface.cpp \
|
||||
android_media_ImageWriter.cpp \
|
||||
android_media_ImageReader.cpp \
|
||||
android_media_MediaCrypto.cpp \
|
||||
@@ -44,6 +45,7 @@ LOCAL_SHARED_LIBRARIES := \
|
||||
libusbhost \
|
||||
libjhead \
|
||||
libexif \
|
||||
libpiex \
|
||||
libstagefright_amrnb_common
|
||||
|
||||
LOCAL_REQUIRED_MODULES := \
|
||||
@@ -54,6 +56,7 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
external/libexif/ \
|
||||
external/piex/ \
|
||||
external/tremor/Tremor \
|
||||
frameworks/base/core/jni \
|
||||
frameworks/base/libs/hwui \
|
||||
|
||||
364
media/jni/android_media_ExifInterface.cpp
Normal file
364
media/jni/android_media_ExifInterface.cpp
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* 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 <utils/Log.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
|
||||
#include <android_runtime/AndroidRuntime.h>
|
||||
|
||||
#include <jni.h>
|
||||
#include <JNIHelp.h>
|
||||
|
||||
#include <nativehelper/ScopedLocalRef.h>
|
||||
|
||||
#include "src/piex_types.h"
|
||||
#include "src/piex.h"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
using namespace android;
|
||||
|
||||
class FileStream : public piex::StreamInterface {
|
||||
private:
|
||||
FILE *mFile;
|
||||
size_t mPosition;
|
||||
size_t mSize;
|
||||
|
||||
public:
|
||||
FileStream(const String8 filename)
|
||||
: mPosition(0),
|
||||
mSize(0) {
|
||||
mFile = fopen(filename.string(), "r");
|
||||
if (mFile == NULL) {
|
||||
return;
|
||||
}
|
||||
// Get the size.
|
||||
fseek(mFile, 0l, SEEK_END);
|
||||
mSize = ftell(mFile);
|
||||
fseek(mFile, 0l, SEEK_SET);
|
||||
}
|
||||
|
||||
~FileStream() {
|
||||
if (mFile != NULL) {
|
||||
fclose(mFile);
|
||||
mFile = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
|
||||
// provided by the caller, guaranteed to be at least "length" bytes long.
|
||||
// On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
|
||||
// 'offset' bytes from the start of the stream.
|
||||
// Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
|
||||
// change the contents of 'data'.
|
||||
piex::Error GetData(
|
||||
const size_t offset, const size_t length, std::uint8_t* data)
|
||||
override {
|
||||
if (mFile == NULL) {
|
||||
return piex::Error::kFail;
|
||||
}
|
||||
|
||||
// Seek first.
|
||||
if (mPosition != offset) {
|
||||
fseek(mFile, offset, SEEK_SET);
|
||||
}
|
||||
|
||||
// Read bytes.
|
||||
size_t size = fread((void*)data, sizeof(std::uint8_t), length, mFile);
|
||||
mPosition += size;
|
||||
|
||||
// Handle errors.
|
||||
if (ferror(mFile)) {
|
||||
return piex::Error::kFail;
|
||||
}
|
||||
if (size == 0 && feof(mFile)) {
|
||||
return piex::Error::kFail;
|
||||
}
|
||||
return piex::Error::kOk;
|
||||
}
|
||||
|
||||
bool exists() {
|
||||
return mFile != NULL;
|
||||
}
|
||||
|
||||
size_t size() {
|
||||
return mSize;
|
||||
}
|
||||
};
|
||||
|
||||
#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 jobject ExifInterface_getRawMetadata(
|
||||
JNIEnv* env, jclass /* clazz */, jstring jfilename) {
|
||||
const char* filenameChars = env->GetStringUTFChars(jfilename, NULL);
|
||||
if (filenameChars == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
String8 filename(filenameChars);
|
||||
env->ReleaseStringUTFChars(jfilename, filenameChars);
|
||||
|
||||
piex::PreviewImageData image_data;
|
||||
memset(&image_data, 0, sizeof(image_data));
|
||||
std::unique_ptr<FileStream> stream(new FileStream(filename));
|
||||
|
||||
if (!stream.get()->exists()) {
|
||||
// File is not exists.
|
||||
ALOGI("File is not exists: %s", filename.string());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!piex::IsRaw(stream.get())) {
|
||||
// Format not supported.
|
||||
ALOGI("Format not supported: %s", filename.string());
|
||||
return NULL;
|
||||
}
|
||||
|
||||
piex::Error err = piex::GetPreviewImageData(stream.get(), &image_data);
|
||||
if (err != piex::Error::kOk) {
|
||||
// The input data seems to be broken.
|
||||
ALOGI("Raw image not detected: %s (error code: %d)", filename.string(), (int32_t)err);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (image_data.thumbnail_offset + image_data.thumbnail_length > stream.get()->size()) {
|
||||
// Corrupted file.
|
||||
ALOGI("Corrupted file: %s", filename.string());
|
||||
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(
|
||||
"%2u:%2u:%2u",
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
return KeyedVectorToHashMap(env, map);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
{ "initRawNative", "()V", (void *)ExifInterface_initRaw },
|
||||
{ "getRawAttributesNative", "(Ljava/lang/String;)Ljava/util/HashMap;",
|
||||
(void*)ExifInterface_getRawMetadata },
|
||||
};
|
||||
|
||||
int register_android_media_ExifInterface(JNIEnv *env) {
|
||||
return AndroidRuntime::registerNativeMethods(
|
||||
env,
|
||||
"android/media/ExifInterface",
|
||||
gMethods,
|
||||
NELEM(gMethods));
|
||||
}
|
||||
@@ -1088,6 +1088,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env)
|
||||
return AndroidRuntime::registerNativeMethods(env,
|
||||
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
|
||||
}
|
||||
extern int register_android_media_ExifInterface(JNIEnv *env);
|
||||
extern int register_android_media_ImageReader(JNIEnv *env);
|
||||
extern int register_android_media_ImageWriter(JNIEnv *env);
|
||||
extern int register_android_media_Crypto(JNIEnv *env);
|
||||
@@ -1219,6 +1220,11 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
|
||||
goto bail;
|
||||
}
|
||||
|
||||
if (register_android_media_ExifInterface(env) < 0) {
|
||||
ALOGE("ERROR: ExifInterface native registration failed");
|
||||
goto bail;
|
||||
}
|
||||
|
||||
/* success -- return valid version number */
|
||||
result = JNI_VERSION_1_4;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user