840 lines
27 KiB
C
840 lines
27 KiB
C
/*
|
|
|
|
Copyright (c) 2008, The Android Open Source Project
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in
|
|
the documentation and/or other materials provided with the
|
|
distribution.
|
|
* Neither the name of Google, Inc. nor the names of its contributors
|
|
may be used to endorse or promote products derived from this
|
|
software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
#include <nativehelper/JNIHelp.h>
|
|
#include <nativehelper/jni.h>
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <dlfcn.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include "jhead.h"
|
|
|
|
#ifndef NELEM
|
|
#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
|
|
#endif
|
|
|
|
// Define the line below to turn on poor man's debugging output
|
|
#undef SUPERDEBUG
|
|
|
|
// Various tests
|
|
#undef REALLOCTEST
|
|
#undef OUTOFMEMORYTEST1
|
|
|
|
static void addExifAttibute(JNIEnv *env, jmethodID putMethod, jobject hashMap, char* key, char* value) {
|
|
jstring jkey = (*env)->NewStringUTF(env, key);
|
|
jstring jvalue = (*env)->NewStringUTF(env, value);
|
|
|
|
jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, putMethod, jkey, jvalue);
|
|
|
|
(*env)->ReleaseStringUTFChars(env, jkey, key);
|
|
(*env)->ReleaseStringUTFChars(env, jvalue, value);
|
|
}
|
|
|
|
extern void ResetJpgfile();
|
|
|
|
static int loadExifInfo(const char* FileName, int readJPG) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("loadExifInfo");
|
|
#endif
|
|
int Modified = FALSE;
|
|
ReadMode_t ReadMode = READ_METADATA;
|
|
if (readJPG) {
|
|
// Must add READ_IMAGE else we can't write the JPG back out.
|
|
ReadMode |= READ_IMAGE;
|
|
}
|
|
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("ResetJpgfile");
|
|
#endif
|
|
ResetJpgfile();
|
|
|
|
// Start with an empty image information structure.
|
|
memset(&ImageInfo, 0, sizeof(ImageInfo));
|
|
ImageInfo.FlashUsed = -1;
|
|
ImageInfo.MeteringMode = -1;
|
|
ImageInfo.Whitebalance = -1;
|
|
|
|
// Store file date/time.
|
|
{
|
|
struct stat st;
|
|
if (stat(FileName, &st) >= 0) {
|
|
ImageInfo.FileDateTime = st.st_mtime;
|
|
ImageInfo.FileSize = st.st_size;
|
|
}
|
|
}
|
|
|
|
strncpy(ImageInfo.FileName, FileName, PATH_MAX);
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("ReadJpegFile");
|
|
#endif
|
|
return ReadJpegFile(FileName, ReadMode);
|
|
}
|
|
|
|
static void saveJPGFile(const char* filename) {
|
|
char backupName[400];
|
|
struct stat buf;
|
|
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("Modified: %s\n", filename);
|
|
#endif
|
|
|
|
strncpy(backupName, filename, 395);
|
|
strcat(backupName, ".t");
|
|
|
|
// Remove any .old file name that may pre-exist
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("removing backup %s", backupName);
|
|
#endif
|
|
unlink(backupName);
|
|
|
|
// Rename the old file.
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("rename %s to %s", filename, backupName);
|
|
#endif
|
|
rename(filename, backupName);
|
|
|
|
// Write the new file.
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("WriteJpegFile %s", filename);
|
|
#endif
|
|
if (WriteJpegFile(filename)) {
|
|
|
|
// Copy the access rights from original file
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("stating old file %s", backupName);
|
|
#endif
|
|
if (stat(backupName, &buf) == 0){
|
|
// set Unix access rights and time to new file
|
|
struct utimbuf mtime;
|
|
chmod(filename, buf.st_mode);
|
|
|
|
mtime.actime = buf.st_mtime;
|
|
mtime.modtime = buf.st_mtime;
|
|
|
|
utime(filename, &mtime);
|
|
}
|
|
|
|
// Now that we are done, remove original file.
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("unlinking old file %s", backupName);
|
|
#endif
|
|
unlink(backupName);
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("returning from saveJPGFile");
|
|
#endif
|
|
} else {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("WriteJpegFile failed, restoring from backup file");
|
|
#endif
|
|
// move back the backup file
|
|
rename(backupName, filename);
|
|
}
|
|
}
|
|
|
|
void copyThumbnailData(uchar* thumbnailData, int thumbnailLen) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** copyThumbnailData\n");
|
|
#endif
|
|
Section_t* ExifSection = FindSection(M_EXIF);
|
|
if (ExifSection == NULL) {
|
|
return;
|
|
}
|
|
int NewExifSize = ImageInfo.ThumbnailOffset+8+thumbnailLen;
|
|
ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
|
|
if (ExifSection->Data == NULL) {
|
|
return;
|
|
}
|
|
uchar* ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
|
|
|
|
memcpy(ThumbnailPointer, thumbnailData, thumbnailLen);
|
|
|
|
ImageInfo.ThumbnailSize = thumbnailLen;
|
|
|
|
Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, thumbnailLen);
|
|
|
|
ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
|
|
ExifSection->Data[1] = (uchar)NewExifSize;
|
|
ExifSection->Size = NewExifSize;
|
|
}
|
|
|
|
static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
|
|
{
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** saveAttributes\n");
|
|
#endif
|
|
// format of attributes string passed from java:
|
|
// "attrCnt attr1=valueLen value1attr2=value2Len value2..."
|
|
// example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
|
|
ExifElement_t* exifElementTable = NULL;
|
|
const char* filename = NULL;
|
|
uchar* thumbnailData = NULL;
|
|
int attrCnt = 0;
|
|
const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
|
|
if (attributes == NULL) {
|
|
goto exit;
|
|
}
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("attributes %s\n", attributes);
|
|
#endif
|
|
|
|
// Get the number of attributes - it's the first number in the string.
|
|
attrCnt = atoi(attributes);
|
|
char* attrPtr = strchr(attributes, ' ') + 1;
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("attribute count %d attrPtr %s\n", attrCnt, attrPtr);
|
|
#endif
|
|
|
|
// Load all the hash exif elements into a more c-like structure
|
|
exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
|
|
if (exifElementTable == NULL) {
|
|
goto exit;
|
|
}
|
|
#ifdef OUTOFMEMORYTEST1
|
|
goto exit;
|
|
#endif
|
|
|
|
int i;
|
|
char tag[100];
|
|
int hasDateTimeTag = FALSE;
|
|
int gpsTagCount = 0;
|
|
int exifTagCount = 0;
|
|
|
|
for (i = 0; i < attrCnt; i++) {
|
|
// get an element from the attribute string and add it to the c structure
|
|
// first, extract the attribute name
|
|
char* tagEnd = strchr(attrPtr, '=');
|
|
if (tagEnd == 0) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("saveAttributes: couldn't find end of tag");
|
|
#endif
|
|
goto exit;
|
|
}
|
|
if (tagEnd - attrPtr > 99) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("saveAttributes: attribute tag way too long");
|
|
#endif
|
|
goto exit;
|
|
}
|
|
memcpy(tag, attrPtr, tagEnd - attrPtr);
|
|
tag[tagEnd - attrPtr] = 0;
|
|
|
|
if (IsGpsTag(tag)) {
|
|
exifElementTable[i].GpsTag = TRUE;
|
|
exifElementTable[i].Tag = GpsTagNameToValue(tag);
|
|
++gpsTagCount;
|
|
} else {
|
|
exifElementTable[i].GpsTag = FALSE;
|
|
exifElementTable[i].Tag = TagNameToValue(tag);
|
|
++exifTagCount;
|
|
}
|
|
attrPtr = tagEnd + 1;
|
|
|
|
if (IsDateTimeTag(exifElementTable[i].Tag)) {
|
|
hasDateTimeTag = TRUE;
|
|
}
|
|
|
|
// next get the length of the attribute value
|
|
int valueLen = atoi(attrPtr);
|
|
attrPtr = strchr(attrPtr, ' ') + 1;
|
|
if (attrPtr == 0) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("saveAttributes: couldn't find end of value len");
|
|
#endif
|
|
goto exit;
|
|
}
|
|
exifElementTable[i].Value = malloc(valueLen + 1);
|
|
if (exifElementTable[i].Value == NULL) {
|
|
goto exit;
|
|
}
|
|
memcpy(exifElementTable[i].Value, attrPtr, valueLen);
|
|
exifElementTable[i].Value[valueLen] = 0;
|
|
exifElementTable[i].DataLength = valueLen;
|
|
|
|
attrPtr += valueLen;
|
|
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
|
|
exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
|
|
#endif
|
|
}
|
|
|
|
filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
|
|
#endif
|
|
loadExifInfo(filename, TRUE);
|
|
|
|
#ifdef SUPERDEBUG
|
|
// DumpExifMap = TRUE;
|
|
ShowTags = TRUE;
|
|
ShowImageInfo(TRUE);
|
|
ALOGE("create exif 2");
|
|
#endif
|
|
|
|
// If the jpg file has a thumbnail, preserve it.
|
|
int thumbnailLength = ImageInfo.ThumbnailSize;
|
|
if (ImageInfo.ThumbnailOffset) {
|
|
Section_t* ExifSection = FindSection(M_EXIF);
|
|
if (ExifSection) {
|
|
uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
|
|
thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
|
|
// if the malloc fails, we just won't copy the thumbnail
|
|
if (thumbnailData) {
|
|
memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
|
|
}
|
|
}
|
|
}
|
|
|
|
create_EXIF(exifElementTable, exifTagCount, gpsTagCount, hasDateTimeTag);
|
|
|
|
if (thumbnailData) {
|
|
copyThumbnailData(thumbnailData, thumbnailLength);
|
|
}
|
|
|
|
exit:
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("cleaning up now in saveAttributes");
|
|
#endif
|
|
// try to clean up resources
|
|
if (attributes) {
|
|
(*env)->ReleaseStringUTFChars(env, jattributes, attributes);
|
|
}
|
|
if (filename) {
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
}
|
|
if (exifElementTable) {
|
|
// free the table
|
|
for (i = 0; i < attrCnt; i++) {
|
|
free(exifElementTable[i].Value);
|
|
}
|
|
free(exifElementTable);
|
|
}
|
|
if (thumbnailData) {
|
|
free(thumbnailData);
|
|
}
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("returning from saveAttributes");
|
|
#endif
|
|
|
|
// Temporarily saving these commented out lines because they represent a lot of figuring out
|
|
// patterns for JNI.
|
|
// // Get link to Method "entrySet"
|
|
// jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
|
|
//
|
|
// // Invoke the "entrySet" method on the HashMap object
|
|
// jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
|
|
//
|
|
// // Get the Set Class
|
|
// jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
|
|
//
|
|
// if (jclass_of_set == 0) {
|
|
// printf("java/util/Set lookup failed\n");
|
|
// return;
|
|
// }
|
|
//
|
|
// // Get link to Method "iterator"
|
|
// jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
|
|
//
|
|
// // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
|
|
// jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
|
|
//
|
|
// // Get the "Iterator" class
|
|
// jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
|
|
//
|
|
// // Get link to Method "hasNext"
|
|
// jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
|
|
//
|
|
// // Invoke - Get the value hasNextMethod
|
|
// jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);
|
|
|
|
// // Get link to Method "hasNext"
|
|
// jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
|
|
//
|
|
// jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
|
|
//
|
|
// jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
|
|
//
|
|
// jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
|
|
}
|
|
|
|
static jboolean appendThumbnail(JNIEnv *env, jobject jobj, jstring jfilename, jstring jthumbnailfilename)
|
|
{
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** appendThumbnail\n");
|
|
#endif
|
|
|
|
const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
if (filename == NULL) {
|
|
return JNI_FALSE;
|
|
}
|
|
const char* thumbnailfilename = (*env)->GetStringUTFChars(env, jthumbnailfilename, NULL);
|
|
if (thumbnailfilename == NULL) {
|
|
return JNI_FALSE;
|
|
}
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("*******before actual call to ReplaceThumbnail\n");
|
|
ShowImageInfo(TRUE);
|
|
#endif
|
|
ReplaceThumbnail(thumbnailfilename);
|
|
#ifdef SUPERDEBUG
|
|
ShowImageInfo(TRUE);
|
|
#endif
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
(*env)->ReleaseStringUTFChars(env, jthumbnailfilename, thumbnailfilename);
|
|
|
|
DiscardData();
|
|
return JNI_TRUE;
|
|
}
|
|
|
|
static void commitChanges(JNIEnv *env, jobject jobj, jstring jfilename)
|
|
{
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** commitChanges\n");
|
|
#endif
|
|
const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
if (filename) {
|
|
saveJPGFile(filename);
|
|
DiscardData();
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
}
|
|
}
|
|
|
|
static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
|
|
{
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** getThumbnail\n");
|
|
#endif
|
|
|
|
const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
if (filename) {
|
|
loadExifInfo(filename, FALSE);
|
|
Section_t* ExifSection = FindSection(M_EXIF);
|
|
if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("no exif section or size == 0, so no thumbnail\n");
|
|
#endif
|
|
goto noThumbnail;
|
|
}
|
|
const jbyte* thumbnailPointer =
|
|
(const jbyte*) (ExifSection->Data + ImageInfo.ThumbnailOffset + 8);
|
|
|
|
jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
|
|
if (byteArray == NULL) {
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
|
|
#endif
|
|
goto noThumbnail;
|
|
}
|
|
(*env)->SetByteArrayRegion(env, byteArray, 0, ImageInfo.ThumbnailSize, thumbnailPointer);
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
|
|
#endif
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
DiscardData();
|
|
return byteArray;
|
|
}
|
|
noThumbnail:
|
|
if (filename) {
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
}
|
|
DiscardData();
|
|
return NULL;
|
|
}
|
|
|
|
static jlongArray getThumbnailRange(JNIEnv *env, jobject jobj, jstring jfilename) {
|
|
jlongArray resultArray = NULL;
|
|
const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
if (filename) {
|
|
loadExifInfo(filename, FALSE);
|
|
Section_t* ExifSection = FindSection(M_EXIF);
|
|
if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
|
|
goto done;
|
|
}
|
|
|
|
jlong result[2];
|
|
result[0] = ExifSection->Offset + ImageInfo.ThumbnailOffset + 8;
|
|
result[1] = ImageInfo.ThumbnailSize;
|
|
|
|
resultArray = (*env)->NewLongArray(env, 2);
|
|
if (resultArray == NULL) {
|
|
goto done;
|
|
}
|
|
|
|
(*env)->SetLongArrayRegion(env, resultArray, 0, 2, result);
|
|
}
|
|
done:
|
|
if (filename) {
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
}
|
|
DiscardData();
|
|
return resultArray;
|
|
}
|
|
|
|
static int attributeCount; // keep track of how many attributes we've added
|
|
|
|
// returns new buffer length
|
|
static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
|
|
// Appends to buf like this: "ImageLength=4 1024"
|
|
|
|
char valueLen[15];
|
|
snprintf(valueLen, 15, "=%d ", (int)strlen(value));
|
|
|
|
// check to see if buf has enough room to append
|
|
int len = strlen(key) + strlen(valueLen) + strlen(value);
|
|
int newLen = strlen(*buf) + len;
|
|
if (newLen >= bufLen) {
|
|
#ifdef REALLOCTEST
|
|
bufLen = newLen + 5;
|
|
ALOGE("reallocing to %d", bufLen);
|
|
#else
|
|
bufLen = newLen + 500;
|
|
#endif
|
|
*buf = realloc(*buf, bufLen);
|
|
if (*buf == NULL) {
|
|
return 0;
|
|
}
|
|
}
|
|
// append the new attribute and value
|
|
snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("buf %s", *buf);
|
|
#endif
|
|
++attributeCount;
|
|
return bufLen;
|
|
}
|
|
|
|
// returns new buffer length
|
|
static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
|
|
char valueStr[20];
|
|
snprintf(valueStr, 20, "%d", value);
|
|
|
|
return addKeyValueString(buf, bufLen, key, valueStr);
|
|
}
|
|
|
|
// returns new buffer length
|
|
static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
|
|
char valueStr[30];
|
|
snprintf(valueStr, 30, format, value);
|
|
|
|
return addKeyValueString(buf, bufLen, key, valueStr);
|
|
}
|
|
|
|
// Returns new buffer length. Rational value will be appended as "numerator/denominator".
|
|
static int addKeyValueRational(char** buf, int bufLen, const char* key, rat_t value) {
|
|
char valueStr[25];
|
|
snprintf(valueStr, sizeof(valueStr), "%u/%u", value.num, value.denom);
|
|
return addKeyValueString(buf, bufLen, key, valueStr);
|
|
}
|
|
|
|
static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
|
|
{
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("******************************** getAttributes\n");
|
|
#endif
|
|
const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
|
|
loadExifInfo(filename, FALSE);
|
|
#ifdef SUPERDEBUG
|
|
ShowImageInfo(TRUE);
|
|
#endif
|
|
(*env)->ReleaseStringUTFChars(env, jfilename, filename);
|
|
|
|
attributeCount = 0;
|
|
#ifdef REALLOCTEST
|
|
int bufLen = 5;
|
|
#else
|
|
int bufLen = 1000;
|
|
#endif
|
|
char* buf = malloc(bufLen);
|
|
if (buf == NULL) {
|
|
return NULL;
|
|
}
|
|
*buf = 0; // start the string out at zero length
|
|
|
|
// save a fake "hasThumbnail" tag to pass to the java ExifInterface
|
|
bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
|
|
ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
|
|
"false" : "true");
|
|
if (bufLen == 0) return NULL;
|
|
|
|
if (ImageInfo.CameraMake[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.CameraModel[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.DateTime[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.DigitizedTime[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "DateTimeDigitized", ImageInfo.DigitizedTime);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.SubSecTime[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "SubSecTime", ImageInfo.SubSecTime);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.SubSecTimeOrig[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeOriginal", ImageInfo.SubSecTimeOrig);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.SubSecTimeDig[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeDigitized", ImageInfo.SubSecTimeDig);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
|
|
if (bufLen == 0) return NULL;
|
|
|
|
bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
|
|
if (bufLen == 0) return NULL;
|
|
|
|
bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
|
|
if (bufLen == 0) return NULL;
|
|
|
|
if (ImageInfo.FlashUsed >= 0) {
|
|
bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
|
|
bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.DigitalZoomRatio > 1.0){
|
|
// Digital zoom used. Shame on you!
|
|
bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%5.3f");
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ExposureTime){
|
|
const char* format;
|
|
if (ImageInfo.ExposureTime < 0.010){
|
|
format = "%6.4f";
|
|
} else {
|
|
format = "%5.3f";
|
|
}
|
|
|
|
bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ApertureFNumber){
|
|
bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%5.3f");
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.Distance){
|
|
bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ISOequivalent){
|
|
bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ExposureBias){
|
|
// If exposure bias was specified, but set to zero, presumably its no bias at all,
|
|
// so only show it if its nonzero.
|
|
bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.Whitebalance >= 0) {
|
|
bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
|
|
if (bufLen == 0) return NULL;
|
|
|
|
|
|
if (ImageInfo.MeteringMode) {
|
|
bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ExposureProgram) {
|
|
bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.ExposureMode) {
|
|
bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
if (ImageInfo.GpsInfoPresent) {
|
|
if (ImageInfo.GpsLatRaw[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsLatRef[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsLongRaw[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsLongRef[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsAlt[0]) {
|
|
bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
|
|
bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsDateStamp[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsTimeStamp[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
if (ImageInfo.GpsProcessingMethod[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
}
|
|
|
|
if (ImageInfo.Comments[0]) {
|
|
bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
|
|
if (bufLen == 0) return NULL;
|
|
}
|
|
|
|
// put the attribute count at the beginnnig of the string
|
|
int finalBufLen = strlen(buf) + 20;
|
|
char* finalResult = malloc(finalBufLen);
|
|
if (finalResult == NULL) {
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
|
|
int k;
|
|
for (k = 0; k < finalBufLen; k++)
|
|
if (!isascii(finalResult[k]))
|
|
finalResult[k] = '?';
|
|
free(buf);
|
|
|
|
#ifdef SUPERDEBUG
|
|
ALOGE("*********Returning result \"%s\"", finalResult);
|
|
#endif
|
|
jstring result = ((*env)->NewStringUTF(env, finalResult));
|
|
free(finalResult);
|
|
DiscardData();
|
|
return result;
|
|
}
|
|
|
|
static const char *classPathName = "android/media/ExifInterface";
|
|
|
|
static JNINativeMethod methods[] = {
|
|
{"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
|
|
{"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
|
|
{"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
|
|
{"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
|
|
{"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
|
|
{"getThumbnailRangeNative", "(Ljava/lang/String;)[J", (void*)getThumbnailRange },
|
|
};
|
|
|
|
/*
|
|
* Register several native methods for one class.
|
|
*/
|
|
static int registerNativeMethods(JNIEnv* env, const char* className,
|
|
JNINativeMethod* gMethods, int numMethods)
|
|
{
|
|
jclass clazz;
|
|
|
|
clazz = (*env)->FindClass(env, className);
|
|
if (clazz == NULL) {
|
|
fprintf(stderr,
|
|
"Native registration unable to find class '%s'\n", className);
|
|
return JNI_FALSE;
|
|
}
|
|
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
|
|
fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
|
|
return JNI_FALSE;
|
|
}
|
|
|
|
return JNI_TRUE;
|
|
}
|
|
|
|
/*
|
|
* Register native methods for all classes we know about.
|
|
*/
|
|
static int registerNatives(JNIEnv* env)
|
|
{
|
|
return jniRegisterNativeMethods(env, classPathName,
|
|
methods, NELEM(methods));
|
|
}
|
|
|
|
/*
|
|
* Set some test stuff up.
|
|
*
|
|
* Returns the JNI version on success, -1 on failure.
|
|
*/
|
|
__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|
{
|
|
JNIEnv* env = NULL;
|
|
jint result = -1;
|
|
|
|
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
|
|
fprintf(stderr, "ERROR: GetEnv failed\n");
|
|
goto bail;
|
|
}
|
|
assert(env != NULL);
|
|
|
|
printf("In mgmain JNI_OnLoad\n");
|
|
|
|
if (registerNatives(env) < 0) {
|
|
fprintf(stderr, "ERROR: Exif native registration failed\n");
|
|
goto bail;
|
|
}
|
|
|
|
/* success -- return valid version number */
|
|
result = JNI_VERSION_1_4;
|
|
|
|
bail:
|
|
return result;
|
|
}
|