blob: dc0699a84644ae689e7bc7583fd9bbda6adc3ec1 [file] [log] [blame]
The Android Open Source Project34a25642009-03-03 19:30:03 -08001/*
2
3Copyright (c) 2008, The Android Open Source Project
4All rights reserved.
5
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions
8are met:
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above copyright
12 notice, this list of conditions and the following disclaimer in
repo syncdb940f22009-07-06 18:03:36 +080013 the documentation and/or other materials provided with the
The Android Open Source Project34a25642009-03-03 19:30:03 -080014 distribution.
15 * Neither the name of Google, Inc. nor the names of its contributors
16 may be used to endorse or promote products derived from this
17 software without specific prior written permission.
18
19THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
repo syncdb940f22009-07-06 18:03:36 +080026OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
The Android Open Source Project34a25642009-03-03 19:30:03 -080027AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30SUCH DAMAGE.
31
32*/
33
34#include <nativehelper/JNIHelp.h>
35#include <nativehelper/jni.h>
36
37#include <assert.h>
Dongwon Kang189fc1d2011-04-13 14:39:37 +090038#include <ctype.h>
The Android Open Source Project34a25642009-03-03 19:30:03 -080039#include <dlfcn.h>
40#include <stdio.h>
41#include <string.h>
42#include <sys/stat.h>
43#include <utils/Log.h>
44
45#include "jhead.h"
46
47#ifndef NELEM
48#define NELEM(x) ((int)(sizeof(x) / sizeof((x)[0])))
49#endif
50
51// Define the line below to turn on poor man's debugging output
52#undef SUPERDEBUG
53
54// Various tests
55#undef REALLOCTEST
56#undef OUTOFMEMORYTEST1
57
58static void addExifAttibute(JNIEnv *env, jmethodID putMethod, jobject hashMap, char* key, char* value) {
59 jstring jkey = (*env)->NewStringUTF(env, key);
60 jstring jvalue = (*env)->NewStringUTF(env, value);
61
62 jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, putMethod, jkey, jvalue);
63
64 (*env)->ReleaseStringUTFChars(env, jkey, key);
65 (*env)->ReleaseStringUTFChars(env, jvalue, value);
66}
67
68extern void ResetJpgfile();
69
70static int loadExifInfo(const char* FileName, int readJPG) {
71#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +000072 ALOGE("loadExifInfo");
The Android Open Source Project34a25642009-03-03 19:30:03 -080073#endif
74 int Modified = FALSE;
75 ReadMode_t ReadMode = READ_METADATA;
76 if (readJPG) {
77 // Must add READ_IMAGE else we can't write the JPG back out.
78 ReadMode |= READ_IMAGE;
79 }
80
81#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +000082 ALOGE("ResetJpgfile");
The Android Open Source Project34a25642009-03-03 19:30:03 -080083#endif
84 ResetJpgfile();
85
86 // Start with an empty image information structure.
87 memset(&ImageInfo, 0, sizeof(ImageInfo));
88 ImageInfo.FlashUsed = -1;
89 ImageInfo.MeteringMode = -1;
90 ImageInfo.Whitebalance = -1;
repo syncdb940f22009-07-06 18:03:36 +080091
The Android Open Source Project34a25642009-03-03 19:30:03 -080092 // Store file date/time.
93 {
94 struct stat st;
95 if (stat(FileName, &st) >= 0) {
96 ImageInfo.FileDateTime = st.st_mtime;
97 ImageInfo.FileSize = st.st_size;
98 }
99 }
100
101 strncpy(ImageInfo.FileName, FileName, PATH_MAX);
102#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000103 ALOGE("ReadJpegFile");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800104#endif
105 return ReadJpegFile(FileName, ReadMode);
106}
107
108static void saveJPGFile(const char* filename) {
109 char backupName[400];
110 struct stat buf;
111
112#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000113 ALOGE("Modified: %s\n", filename);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800114#endif
115
116 strncpy(backupName, filename, 395);
117 strcat(backupName, ".t");
118
119 // Remove any .old file name that may pre-exist
120#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000121 ALOGE("removing backup %s", backupName);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800122#endif
123 unlink(backupName);
124
125 // Rename the old file.
126#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000127 ALOGE("rename %s to %s", filename, backupName);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800128#endif
129 rename(filename, backupName);
130
131 // Write the new file.
132#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000133 ALOGE("WriteJpegFile %s", filename);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800134#endif
135 if (WriteJpegFile(filename)) {
repo syncdb940f22009-07-06 18:03:36 +0800136
The Android Open Source Project34a25642009-03-03 19:30:03 -0800137 // Copy the access rights from original file
138#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000139 ALOGE("stating old file %s", backupName);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800140#endif
141 if (stat(backupName, &buf) == 0){
142 // set Unix access rights and time to new file
143 struct utimbuf mtime;
144 chmod(filename, buf.st_mode);
repo syncdb940f22009-07-06 18:03:36 +0800145
The Android Open Source Project34a25642009-03-03 19:30:03 -0800146 mtime.actime = buf.st_mtime;
147 mtime.modtime = buf.st_mtime;
repo syncdb940f22009-07-06 18:03:36 +0800148
The Android Open Source Project34a25642009-03-03 19:30:03 -0800149 utime(filename, &mtime);
150 }
repo syncdb940f22009-07-06 18:03:36 +0800151
The Android Open Source Project34a25642009-03-03 19:30:03 -0800152 // Now that we are done, remove original file.
153#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000154 ALOGE("unlinking old file %s", backupName);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800155#endif
156 unlink(backupName);
157#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000158 ALOGE("returning from saveJPGFile");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800159#endif
160 } else {
161#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000162 ALOGE("WriteJpegFile failed, restoring from backup file");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800163#endif
164 // move back the backup file
165 rename(backupName, filename);
166 }
167}
168
169void copyThumbnailData(uchar* thumbnailData, int thumbnailLen) {
170#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000171 ALOGE("******************************** copyThumbnailData\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800172#endif
173 Section_t* ExifSection = FindSection(M_EXIF);
174 if (ExifSection == NULL) {
175 return;
176 }
177 int NewExifSize = ImageInfo.ThumbnailOffset+8+thumbnailLen;
178 ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
179 if (ExifSection->Data == NULL) {
180 return;
181 }
182 uchar* ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
183
184 memcpy(ThumbnailPointer, thumbnailData, thumbnailLen);
185
186 ImageInfo.ThumbnailSize = thumbnailLen;
187
188 Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, thumbnailLen);
189
190 ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
191 ExifSection->Data[1] = (uchar)NewExifSize;
192 ExifSection->Size = NewExifSize;
193}
194
195static void saveAttributes(JNIEnv *env, jobject jobj, jstring jfilename, jstring jattributes)
196{
197#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000198 ALOGE("******************************** saveAttributes\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800199#endif
200 // format of attributes string passed from java:
201 // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
202 // example input: "4 ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
203 ExifElement_t* exifElementTable = NULL;
204 const char* filename = NULL;
205 uchar* thumbnailData = NULL;
206 int attrCnt = 0;
207 const char* attributes = (*env)->GetStringUTFChars(env, jattributes, NULL);
208 if (attributes == NULL) {
209 goto exit;
210 }
211#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000212 ALOGE("attributes %s\n", attributes);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800213#endif
214
215 // Get the number of attributes - it's the first number in the string.
216 attrCnt = atoi(attributes);
217 char* attrPtr = strchr(attributes, ' ') + 1;
218#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000219 ALOGE("attribute count %d attrPtr %s\n", attrCnt, attrPtr);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800220#endif
221
222 // Load all the hash exif elements into a more c-like structure
223 exifElementTable = malloc(sizeof(ExifElement_t) * attrCnt);
224 if (exifElementTable == NULL) {
225 goto exit;
226 }
227#ifdef OUTOFMEMORYTEST1
228 goto exit;
229#endif
230
231 int i;
232 char tag[100];
Angus Kong482486a2012-01-19 15:56:10 +0800233 int hasDateTimeTag = FALSE;
The Android Open Source Project34a25642009-03-03 19:30:03 -0800234 int gpsTagCount = 0;
235 int exifTagCount = 0;
236
237 for (i = 0; i < attrCnt; i++) {
238 // get an element from the attribute string and add it to the c structure
239 // first, extract the attribute name
240 char* tagEnd = strchr(attrPtr, '=');
241 if (tagEnd == 0) {
242#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000243 ALOGE("saveAttributes: couldn't find end of tag");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800244#endif
245 goto exit;
246 }
247 if (tagEnd - attrPtr > 99) {
248#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000249 ALOGE("saveAttributes: attribute tag way too long");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800250#endif
251 goto exit;
252 }
253 memcpy(tag, attrPtr, tagEnd - attrPtr);
254 tag[tagEnd - attrPtr] = 0;
255
256 if (IsGpsTag(tag)) {
257 exifElementTable[i].GpsTag = TRUE;
258 exifElementTable[i].Tag = GpsTagNameToValue(tag);
259 ++gpsTagCount;
260 } else {
261 exifElementTable[i].GpsTag = FALSE;
262 exifElementTable[i].Tag = TagNameToValue(tag);
263 ++exifTagCount;
264 }
265 attrPtr = tagEnd + 1;
266
Angus Kong482486a2012-01-19 15:56:10 +0800267 if (IsDateTimeTag(exifElementTable[i].Tag)) {
268 hasDateTimeTag = TRUE;
269 }
270
The Android Open Source Project34a25642009-03-03 19:30:03 -0800271 // next get the length of the attribute value
272 int valueLen = atoi(attrPtr);
273 attrPtr = strchr(attrPtr, ' ') + 1;
274 if (attrPtr == 0) {
275#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000276 ALOGE("saveAttributes: couldn't find end of value len");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800277#endif
278 goto exit;
279 }
280 exifElementTable[i].Value = malloc(valueLen + 1);
281 if (exifElementTable[i].Value == NULL) {
282 goto exit;
283 }
284 memcpy(exifElementTable[i].Value, attrPtr, valueLen);
285 exifElementTable[i].Value[valueLen] = 0;
286 exifElementTable[i].DataLength = valueLen;
287
288 attrPtr += valueLen;
289
290#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000291 ALOGE("tag %s id %d value %s data length=%d isGps=%d", tag, exifElementTable[i].Tag,
The Android Open Source Project34a25642009-03-03 19:30:03 -0800292 exifElementTable[i].Value, exifElementTable[i].DataLength, exifElementTable[i].GpsTag);
293#endif
294 }
295
296 filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
297#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000298 ALOGE("Call loadAttributes() with filename is %s. Loading exif info\n", filename);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800299#endif
300 loadExifInfo(filename, TRUE);
301
302#ifdef SUPERDEBUG
303// DumpExifMap = TRUE;
304 ShowTags = TRUE;
305 ShowImageInfo(TRUE);
Steve Block7a314da2012-01-06 19:10:19 +0000306 ALOGE("create exif 2");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800307#endif
308
309 // If the jpg file has a thumbnail, preserve it.
310 int thumbnailLength = ImageInfo.ThumbnailSize;
311 if (ImageInfo.ThumbnailOffset) {
312 Section_t* ExifSection = FindSection(M_EXIF);
313 if (ExifSection) {
314 uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
315 thumbnailData = (uchar*)malloc(ImageInfo.ThumbnailSize);
316 // if the malloc fails, we just won't copy the thumbnail
317 if (thumbnailData) {
318 memcpy(thumbnailData, thumbnailPointer, thumbnailLength);
319 }
320 }
321 }
322
Angus Kong482486a2012-01-19 15:56:10 +0800323 create_EXIF(exifElementTable, exifTagCount, gpsTagCount, hasDateTimeTag);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800324
325 if (thumbnailData) {
326 copyThumbnailData(thumbnailData, thumbnailLength);
327 }
328
329exit:
330#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000331 ALOGE("cleaning up now in saveAttributes");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800332#endif
333 // try to clean up resources
334 if (attributes) {
335 (*env)->ReleaseStringUTFChars(env, jattributes, attributes);
336 }
337 if (filename) {
338 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
339 }
340 if (exifElementTable) {
341 // free the table
342 for (i = 0; i < attrCnt; i++) {
343 free(exifElementTable[i].Value);
344 }
345 free(exifElementTable);
346 }
347 if (thumbnailData) {
348 free(thumbnailData);
349 }
350#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000351 ALOGE("returning from saveAttributes");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800352#endif
353
The Android Open Source Project34a25642009-03-03 19:30:03 -0800354// Temporarily saving these commented out lines because they represent a lot of figuring out
355// patterns for JNI.
356// // Get link to Method "entrySet"
357// jmethodID entrySetMethod = (*env)->GetMethodID(env, jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
358//
359// // Invoke the "entrySet" method on the HashMap object
360// jobject jobject_of_entryset = (*env)->CallObjectMethod(env, hashMap, entrySetMethod);
361//
362// // Get the Set Class
363// jclass jclass_of_set = (*env)->FindClass(env, "java/util/Set");
364//
365// if (jclass_of_set == 0) {
366// printf("java/util/Set lookup failed\n");
367// return;
368// }
369//
370// // Get link to Method "iterator"
371// jmethodID iteratorMethod = (*env)->GetMethodID(env, jclass_of_set, "iterator", "()Ljava/util/Iterator;");
372//
373// // Invoke the "iterator" method on the jobject_of_entryset variable of type Set
374// jobject jobject_of_iterator = (*env)->CallObjectMethod(env, jobject_of_entryset, iteratorMethod);
375//
376// // Get the "Iterator" class
377// jclass jclass_of_iterator = (*env)->FindClass(env, "java/util/Iterator");
378//
379// // Get link to Method "hasNext"
380// jmethodID hasNextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "hasNext", "()Z");
381//
382// // Invoke - Get the value hasNextMethod
383// jboolean bHasNext = (*env)->CallBooleanMethod(env, jobject_of_iterator, hasNextMethod);
384
385// // Get link to Method "hasNext"
386// jmethodID nextMethod = (*env)->GetMethodID(env, jclass_of_iterator, "next", "()Ljava/util/Map/Entry;");
387//
388// jclass jclass_of_mapentry = (*env)->FindClass(env, "java/util/Map/Entry");
389//
390// jmethodID getKeyMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getKey", "()Ljava/lang/Object");
391//
392// jmethodID getValueMethod = (*env)->GetMethodID(env, jclass_of_mapentry, "getValue", "()Ljava/lang/Object");
393}
394
395static jboolean appendThumbnail(JNIEnv *env, jobject jobj, jstring jfilename, jstring jthumbnailfilename)
396{
397#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000398 ALOGE("******************************** appendThumbnail\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800399#endif
400
401 const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
402 if (filename == NULL) {
403 return JNI_FALSE;
404 }
405 const char* thumbnailfilename = (*env)->GetStringUTFChars(env, jthumbnailfilename, NULL);
406 if (thumbnailfilename == NULL) {
407 return JNI_FALSE;
408 }
409 #ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000410 ALOGE("*******before actual call to ReplaceThumbnail\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800411 ShowImageInfo(TRUE);
412 #endif
413 ReplaceThumbnail(thumbnailfilename);
414 #ifdef SUPERDEBUG
415 ShowImageInfo(TRUE);
416 #endif
417 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
418 (*env)->ReleaseStringUTFChars(env, jthumbnailfilename, thumbnailfilename);
repo syncdb940f22009-07-06 18:03:36 +0800419
The Android Open Source Project34a25642009-03-03 19:30:03 -0800420 DiscardData();
421 return JNI_TRUE;
422}
423
424static void commitChanges(JNIEnv *env, jobject jobj, jstring jfilename)
425{
426#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000427 ALOGE("******************************** commitChanges\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800428#endif
429 const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
430 if (filename) {
431 saveJPGFile(filename);
Chih-Chung Chang6a3cf8f2009-09-21 10:20:27 +0800432 DiscardData();
The Android Open Source Project34a25642009-03-03 19:30:03 -0800433 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
434 }
435}
436
437static jbyteArray getThumbnail(JNIEnv *env, jobject jobj, jstring jfilename)
438{
439#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000440 ALOGE("******************************** getThumbnail\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800441#endif
442
443 const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
444 if (filename) {
445 loadExifInfo(filename, FALSE);
446 Section_t* ExifSection = FindSection(M_EXIF);
447 if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
448#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000449 ALOGE("no exif section or size == 0, so no thumbnail\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800450#endif
451 goto noThumbnail;
452 }
453 uchar* thumbnailPointer = ExifSection->Data + ImageInfo.ThumbnailOffset + 8;
454
455 jbyteArray byteArray = (*env)->NewByteArray(env, ImageInfo.ThumbnailSize);
456 if (byteArray == NULL) {
457#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000458 ALOGE("couldn't allocate thumbnail memory, so no thumbnail\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800459#endif
460 goto noThumbnail;
461 }
462 (*env)->SetByteArrayRegion(env, byteArray, 0, ImageInfo.ThumbnailSize, thumbnailPointer);
463#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000464 ALOGE("thumbnail size %d\n", ImageInfo.ThumbnailSize);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800465#endif
466 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
Jiangping Wueb7bd102010-08-27 10:56:55 +0200467 DiscardData();
The Android Open Source Project34a25642009-03-03 19:30:03 -0800468 return byteArray;
469 }
470noThumbnail:
471 if (filename) {
472 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
473 }
474 DiscardData();
475 return NULL;
476}
477
Jeff Sharkey31b17e62013-08-21 11:27:36 -0700478static jlongArray getThumbnailRange(JNIEnv *env, jobject jobj, jstring jfilename) {
479 jlongArray resultArray = NULL;
480 const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
481 if (filename) {
482 loadExifInfo(filename, FALSE);
483 Section_t* ExifSection = FindSection(M_EXIF);
484 if (ExifSection == NULL || ImageInfo.ThumbnailSize == 0) {
485 goto done;
486 }
487
488 jlong result[2];
489 result[0] = ExifSection->Offset + ImageInfo.ThumbnailOffset + 8;
490 result[1] = ImageInfo.ThumbnailSize;
491
492 resultArray = (*env)->NewLongArray(env, 2);
493 if (resultArray == NULL) {
494 goto done;
495 }
496
497 (*env)->SetLongArrayRegion(env, resultArray, 0, 2, result);
498 }
499done:
500 if (filename) {
501 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
502 }
503 DiscardData();
504 return resultArray;
505}
506
The Android Open Source Project34a25642009-03-03 19:30:03 -0800507static int attributeCount; // keep track of how many attributes we've added
508
509// returns new buffer length
510static int addKeyValueString(char** buf, int bufLen, const char* key, const char* value) {
511 // Appends to buf like this: "ImageLength=4 1024"
512
513 char valueLen[15];
514 snprintf(valueLen, 15, "=%d ", (int)strlen(value));
515
516 // check to see if buf has enough room to append
517 int len = strlen(key) + strlen(valueLen) + strlen(value);
518 int newLen = strlen(*buf) + len;
519 if (newLen >= bufLen) {
520#ifdef REALLOCTEST
521 bufLen = newLen + 5;
Steve Block7a314da2012-01-06 19:10:19 +0000522 ALOGE("reallocing to %d", bufLen);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800523#else
524 bufLen = newLen + 500;
525#endif
526 *buf = realloc(*buf, bufLen);
527 if (*buf == NULL) {
528 return 0;
529 }
530 }
531 // append the new attribute and value
532 snprintf(*buf + strlen(*buf), bufLen, "%s%s%s", key, valueLen, value);
533#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000534 ALOGE("buf %s", *buf);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800535#endif
536 ++attributeCount;
537 return bufLen;
538}
539
540// returns new buffer length
541static int addKeyValueInt(char** buf, int bufLen, const char* key, int value) {
542 char valueStr[20];
543 snprintf(valueStr, 20, "%d", value);
544
545 return addKeyValueString(buf, bufLen, key, valueStr);
546}
547
548// returns new buffer length
549static int addKeyValueDouble(char** buf, int bufLen, const char* key, double value, const char* format) {
550 char valueStr[30];
551 snprintf(valueStr, 30, format, value);
552
553 return addKeyValueString(buf, bufLen, key, valueStr);
554}
555
Wu-cheng Li574d52d2010-01-31 22:34:33 +0800556// Returns new buffer length. Rational value will be appended as "numerator/denominator".
557static int addKeyValueRational(char** buf, int bufLen, const char* key, rat_t value) {
558 char valueStr[25];
559 snprintf(valueStr, sizeof(valueStr), "%u/%u", value.num, value.denom);
560 return addKeyValueString(buf, bufLen, key, valueStr);
561}
562
The Android Open Source Project34a25642009-03-03 19:30:03 -0800563static jstring getAttributes(JNIEnv *env, jobject jobj, jstring jfilename)
564{
565#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000566 ALOGE("******************************** getAttributes\n");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800567#endif
568 const char* filename = (*env)->GetStringUTFChars(env, jfilename, NULL);
569 loadExifInfo(filename, FALSE);
570#ifdef SUPERDEBUG
571 ShowImageInfo(TRUE);
572#endif
573 (*env)->ReleaseStringUTFChars(env, jfilename, filename);
574
575 attributeCount = 0;
576#ifdef REALLOCTEST
577 int bufLen = 5;
578#else
579 int bufLen = 1000;
580#endif
581 char* buf = malloc(bufLen);
582 if (buf == NULL) {
583 return NULL;
584 }
585 *buf = 0; // start the string out at zero length
586
587 // save a fake "hasThumbnail" tag to pass to the java ExifInterface
588 bufLen = addKeyValueString(&buf, bufLen, "hasThumbnail",
589 ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE || ImageInfo.ThumbnailSize == 0 ?
590 "false" : "true");
591 if (bufLen == 0) return NULL;
592
593 if (ImageInfo.CameraMake[0]) {
594 bufLen = addKeyValueString(&buf, bufLen, "Make", ImageInfo.CameraMake);
595 if (bufLen == 0) return NULL;
596 }
597 if (ImageInfo.CameraModel[0]) {
598 bufLen = addKeyValueString(&buf, bufLen, "Model", ImageInfo.CameraModel);
599 if (bufLen == 0) return NULL;
600 }
601 if (ImageInfo.DateTime[0]) {
602 bufLen = addKeyValueString(&buf, bufLen, "DateTime", ImageInfo.DateTime);
603 if (bufLen == 0) return NULL;
604 }
Zhijun He76aba452013-11-22 18:09:28 -0800605 if (ImageInfo.DigitizedTime[0]) {
606 bufLen = addKeyValueString(&buf, bufLen, "DateTimeDigitized", ImageInfo.DigitizedTime);
607 if (bufLen == 0) return NULL;
608 }
609 if (ImageInfo.SubSecTime[0]) {
610 bufLen = addKeyValueString(&buf, bufLen, "SubSecTime", ImageInfo.SubSecTime);
611 if (bufLen == 0) return NULL;
612 }
613 if (ImageInfo.SubSecTimeOrig[0]) {
614 bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeOriginal", ImageInfo.SubSecTimeOrig);
615 if (bufLen == 0) return NULL;
616 }
617 if (ImageInfo.SubSecTimeDig[0]) {
618 bufLen = addKeyValueString(&buf, bufLen, "SubSecTimeDigitized", ImageInfo.SubSecTimeDig);
619 if (bufLen == 0) return NULL;
620 }
621
The Android Open Source Project34a25642009-03-03 19:30:03 -0800622 bufLen = addKeyValueInt(&buf, bufLen, "ImageWidth", ImageInfo.Width);
623 if (bufLen == 0) return NULL;
624
625 bufLen = addKeyValueInt(&buf, bufLen, "ImageLength", ImageInfo.Height);
626 if (bufLen == 0) return NULL;
627
628 bufLen = addKeyValueInt(&buf, bufLen, "Orientation", ImageInfo.Orientation);
629 if (bufLen == 0) return NULL;
630
Ray Chen2856a272010-12-07 18:19:28 +0800631 if (ImageInfo.FlashUsed >= 0) {
632 bufLen = addKeyValueInt(&buf, bufLen, "Flash", ImageInfo.FlashUsed);
633 if (bufLen == 0) return NULL;
634 }
The Android Open Source Project34a25642009-03-03 19:30:03 -0800635
Wu-cheng Li574d52d2010-01-31 22:34:33 +0800636 if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
637 bufLen = addKeyValueRational(&buf, bufLen, "FocalLength", ImageInfo.FocalLength);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800638 if (bufLen == 0) return NULL;
639 }
640
641 if (ImageInfo.DigitalZoomRatio > 1.0){
642 // Digital zoom used. Shame on you!
643 bufLen = addKeyValueDouble(&buf, bufLen, "DigitalZoomRatio", ImageInfo.DigitalZoomRatio, "%1.3f");
644 if (bufLen == 0) return NULL;
645 }
646
647 if (ImageInfo.ExposureTime){
648 const char* format;
649 if (ImageInfo.ExposureTime < 0.010){
650 format = "%6.4f";
651 } else {
652 format = "%5.3f";
653 }
654
655 bufLen = addKeyValueDouble(&buf, bufLen, "ExposureTime", (double)ImageInfo.ExposureTime, format);
656 if (bufLen == 0) return NULL;
657 }
658
659 if (ImageInfo.ApertureFNumber){
Yin-Chia Yeh5c8f9372015-06-07 12:58:20 -0700660 bufLen = addKeyValueDouble(&buf, bufLen, "FNumber", (double)ImageInfo.ApertureFNumber, "%3.3f");
The Android Open Source Project34a25642009-03-03 19:30:03 -0800661 if (bufLen == 0) return NULL;
662 }
663
664 if (ImageInfo.Distance){
665 bufLen = addKeyValueDouble(&buf, bufLen, "SubjectDistance", (double)ImageInfo.Distance, "%4.2f");
666 if (bufLen == 0) return NULL;
667 }
668
669 if (ImageInfo.ISOequivalent){
670 bufLen = addKeyValueInt(&buf, bufLen, "ISOSpeedRatings", ImageInfo.ISOequivalent);
671 if (bufLen == 0) return NULL;
672 }
673
674 if (ImageInfo.ExposureBias){
675 // If exposure bias was specified, but set to zero, presumably its no bias at all,
676 // so only show it if its nonzero.
677 bufLen = addKeyValueDouble(&buf, bufLen, "ExposureBiasValue", (double)ImageInfo.ExposureBias, "%4.2f");
678 if (bufLen == 0) return NULL;
679 }
680
Ray Chen2856a272010-12-07 18:19:28 +0800681 if (ImageInfo.Whitebalance >= 0) {
682 bufLen = addKeyValueInt(&buf, bufLen, "WhiteBalance", ImageInfo.Whitebalance);
683 if (bufLen == 0) return NULL;
684 }
The Android Open Source Project34a25642009-03-03 19:30:03 -0800685
686 bufLen = addKeyValueInt(&buf, bufLen, "LightSource", ImageInfo.LightSource);
687 if (bufLen == 0) return NULL;
688
689
690 if (ImageInfo.MeteringMode) {
691 bufLen = addKeyValueInt(&buf, bufLen, "MeteringMode", ImageInfo.MeteringMode);
692 if (bufLen == 0) return NULL;
693 }
694
695 if (ImageInfo.ExposureProgram) {
696 bufLen = addKeyValueInt(&buf, bufLen, "ExposureProgram", ImageInfo.ExposureProgram);
697 if (bufLen == 0) return NULL;
698 }
699
700 if (ImageInfo.ExposureMode) {
701 bufLen = addKeyValueInt(&buf, bufLen, "ExposureMode", ImageInfo.ExposureMode);
702 if (bufLen == 0) return NULL;
703 }
704
705 if (ImageInfo.GpsInfoPresent) {
Ray Chen8d617232010-03-10 14:59:44 -0800706 if (ImageInfo.GpsLatRaw[0]) {
707 bufLen = addKeyValueString(&buf, bufLen, "GPSLatitude", ImageInfo.GpsLatRaw);
708 if (bufLen == 0) return NULL;
709 }
710 if (ImageInfo.GpsLatRef[0]) {
711 bufLen = addKeyValueString(&buf, bufLen, "GPSLatitudeRef", ImageInfo.GpsLatRef);
712 if (bufLen == 0) return NULL;
713 }
714 if (ImageInfo.GpsLongRaw[0]) {
715 bufLen = addKeyValueString(&buf, bufLen, "GPSLongitude", ImageInfo.GpsLongRaw);
716 if (bufLen == 0) return NULL;
717 }
718 if (ImageInfo.GpsLongRef[0]) {
719 bufLen = addKeyValueString(&buf, bufLen, "GPSLongitudeRef", ImageInfo.GpsLongRef);
720 if (bufLen == 0) return NULL;
721 }
The Android Open Source Project34a25642009-03-03 19:30:03 -0800722 if (ImageInfo.GpsAlt[0]) {
Wu-cheng Li1ed81972010-05-21 12:02:32 +0800723 bufLen = addKeyValueRational(&buf, bufLen, "GPSAltitude", ImageInfo.GpsAltRaw);
724 bufLen = addKeyValueInt(&buf, bufLen, "GPSAltitudeRef", ImageInfo.GpsAltRef);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800725 if (bufLen == 0) return NULL;
726 }
Ray Chena39920c2010-01-15 13:58:10 -0800727 if (ImageInfo.GpsDateStamp[0]) {
728 bufLen = addKeyValueString(&buf, bufLen, "GPSDateStamp", ImageInfo.GpsDateStamp);
729 if (bufLen == 0) return NULL;
730 }
731 if (ImageInfo.GpsTimeStamp[0]) {
732 bufLen = addKeyValueString(&buf, bufLen, "GPSTimeStamp", ImageInfo.GpsTimeStamp);
733 if (bufLen == 0) return NULL;
734 }
Ray Chen434623a2010-03-10 14:59:44 -0800735 if (ImageInfo.GpsProcessingMethod[0]) {
736 bufLen = addKeyValueString(&buf, bufLen, "GPSProcessingMethod", ImageInfo.GpsProcessingMethod);
737 if (bufLen == 0) return NULL;
738 }
The Android Open Source Project34a25642009-03-03 19:30:03 -0800739 }
740
741 if (ImageInfo.Comments[0]) {
742 bufLen = addKeyValueString(&buf, bufLen, "UserComment", ImageInfo.Comments);
743 if (bufLen == 0) return NULL;
744 }
745
746 // put the attribute count at the beginnnig of the string
747 int finalBufLen = strlen(buf) + 20;
748 char* finalResult = malloc(finalBufLen);
749 if (finalResult == NULL) {
750 free(buf);
751 return NULL;
752 }
753 snprintf(finalResult, finalBufLen, "%d %s", attributeCount, buf);
754 int k;
755 for (k = 0; k < finalBufLen; k++)
Dongwon Kang189fc1d2011-04-13 14:39:37 +0900756 if (!isascii(finalResult[k]))
The Android Open Source Project34a25642009-03-03 19:30:03 -0800757 finalResult[k] = '?';
758 free(buf);
759
760#ifdef SUPERDEBUG
Steve Block7a314da2012-01-06 19:10:19 +0000761 ALOGE("*********Returning result \"%s\"", finalResult);
The Android Open Source Project34a25642009-03-03 19:30:03 -0800762#endif
763 jstring result = ((*env)->NewStringUTF(env, finalResult));
764 free(finalResult);
765 DiscardData();
766 return result;
767}
768
repo syncdb940f22009-07-06 18:03:36 +0800769static const char *classPathName = "android/media/ExifInterface";
The Android Open Source Project34a25642009-03-03 19:30:03 -0800770
771static JNINativeMethod methods[] = {
772 {"saveAttributesNative", "(Ljava/lang/String;Ljava/lang/String;)V", (void*)saveAttributes },
773 {"getAttributesNative", "(Ljava/lang/String;)Ljava/lang/String;", (void*)getAttributes },
774 {"appendThumbnailNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)appendThumbnail },
775 {"commitChangesNative", "(Ljava/lang/String;)V", (void*)commitChanges },
776 {"getThumbnailNative", "(Ljava/lang/String;)[B", (void*)getThumbnail },
Jeff Sharkey31b17e62013-08-21 11:27:36 -0700777 {"getThumbnailRangeNative", "(Ljava/lang/String;)[J", (void*)getThumbnailRange },
The Android Open Source Project34a25642009-03-03 19:30:03 -0800778};
779
780/*
781 * Register several native methods for one class.
782 */
783static int registerNativeMethods(JNIEnv* env, const char* className,
784 JNINativeMethod* gMethods, int numMethods)
785{
786 jclass clazz;
787
788 clazz = (*env)->FindClass(env, className);
789 if (clazz == NULL) {
790 fprintf(stderr,
791 "Native registration unable to find class '%s'\n", className);
792 return JNI_FALSE;
793 }
794 if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
795 fprintf(stderr, "RegisterNatives failed for '%s'\n", className);
796 return JNI_FALSE;
797 }
798
799 return JNI_TRUE;
800}
801
802/*
803 * Register native methods for all classes we know about.
804 */
805static int registerNatives(JNIEnv* env)
806{
807 return jniRegisterNativeMethods(env, classPathName,
808 methods, NELEM(methods));
809}
810
811/*
812 * Set some test stuff up.
813 *
814 * Returns the JNI version on success, -1 on failure.
815 */
816__attribute__ ((visibility("default"))) jint JNI_OnLoad(JavaVM* vm, void* reserved)
817{
818 JNIEnv* env = NULL;
819 jint result = -1;
820
821 if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
822 fprintf(stderr, "ERROR: GetEnv failed\n");
823 goto bail;
824 }
825 assert(env != NULL);
826
827 printf("In mgmain JNI_OnLoad\n");
828
829 if (registerNatives(env) < 0) {
830 fprintf(stderr, "ERROR: Exif native registration failed\n");
831 goto bail;
832 }
833
834 /* success -- return valid version number */
835 result = JNI_VERSION_1_4;
836
837bail:
838 return result;
839}