| /* |
| |
| 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; |
| } |