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;
}
示例#2
0
static void
print_exif_info(struct jpeg_marker_struct const marker) {
/*----------------------------------------------------------------------------
   Dump as informational messages the contents of the Jpeg miscellaneous
   marker 'marker', assuming it is an Exif header.
-----------------------------------------------------------------------------*/
    ImageInfo_t imageInfo;
    const char * error;

    assert(marker.data_length >= 6);

    process_EXIF(marker.data+6, marker.data_length-6, 
                 &imageInfo, FALSE, &error);

    if (error) {
        pm_message("EXIF header is invalid.  %s", error);
        strfree(error);
    } else
        ShowImageInfo(&imageInfo);
}
示例#3
0
void show_exif_window(GtkWidget* widget, ExifWin * win)
{	
	GError* error = NULL;
	
	const char* current_image = image_list_get_current_file_path(win->mw->img_list);
		
	win->exif_window = (GtkWindow*)gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_resizable (win->exif_window, TRUE);
	gtk_window_set_default_size (win->exif_window, 400, 400);
    gtk_window_set_position(win->exif_window,GTK_WIN_POS_CENTER);
    gtk_window_set_title(win->exif_window, "Exif information");
	
	win->box =  (GtkVBox*)gtk_vbox_new (FALSE,0);
	win->hbox = (GtkHBox*)gtk_hbox_new (FALSE,0);
	
	win->exif_label = (GtkLabel*)gtk_label_new("Exif data");
	gtk_label_set_justify(GTK_LABEL(win->exif_label), GTK_JUSTIFY_CENTER);
    gtk_box_pack_start(GTK_BOX(win->box), (GtkWidget*)win->exif_label, FALSE, FALSE, 5);
	
	win->exif_button = (GtkButton*)gtk_button_new_with_label("Close");

	win->list = gtk_tree_view_new();
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(win->list), FALSE);
	
	win->align = gtk_alignment_new( 1,0 ,0,0);
    gtk_container_add( (GtkContainer*)win->align, (GtkWidget*)win->exif_button);
	
	win->scroll = (GtkScrolledWindow*)gtk_scrolled_window_new(NULL,NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(win->scroll),
                				   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_scrolled_window_add_with_viewport(win->scroll, win->list);
	
    gtk_box_pack_start(GTK_BOX(win->box), GTK_WIDGET(win->scroll), TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(win->box), GTK_WIDGET(gtk_hseparator_new()), FALSE, TRUE,0);
	gtk_box_pack_start(GTK_BOX(win->box), GTK_WIDGET(win->hbox), FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(win->hbox),GTK_WIDGET(win->align), TRUE, TRUE,  0);
	
	int Modified = FALSE;
    ReadMode_t ReadMode;
    ReadMode = READ_METADATA;
    CurrentFile = current_image;
    FilesMatched = 1; 

    ResetJpgfile();

    memset(&ImageInfo, 0, sizeof(ImageInfo));
    ImageInfo.FlashUsed = -1;
    ImageInfo.MeteringMode = -1;
    ImageInfo.Whitebalance = -1;
	
	    {
        struct stat st;
        if (stat(current_image, &st) >= 0){
            ImageInfo.FileDateTime = st.st_mtime;
            ImageInfo.FileSize = st.st_size;
        }else{
            printf("No such file");
        }
    }
	
	if (current_image == NULL)
		return;
	
	strncpy(ImageInfo.FileName, current_image, PATH_MAX);  
		
    if (!ReadJpegFile(current_image, READ_METADATA)) return;

	DoModify = TRUE;
	Modified = TRUE;
    ReadMode = READ_IMAGE;

    if (!ReadJpegFile(current_image, ReadMode)) return;

	create_EXIF();
    CheckFileSkip();
	ShowTags = TRUE;
	ShowConciseImageInfo();
    ShowImageInfo(TRUE);

	
	char  buf[35];
	char  buf1[35];
	char  buf2[35];
	char  buf3[35];
	char  buf4[35];
	char  buf5[35];
	char  buf6[35];
	char  buf7[35];
	char  buf8[35];
	char  buf9[35];
	char buf10[35];
	
	char* size = itoa(ImageInfo.FileSize,buf);
	
	char* width = itoa(ImageInfo.Width, buf1);
	char* height = itoa(ImageInfo.Height, buf2);
	
	char Temp[20];
    FileTimeAsString(Temp);
	
	const char* largest_offset = itoa(ImageInfo.LargestExifOffset, buf3);
	const char* thumbnail_size    = itoa(ImageInfo.ThumbnailSize, buf4);
	const char* ThumbnailOffset   = itoa(ImageInfo.ThumbnailOffset, buf5);
	const char* image_xdensity    = itoa(ImageInfo.JfifHeader.XDensity, buf6);
	const char* image_ydensity    = itoa(ImageInfo.JfifHeader.XDensity, buf7);
	const char* zoom              = itoa(ImageInfo.DigitalZoomRatio, buf8);
	const char* iso               = itoa(ImageInfo.ISOequivalent, buf9);
	const char* distance          = itoa(ImageInfo.DistanceRange, buf10);
	
    float value = ImageInfo.ExposureTime; 
	char *str = (char*)malloc(20);
	sprintf(str, "%f\n", value);
	
	float value2 = ImageInfo.ApertureFNumber;
	char *str2   = (char*)malloc(20);
	sprintf(str2, "f/%3.1f" , value2); 
	
	float value3 = ImageInfo.FocalLength35mmEquiv;
	char *str3   = (char*)malloc(20);
	sprintf(str3, "f(35)=%dmm" , value3); 
	
	init_list(win->list);

	add_to_list(win->list, "File name:   ", ImageInfo.FileName);
	add_to_list(win->list, "File size (bytes)   :", size);
	add_to_list(win->list, "File date:   ", Temp);
	
	if (ImageInfo.CameraMake[0]){
	    add_to_list(win->list, "Camera make:   ", ImageInfo.CameraMake);
	    add_to_list(win->list, "Camer model:   ", ImageInfo.CameraModel);
    }

	add_to_list(win->list, "Date time:   ", ctime(&ImageInfo.FileDateTime));
	
	if (ImageInfo.IsColor == 0)
	{
        add_to_list(win->list, "Color/bw:   ", "Black and White");
    }
	else
	{
	    add_to_list(win->list, "Color/bw:   ", "Non Black and White");
	}
	
	add_to_list(win->list, "Image type   :", "image/jpg");
	
	add_to_list(win->list, "Image Width:   ", width);
	add_to_list(win->list, "Image Height:   ",height);
	
    add_to_list(win->list, "Largest Exif Offset:   ", largest_offset);
    
	add_to_list(win->list, "Thumbnail size:   ", thumbnail_size);
	add_to_list(win->list, "Thumbnail offset:   ", ThumbnailOffset);
	
	add_to_list(win->list, "Image comments:   ", ImageInfo.Comments);
	
	add_to_list(win->list, "Image X Density:   ",image_xdensity);
	add_to_list(win->list, "Image Y Density:   ", image_xdensity);
	
	add_to_list(win->list, "Digital zoom ratio:   ", zoom);
	
	if (ImageInfo.FlashUsed > 0)
		add_to_list(win->list, "Flash used:   ", "yes");
	else
		add_to_list(win->list, "Flash used:   ", "No");
	
	add_to_list(win->list, "Exposure Time (s):   ", str);
	add_to_list(win->list, "Aperture FNumber:   ", str2);
	add_to_list(win->list, "Focal Length 35mm Equiv:   ", str3);
	add_to_list(win->list, "ISOequivalent:   ", iso);
	add_to_list(win->list, "Distance range:   ", distance);
	
	switch(ImageInfo.ExposureProgram)
	{
	    case 1:
            add_to_list(win->list, "Expusure:   ", "manual");
            break;
        case 2:
		    add_to_list(win->list, "Expusure:   ", "program (auto)");
            break;
        case 3:
			add_to_list(win->list, "Expusure:   ", "aperture priority (semi-auto)");
            break;
        case 4:
		    add_to_list(win->list, "Expusure:   ", "shutter priority (semi-auto)");
            break;
        case 5:
	    	add_to_list(win->list, "Expusure:   ", "Creative Program (based towards depth of field)");
            break;
        case 6:
		    add_to_list(win->list, "Expusure:   ", "Action program (based towards fast shutter speed)");
            break;
        case 7:
			add_to_list(win->list, "Expusure:   ", "Portrait Mode");
            break;
        case 8:
		    add_to_list(win->list, "Expusure:   ", "LandscapeMode");
            break;
        default:
            break;
	}
	
	if (ImageInfo.MeteringMode > 0)\
	   {
             switch(ImageInfo.MeteringMode) 
		     {
                case 1:
				     add_to_list(win->list, "Metering Mode:   ", "average");
					 break;
                case 2: 
				     add_to_list(win->list, "Metering Mode:   ", "center weight");
					 break;
                case 3:
				     add_to_list(win->list, "Metering Mode:   ", "spot");
					 break;
                case 4:
				     add_to_list(win->list, "Metering Mode:   ", "multi spot");
					 break;
                case 5: 
				     add_to_list(win->list, "Metering Mode:   ", "pattern");
					 break;
                case 6: 
					 printf("partial\n"); 
					 break;
                case 255:
					 printf("other\n");
					 break;
                default: 
					 printf("unknown (%d)\n",ImageInfo.MeteringMode); 
					 break;
             }
	    }
	
	g_signal_connect( win->exif_button, "clicked", G_CALLBACK(on_close), win );
	
	
	gtk_container_add(GTK_CONTAINER(win->exif_window), GTK_WIDGET(win->box));
	gtk_widget_show_all((GtkWidget*)win->exif_window);
	
	g_free(str);
	g_free(str2);
	g_free(str3);
}
示例#4
0
文件: jhead.c 项目: AwelEshetu/cwm
//--------------------------------------------------------------------------
// Do selected operations to one file at a time.
//--------------------------------------------------------------------------
void ProcessFile(const char * FileName)
{
    int Modified = FALSE;
    ReadMode_t ReadMode = READ_EXIF;
    CurrentFile = FileName;
    ResetJpgfile();

    // Start with an empty image information structure.
    memset(&ImageInfo, 0, sizeof(ImageInfo));
    ImageInfo.FlashUsed = -1;
    ImageInfo.MeteringMode = -1;

    // Store file date/time.
    {
        struct stat st;
        if (stat(FileName, &st) >= 0){
            ImageInfo.FileDateTime = st.st_mtime;
            ImageInfo.FileSize = st.st_size;
        }else{
            ErrFatal("No such file");
        }
    }

    strncpy(ImageInfo.FileName, FileName, PATH_MAX);

    if (ApplyCommand){
        // Applying a command is special - the headers from the file have to be
        // pre-read, then the command executed, and then the image part of the file read.

        if (!ReadJpegFile(FileName, READ_EXIF)) return;

        #ifdef MATTHIAS
            if (AutoResize){
                // Automatic resize computation - to customize for each run...
                if (AutoResizeCmdStuff() == 0){
                    DiscardData();
                    return;
                }
            }
        #endif // MATTHIAS


        if (CheckFileSkip()){
            DiscardData();
            return;
        }

        DiscardAllButExif();

        DoCommand(FileName);
        Modified = TRUE;

        ReadMode = READ_IMAGE;   // Don't re-read exif section again on next read.
    }else if (ExifXferScrFile){
        char RelativeExifName[PATH_MAX+1];

        // Make a relative name.
        RelativeName(RelativeExifName, ExifXferScrFile, FileName);

        if(!ReadJpegFile(RelativeExifName, READ_EXIF)) return;

        DiscardAllButExif();    // Don't re-read exif section again on next read.

        Modified = TRUE;
        ReadMode = READ_IMAGE;
    }

    FilesMatched += 1;

    FilesMatched = TRUE; // Turns off complaining that nothing matched.

    if (DoModify){
        ReadMode |= READ_IMAGE;
    }

    if (!ReadJpegFile(FileName, ReadMode)) return;

    if (CheckFileSkip()){
        DiscardData();
        return;
    }

    if (ShowConcise){
        ShowConciseImageInfo();
    }else{
        if (!(DoModify || DoReadAction) || ShowTags){
	    if (DoN3) {
		ShowImageInfoInN3();
	    } else {
		ShowImageInfo();
	    }
        }
    }

    if (ThumbnailName){
        if (ImageInfo.ThumbnailPointer){
            FILE * ThumbnailFile;
            char OutFileName[PATH_MAX+1];

            // Make a relative name.
            RelativeName(OutFileName, ThumbnailName, FileName);

#ifndef _WIN32
            if (strcmp(ThumbnailName, "-") == 0){
                // A filename of '-' indicates thumbnail goes to stdout.
                // This doesn't make much sense under Windows, so this feature is unix only.
                ThumbnailFile = stdout;
            }else
#endif
            {
                ThumbnailFile = fopen(OutFileName,"wb");
            }

            if (ThumbnailFile){
                fwrite(ImageInfo.ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile);
                fclose(ThumbnailFile);
                if (ThumbnailFile != stdout){
                    printf("Created: '%s'\n", OutFileName);
                }else{
                    // No point in printing to stdout when that is where the thumbnail goes!
                }
            }else{
                ErrFatal("Could not write thumbnail file");
            }
        }else{
            printf("Image '%s' contains no thumbnail\n",FileName);
        }
    }

#ifdef MATTHIAS
    if (EditComment || CommentInsertfileName || AddComment || RemComment){
#else
    if (EditComment || CommentInsertfileName){
#endif
        Section_t * CommentSec;
        char Comment[1000];
        int CommentSize;

        CommentSec = FindSection(M_COM);

        if (CommentSec == NULL){
            unsigned char * DummyData;

            DummyData = (uchar *) malloc(3);
            DummyData[0] = 0;
            DummyData[1] = 2;
            DummyData[2] = 0;
            CommentSec = CreateSection(M_COM, DummyData, 2);
        }

        CommentSize = CommentSec->Size-2;

        if (CommentInsertfileName){
            // Read a new comment section from file.
            char CommentFileName[PATH_MAX+1];
            FILE * CommentFile;

            // Make a relative name.
            RelativeName(CommentFileName, CommentInsertfileName, FileName);

            CommentFile = fopen(CommentFileName,"r");
            if (CommentFile == NULL){
                printf("Could not open '%s'\n",CommentFileName);
            }else{
                // Read it in.
                // Replace the section.
                CommentSize = fread(Comment, 1, 999, CommentFile);
                fclose(CommentFile);
                if (CommentSize < 0) CommentSize = 0;
            }
        }else{
#ifdef MATTHIAS
            if (ModifyDescriptComment(Comment, (char *)CommentSec->Data+2)){
                Modified = TRUE;
                CommentSize = strlen(Comment);
            }
            if (EditComment)
#else
            memcpy(Comment, (char *)CommentSec->Data+2, CommentSize);
#endif
            {
                char EditFileName[PATH_MAX+4];
                strcpy(EditFileName, FileName);
                strcat(EditFileName, ".txt");

                CommentSize = FileEditComment(EditFileName, Comment, CommentSize);
            }
        }

        if (strcmp(Comment, (char *)CommentSec->Data+2)){
            // Discard old comment section and put a new one in.
            int size;
            size = CommentSize+2;
            free(CommentSec->Data);
            CommentSec->Size = size;
            CommentSec->Data = malloc(size);
            CommentSec->Data[0] = (uchar)(size >> 8);
            CommentSec->Data[1] = (uchar)(size);
            memcpy((CommentSec->Data)+2, Comment, size-2);
            Modified = TRUE;
        }
        if (!Modified){
            printf("Comment not modified\n");
        }
    }
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.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, "%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) {
        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 (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 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

// 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");
}