Beispiel #1
0
//--------------------------------------------------------------------------
// GPS Lat/Long extraction helper function
//--------------------------------------------------------------------------
void CExifParse::GetLatLong(
        const unsigned int Format,
        const unsigned char* ValuePtr,
        const int ComponentSize,
        char *latLongString)
{
  if (Format != FMT_URATIONAL)
  {
    ErrNonfatal("Illegal number format %d for GPS Lat/Long", Format, 0);
  }
  else
  {
    double Values[3];
    for (unsigned a=0; a<3 ;a++)
    {
      Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
    }
    if (Values[0] < 0 || Values[0] > 180 || Values[1] < 0 || Values[1] >= 60 || Values[2] < 0 || Values[2] >= 60)
    {
      // Ignore invalid values (DMS format expected)
      ErrNonfatal("Invalid Lat/Long value", 0, 0);
      latLongString[0] = 0;
    }
    else
    {
      char latLong[30];
      sprintf(latLong, "%3.0fd %2.0f' %5.2f\"", Values[0], Values[1], Values[2]);
      strcat(latLongString, latLong);
    }
  }
}
Beispiel #2
0
//--------------------------------------------------------------------------
// GPS Lat/Long extraction helper function
//--------------------------------------------------------------------------
void CExifParse::GetLatLong(
        const unsigned int Format,
        const unsigned char* ValuePtr,
        const int ComponentSize,
        char *latLongString)
{
  if (Format != FMT_URATIONAL)
  {
    ErrNonfatal("Illegal number format %d for GPS Lat/Long", Format, 0);
  }
  else
  {
    double Values[3];
    for (unsigned a=0; a<3 ;a++)
    {
      Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
    }
    char latLong[30];
    sprintf(latLong, "%3.0fd %2.0f' %5.2f\"", Values[0], Values[1], Values[2]);
    strcat(latLongString, latLong);
  }
}
Beispiel #3
0
//--------------------------------------------------------------------------
// Evaluate number, be it int, rational, or float from directory.
//--------------------------------------------------------------------------
double CExifParse::ConvertAnyFormat(const void* const ValuePtr, int Format)
{
  double Value;
  Value = 0;

  switch(Format)
  {
    case FMT_SBYTE:     Value = *(const   signed char*)ValuePtr;          break;
    case FMT_BYTE:      Value = *(const unsigned char*)ValuePtr;          break;

    case FMT_USHORT:    Value = Get16(ValuePtr, m_MotorolaOrder);   break;
    case FMT_ULONG:     Value = (unsigned)Get32(ValuePtr, m_MotorolaOrder);   break;

    case FMT_URATIONAL:
    case FMT_SRATIONAL:
    {
      int Num,Den;
      Num = Get32(ValuePtr, m_MotorolaOrder);
      Den = Get32(4+(const char *)ValuePtr, m_MotorolaOrder);

      if (Den == 0)    Value = 0;
      else             Value = (double)Num/Den;
    }
    break;

    case FMT_SSHORT:    Value = (signed short)Get16(ValuePtr, m_MotorolaOrder);    break;
    case FMT_SLONG:     Value = Get32(ValuePtr, m_MotorolaOrder);                  break;

    // Not sure if this is correct (never seen float used in Exif format)
    case FMT_SINGLE:    Value = (double)*(const float*)ValuePtr;          break;
    case FMT_DOUBLE:    Value = *(const double*)ValuePtr;                 break;

    default:
      ErrNonfatal("Illegal format code %d",Format,0);
  }
  return Value;
}
Beispiel #4
0
//--------------------------------------------------------------------------
// Process GPS info directory
//--------------------------------------------------------------------------
void CExifParse::ProcessGpsInfo(
                    const unsigned char* const DirStart,
                    int ByteCountUnused,
                    const unsigned char* const OffsetBase,
                    unsigned ExifLength)
{
  int NumDirEntries = Get16(DirStart, m_MotorolaOrder);

  for (int de=0;de<NumDirEntries;de++)
  {
    const unsigned char* DirEntry = DIR_ENTRY_ADDR(DirStart, de);

    unsigned Tag        = Get16(DirEntry, m_MotorolaOrder);
    unsigned Format     = Get16(DirEntry+2, m_MotorolaOrder);
    unsigned Components = (unsigned)Get32(DirEntry+4, m_MotorolaOrder);
    if (Format == 0 || Format > NUM_FORMATS)
    {
      ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
      continue;
    }

    unsigned ComponentSize = BytesPerFormat[Format - 1];
    unsigned ByteCount = Components * ComponentSize;

    const unsigned char* ValuePtr;

    if (ByteCount > 4)
    {
      unsigned OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder);
      // If its bigger than 4 bytes, the dir entry contains an offset.
      if (OffsetVal+ByteCount > ExifLength)
      {
        // Bogus pointer offset and / or bytecount value
        ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
        continue;
      }
      ValuePtr = OffsetBase+OffsetVal;
    }
    else
    {
      // 4 bytes or less and value is in the dir entry itself
      ValuePtr = DirEntry+8;
    }

    switch(Tag)
    {
      case TAG_GPS_LAT_REF:
        m_ExifInfo->GpsLat[0] = ValuePtr[0];
        m_ExifInfo->GpsLat[1] = 0;
      break;

      case TAG_GPS_LONG_REF:
        m_ExifInfo->GpsLong[0] = ValuePtr[0];
        m_ExifInfo->GpsLong[1] = 0;
      break;

      case TAG_GPS_LAT:
        GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLat);
      break;
      case TAG_GPS_LONG:
        GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLong);
      break;

      case TAG_GPS_ALT_REF:
        if (ValuePtr[0] != 0)
          m_ExifInfo->GpsAlt[0] = '-';
        m_ExifInfo->GpsAlt[1] = 0;
      break;

      case TAG_GPS_ALT:
        {
          char temp[18];
          sprintf(temp,"%dm", Get32(ValuePtr, m_MotorolaOrder));
          strcat(m_ExifInfo->GpsAlt, temp);
        }
      break;
    }
  }
}
Beispiel #5
0
//--------------------------------------------------------------------------
// Process one of the nested EXIF directories.
//--------------------------------------------------------------------------
void CExifParse::ProcessDir(const unsigned char* const DirStart,
                            const unsigned char* const OffsetBase,
                            const unsigned ExifLength,
                            int NestingLevel)
{
  if (NestingLevel > 4)
  {
    ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0);
    return;
  }

  char IndentString[25];
  memset(IndentString, ' ', 25);
  IndentString[NestingLevel * 4] = '\0';


  int NumDirEntries = Get16((const void*)DirStart, m_MotorolaOrder);

  const unsigned char* const DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
  if (DirEnd+4 > (OffsetBase+ExifLength))
  {
    if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength)
    {
      // Version 1.3 of jhead would truncate a bit too much.
      // This also caught later on as well.
    }
    else
    {
      ErrNonfatal("Illegally sized directory", 0,0);
      return;
    }
  }

  for (int de=0;de<NumDirEntries;de++)
  {
    int Tag, Format, Components;
    unsigned char* ValuePtr;
    int ByteCount;
    const unsigned char* const DirEntry = DIR_ENTRY_ADDR(DirStart, de);

    Tag = Get16(DirEntry, m_MotorolaOrder);
    Format = Get16(DirEntry+2, m_MotorolaOrder);
    Components = Get32(DirEntry+4, m_MotorolaOrder);

    if (Format <= 0 || Format > NUM_FORMATS)
    {
      ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
      continue;
    }

    if ((unsigned)Components > 0x10000)
    {
      ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag);
      continue;
    }

    ByteCount = Components * BytesPerFormat[Format - 1];

    if (ByteCount > 4)
    {
      unsigned OffsetVal;
      OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder);
      // If its bigger than 4 bytes, the dir entry contains an offset.
      if (OffsetVal+ByteCount > ExifLength)
      {
        // Bogus pointer offset and / or bytecount value
        ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
        continue;
      }
      ValuePtr = (unsigned char*)(const_cast<unsigned char*>(OffsetBase)+OffsetVal);

      if (OffsetVal > m_LargestExifOffset)
      {
        m_LargestExifOffset = OffsetVal;
      }

    }
    else {
      // 4 bytes or less and value is in the dir entry itself
      ValuePtr = (unsigned char*)(const_cast<unsigned char*>(DirEntry)+8);
    }


    // Extract useful components of tag
    switch(Tag)
    {
      case TAG_DESCRIPTION:
      {
        int length = max(ByteCount, 0);
        length = min(length, MAX_COMMENT);
        strncpy(m_ExifInfo->Description, (char *)ValuePtr, length);
        m_ExifInfo->Description[length] = '\0';
        break;
      }
      case TAG_MAKE:
      {
        int space = sizeof(m_ExifInfo->CameraMake);
        if (space > 0)
        {
          strncpy(m_ExifInfo->CameraMake, (char *)ValuePtr, space - 1);
          m_ExifInfo->CameraMake[space - 1] = '\0';
        }
        break;
      }
      case TAG_MODEL:
      {
        int space = sizeof(m_ExifInfo->CameraModel);
        if (space > 0)
        {
          strncpy(m_ExifInfo->CameraModel, (char *)ValuePtr, space - 1);
          m_ExifInfo->CameraModel[space - 1] = '\0';
        }
        break;
      }
//      case TAG_SOFTWARE:          strncpy(m_ExifInfo->Software, ValuePtr, 5);    break;
      case TAG_FOCALPLANEXRES:    m_FocalPlaneXRes  = ConvertAnyFormat(ValuePtr, Format);               break;
      case TAG_THUMBNAIL_OFFSET:  m_ExifInfo->ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);     break;
      case TAG_THUMBNAIL_LENGTH:  m_ExifInfo->ThumbnailSize   = (unsigned)ConvertAnyFormat(ValuePtr, Format);     break;

      case TAG_MAKER_NOTE:
        continue;
      break;

      case TAG_DATETIME_ORIGINAL:
      {

        int space = sizeof(m_ExifInfo->DateTime);
        if (space > 0)
        {
          strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1);
          m_ExifInfo->DateTime[space - 1] = '\0';
          // If we get a DATETIME_ORIGINAL, we use that one.
          m_DateFound = true;
        }
        break;
      }
      case TAG_DATETIME_DIGITIZED:
      case TAG_DATETIME:
      {
        if (!m_DateFound)
        {
          // If we don't already have a DATETIME_ORIGINAL, use whatever
          // time fields we may have.
          int space = sizeof(m_ExifInfo->DateTime);
          if (space > 0)
          {
            strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1);
            m_ExifInfo->DateTime[space - 1] = '\0';
          }
        }
        break;
      }
      case TAG_USERCOMMENT:
      {
        // The UserComment allows comments without the charset limitations of ImageDescription.
        // Therefore the UserComment field is prefixed by a CharacterCode field (8 Byte):
        //  - ASCII:         'ASCII\0\0\0'
        //  - Unicode:       'UNICODE\0'
        //  - JIS X208-1990: 'JIS\0\0\0\0\0'
        //  - Unknown:       '\0\0\0\0\0\0\0\0' (application specific)

        m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNKNOWN;

        const int EXIF_COMMENT_CHARSET_LENGTH = 8;
        if (ByteCount >= EXIF_COMMENT_CHARSET_LENGTH)
        {
          // As some implementations use spaces instead of \0 for the padding,
          // we're not so strict and check only the prefix.
          if (memcmp(ValuePtr, "ASCII", 5) == 0)
            m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_ASCII;
          else if (memcmp(ValuePtr, "UNICODE", 7) == 0)
            m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNICODE;
          else if (memcmp(ValuePtr, "JIS", 3) == 0)
            m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_JIS;

          int length = ByteCount - EXIF_COMMENT_CHARSET_LENGTH;
          length = min(length, MAX_COMMENT);
          memcpy(m_ExifInfo->Comments, ValuePtr + EXIF_COMMENT_CHARSET_LENGTH, length);
          m_ExifInfo->Comments[length] = '\0';
//          FixComment(comment);                          // Ensure comment is printable
        }
      }
      break;

      case TAG_XP_COMMENT:
      {
        // The XP user comment field is always unicode (UCS-2) encoded
        m_ExifInfo->XPCommentsCharset = EXIF_COMMENT_CHARSET_UNICODE;
        size_t length = min(ByteCount, MAX_COMMENT);
        memcpy(m_ExifInfo->XPComment, ValuePtr, length);
        m_ExifInfo->XPComment[length] = '\0';
      }
      break;

      case TAG_FNUMBER:
        // Simplest way of expressing aperture, so I trust it the most.
        // (overwrite previously computd value if there is one)
        m_ExifInfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_APERTURE:
      case TAG_MAXAPERTURE:
        // More relevant info always comes earlier, so only use this field if we don't
        // have appropriate aperture information yet.
        if (m_ExifInfo->ApertureFNumber == 0)
        {
          m_ExifInfo->ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5);
        }
      break;

      case TAG_FOCALLENGTH:
        // Nice digital cameras actually save the focal length as a function
        // of how far they are zoomed in.
        m_ExifInfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_SUBJECT_DISTANCE:
        // Inidcates the distacne the autofocus camera is focused to.
        // Tends to be less accurate as distance increases.
        {
          float distance = (float)ConvertAnyFormat(ValuePtr, Format);
          m_ExifInfo->Distance = distance;
        }
      break;

      case TAG_EXPOSURETIME:
        {
        // Simplest way of expressing exposure time, so I trust it most.
        // (overwrite previously computd value if there is one)
        float expTime = (float)ConvertAnyFormat(ValuePtr, Format);
        if (expTime)
          m_ExifInfo->ExposureTime = expTime;
        }
      break;

      case TAG_SHUTTERSPEED:
        // More complicated way of expressing exposure time, so only use
        // this value if we don't already have it from somewhere else.
        if (m_ExifInfo->ExposureTime == 0)
        {
          m_ExifInfo->ExposureTime = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)));
        }
      break;

      case TAG_FLASH:
        m_ExifInfo->FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_ORIENTATION:
        m_ExifInfo->Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
        if (m_ExifInfo->Orientation < 0 || m_ExifInfo->Orientation > 8)
        {
          ErrNonfatal("Undefined rotation value %d", m_ExifInfo->Orientation, 0);
          m_ExifInfo->Orientation = 0;
        }
      break;

      case TAG_EXIF_IMAGELENGTH:
      case TAG_EXIF_IMAGEWIDTH:
        // Use largest of height and width to deal with images that have been
        // rotated to portrait format.
        {
          int a = (int)ConvertAnyFormat(ValuePtr, Format);
          if (m_ExifImageWidth < a) m_ExifImageWidth = a;
        }
      break;

      case TAG_FOCALPLANEUNITS:
        switch((int)ConvertAnyFormat(ValuePtr, Format))
        {
          // According to the information I was using, 2 means meters.
          // But looking at the Cannon powershot's files, inches is the only
          // sensible value.
          case 1: m_FocalPlaneUnits = 25.4; break;  // inch
          case 2: m_FocalPlaneUnits = 25.4; break;
          case 3: m_FocalPlaneUnits = 10;   break;  // centimeter
          case 4: m_FocalPlaneUnits = 1;    break;  // millimeter
          case 5: m_FocalPlaneUnits = .001; break;  // micrometer
        }
      break;

      case TAG_EXPOSURE_BIAS:
        m_ExifInfo->ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_WHITEBALANCE:
        m_ExifInfo->Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_LIGHT_SOURCE:
        //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both
        m_ExifInfo->LightSource = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_METERING_MODE:
        m_ExifInfo->MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_EXPOSURE_PROGRAM:
        m_ExifInfo->ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_EXPOSURE_INDEX:
        if (m_ExifInfo->ISOequivalent == 0)
        {
          // Exposure index and ISO equivalent are often used interchangeably,
          // so we will do the same.
          // http://photography.about.com/library/glossary/bldef_ei.htm
          m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
        }
      break;

      case TAG_ISO_EQUIVALENT:
        m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
        if (m_ExifInfo->ISOequivalent < 50)
          m_ExifInfo->ISOequivalent *= 200;          // Fixes strange encoding on some older digicams.
      break;

      case TAG_EXPOSURE_MODE:
        m_ExifInfo->ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_DIGITALZOOMRATIO:
        m_ExifInfo->DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format);
      break;

      case TAG_EXIF_OFFSET:
      case TAG_INTEROP_OFFSET:
      {
        const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder);
        if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength)
        {
          ErrNonfatal("Illegal exif or interop ofset directory link",0,0);
        }
        else
        {
          ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
        }
        continue;
      }
      break;

      case TAG_GPSINFO:
      {
        const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder);
        if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength)
        {
          ErrNonfatal("Illegal GPS directory link",0,0);
        }
        else
        {
          ProcessGpsInfo(SubdirStart, ByteCount, OffsetBase, ExifLength);
        }
        continue;
      }
      break;

      case TAG_FOCALLENGTH_35MM:
        // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002)
        // if its present, use it to compute equivalent focal length instead of
        // computing it from sensor geometry and actual focal length.
        m_ExifInfo->FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format);
      break;
    }
  }


  // In addition to linking to subdirectories via exif tags,
  // there's also a potential link to another directory at the end of each
  // directory.  this has got to be the result of a committee!
  unsigned Offset;

  if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength)
  {
    Offset = (unsigned)Get32(DirStart+2+12*NumDirEntries, m_MotorolaOrder);
    if (Offset)
    {
      const unsigned char* const SubdirStart = OffsetBase + Offset;
      if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase)
      {
        if (SubdirStart > OffsetBase && SubdirStart < OffsetBase+ExifLength+20)
        {
          // Jhead 1.3 or earlier would crop the whole directory!
          // As Jhead produces this form of format incorrectness,
          // I'll just let it pass silently
        }
        else
        {
          ErrNonfatal("Illegal subdirectory link",0,0);
        }
      }
      else
      {
        if (SubdirStart <= OffsetBase+ExifLength)
        {
          ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
        }
      }
      if (Offset > m_LargestExifOffset)
      {
        m_LargestExifOffset = Offset;
      }
    }
  }
  else
  {
    // The exif header ends before the last next directory pointer.
  }

  if (m_ExifInfo->ThumbnailOffset)
  {
    m_ExifInfo->ThumbnailAtEnd = false;

    if (m_ExifInfo->ThumbnailOffset <= ExifLength)
    {
      if (m_ExifInfo->ThumbnailSize > ExifLength - m_ExifInfo->ThumbnailOffset)
      {
        // If thumbnail extends past exif header, only save the part that
        // actually exists.  Canon's EOS viewer utility will do this - the
        // thumbnail extracts ok with this hack.
        m_ExifInfo->ThumbnailSize = ExifLength - m_ExifInfo->ThumbnailOffset;
      }
    }
  }
}
Beispiel #6
0
//--------------------------------------------------------------------------
// Parse the marker stream until SOS or EOI is seen;
//--------------------------------------------------------------------------
int ReadJpegSections (FILE * infile, ReadMode_t ReadMode)
{
    int a;
    int HaveCom = FALSE;

    a = fgetc(infile);

    if (a != 0xff || fgetc(infile) != M_SOI){
        return FALSE;
    }

    ImageInfo.JfifHeader.XDensity = ImageInfo.JfifHeader.YDensity = 300;
    ImageInfo.JfifHeader.ResolutionUnits = 1;

    for(;;){
        int itemlen;
        int prev;
        int marker = 0;
        int ll,lh, got;
        uchar * Data;

        CheckSectionsAllocated();

        prev = 0;
        for (a=0;;a++){
            marker = fgetc(infile);
            if (marker != 0xff && prev == 0xff) break;
            prev = marker;
        }

        if (a > 10){
            ErrNonfatal("Extraneous %d padding bytes before section %02X",a-1,marker);
        }

        Sections[SectionsRead].Type = marker;
  
        // Read the length of the section.
        lh = fgetc(infile);
        ll = fgetc(infile);

        itemlen = (lh << 8) | ll;

        if (itemlen < 2){
            ErrFatal("invalid marker");
        }

        Sections[SectionsRead].Size = itemlen;

        Data = (uchar *)malloc(itemlen);
        if (Data == NULL){
            ErrFatal("Could not allocate memory");
        }
        Sections[SectionsRead].Data = Data;

        // Store first two pre-read bytes.
        Data[0] = (uchar)lh;
        Data[1] = (uchar)ll;

        got = fread(Data+2, 1, itemlen-2, infile); // Read the whole section.
        if (got != itemlen-2){
            ErrFatal("Premature end of file?");
        }
        SectionsRead += 1;

        switch(marker){

            case M_SOS:   // stop before hitting compressed data 
                // If reading entire image is requested, read the rest of the data.
                if (ReadMode & READ_IMAGE){
                    int cp, ep, size;
                    // Determine how much file is left.
                    cp = ftell(infile);
                    fseek(infile, 0, SEEK_END);
                    ep = ftell(infile);
                    fseek(infile, cp, SEEK_SET);

                    size = ep-cp;
                    Data = (uchar *)malloc(size);
                    if (Data == NULL){
                        ErrFatal("could not allocate data for entire image");
                    }

                    got = fread(Data, 1, size, infile);
                    if (got != size){
                        ErrFatal("could not read the rest of the image");
                    }

                    CheckSectionsAllocated();
                    Sections[SectionsRead].Data = Data;
                    Sections[SectionsRead].Size = size;
                    Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
                    SectionsRead ++;
                    HaveAll = 1;
                }
                return TRUE;

            case M_EOI:   // in case it's a tables-only JPEG stream
                fprintf(stderr,"No image in jpeg!\n");
                return FALSE;

            case M_COM: // Comment section
                if (HaveCom || ((ReadMode & READ_METADATA) == 0)){
                    // Discard this section.
                    free(Sections[--SectionsRead].Data);
                }else{
                    process_COM(Data, itemlen);
                    HaveCom = TRUE;
                }
                break;

            case M_JFIF:
                // Regular jpegs always have this tag, exif images have the exif
                // marker instead, althogh ACDsee will write images with both markers.
                // this program will re-create this marker on absence of exif marker.
                // hence no need to keep the copy from the file.
                if (memcmp(Data+2, "JFIF\0",5)){
                    fprintf(stderr,"Header missing JFIF marker\n");
                }
                if (itemlen < 16){
                    fprintf(stderr,"Jfif header too short\n");
                    goto ignore;
                }

                ImageInfo.JfifHeader.Present = TRUE;
                ImageInfo.JfifHeader.ResolutionUnits = Data[9];
                ImageInfo.JfifHeader.XDensity = (Data[10]<<8) | Data[11];
                ImageInfo.JfifHeader.YDensity = (Data[12]<<8) | Data[13];
                if (ShowTags){
                    printf("JFIF SOI marker: Units: %d ",ImageInfo.JfifHeader.ResolutionUnits);
                    switch(ImageInfo.JfifHeader.ResolutionUnits){
                        case 0: printf("(aspect ratio)"); break;
                        case 1: printf("(dots per inch)"); break;
                        case 2: printf("(dots per cm)"); break;
                        default: printf("(unknown)"); break;
                    }
                    printf("  X-density=%d Y-density=%d\n",ImageInfo.JfifHeader.XDensity, ImageInfo.JfifHeader.YDensity);

                    if (Data[14] || Data[15]){
                        fprintf(stderr,"Ignoring jfif header thumbnail\n");
                    }
                }

                ignore:

                free(Sections[--SectionsRead].Data);
                break;

            case M_EXIF:
                // There can be different section using the same marker.
                if (ReadMode & READ_METADATA){
                    if (memcmp(Data+2, "Exif", 4) == 0){
                        process_EXIF(Data, itemlen);
                        break;
                    }else if (memcmp(Data+2, "http:", 5) == 0){
                        Sections[SectionsRead-1].Type = M_XMP; // Change tag for internal purposes.
                        if (ShowTags){
                            printf("Image cotains XMP section, %d bytes long\n", itemlen);
                            if (ShowTags){
                                ShowXmp(Sections[SectionsRead-1]);
                            }
                        }
                        break;
                    }
                }
                // Oterwise, discard this section.
                free(Sections[--SectionsRead].Data);
                break;

            case M_IPTC:
                if (ReadMode & READ_METADATA){
                    if (ShowTags){
                        printf("Image cotains IPTC section, %d bytes long\n", itemlen);
                    }
                    // Note: We just store the IPTC section.  Its relatively straightforward
                    // and we don't act on any part of it, so just display it at parse time.
                }else{
                    free(Sections[--SectionsRead].Data);
                }
                break;
           
            case M_SOF0: 
            case M_SOF1: 
            case M_SOF2: 
            case M_SOF3: 
            case M_SOF5: 
            case M_SOF6: 
            case M_SOF7: 
            case M_SOF9: 
            case M_SOF10:
            case M_SOF11:
            case M_SOF13:
            case M_SOF14:
            case M_SOF15:
                process_SOFn(Data, marker);
                break;
            default:
                // Skip any other sections.
                if (ShowTags){
                    printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
                }
                break;
        }
    }
    return TRUE;
}
Beispiel #7
0
//--------------------------------------------------------------------------
//  Process and display IPTC marker.
//
//  IPTC block consists of:
//      - Marker:               1 byte      (0xED)
//      - Block length:         2 bytes
//      - IPTC Signature:       14 bytes    ("Photoshop 3.0\0")
//      - 8BIM Signature        4 bytes     ("8BIM")
//      - IPTC Block start      2 bytes     (0x04, 0x04)
//      - IPTC Header length    1 byte
//      - IPTC header           Header is padded to even length, counting the length byte
//      - Length                4 bytes
//      - IPTC Data which consists of a number of entries, each of which has the following format:
//              - Signature     2 bytes     (0x1C02)
//              - Entry type    1 byte      (for defined entry types, see #defines above)
//              - entry length  2 bytes
//              - entry data    'entry length' bytes
//
//--------------------------------------------------------------------------
void show_IPTC (unsigned char* Data, unsigned int itemlen)
{
    const char IptcSig1[] = "Photoshop 3.0";
    const char IptcSig2[] = "8BIM";
    const char IptcSig3[] = {0x04, 0x04};

    unsigned char * pos    = Data + sizeof(short);   // position data pointer after length field
    unsigned char * maxpos = Data+itemlen;
    unsigned char headerLen = 0;
    unsigned char dataLen = 0;

    if (itemlen < 25) goto corrupt;

    // Check IPTC signatures
    if (memcmp(pos, IptcSig1, sizeof(IptcSig1)-1) != 0) goto badsig;
    pos += sizeof(IptcSig1);      // move data pointer to the next field

    if (memcmp(pos, IptcSig2, sizeof(IptcSig2)-1) != 0) goto badsig;
    pos += sizeof(IptcSig2)-1;          // move data pointer to the next field


	while (memcmp(pos, IptcSig3, sizeof(IptcSig3)) != 0) { // loop on valid Photoshop blocks

		pos += sizeof(IptcSig3); // move data pointer to the Header Length
		// Skip header
		headerLen = *pos; // get header length and move data pointer to the next field
		pos += (headerLen & 0xfe) + 2; // move data pointer to the next field (Header is padded to even length, counting the length byte)

		pos += 3; // move data pointer to length, assume only one byte, TODO: use all 4 bytes

		dataLen = *pos++;
		pos += dataLen; // skip data section

		if (memcmp(pos, IptcSig2, sizeof(IptcSig2) - 1) != 0) {
			badsig: if (ShowTags) {
				ErrNonfatal("IPTC type signature mismatch\n", 0, 0);
			}
			return;
		}
		pos += sizeof(IptcSig2) - 1; // move data pointer to the next field
    }

    pos += sizeof(IptcSig3);          // move data pointer to the next field

    if (pos >= maxpos) goto corrupt;

    // IPTC section found

    // Skip header
    headerLen = *pos++;                     // get header length and move data pointer to the next field
    pos += headerLen + 1 - (headerLen % 2); // move data pointer to the next field (Header is padded to even length, counting the length byte)

    if (pos+4 >= maxpos) goto corrupt;

    // Get length (from motorola format)
    //length = (*pos << 24) | (*(pos+1) << 16) | (*(pos+2) << 8) | *(pos+3);

    pos += 4;                    // move data pointer to the next field

    printf("======= IPTC data: =======\n");

    // Now read IPTC data
    while (pos < (Data + itemlen-5)) {
        short  signature;
        unsigned char   type = 0;
        short  length = 0;
        const char * description = NULL;

        if (pos+5 > maxpos) goto corrupt;

        signature = (*pos << 8) + (*(pos+1));
        pos += 2;

        if (signature != 0x1C01 && signature != 0x1c02) break;

        type    = *pos++;
        length  = (*pos << 8) + (*(pos+1));
        pos    += 2;                          // Skip tag length

        if (pos+length > maxpos) goto corrupt;
        // Process tag here
        switch (type) {
            case IPTC_RECORD_VERSION:
                printf("Record vers.  : %d\n", (*pos << 8) + (*(pos+1)));
                break;

            case IPTC_SUPLEMENTAL_CATEGORIES:  description = "SuplementalCategories"; break;
            case IPTC_KEYWORDS:                description = "Keywords"; break;
            case IPTC_CAPTION:                 description = "Caption"; break;
            case IPTC_AUTHOR:                  description = "Author"; break;
            case IPTC_HEADLINE:                description = "Headline"; break;
            case IPTC_SPECIAL_INSTRUCTIONS:    description = "Spec. Instr."; break;
            case IPTC_CATEGORY:                description = "Category"; break;
            case IPTC_BYLINE:                  description = "Byline"; break;
            case IPTC_BYLINE_TITLE:            description = "Byline Title"; break;
            case IPTC_CREDIT:                  description = "Credit"; break;
            case IPTC_SOURCE:                  description = "Source"; break;
            case IPTC_COPYRIGHT_NOTICE:        description = "(C)Notice"; break;
            case IPTC_OBJECT_NAME:             description = "Object Name"; break;
            case IPTC_CITY:                    description = "City"; break;
            case IPTC_STATE:                   description = "State"; break;
            case IPTC_COUNTRY:                 description = "Country"; break;
            case IPTC_TRANSMISSION_REFERENCE:  description = "OriginalTransmissionReference"; break;
            case IPTC_DATE:                    description = "DateCreated"; break;
            case IPTC_COPYRIGHT:               description = "(C)Flag"; break;
            case IPTC_REFERENCE_SERVICE:       description = "Country Code"; break;
            case IPTC_COUNTRY_CODE:            description = "Ref. Service"; break;
            case IPTC_TIME_CREATED:            description = "Time Created"; break;
            case IPTC_SUB_LOCATION:            description = "Sub Location"; break;
            case IPTC_IMAGE_TYPE:              description = "Image type"; break;

            default:
                if (ShowTags){
                    printf("Unrecognised IPTC tag: %d\n", type );
                }
            break;
        }
        if (description != NULL) {
            char TempBuf[32];
            memset(TempBuf, 0, sizeof(TempBuf));
            memset(TempBuf, ' ', 14);
            memcpy(TempBuf, description, strlen(description));
            strcat(TempBuf, ":"); 
            printf("%s %*.*s\n", TempBuf, length, length, pos);
        }
        pos += length;
    }
    return;
corrupt:
    ErrNonfatal("Pointer corruption in IPTC\n",0,0);
}
//--------------------------------------------------------------------------
// Process GPS info directory
//--------------------------------------------------------------------------
void ProcessGpsInfo(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength)
{
    int de;
    unsigned a;
    int NumDirEntries;

    NumDirEntries = Get16u(DirStart);
    #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))

    if (ShowTags){
        xprintf("(dir has %d entries)\n",NumDirEntries);
    }

    ImageInfo.GpsInfoPresent = TRUE;
    strcpy(ImageInfo.GpsLat, "? ?");
    strcpy(ImageInfo.GpsLong, "? ?");
    ImageInfo.GpsAlt[0] = 0; 

    for (de=0;de<NumDirEntries;de++){
        unsigned Tag, Format, Components;
        unsigned char * ValuePtr;
        int ComponentSize;
        unsigned ByteCount;
        unsigned char * DirEntry;
        DirEntry = DIR_ENTRY_ADDR(DirStart, de);

        if (DirEntry+12 > OffsetBase+ExifLength){
            ErrNonfatal("GPS info directory goes past end of exif",0,0);
            return;
        }

        Tag = Get16u(DirEntry);
        Format = Get16u(DirEntry+2);
        Components = Get32u(DirEntry+4);

        if ((Format-1) >= NUM_FORMATS) {
            // (-1) catches illegal zero case as unsigned underflows to positive large.
            ErrNonfatal("Illegal number format %d for Exif gps tag %04x", Format, Tag);
            continue;
        }

        ComponentSize = BytesPerFormat[Format];
        ByteCount = Components * ComponentSize;

        if (ByteCount > 4){
            unsigned OffsetVal;
            OffsetVal = Get32u(DirEntry+8);
            // If its bigger than 4 bytes, the dir entry contains an offset.
            if (OffsetVal+ByteCount > ExifLength){
                // Bogus pointer offset and / or bytecount value
                ErrNonfatal("Illegal value pointer for Exif gps tag %04x", Tag,0);
                continue;
            }
            ValuePtr = OffsetBase+OffsetVal;
        }else{
            // 4 bytes or less and value is in the dir entry itself
            ValuePtr = DirEntry+8;
        }

        switch(Tag){
            char FmtString[21];
            char TempString[50];
            double Values[3];

            case TAG_GPS_LAT_REF:
                ImageInfo.GpsLat[0] = ValuePtr[0];
                break;

            case TAG_GPS_LONG_REF:
                ImageInfo.GpsLong[0] = ValuePtr[0];
                break;

            case TAG_GPS_LAT:
            case TAG_GPS_LONG:
                if (Format != FMT_URATIONAL){
                    ErrNonfatal("Inappropriate format (%d) for Exif GPS coordinates!", Format, 0);
                }
                strcpy(FmtString, "%0.0fd %0.0fm %0.0fs");
                for (a=0;a<3;a++){
                    int den, digits;

                    den = Get32s(ValuePtr+4+a*ComponentSize);
                    digits = 0;
                    while (den > 1 && digits <= 6){
                        den = den / 10;
                        digits += 1;
                    }
                    if (digits > 6) digits = 6;
                    FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0));
                    FmtString[3+a*7] = (char)('0'+digits);

                    Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
                }

                sprintf(TempString, FmtString, Values[0], Values[1], Values[2]);

                if (Tag == TAG_GPS_LAT){
                    strncpy(ImageInfo.GpsLat+2, TempString, 29);
                }else{
                    strncpy(ImageInfo.GpsLong+2, TempString, 29);
                }
                break;

            case TAG_GPS_ALT_REF:
                ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' ');
                break;

            case TAG_GPS_ALT:
                sprintf(ImageInfo.GpsAlt + 1, "%.2fm", 
                    ConvertAnyFormat(ValuePtr, Format));
                break;
        }

        if (ShowTags){
            // Show tag value.
            if (Tag < MAX_GPS_TAG){
                xprintf("        GPS%s =", GpsTags[Tag]);
            }else{
                // Show unknown tag
                xprintf("        Illegal GPS tag %04x=", Tag);
            }

            switch(Format){
                case FMT_UNDEFINED:
                    // Undefined is typically an ascii string.

                case FMT_STRING:
                    // String arrays printed without function call (different from int arrays)
                    {
                        xprintf("\"");
                        for (a=0;a<ByteCount;a++){
                            int ZeroSkipped = 0;
                            if (ValuePtr[a] >= 32){
                                if (ZeroSkipped){
                                    xprintf("?");
                                    ZeroSkipped = 0;
                                }
                                putchar(ValuePtr[a]);
                            }else{
                                if (ValuePtr[a] == 0){
                                    ZeroSkipped = 1;
                                }
                            }
                        }
                        xprintf("\"\n");
                    }
                    break;

                default:
                    // Handle arrays of numbers later (will there ever be?)
                    for (a=0;;){
                        PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount);
                        if (++a >= Components) break;
                        xprintf(", ");
                    }
                    xprintf("\n");
            }
        }
    }
}
//--------------------------------------------------------------------------
// Process exif format directory, as used by Cannon maker note
//--------------------------------------------------------------------------
void ProcessCanonMakerNoteDir(unsigned char * DirStart, unsigned char * OffsetBase, 
        unsigned ExifLength)
{
    int de;
    int a;
    int NumDirEntries;

    NumDirEntries = Get16u(DirStart);
    #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))

    {
        unsigned char * DirEnd;
        DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
        if (DirEnd > (OffsetBase+ExifLength)){
            ErrNonfatal("Illegally sized directory",0,0);
            return;
        }

        if (DumpExifMap){
            printf("Map: %05td-%05td: Directory (makernote)\n",DirStart-OffsetBase, DirEnd-OffsetBase);
        }
    }

    if (ShowTags){
        printf("(dir has %d entries)\n",NumDirEntries);
    }

    for (de=0;de<NumDirEntries;de++){
        int Tag, Format, Components;
        unsigned char * ValuePtr;
        int ByteCount;
        unsigned char * DirEntry;
        DirEntry = DIR_ENTRY_ADDR(DirStart, de);

        Tag = Get16u(DirEntry);
        Format = Get16u(DirEntry+2);
        Components = Get32u(DirEntry+4);

        if ((Format-1) >= NUM_FORMATS) {
            // (-1) catches illegal zero case as unsigned underflows to positive large.
            ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
            continue;
        }

        if ((unsigned)Components > 0x10000){
            ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag);
            continue;
        }

        ByteCount = Components * BytesPerFormat[Format];

        if (ByteCount > 4){
            unsigned OffsetVal;
            OffsetVal = Get32u(DirEntry+8);
            // If its bigger than 4 bytes, the dir entry contains an offset.
            if (OffsetVal+ByteCount > ExifLength){
                // Bogus pointer offset and / or bytecount value
                ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
                continue;
            }
            ValuePtr = OffsetBase+OffsetVal;

            if (DumpExifMap){
                printf("Map: %05d-%05d:   Data for makernote tag %04x\n",OffsetVal, OffsetVal+ByteCount, Tag);
            }
        }else{
            // 4 bytes or less and value is in the dir entry itself
            ValuePtr = DirEntry+8;
        }

        if (ShowTags){
            // Show tag name
            printf("            Canon maker tag %04x Value = ", Tag);
        }

        // Show tag value.
        switch(Format){

            case FMT_UNDEFINED:
                // Undefined is typically an ascii string.

            case FMT_STRING:
                // String arrays printed without function call (different from int arrays)
                if (ShowTags){
                    printf("\"");
                    for (a=0;a<ByteCount;a++){
                        int ZeroSkipped = 0;
                        if (ValuePtr[a] >= 32){
                            if (ZeroSkipped){
                                printf("?");
                                ZeroSkipped = 0;
                            }
                            putchar(ValuePtr[a]);
                        }else{
                            if (ValuePtr[a] == 0){
                                ZeroSkipped = 1;
                            }
                        }
                    }
                    printf("\"\n");
                }
                break;

            default:
                if (ShowTags){
                    PrintFormatNumber(ValuePtr, Format, ByteCount);
                    printf("\n");
                }
        }
        if (Tag == 1 && Components > 16){
            int IsoCode = Get16u(ValuePtr + 16*sizeof(unsigned short));
            if (IsoCode >= 16 && IsoCode <= 24){
                ImageInfo.ISOequivalent = 50 << (IsoCode-16);
            } 
        }

        if (Tag == 4 && Format == FMT_USHORT){
            if (Components > 7){
                int WhiteBalance = Get16u(ValuePtr + 7*sizeof(unsigned short));
                switch(WhiteBalance){
                    // 0=Auto, 6=Custom
                    case 1: ImageInfo.LightSource = 1; break; // Sunny
                    case 2: ImageInfo.LightSource = 1; break; // Cloudy
                    case 3: ImageInfo.LightSource = 3; break; // Thungsten
                    case 4: ImageInfo.LightSource = 2; break; // Fourescent
                    case 5: ImageInfo.LightSource = 4; break; // Flash
                }
            }
            if (Components > 19 && ImageInfo.Distance <= 0) {
                // Inidcates the distance the autofocus camera is focused to.
                // Tends to be less accurate as distance increases.
                int temp_dist = Get16u(ValuePtr + 19*sizeof(unsigned short));
printf("temp dist=%d\n",temp_dist);		
                if (temp_dist != 65535){
                    ImageInfo.Distance = (float)temp_dist/100;
                }else{
                    ImageInfo.Distance = -1 /* infinity */;
                }
            }
        }
    }
}
Beispiel #10
0
//--------------------------------------------------------------------------
// Process GPS info directory
//--------------------------------------------------------------------------
void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char * OffsetBase, unsigned ExifLength)
{
    int de;
    unsigned a;
    int NumDirEntries;

    NumDirEntries = Get16u(DirStart);
    #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))

    if (ShowTags){
        printf("(dir has %d entries)\n",NumDirEntries);
    }

    ImageInfo.GpsInfoPresent = TRUE;
    strcpy(ImageInfo.GpsLat, "? ?");
    strcpy(ImageInfo.GpsLong, "? ?");
    ImageInfo.GpsAlt[0] = 0;

    for (de=0;de<NumDirEntries;de++){
        unsigned Tag, Format, Components;
        unsigned char * ValuePtr;
        int ComponentSize;
        unsigned ByteCount;
        unsigned char * DirEntry;
        DirEntry = DIR_ENTRY_ADDR(DirStart, de);

        if (DirEntry+12 > OffsetBase+ExifLength){
            ErrNonfatal("GPS info directory goes past end of exif",0,0);
            return;
        }

        Tag = Get16u(DirEntry);
        Format = Get16u(DirEntry+2);
        Components = Get32u(DirEntry+4);

        if ((Format-1) >= NUM_FORMATS) {
            // (-1) catches illegal zero case as unsigned underflows to positive large.
            ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
            continue;
        }

        ComponentSize = BytesPerFormat[Format];
        ByteCount = Components * ComponentSize;

#ifdef SUPERDEBUG
    printf("GPS tag %x format %s #components %d componentsize %d bytecount %d", Tag, formatStr(Format), Components, ComponentSize,
            ByteCount);
#endif

        if (ByteCount > 4){
            unsigned OffsetVal;
            OffsetVal = Get32u(DirEntry+8);
            // If its bigger than 4 bytes, the dir entry contains an offset.
            if (OffsetVal+ByteCount > ExifLength){
                // Bogus pointer offset and / or bytecount value
                ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
                continue;
            }
            ValuePtr = OffsetBase+OffsetVal;
        }else{
            // 4 bytes or less and value is in the dir entry itself
            ValuePtr = DirEntry+8;
        }

        switch(Tag){
            char FmtString[21];
            char TempString[56];
            double Values[3];

            case TAG_GPS_LAT_REF:
                ImageInfo.GpsLat[0] = ValuePtr[0];
                ImageInfo.GpsLatRef[0] = ValuePtr[0];
                ImageInfo.GpsLatRef[1] = '\0';
                break;

            case TAG_GPS_LONG_REF:
                ImageInfo.GpsLong[0] = ValuePtr[0];
                ImageInfo.GpsLongRef[0] = ValuePtr[0];
                ImageInfo.GpsLongRef[1] = '\0';
                break;

            case TAG_GPS_LAT:
            case TAG_GPS_LONG:
                if (Format != FMT_URATIONAL){
                    ErrNonfatal("Inappropriate format (%d) for GPS coordinates!", Format, 0);
                }
                strcpy(FmtString, "%0.0fd %0.0fm %0.0fs");
                for (a=0;a<3;a++){
                    int den, digits;

                    den = Get32s(ValuePtr+4+a*ComponentSize);
                    digits = 0;
                    while (den > 1 && digits <= 6){
                        den = den / 10;
                        digits += 1;
                    }
                    if (digits > 6) digits = 6;
                    FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0));
                    FmtString[3+a*7] = (char)('0'+digits);

                    Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
                }

                sprintf(TempString, FmtString, Values[0], Values[1], Values[2]);

                if (Tag == TAG_GPS_LAT){
                    strncpy(ImageInfo.GpsLat+2, TempString, 29);
                }else{
                    strncpy(ImageInfo.GpsLong+2, TempString, 29);
                }

                sprintf(TempString, "%d/%d,%d/%d,%d/%d",
                    Get32s(ValuePtr), Get32s(4+(char*)ValuePtr),
                    Get32s(8+(char*)ValuePtr), Get32s(12+(char*)ValuePtr),
                    Get32s(16+(char*)ValuePtr), Get32s(20+(char*)ValuePtr));
                if (Tag == TAG_GPS_LAT){
                    strncpy(ImageInfo.GpsLatRaw, TempString, 31);
                }else{
                    strncpy(ImageInfo.GpsLongRaw, TempString, 31);
                }
                break;

            case TAG_GPS_ALT_REF:
                ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' ');
                ImageInfo.GpsAltRef = (char)ValuePtr[0];
                break;

            case TAG_GPS_ALT:
                sprintf(ImageInfo.GpsAlt + 1, "%.2fm", 
                    ConvertAnyFormat(ValuePtr, Format));
                ImageInfo.GpsAltRaw.num = Get32u(ValuePtr);
                ImageInfo.GpsAltRaw.denom = Get32u(4+(char *)ValuePtr);
                break;

            case TAG_GPS_TIMESTAMP:
                snprintf(ImageInfo.GpsTimeStamp,
                    sizeof(ImageInfo.GpsTimeStamp), "%d:%d:%d",
                    (int) ConvertAnyFormat(ValuePtr, Format),
                    (int) ConvertAnyFormat(ValuePtr + 8, Format),
                    (int) ConvertAnyFormat(ValuePtr + 16, Format)
                );
                break;

            case TAG_GPS_DATESTAMP:
                strncpy(ImageInfo.GpsDateStamp, (char*)ValuePtr, sizeof(ImageInfo.GpsDateStamp));
                break;

            case TAG_GPS_PROCESSING_METHOD:
                if (ByteCount > EXIF_ASCII_PREFIX_LEN &&
                    memcmp(ValuePtr, ExifAsciiPrefix, EXIF_ASCII_PREFIX_LEN) == 0) {
                    int length =
                        ByteCount < GPS_PROCESSING_METHOD_LEN + EXIF_ASCII_PREFIX_LEN ?
                        ByteCount - EXIF_ASCII_PREFIX_LEN : GPS_PROCESSING_METHOD_LEN;
                    memcpy(ImageInfo.GpsProcessingMethod,
                        (char*)(ValuePtr + EXIF_ASCII_PREFIX_LEN), length);
                    ImageInfo.GpsProcessingMethod[length] = 0;
                } else {
                    ALOGW("Unsupported encoding for GPSProcessingMethod");
                }
                break;
        }

        if (ShowTags){
            // Show tag value.
            if (Tag < MAX_GPS_TAG){
                printf("        %s =", GpsTags[Tag].Desc);
            }else{
                // Show unknown tag
                printf("        Illegal GPS tag %04x=", Tag);
            }

            switch(Format){
                case FMT_UNDEFINED:
                    // Undefined is typically an ascii string.

                case FMT_STRING:
                    // String arrays printed without function call (different from int arrays)
                    {
                        printf("\"");
                        for (a=0;a<ByteCount;a++){
                            int ZeroSkipped = 0;
                            if (ValuePtr[a] >= 32){
                                if (ZeroSkipped){
                                    printf("?");
                                    ZeroSkipped = 0;
                                }
                                putchar(ValuePtr[a]);
                            }else{
                                if (ValuePtr[a] == 0){
                                    ZeroSkipped = 1;
                                }
                            }
                        }
                        printf("\"\n");
                    }
                    break;

                default:
                    // Handle arrays of numbers later (will there ever be?)
                    for (a=0;;){
                        PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount);
                        if (++a >= Components) break;
                        printf(", ");
                    }
                    printf("\n");
            }
        }
    }
}