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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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 //-------------------------------------------------------------------------- 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