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