1//--------------------------------------------------------------------------
2// Program to pull the information out of various types of EXIF digital
3// camera files and show it in a reasonably consistent way
4//
5// This module handles basic Jpeg file handling
6//
7// Matthias Wandel
8//--------------------------------------------------------------------------
9#include <utils/Log.h>
10#include "jhead.h"
11
12// Storage for simplified info extracted from file.
13ImageInfo_t ImageInfo;
14
15
16static Section_t * Sections = NULL;
17static int SectionsAllocated;
18static int SectionsRead;
19static int HaveAll;
20
21// Define the line below to turn on poor man's debugging output
22#undef SUPERDEBUG
23
24#ifdef SUPERDEBUG
25#define printf LOGE
26#endif
27
28
29
30#define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
31//--------------------------------------------------------------------------
32// Get 16 bits motorola order (always) for jpeg header stuff.
33//--------------------------------------------------------------------------
34static int Get16m(const void * Short)
35{
36    return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
37}
38
39
40//--------------------------------------------------------------------------
41// Process a COM marker.
42// We want to print out the marker contents as legible text;
43// we must guard against random junk and varying newline representations.
44//--------------------------------------------------------------------------
45static void process_COM (const uchar * Data, int length)
46{
47    int ch;
48    char Comment[MAX_COMMENT_SIZE+1];
49    int nch;
50    int a;
51
52    nch = 0;
53
54    if (length > MAX_COMMENT_SIZE) length = MAX_COMMENT_SIZE; // Truncate if it won't fit in our structure.
55
56    for (a=2;a<length;a++){
57        ch = Data[a];
58
59        if (ch == '\r' && Data[a+1] == '\n') continue; // Remove cr followed by lf.
60
61        if (ch >= 32 || ch == '\n' || ch == '\t'){
62            Comment[nch++] = (char)ch;
63        }else{
64            Comment[nch++] = '?';
65        }
66    }
67
68    Comment[nch] = '\0'; // Null terminate
69
70    if (ShowTags){
71        printf("COM marker comment: %s\n",Comment);
72    }
73
74    strcpy(ImageInfo.Comments,Comment);
75    ImageInfo.CommentWidchars = 0;
76}
77
78
79//--------------------------------------------------------------------------
80// Process a SOFn marker.  This is useful for the image dimensions
81//--------------------------------------------------------------------------
82static void process_SOFn (const uchar * Data, int marker)
83{
84    int data_precision, num_components;
85
86    data_precision = Data[2];
87    ImageInfo.Height = Get16m(Data+3);
88    ImageInfo.Width = Get16m(Data+5);
89    num_components = Data[7];
90
91    if (num_components == 3){
92        ImageInfo.IsColor = 1;
93    }else{
94        ImageInfo.IsColor = 0;
95    }
96
97    ImageInfo.Process = marker;
98
99    if (ShowTags){
100        printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",
101                   ImageInfo.Width, ImageInfo.Height, num_components, data_precision);
102    }
103}
104
105
106//--------------------------------------------------------------------------
107// Check sections array to see if it needs to be increased in size.
108//--------------------------------------------------------------------------
109void CheckSectionsAllocated(void)
110{
111    if (SectionsRead > SectionsAllocated){
112        ErrFatal("allocation screwup");
113    }
114    if (SectionsRead >= SectionsAllocated){
115        SectionsAllocated += SectionsAllocated/2;
116        Sections = (Section_t *)realloc(Sections, sizeof(Section_t)*SectionsAllocated);
117        if (Sections == NULL){
118            ErrFatal("could not allocate data for entire image");
119        }
120    }
121}
122
123
124//--------------------------------------------------------------------------
125// Parse the marker stream until SOS or EOI is seen;
126//--------------------------------------------------------------------------
127int ReadJpegSections (FILE * infile, ReadMode_t ReadMode)
128{
129    int a;
130    int HaveCom = FALSE;
131
132    a = fgetc(infile);
133
134    if (a != 0xff || fgetc(infile) != M_SOI){
135        return FALSE;
136    }
137    for(;;){
138        int itemlen;
139        int marker = 0;
140        int ll,lh, got;
141        uchar * Data;
142
143        CheckSectionsAllocated();
144
145        for (a=0;a<=16;a++){
146            marker = fgetc(infile);
147            if (marker != 0xff) break;
148
149            if (a >= 16){
150                fprintf(stderr,"too many padding bytes\n");
151                return FALSE;
152            }
153        }
154
155
156        Sections[SectionsRead].Type = marker;
157
158        // Read the length of the section.
159        lh = fgetc(infile);
160        ll = fgetc(infile);
161
162        itemlen = (lh << 8) | ll;
163
164        if (itemlen < 2){
165//            ErrFatal("invalid marker");
166			LOGE("invalid marker");
167	        return FALSE;
168        }
169
170        Sections[SectionsRead].Size = itemlen;
171
172        Data = (uchar *)malloc(itemlen);
173        if (Data == NULL){
174	    // ErrFatal("Could not allocate memory");
175	    LOGE("Could not allocate memory");
176	    return 0;
177        }
178        Sections[SectionsRead].Data = Data;
179
180        // Store first two pre-read bytes.
181        Data[0] = (uchar)lh;
182        Data[1] = (uchar)ll;
183
184        got = fread(Data+2, 1, itemlen-2, infile); // Read the whole section.
185        if (got != itemlen-2){
186//            ErrFatal("Premature end of file?");
187		   LOGE("Premature end of file?");
188	      return FALSE;
189        }
190        SectionsRead += 1;
191
192        printf("reading marker %d", marker);
193        switch(marker){
194
195            case M_SOS:   // stop before hitting compressed data
196                // If reading entire image is requested, read the rest of the data.
197                if (ReadMode & READ_IMAGE){
198                    int cp, ep, size;
199                    // Determine how much file is left.
200                    cp = ftell(infile);
201                    fseek(infile, 0, SEEK_END);
202                    ep = ftell(infile);
203                    fseek(infile, cp, SEEK_SET);
204
205                    size = ep-cp;
206                    Data = (uchar *)malloc(size);
207                    if (Data == NULL){
208		            // ErrFatal("could not allocate data for entire image");
209		            LOGE("could not allocate data for entire image");
210    		        return FALSE;
211                    }
212
213                    got = fread(Data, 1, size, infile);
214                    if (got != size){
215			        // ErrFatal("could not read the rest of the image");
216			        LOGE("could not read the rest of the image");
217				    return FALSE;
218                    }
219
220                    CheckSectionsAllocated();
221                    Sections[SectionsRead].Data = Data;
222                    Sections[SectionsRead].Size = size;
223                    Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
224                    SectionsRead ++;
225                    HaveAll = 1;
226                }
227                return TRUE;
228
229            case M_EOI:   // in case it's a tables-only JPEG stream
230                fprintf(stderr,"No image in jpeg!\n");
231                return FALSE;
232
233            case M_COM: // Comment section
234                if (HaveCom || ((ReadMode & READ_METADATA) == 0)){
235                    // Discard this section.
236                    free(Sections[--SectionsRead].Data);
237                }else{
238                    process_COM(Data, itemlen);
239                    HaveCom = TRUE;
240                }
241                break;
242
243            case M_JFIF:
244                // Regular jpegs always have this tag, exif images have the exif
245                // marker instead, althogh ACDsee will write images with both markers.
246                // this program will re-create this marker on absence of exif marker.
247                // hence no need to keep the copy from the file.
248                free(Sections[--SectionsRead].Data);
249                break;
250
251            case M_EXIF:
252                // There can be different section using the same marker.
253                if (ReadMode & READ_METADATA){
254                    if (memcmp(Data+2, "Exif", 4) == 0){
255                        process_EXIF(Data, itemlen);
256                        break;
257                    }else if (memcmp(Data+2, "http:", 5) == 0){
258                        Sections[SectionsRead-1].Type = M_XMP; // Change tag for internal purposes.
259                        if (ShowTags){
260                            printf("Image cotains XMP section, %d bytes long\n", itemlen);
261                            if (ShowTags){
262                                ShowXmp(Sections[SectionsRead-1]);
263                            }
264                        }
265                        break;
266                    }
267                }
268                // Oterwise, discard this section.
269                free(Sections[--SectionsRead].Data);
270                break;
271
272            case M_IPTC:
273                if (ReadMode & READ_METADATA){
274                    if (ShowTags){
275                        printf("Image cotains IPTC section, %d bytes long\n", itemlen);
276                    }
277                    // Note: We just store the IPTC section.  Its relatively straightforward
278                    // and we don't act on any part of it, so just display it at parse time.
279                }else{
280                    free(Sections[--SectionsRead].Data);
281                }
282                break;
283
284            case M_SOF0:
285            case M_SOF1:
286            case M_SOF2:
287            case M_SOF3:
288            case M_SOF5:
289            case M_SOF6:
290            case M_SOF7:
291            case M_SOF9:
292            case M_SOF10:
293            case M_SOF11:
294            case M_SOF13:
295            case M_SOF14:
296            case M_SOF15:
297                process_SOFn(Data, marker);
298                break;
299            default:
300                // Skip any other sections.
301                if (ShowTags){
302                    printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
303                }
304                break;
305        }
306    }
307    return TRUE;
308}
309
310//--------------------------------------------------------------------------
311// Discard read data.
312//--------------------------------------------------------------------------
313void DiscardData(void)
314{
315    int a;
316
317    for (a=0;a<SectionsRead;a++){
318        free(Sections[a].Data);
319    }
320
321    memset(&ImageInfo, 0, sizeof(ImageInfo));
322    SectionsRead = 0;
323    HaveAll = 0;
324}
325
326//--------------------------------------------------------------------------
327// Read image data.
328//--------------------------------------------------------------------------
329int ReadJpegFile(const char * FileName, ReadMode_t ReadMode)
330{
331    FILE * infile;
332    int ret;
333
334    infile = fopen(FileName, "rb"); // Unix ignores 'b', windows needs it.
335
336    if (infile == NULL) {
337        LOGE("can't open '%s'", FileName);
338        fprintf(stderr, "can't open '%s'\n", FileName);
339        return FALSE;
340    }
341
342    // Scan the JPEG headers.
343    printf("ReadJpegSections");
344    ret = ReadJpegSections(infile, ReadMode);
345    if (!ret){
346        LOGE("Not JPEG: %s", FileName);
347        fprintf(stderr,"Not JPEG: %s\n",FileName);
348    }
349
350    fclose(infile);
351
352    if (ret == FALSE){
353        DiscardData();
354    }
355    return ret;
356}
357
358
359//--------------------------------------------------------------------------
360// Replace or remove exif thumbnail
361//--------------------------------------------------------------------------
362int SaveThumbnail(char * ThumbFileName)
363{
364    FILE * ThumbnailFile;
365
366    if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailSize == 0){
367        fprintf(stderr,"Image contains no thumbnail\n");
368        return FALSE;
369    }
370
371    if (strcmp(ThumbFileName, "-") == 0){
372        // A filename of '-' indicates thumbnail goes to stdout.
373        // This doesn't make much sense under Windows, so this feature is unix only.
374        ThumbnailFile = stdout;
375    }else{
376        ThumbnailFile = fopen(ThumbFileName,"wb");
377    }
378
379    if (ThumbnailFile){
380        uchar * ThumbnailPointer;
381        Section_t * ExifSection;
382        ExifSection = FindSection(M_EXIF);
383        ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
384
385        fwrite(ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile);
386        fclose(ThumbnailFile);
387        return TRUE;
388    }else{
389        // ErrFatal("Could not write thumbnail file");
390        LOGE("Could not write thumbnail file");
391        return FALSE;
392    }
393}
394
395//--------------------------------------------------------------------------
396// Replace or remove exif thumbnail
397//--------------------------------------------------------------------------
398int ReplaceThumbnail(const char * ThumbFileName)
399{
400    FILE * ThumbnailFile;
401    int ThumbLen, NewExifSize;
402    Section_t * ExifSection;
403    uchar * ThumbnailPointer;
404
405    if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){
406        if (ThumbFileName == NULL){
407            // Delete of nonexistent thumbnail (not even pointers present)
408            // No action, no error.
409            return FALSE;
410        }
411
412        // Adding or removing of thumbnail is not possible - that would require rearranging
413        // of the exif header, which is risky, and jhad doesn't know how to do.
414        fprintf(stderr,"Image contains no thumbnail to replace - add is not possible\n");
415#ifdef SUPERDEBUG
416        LOGE("Image contains no thumbnail to replace - add is not possible\n");
417#endif
418        return FALSE;
419    }
420
421    if (ThumbFileName){
422        ThumbnailFile = fopen(ThumbFileName,"rb");
423
424        if (ThumbnailFile == NULL){
425	        //ErrFatal("Could not read thumbnail file");
426	        LOGE("Could not read thumbnail file");
427            return FALSE;
428        }
429
430        // get length
431        fseek(ThumbnailFile, 0, SEEK_END);
432
433        ThumbLen = ftell(ThumbnailFile);
434        fseek(ThumbnailFile, 0, SEEK_SET);
435
436        if (ThumbLen + ImageInfo.ThumbnailOffset > 0x10000-20){
437	        //ErrFatal("Thumbnail is too large to insert into exif header");
438	        LOGE("Thumbnail is too large to insert into exif header");
439	        return FALSE;
440        }
441    }else{
442        if (ImageInfo.ThumbnailSize == 0){
443             return FALSE;
444        }
445
446        ThumbLen = 0;
447        ThumbnailFile = NULL;
448    }
449
450    ExifSection = FindSection(M_EXIF);
451
452    NewExifSize = ImageInfo.ThumbnailOffset+8+ThumbLen;
453    ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
454
455    ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
456
457    if (ThumbnailFile){
458        fread(ThumbnailPointer, ThumbLen, 1, ThumbnailFile);
459        fclose(ThumbnailFile);
460    }
461
462    ImageInfo.ThumbnailSize = ThumbLen;
463
464    Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, ThumbLen);
465
466    ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
467    ExifSection->Data[1] = (uchar)NewExifSize;
468    ExifSection->Size = NewExifSize;
469
470#ifdef SUPERDEBUG
471        LOGE("ReplaceThumbnail successful thumblen %d", ThumbLen);
472#endif
473    return TRUE;
474}
475
476
477//--------------------------------------------------------------------------
478// Discard everything but the exif and comment sections.
479//--------------------------------------------------------------------------
480void DiscardAllButExif(void)
481{
482    Section_t ExifKeeper;
483    Section_t CommentKeeper;
484    Section_t IptcKeeper;
485    Section_t XmpKeeper;
486    int a;
487
488    memset(&ExifKeeper, 0, sizeof(ExifKeeper));
489    memset(&CommentKeeper, 0, sizeof(CommentKeeper));
490    memset(&IptcKeeper, 0, sizeof(IptcKeeper));
491    memset(&XmpKeeper, 0, sizeof(IptcKeeper));
492
493    for (a=0;a<SectionsRead;a++){
494        if (Sections[a].Type == M_EXIF && ExifKeeper.Type == 0){
495           ExifKeeper = Sections[a];
496        }else if (Sections[a].Type == M_XMP && XmpKeeper.Type == 0){
497           XmpKeeper = Sections[a];
498        }else if (Sections[a].Type == M_COM && CommentKeeper.Type == 0){
499            CommentKeeper = Sections[a];
500        }else if (Sections[a].Type == M_IPTC && IptcKeeper.Type == 0){
501            IptcKeeper = Sections[a];
502        }else{
503            free(Sections[a].Data);
504        }
505    }
506    SectionsRead = 0;
507    if (ExifKeeper.Type){
508        CheckSectionsAllocated();
509        Sections[SectionsRead++] = ExifKeeper;
510    }
511    if (CommentKeeper.Type){
512        CheckSectionsAllocated();
513        Sections[SectionsRead++] = CommentKeeper;
514    }
515    if (IptcKeeper.Type){
516        CheckSectionsAllocated();
517        Sections[SectionsRead++] = IptcKeeper;
518    }
519
520    if (XmpKeeper.Type){
521        CheckSectionsAllocated();
522        Sections[SectionsRead++] = XmpKeeper;
523    }
524}
525
526//--------------------------------------------------------------------------
527// Write image data back to disk.
528//--------------------------------------------------------------------------
529int WriteJpegFile(const char * FileName)
530{
531    FILE * outfile;
532    int a;
533
534    if (!HaveAll){
535        LOGE("Can't write back - didn't read all");
536        return FALSE;
537    }
538
539    outfile = fopen(FileName,"wb");
540    if (outfile == NULL){
541        LOGE("Could not open file for write");
542        return FALSE;
543    }
544
545    // Initial static jpeg marker.
546    fputc(0xff,outfile);
547    fputc(0xd8,outfile);
548
549    if (Sections[0].Type != M_EXIF && Sections[0].Type != M_JFIF){
550        // The image must start with an exif or jfif marker.  If we threw those away, create one.
551        static uchar JfifHead[18] = {
552            0xff, M_JFIF,
553            0x00, 0x10, 'J' , 'F' , 'I' , 'F' , 0x00, 0x01,
554            0x01, 0x01, 0x01, 0x2C, 0x01, 0x2C, 0x00, 0x00
555        };
556        fwrite(JfifHead, 18, 1, outfile);
557    }
558
559    int writeOk = FALSE;
560    int nWrite = 0;
561    // Write all the misc sections
562    for (a=0;a<SectionsRead-1;a++){
563        fputc(0xff,outfile);
564        fputc((unsigned char)Sections[a].Type, outfile);
565	nWrite = fwrite(Sections[a].Data, 1, Sections[a].Size, outfile);
566        writeOk = (nWrite == Sections[a].Size);
567        if(!writeOk){
568            LOGE("write section %d failed expect %d actual %d",a,Sections[a].Size,nWrite);
569            break;
570        }
571    }
572
573    // Write the remaining image data.
574    if (writeOk){
575        nWrite = fwrite(Sections[a].Data, 1,Sections[a].Size, outfile);
576	writeOk = (nWrite == Sections[a].Size);
577        if (!writeOk){
578            LOGE("write section %d failed expect %d actual %d",a,Sections[a].Size,nWrite);
579        }
580    }
581
582    fclose(outfile);
583    return writeOk;
584}
585
586
587//--------------------------------------------------------------------------
588// Check if image has exif header.
589//--------------------------------------------------------------------------
590Section_t * FindSection(int SectionType)
591{
592    int a;
593
594    for (a=0;a<SectionsRead;a++){
595        if (Sections[a].Type == SectionType){
596            return &Sections[a];
597        }
598    }
599    // Could not be found.
600    return NULL;
601}
602
603//--------------------------------------------------------------------------
604// Remove a certain type of section.
605//--------------------------------------------------------------------------
606int RemoveSectionType(int SectionType)
607{
608    int a;
609    for (a=0;a<SectionsRead-1;a++){
610        if (Sections[a].Type == SectionType){
611            // Free up this section
612            free (Sections[a].Data);
613            // Move succeding sections back by one to close space in array.
614            memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
615            SectionsRead -= 1;
616            return TRUE;
617        }
618    }
619    return FALSE;
620}
621
622//--------------------------------------------------------------------------
623// Remove sectons not part of image and not exif or comment sections.
624//--------------------------------------------------------------------------
625int RemoveUnknownSections(void)
626{
627    int a;
628    int Modified = FALSE;
629    for (a=0;a<SectionsRead-1;){
630        switch(Sections[a].Type){
631            case  M_SOF0:
632            case  M_SOF1:
633            case  M_SOF2:
634            case  M_SOF3:
635            case  M_SOF5:
636            case  M_SOF6:
637            case  M_SOF7:
638            case  M_SOF9:
639            case  M_SOF10:
640            case  M_SOF11:
641            case  M_SOF13:
642            case  M_SOF14:
643            case  M_SOF15:
644            case  M_SOI:
645            case  M_EOI:
646            case  M_SOS:
647            case  M_JFIF:
648            case  M_EXIF:
649            case  M_XMP:
650            case  M_COM:
651            case  M_DQT:
652            case  M_DHT:
653            case  M_DRI:
654            case  M_IPTC:
655                // keep.
656                a++;
657                break;
658            default:
659                // Unknown.  Delete.
660                free (Sections[a].Data);
661                // Move succeding sections back by one to close space in array.
662                memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
663                SectionsRead -= 1;
664                Modified = TRUE;
665        }
666    }
667    return Modified;
668}
669
670//--------------------------------------------------------------------------
671// Add a section (assume it doesn't already exist) - used for
672// adding comment sections and exif sections
673//--------------------------------------------------------------------------
674Section_t * CreateSection(int SectionType, unsigned char * Data, int Size)
675{
676    Section_t * NewSection;
677    int a;
678    int NewIndex;
679    NewIndex = 2;
680
681    if (SectionType == M_EXIF) NewIndex = 0; // Exif alwas goes first!
682
683    // Insert it in third position - seems like a safe place to put
684    // things like comments.
685
686    if (SectionsRead < NewIndex){
687        // ErrFatal("Too few sections!");
688        LOGE("Too few sections!");
689        return FALSE;
690    }
691
692    CheckSectionsAllocated();
693    for (a=SectionsRead;a>NewIndex;a--){
694        Sections[a] = Sections[a-1];
695    }
696    SectionsRead += 1;
697
698    NewSection = Sections+NewIndex;
699
700    NewSection->Type = SectionType;
701    NewSection->Size = Size;
702    NewSection->Data = Data;
703
704    return NewSection;
705}
706
707
708//--------------------------------------------------------------------------
709// Initialisation.
710//--------------------------------------------------------------------------
711void ResetJpgfile(void)
712{
713    if (Sections == NULL){
714        Sections = (Section_t *)malloc(sizeof(Section_t)*5);
715        SectionsAllocated = 5;
716    }
717
718    SectionsRead = 0;
719    HaveAll = 0;
720}
721