summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit426377d9ce95606bac741d99b5b0a3e2038924fe (patch)
treee369cfdda7dfd42203aee8100cad25e2033f1cc8 /main.c
downloadjhead-426377d9ce95606bac741d99b5b0a3e2038924fe.tar.gz
Diffstat (limited to 'main.c')
-rw-r--r--main.c759
1 files changed, 759 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..12ee58e
--- /dev/null
+++ b/main.c
@@ -0,0 +1,759 @@
+/*
+
+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 <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
+ LOGE("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
+ LOGE("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
+ LOGE("ReadJpegFile");
+#endif
+ return ReadJpegFile(FileName, ReadMode);
+}
+
+static void saveJPGFile(const char* filename) {
+ char backupName[400];
+ struct stat buf;
+
+#ifdef SUPERDEBUG
+ LOGE("Modified: %s\n", filename);
+#endif
+
+ strncpy(backupName, filename, 395);
+ strcat(backupName, ".t");
+
+ // Remove any .old file name that may pre-exist
+#ifdef SUPERDEBUG
+ LOGE("removing backup %s", backupName);
+#endif
+ unlink(backupName);
+
+ // Rename the old file.
+#ifdef SUPERDEBUG
+ LOGE("rename %s to %s", filename, backupName);
+#endif
+ rename(filename, backupName);
+
+ // Write the new file.
+#ifdef SUPERDEBUG
+ LOGE("WriteJpegFile %s", filename);
+#endif
+ if (WriteJpegFile(filename)) {
+
+ // Copy the access rights from original file
+#ifdef SUPERDEBUG
+ LOGE("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
+ LOGE("unlinking old file %s", backupName);
+#endif
+ unlink(backupName);
+#ifdef SUPERDEBUG
+ LOGE("returning from saveJPGFile");
+#endif
+ } else {
+#ifdef SUPERDEBUG
+ LOGE("WriteJpegFile failed, restoring from backup file");
+#endif
+ // move back the backup file
+ rename(backupName, filename);
+ }
+}
+
+void copyThumbnailData(uchar* thumbnailData, int thumbnailLen) {
+#ifdef SUPERDEBUG
+ LOGE("******************************** 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
+ LOGE("******************************** 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
+ LOGE("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
+ LOGE("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 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
+ LOGE("saveAttributes: couldn't find end of tag");
+#endif
+ goto exit;
+ }
+ if (tagEnd - attrPtr > 99) {
+#ifdef SUPERDEBUG
+ LOGE("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;
+
+ // next get the length of the attribute value
+ int valueLen = atoi(attrPtr);
+ attrPtr = strchr(attrPtr, ' ') + 1;
+ if (attrPtr == 0) {
+#ifdef SUPERDEBUG
+ LOGE("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
+ LOGE("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
+ LOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
+#endif
+ loadExifInfo(filename, TRUE);
+
+#ifdef SUPERDEBUG
+// DumpExifMap = TRUE;
+ ShowTags = TRUE;
+ ShowImageInfo(TRUE);
+ LOGE("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);
+
+ if (thumbnailData) {
+ copyThumbnailData(thumbnailData, thumbnailLength);
+ }
+
+exit:
+#ifdef SUPERDEBUG
+ LOGE("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
+ LOGE("returning from saveAttributes");
+#endif
+
+ DiscardData();
+
+// 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
+ LOGE("******************************** 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
+ LOGE("*******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
+ LOGE("******************************** commitChanges\n");
+#endif
+ const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
+ if (filename) {
+ saveJPGFile(filename);
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ }
+}
+
+static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** 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
+ LOGE("no exif section or size == 0, so no thumbnail\n");
+#endif
+ goto noThumbnail;
+ }
+ uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
+
+ jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
+ if (byteArray == NULL) {
+#ifdef SUPERDEBUG
+ LOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
+#endif
+ goto noThumbnail;
+ }
+ jboolean isCopy;
+ jbyte* thumbnailDataPtr = (*env)->GetByteArrayElements(env, byteArray, &isCopy);
+ memcpy(thumbnailDataPtr, thumbnailPointer, ImageInfo.ThumbnailSize);
+#ifdef SUPERDEBUG
+ LOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
+#endif
+ if (isCopy == JNI_TRUE) {
+ (*env)->ReleaseByteArrayElements(env, byteArray, thumbnailDataPtr, 0);
+ }
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ return byteArray;
+ }
+noThumbnail:
+ if (filename) {
+ (*env)->ReleaseStringUTFChars(env, jfilename, filename);
+ }
+ DiscardData();
+ return NULL;
+}
+
+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;
+ LOGE("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
+ LOGE("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);
+}
+
+static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
+{
+#ifdef SUPERDEBUG
+ LOGE("******************************** 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;
+ }
+ 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;
+
+ bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
+ if (bufLen == 0) return NULL;
+
+ if (ImageInfo.FocalLength){
+ bufLen = addKeyValueInt(&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, "%1.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, "%3.1f");
+ 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;
+ }
+
+ 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) {
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
+ if (bufLen == 0) return NULL;
+ bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
+ if (bufLen == 0) return NULL;
+ if (ImageInfo.GpsAlt[0]) {
+ bufLen = addKeyValueString(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAlt);
+ 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 (finalResult[k] > 127)
+ finalResult[k] = '?';
+ free(buf);
+
+#ifdef SUPERDEBUG
+ LOGE("*********Returning result \"%s\"", finalResult);
+#endif
+ jstring result = ((*env)->NewStringUTF(env, finalResult));
+ free(finalResult);
+ DiscardData();
+ return result;
+}
+
+static const char *classPathName = "com/android/camera/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 },
+};
+
+/*
+ * 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;
+}