• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
13 ImageInfo_t ImageInfo;
14 
15 
16 static Section_t * Sections = NULL;
17 static int SectionsAllocated;
18 static int SectionsRead;
19 static 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 //--------------------------------------------------------------------------
Get16m(const void * Short)34 static 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 //--------------------------------------------------------------------------
process_COM(const uchar * Data,int length)45 static 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 //--------------------------------------------------------------------------
process_SOFn(const uchar * Data,int marker)82 static 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 //--------------------------------------------------------------------------
CheckSectionsAllocated(void)109 void 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 //--------------------------------------------------------------------------
ReadJpegSections(FILE * infile,ReadMode_t ReadMode)127 int 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 //--------------------------------------------------------------------------
DiscardData(void)313 void 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 //--------------------------------------------------------------------------
ReadJpegFile(const char * FileName,ReadMode_t ReadMode)329 int 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 //--------------------------------------------------------------------------
SaveThumbnail(char * ThumbFileName)362 int 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 //--------------------------------------------------------------------------
ReplaceThumbnail(const char * ThumbFileName)398 int 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 //--------------------------------------------------------------------------
DiscardAllButExif(void)480 void 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 //--------------------------------------------------------------------------
WriteJpegFile(const char * FileName)529 int 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 //--------------------------------------------------------------------------
FindSection(int SectionType)590 Section_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 //--------------------------------------------------------------------------
RemoveSectionType(int SectionType)606 int 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 //--------------------------------------------------------------------------
RemoveUnknownSections(void)625 int 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 //--------------------------------------------------------------------------
CreateSection(int SectionType,unsigned char * Data,int Size)674 Section_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 //--------------------------------------------------------------------------
ResetJpgfile(void)711 void 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