Home | History | Annotate | Download | only in jhead
      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