ImgSource EXIF Editing Sample


Here is a sample for modifiying EXIF data that lives in a JPG file. This will not work on TIFFs (ImgSource currently has no way to modify EXIF in TIFF, and there is no timeline that says when it will).

This only applies to ImgSource 3.0.44.0 and higher.



HISSRC hSrc = IS3OpenFileSource(sourceJPGFile);
                                           
force a JPG marker read
UINT32 w, h, d;
IS3GetJPGDims(hSrc, &w, &h, &d, NULL);

// copy all JPEG_APPx markes from the source to the ImgSource
// JPG output marker array
IS3CopyJPGInputMetadataToOutputMetadata();

// loop over all input JPEG_APPx markers
int uMarkerCount = IS3GetJPGInputMarkerCount();
for (int uMarkerIdx=0; uMarkerIdx < uMarkerCount; uMarkerIdx++)
{
  // peek at this marker...
  UINT32 uMarkerLen = 0;
  UINT32 uMarkerType = 0;
  IS3GetJPGInputMarkerInfo(uMarkerIdx, &uMarkerLen, &uMarkerType);
  
  // need JPEG_APP1 (0xe1)
  if (uMarkerType == 0xe1)
  {
     bool bModifiedData = false;

     // get the marker data
     HGLOBAL hMarkerData = NULL;
     IS3GetJPGInputMarker(uMarkerIdx, &hMarkerData, &uMarkerLen, &uMarkerType);
     
     BYTE *pMarkerData = (BYTE *)hMarkerData;

     if (pMarkerData)
     {
        // attempt to parse it for EXIF data
        HANDLE hEXIF = IS3EXIFInitializeFromByteArray(pMarkerData, uMarkerLen, 1);
        if (hEXIF)
        {

           __int32 iTagPos = 0;
           __int32 iIFDPos = 0;
           UINT32 uOutFlags = 0;
        
           // 274 = orient, 0x9003 = DateTimeOriginal, etc..
           UINT32 uTagID = 0x9003;
           BOOL bFoundTag = IS3EXIFGetTagPos(hEXIF, uTagID, 63, &iTagPos, &iIFDPos, &uOutFlags);

           // done with the parser
           IS3EXIFRelease(hEXIF);
        
           // don't even bother looking at EXIF from TIFF 
           bool bIsTiffTag = ((uOutFlags & 0x01) != 0);

           // this will be important, later on
           bool bIsMotorolaOrder = ((uOutFlags & 0x02) != 0);

           if (bFoundTag && (iTagPos > 0) && (!bIsTiffTag)) 
           {
              /*--------------------------------------------
                 If the tag is stored in Motorola byte order,
                 all members of this structure must be byte-swapped
                 before you can read them correctly.
              --------------------------------------------
              typedef   struct 
              {
                 UINT16  tdir_tag;
                 UINT16  tdir_type;
                 UINT32  tdir_count;
                 UINT32  tdir_offset;
              } ISTIFFDirEntry;
           
              /*--------------------------------------------
                Tag Type Values
           
                tag type         ID      Format                         Byte-swap
                TIFF_BYTE       = 1,     8-bit unsigned integer 
                TIFF_ASCII      = 2,     8-bit bytes w/ last byte null 
                TIFF_SHORT      = 3,     16-bit unsigned integer            * 
                TIFF_LONG       = 4,     32-bit unsigned integer            *
                TIFF_RATIONAL   = 5,     64-bit unsigned fraction           &
                TIFF_SBYTE      = 6,     8-bit signed integer 
                TIFF_UNDEFINED  = 7,     8-bit untyped data 
                TIFF_SSHORT     = 8,     16-bit signed integer              *
                TIFF_SLONG      = 9,     32-bit signed integer              *
                TIFF_SRATIONAL  = 10,    64-bit signed fraction             &
                TIFF_FLOAT      = 11,    32-bit IEEE floating point
                TIFF_DOUBLE     = 12,    64-bit IEEE floating point
             
                  * = requires byte swap
                  & = requires byte swap on both parts
              --------------------------------------------
           
              // sizes of the types above. first is 0 because there's no 0 tag type.
              int typeSize[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};

               /*--------------------------------------------
                 now that we know the tag exists and where it lives,
                 we can look at it. well, almost. first we need to 
                 do some byte-swapping in case the data is encoded in
                 Motorola byte order. and, to help avoid confusion, we'll
                 make a copy of that data and work on the copy.
              --------------------------------------------

              ISTIFFDirEntry *pOriginalTDE = (ISTIFFDirEntry *)(pMarkerData + iTagPos);
              ISTIFFDirEntry tde;
              memcpy(&tde, pOriginalTDE, sizeof(ISTIFFDirEntry));
           
              if (bIsMotorolaOrder)
              {
                 // swap all the data
                 IS3SwapShort(&tde.tdir_tag);
                 IS3SwapShort(&tde.tdir_type);
                 IS3SwapLong(&tde.tdir_count);
                 IS3SwapLong(&tde.tdir_offset);
              }
           
              /*--------------------------------------------
                 now we can look at the tde structure and learn about
                 what the tag holds.

                 first, the tag ID... that should match the tag we requested.
                 if it doesn't, there's something seriously wrong with ImgSource.
              --------------------------------------------

              if (uTagID == tde.tdir_tag)
              {

                 /*--------------------------------------------
                    now we need to locate the actual tag data.
                    note that the tde struct doesn't have a "data"
                    member. that's because tag data is usually stored at some 
                    memory location offset from the start of the IFD - usually. 
                    if the amount of data is four bytes or smaller, however, 
                    the data is stored in the tdir_offset member itself.

                    so, we need to calculate the amount of data the tag
                    refers to before we can know where the actual data lives. 
                    this is calculated by multiplying the tag data count by 
                    the size of the tag data type.
                 --------------------------------------------
           
                 UINT32 uDataSize = typeSize[tde.tdir_type] * tde.tdir_count;

                 BYTE *pTagData = NULL;
                 if (uDataSize > 4)
                 {
                    // tag data is stored at this location:
                    pTagData = pMarkerData + (tde.tdir_offset) + iIFDPos;
                 }
                 else
                 {
                    // tag data is stored in the tdir_offset itself
                    pTagData = (BYTE *)(&pOriginalTDE->tdir_offset);
                 }

                 /*--------------------------------------------
                    we know where the data lives, so now we can modify the
                    tde.tdir_count bytes starting at pTagData. 
                    
                    Note: DO NOT change more than uDataSize bytes, or you 
                    will run into tag data for other tags (or into other tags,
                    if the data in in the tdir_offset member).

                    Note: DO NOT attempt to change the size of any tag data.
                    even if you are careful to update the offsets for all tags
                    in an IFD (and any IFDs that follow it in the EXIF data block),
                    you will most likely destroy any MakerNote information 
                    stored in the EXIF block because various camera manufacturers 
                    use unusual schemes to calculate the data offsets for tags 
                    stored in MakerNotes. without knowing the specific MakerNote
                    format used by the camera, you will likely break all the tag
                    offsets.

                    before modifing anything, though, we need to be aware of 
                    byte-order issues, the same as we did when we looked at the 
                    tag structure: if the tag is in motorola byte order, we 
                    need to do byte-swapping on the data types that require 
                    it (16 and 32 bit integers and the 'rational' types).

                    ex. if we're setting 8 bit data (8-bit ints, TIFF_ASCII, or
                    the TIFF_UNDEFINED types), we simply set the bytes we want:

                       // change the year in tag 0x9003 (DateTimeOriginal) to '1998'
                       pTagData[0] = '1';
                       pTagData[1] = '9';
                       pTagData[2] = '9';
                       pTagData[3] = '8';

                    ex. set some TIFF_BYTE data:
                       BYTE data[7] = {8,6,7,5,3,0,9};
                       memcpy(pTagData, data, 7);

                    ex. set some 16-bit integers

                       UINT16 data1 = 1938;
                       UINT16 data2 = 1945;
                       if (bIsMotorolaOrder)
                       {
                          IS3SwapShort(&data1);
                          IS3SwapShort(&data2);
                       }
                       memcpy(pTagData, &data1, 2);
                       memcpy(pTagData + 2, &data2, 2);

                    ex. set some 32-bit integers

                       UINT32 data1 = 11000;
                       UINT32 data2 = 3000;
                       if (bIsMotorolaOrder)
                       {
                          IS3SwapLong(&data1);
                          IS3SwapLong(&data2);
                       }
                       memcpy(pTagData, &data1, 4);
                       memcpy(pTagData + 4, &data2, 4);

                    ex. set a TIFF_RATIONAL value

                       UINT32 numerator = 55;
                       UINT32 denominator = 75;
                       if (bIsMotorolaOrder)
                       {
                          IS3SwapLong(&numerator);
                          IS3SwapLong(&denominator);
                       }
                       memcpy(pTagData, &numerator, 4);
                       memcpy(pTagData + 4, &denominator, 4);

                    ex. set a TIFF_DOUBLE value

                       double val = 1.21;
                       memcpy(pTagData, &val, 8);

                    etc.
                 --------------------------------------------

                 // ... your code goes here ...

                 pTagData[0] = '1';
                 pTagData[1] = '9';
                 pTagData[2] = '9';
                 pTagData[3] = '8';

                 // did you modify anything?
                 bModifiedData = true; 

              } // tag received = tag requested

           } // found the tag, it's not a TIFF tag, and the position is OK
        
        } // EXIF OK

        /*--------------------------------------------
           set that modified data block into ImgSource's output 
           JPEG markers array. 

           note that at the very beginning of this, we copied all
           input markers to the output marker array using
           IS3CopyJPGInputMetadataToOutputMetadata(). so now, we're
           just updating the markers we actually changed.

           when we go to write the new file, it will contain all
           JPEG_APPx markers: those we didn't change, and those we did.
        --------------------------------------------
        if (bModifiedData)
        {
           IS3SetJPGOutputMarker(uMarkerIdx, pMarkerData + 4, uMarkerLen - 4, 0xe1);
        }

        // OK to free. the call above makes a copy of the data.
        GlobalFree(hMarkerData);

     } // marker not NULL            

  } // APP1 marker test

} // marker loop

/*--------------------------------------------
  OK. so that was all very exciting.

  at this point we have a bunch of data blocks
  sitting in ImgSource's JPG output marker array, ready
  to be written to an output JPG file. most importantly, 
  we have a set of markers that are nearly identical 
  to the markers in the original - they are identical
  in order, in type, in size, and on the whole, in 
  content. the only difference is that you might have 
  changed the values of a few bytes in a few places in 
  one (usually, though possibly multiple) JPEG_APP1 marker.

  now, we'll give these markers to IS3CopyJPGWithNewMarkers
  which will copy the source file into a new file, replacing
  each JPEG_APPx markers it finds with a marker from 
  ImgSource's JPEG output marker array. and, specifically, 
  it will replace inMarker[i] with outMarker[i] - that's why 
  it's important that order and type match.
--------------------------------------------

// rewind the source
IS3Seek(hSrc,0,0);

// open output
HISDEST hDest = IS3OpenFileDest(TEMP"pix/foo.jpg", 0);

/*--------------------------------------------
  the 1 << 6 here sets bit #6 in the flags.

  that tells the function to do a 'replace mode'
  copy. this mode preserves marker ordering and 
  positioning. this is important to JPEG/EXIF files,
  because some readers (especially cameras) are
  sensitive to the position of the markers.
--------------------------------------------
IS3CopyJPGWithNewMarkers(hSrc, hDest, 1 << 6);

// done!!!

IS3CloseDest(hDest);
IS3CloseSource(hSrc);


Copyright © 2013, Smaller Animals Software, Inc.

Smaller Animals News

ImgSource

ThumbNailer 10