C++程序  |  1984行  |  76.03 KB

//--------------------------------------------------------------------------
// Program to pull the information out of various types of EXIF digital
// camera files and show it in a reasonably consistent way
//
// This module parses the very complicated exif structures.
//
// Matthias Wandel
//--------------------------------------------------------------------------
#include "jhead.h"

#include <math.h>
#include <ctype.h>
#include <utils/Log.h>

static unsigned char * DirWithThumbnailPtrs;
static double FocalplaneXRes;
static double FocalplaneUnits;
static int ExifImageWidth;
static int MotorolaOrder = 0;

// for fixing the rotation.
static void * OrientationPtr[2];
static int    OrientationNumFormat[2];
int NumOrientations = 0;


// Define the line below to turn on poor man's debugging output
#undef SUPERDEBUG

#ifdef SUPERDEBUG
#define printf ALOGE
#endif

//--------------------------------------------------------------------------
// Table of Jpeg encoding process names
static const TagTable_t ProcessTable[] = {
    { M_SOF0,   "Baseline", 0, 0},
    { M_SOF1,   "Extended sequential", 0, 0},
    { M_SOF2,   "Progressive", 0, 0},
    { M_SOF3,   "Lossless", 0, 0},
    { M_SOF5,   "Differential sequential", 0, 0},
    { M_SOF6,   "Differential progressive", 0, 0},
    { M_SOF7,   "Differential lossless", 0, 0},
    { M_SOF9,   "Extended sequential, arithmetic coding", 0, 0},
    { M_SOF10,  "Progressive, arithmetic coding", 0, 0},
    { M_SOF11,  "Lossless, arithmetic coding", 0, 0},
    { M_SOF13,  "Differential sequential, arithmetic coding", 0, 0},
    { M_SOF14,  "Differential progressive, arithmetic coding", 0, 0},
    { M_SOF15,  "Differential lossless, arithmetic coding", 0, 0},
};

#define PROCESS_TABLE_SIZE  (sizeof(ProcessTable) / sizeof(TagTable_t))

// 1 - "The 0th row is at the visual top of the image,    and the 0th column is the visual left-hand side."
// 2 - "The 0th row is at the visual top of the image,    and the 0th column is the visual right-hand side."
// 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side."
// 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side."

// 5 - "The 0th row is the visual left-hand side of of the image,  and the 0th column is the visual top."
// 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top."
// 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom."
// 8 - "The 0th row is the visual left-hand side of of the image,  and the 0th column is the visual bottom."

// Note: The descriptions here are the same as the name of the command line
// option to pass to jpegtran to right the image

static const char * OrientTab[9] = {
    "Undefined",
    "Normal",           // 1
    "flip horizontal",  // left right reversed mirror
    "rotate 180",       // 3
    "flip vertical",    // upside down mirror
    "transpose",        // Flipped about top-left <--> bottom-right axis.
    "rotate 90",        // rotate 90 cw to right it.
    "transverse",       // flipped about top-right <--> bottom-left axis
    "rotate 270",       // rotate 270 to right it.
};

const int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};

//--------------------------------------------------------------------------
// Describes tag values

#define TAG_INTEROP_INDEX          0x0001
#define TAG_INTEROP_VERSION        0x0002
#define TAG_IMAGE_WIDTH            0x0100
#define TAG_IMAGE_LENGTH           0x0101
#define TAG_BITS_PER_SAMPLE        0x0102
#define TAG_COMPRESSION            0x0103
#define TAG_PHOTOMETRIC_INTERP     0x0106
#define TAG_FILL_ORDER             0x010A
#define TAG_DOCUMENT_NAME          0x010D
#define TAG_IMAGE_DESCRIPTION      0x010E
#define TAG_MAKE                   0x010F
#define TAG_MODEL                  0x0110
#define TAG_SRIP_OFFSET            0x0111
#define TAG_ORIENTATION            0x0112
#define TAG_SAMPLES_PER_PIXEL      0x0115
#define TAG_ROWS_PER_STRIP         0x0116
#define TAG_STRIP_BYTE_COUNTS      0x0117
#define TAG_X_RESOLUTION           0x011A
#define TAG_Y_RESOLUTION           0x011B
#define TAG_PLANAR_CONFIGURATION   0x011C
#define TAG_RESOLUTION_UNIT        0x0128
#define TAG_TRANSFER_FUNCTION      0x012D
#define TAG_SOFTWARE               0x0131
#define TAG_DATETIME               0x0132
#define TAG_ARTIST                 0x013B
#define TAG_WHITE_POINT            0x013E
#define TAG_PRIMARY_CHROMATICITIES 0x013F
#define TAG_TRANSFER_RANGE         0x0156
#define TAG_JPEG_PROC              0x0200
#define TAG_THUMBNAIL_OFFSET       0x0201
#define TAG_THUMBNAIL_LENGTH       0x0202
#define TAG_Y_CB_CR_COEFFICIENTS   0x0211
#define TAG_Y_CB_CR_SUB_SAMPLING   0x0212
#define TAG_Y_CB_CR_POSITIONING    0x0213
#define TAG_REFERENCE_BLACK_WHITE  0x0214
#define TAG_RELATED_IMAGE_WIDTH    0x1001
#define TAG_RELATED_IMAGE_LENGTH   0x1002
#define TAG_CFA_REPEAT_PATTERN_DIM 0x828D
#define TAG_CFA_PATTERN1           0x828E
#define TAG_BATTERY_LEVEL          0x828F
#define TAG_COPYRIGHT              0x8298
#define TAG_EXPOSURETIME           0x829A
#define TAG_FNUMBER                0x829D
#define TAG_IPTC_NAA               0x83BB
#define TAG_EXIF_OFFSET            0x8769
#define TAG_INTER_COLOR_PROFILE    0x8773
#define TAG_EXPOSURE_PROGRAM       0x8822
#define TAG_SPECTRAL_SENSITIVITY   0x8824
#define TAG_GPSINFO                0x8825
#define TAG_ISO_EQUIVALENT         0x8827
#define TAG_OECF                   0x8828
#define TAG_EXIF_VERSION           0x9000
#define TAG_DATETIME_ORIGINAL      0x9003
#define TAG_DATETIME_DIGITIZED     0x9004
#define TAG_COMPONENTS_CONFIG      0x9101
#define TAG_CPRS_BITS_PER_PIXEL    0x9102
#define TAG_SHUTTERSPEED           0x9201
#define TAG_APERTURE               0x9202
#define TAG_BRIGHTNESS_VALUE       0x9203
#define TAG_EXPOSURE_BIAS          0x9204
#define TAG_MAXAPERTURE            0x9205
#define TAG_SUBJECT_DISTANCE       0x9206
#define TAG_METERING_MODE          0x9207
#define TAG_LIGHT_SOURCE           0x9208
#define TAG_FLASH                  0x9209
#define TAG_FOCALLENGTH            0x920A
#define TAG_MAKER_NOTE             0x927C
#define TAG_USERCOMMENT            0x9286
#define TAG_SUBSEC_TIME            0x9290
#define TAG_SUBSEC_TIME_ORIG       0x9291
#define TAG_SUBSEC_TIME_DIG        0x9292

#define TAG_WINXP_TITLE            0x9c9b // Windows XP - not part of exif standard.
#define TAG_WINXP_COMMENT          0x9c9c // Windows XP - not part of exif standard.
#define TAG_WINXP_AUTHOR           0x9c9d // Windows XP - not part of exif standard.
#define TAG_WINXP_KEYWORDS         0x9c9e // Windows XP - not part of exif standard.
#define TAG_WINXP_SUBJECT          0x9c9f // Windows XP - not part of exif standard.

#define TAG_FLASH_PIX_VERSION      0xA000
#define TAG_COLOR_SPACE            0xA001
#define TAG_EXIF_IMAGEWIDTH        0xA002
#define TAG_EXIF_IMAGELENGTH       0xA003
#define TAG_RELATED_AUDIO_FILE     0xA004
#define TAG_INTEROP_OFFSET         0xA005
#define TAG_FLASH_ENERGY           0xA20B
#define TAG_SPATIAL_FREQ_RESP      0xA20C
#define TAG_FOCAL_PLANE_XRES       0xA20E
#define TAG_FOCAL_PLANE_YRES       0xA20F
#define TAG_FOCAL_PLANE_UNITS      0xA210
#define TAG_SUBJECT_LOCATION       0xA214
#define TAG_EXPOSURE_INDEX         0xA215
#define TAG_SENSING_METHOD         0xA217
#define TAG_FILE_SOURCE            0xA300
#define TAG_SCENE_TYPE             0xA301
#define TAG_CFA_PATTERN            0xA302
#define TAG_CUSTOM_RENDERED        0xA401
#define TAG_EXPOSURE_MODE          0xA402
#define TAG_WHITEBALANCE           0xA403
#define TAG_DIGITALZOOMRATIO       0xA404
#define TAG_FOCALLENGTH_35MM       0xA405
#define TAG_SCENE_CAPTURE_TYPE     0xA406
#define TAG_GAIN_CONTROL           0xA407
#define TAG_CONTRAST               0xA408
#define TAG_SATURATION             0xA409
#define TAG_SHARPNESS              0xA40A
#define TAG_DISTANCE_RANGE         0xA40C

// TODO: replace the ", 0" values in this table with the correct format, e.g. ", FMT_USHORT"
static const TagTable_t TagTable[] = {
  { TAG_INTEROP_INDEX,          "InteropIndex", 0, 0},
  { TAG_INTEROP_VERSION,        "InteropVersion", 0, 0},
  { TAG_IMAGE_WIDTH,            "ImageWidth", FMT_USHORT, 1},
  { TAG_IMAGE_LENGTH,           "ImageLength", FMT_USHORT, 1},
  { TAG_BITS_PER_SAMPLE,        "BitsPerSample", FMT_USHORT, 3},
  { TAG_COMPRESSION,            "Compression", FMT_USHORT, 1},
  { TAG_PHOTOMETRIC_INTERP,     "PhotometricInterpretation", FMT_USHORT, 1},
  { TAG_FILL_ORDER,             "FillOrder", 0, 0},
  { TAG_DOCUMENT_NAME,          "DocumentName", 0, 0},
  { TAG_IMAGE_DESCRIPTION,      "ImageDescription", 0, 0 },
  { TAG_MAKE,                   "Make", FMT_STRING, -1},
  { TAG_MODEL,                  "Model", FMT_STRING, -1},
  { TAG_SRIP_OFFSET,            "StripOffsets", FMT_USHORT, 1},
  { TAG_ORIENTATION,            "Orientation", FMT_USHORT, 1},
  { TAG_SAMPLES_PER_PIXEL,      "SamplesPerPixel", FMT_USHORT, 3},
  { TAG_ROWS_PER_STRIP,         "RowsPerStrip", FMT_USHORT, 1},
  { TAG_STRIP_BYTE_COUNTS,      "StripByteCounts", FMT_USHORT, 1},
  { TAG_X_RESOLUTION,           "XResolution", FMT_URATIONAL, 1},
  { TAG_Y_RESOLUTION,           "YResolution", FMT_URATIONAL, 1},
  { TAG_PLANAR_CONFIGURATION,   "PlanarConfiguration", FMT_USHORT, 1},
  { TAG_RESOLUTION_UNIT,        "ResolutionUnit", FMT_USHORT, 1},
  { TAG_TRANSFER_FUNCTION,      "TransferFunction", FMT_USHORT, 768},
  { TAG_SOFTWARE,               "Software", FMT_STRING, -1},
  { TAG_DATETIME,               "DateTime", FMT_STRING, 20},
  { TAG_ARTIST,                 "Artist", FMT_STRING, -1},
  { TAG_WHITE_POINT,            "WhitePoint", FMT_SRATIONAL, 2},
  { TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities", FMT_SRATIONAL, 6},
  { TAG_TRANSFER_RANGE,         "TransferRange", 0, 0},
  { TAG_JPEG_PROC,              "JPEGProc", 0, 0},
  { TAG_THUMBNAIL_OFFSET,       "ThumbnailOffset", 0, 0},
  { TAG_THUMBNAIL_LENGTH,       "ThumbnailLength", 0, 0},
  { TAG_Y_CB_CR_COEFFICIENTS,   "YCbCrCoefficients", FMT_SRATIONAL, 3},
  { TAG_Y_CB_CR_SUB_SAMPLING,   "YCbCrSubSampling", FMT_USHORT, 2},
  { TAG_Y_CB_CR_POSITIONING,    "YCbCrPositioning", FMT_USHORT, 1},
  { TAG_REFERENCE_BLACK_WHITE,  "ReferenceBlackWhite", FMT_SRATIONAL, 6},
  { TAG_RELATED_IMAGE_WIDTH,    "RelatedImageWidth", 0, 0},
  { TAG_RELATED_IMAGE_LENGTH,   "RelatedImageLength", 0, 0},
  { TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim", 0, 0},
  { TAG_CFA_PATTERN1,           "CFAPattern", 0, 0},
  { TAG_BATTERY_LEVEL,          "BatteryLevel", 0, 0},
  { TAG_COPYRIGHT,              "Copyright", FMT_STRING, -1},
  { TAG_EXPOSURETIME,           "ExposureTime", FMT_SRATIONAL, 1},
  { TAG_FNUMBER,                "FNumber", FMT_SRATIONAL, 1},
  { TAG_IPTC_NAA,               "IPTC/NAA", 0, 0},
  { TAG_EXIF_OFFSET,            "ExifOffset", 0, 0},
  { TAG_INTER_COLOR_PROFILE,    "InterColorProfile", 0, 0},
  { TAG_EXPOSURE_PROGRAM,       "ExposureProgram", FMT_SSHORT, 1},
  { TAG_SPECTRAL_SENSITIVITY,   "SpectralSensitivity", FMT_STRING, -1},
  { TAG_GPSINFO,                "GPS Dir offset", 0, 0},
  { TAG_ISO_EQUIVALENT,         "ISOSpeedRatings", FMT_SSHORT, -1},
  { TAG_OECF,                   "OECF", 0, 0},
  { TAG_EXIF_VERSION,           "ExifVersion", FMT_BYTE, 4},
  { TAG_DATETIME_ORIGINAL,      "DateTimeOriginal", FMT_STRING, 20},
  { TAG_DATETIME_DIGITIZED,     "DateTimeDigitized", FMT_STRING, 20},
  { TAG_COMPONENTS_CONFIG,      "ComponentsConfiguration", FMT_BYTE, 4},
  { TAG_CPRS_BITS_PER_PIXEL,    "CompressedBitsPerPixel", FMT_SRATIONAL, 1},
  { TAG_SHUTTERSPEED,           "ShutterSpeedValue", FMT_SRATIONAL, 1},
  { TAG_APERTURE,               "ApertureValue", FMT_URATIONAL, 1},
  { TAG_BRIGHTNESS_VALUE,       "BrightnessValue", FMT_SRATIONAL, 1},
  { TAG_EXPOSURE_BIAS,          "ExposureBiasValue", FMT_SRATIONAL, 1},
  { TAG_MAXAPERTURE,            "MaxApertureValue", FMT_URATIONAL, 1},
  { TAG_SUBJECT_DISTANCE,       "SubjectDistance", FMT_URATIONAL, 1},
  { TAG_METERING_MODE,          "MeteringMode", FMT_USHORT, 1},
  { TAG_LIGHT_SOURCE,           "LightSource", FMT_USHORT, 1},
  { TAG_FLASH,                  "Flash", FMT_USHORT, 1},
  { TAG_FOCALLENGTH,            "FocalLength", FMT_URATIONAL, 1},
  { TAG_MAKER_NOTE,             "MakerNote", FMT_STRING, -1},
  { TAG_USERCOMMENT,            "UserComment", FMT_STRING, -1},
  { TAG_SUBSEC_TIME,            "SubSecTime", FMT_STRING, -1},
  { TAG_SUBSEC_TIME_ORIG,       "SubSecTimeOriginal", FMT_STRING, -1},
  { TAG_SUBSEC_TIME_DIG,        "SubSecTimeDigitized", FMT_STRING, -1},
  { TAG_WINXP_TITLE,            "Windows-XP Title", 0, 0},
  { TAG_WINXP_COMMENT,          "Windows-XP comment", 0, 0},
  { TAG_WINXP_AUTHOR,           "Windows-XP author", 0, 0},
  { TAG_WINXP_KEYWORDS,         "Windows-XP keywords", 0, 0},
  { TAG_WINXP_SUBJECT,          "Windows-XP subject", 0, 0},
  { TAG_FLASH_PIX_VERSION,      "FlashPixVersion", FMT_BYTE, 4},
  { TAG_COLOR_SPACE,            "ColorSpace", FMT_USHORT, 1},
  { TAG_EXIF_IMAGEWIDTH,        "ExifImageWidth", 0, 0},
  { TAG_EXIF_IMAGELENGTH,       "ExifImageLength", 0, 0},
  { TAG_RELATED_AUDIO_FILE,     "RelatedAudioFile", 0, 0},
  { TAG_INTEROP_OFFSET,         "InteroperabilityOffset", 0, 0},
  { TAG_FLASH_ENERGY,           "FlashEnergy", FMT_URATIONAL, 1},
  { TAG_SPATIAL_FREQ_RESP,      "SpatialFrequencyResponse", FMT_STRING, -1},
  { TAG_FOCAL_PLANE_XRES,       "FocalPlaneXResolution", FMT_URATIONAL, 1},
  { TAG_FOCAL_PLANE_YRES,       "FocalPlaneYResolution", FMT_URATIONAL, 1},
  { TAG_FOCAL_PLANE_UNITS,      "FocalPlaneResolutionUnit", FMT_USHORT, 1},
  { TAG_SUBJECT_LOCATION,       "SubjectLocation", FMT_USHORT, 2},
  { TAG_EXPOSURE_INDEX,         "ExposureIndex", FMT_URATIONAL, 1},
  { TAG_SENSING_METHOD,         "SensingMethod", FMT_USHORT, 1},
  { TAG_FILE_SOURCE,            "FileSource", 0, 1},
  { TAG_SCENE_TYPE,             "SceneType", 0, 1},
  { TAG_CFA_PATTERN,            "CFA Pattern", 0, -1},
  { TAG_CUSTOM_RENDERED,        "CustomRendered", FMT_USHORT, 1},
  { TAG_EXPOSURE_MODE,          "ExposureMode", FMT_USHORT, 1},
  { TAG_WHITEBALANCE,           "WhiteBalance", FMT_USHORT, 1},
  { TAG_DIGITALZOOMRATIO,       "DigitalZoomRatio", FMT_URATIONAL, 1},
  { TAG_FOCALLENGTH_35MM,       "FocalLengthIn35mmFilm", FMT_USHORT, 1},
  { TAG_SCENE_CAPTURE_TYPE,     "SceneCaptureType", FMT_USHORT, 1},
  { TAG_GAIN_CONTROL,           "GainControl", FMT_URATIONAL, 1},
  { TAG_CONTRAST,               "Contrast", FMT_USHORT, 1},
  { TAG_SATURATION,             "Saturation", FMT_USHORT, 1},
  { TAG_SHARPNESS,              "Sharpness", FMT_USHORT, 1},
  { TAG_DISTANCE_RANGE,         "SubjectDistanceRange", FMT_USHORT, 1},
} ;

#define TAG_TABLE_SIZE  (sizeof(TagTable) / sizeof(TagTable_t))

int TagNameToValue(const char* tagName)
{
    unsigned int i;
    for (i = 0; i < TAG_TABLE_SIZE; i++) {
        if (strcmp(TagTable[i].Desc, tagName) == 0) {
            printf("found tag %s val %d", TagTable[i].Desc, TagTable[i].Tag);
            return TagTable[i].Tag;
        }
    }
    printf("tag %s NOT FOUND", tagName);
    return -1;
}

int IsDateTimeTag(unsigned short tag)
{
    return ((tag == TAG_DATETIME)? TRUE: FALSE);
}

//--------------------------------------------------------------------------
// Convert a 16 bit unsigned value to file's native byte order
//--------------------------------------------------------------------------
static void Put16u(void * Short, unsigned short PutValue)
{
    if (MotorolaOrder){
        ((uchar *)Short)[0] = (uchar)(PutValue>>8);
        ((uchar *)Short)[1] = (uchar)PutValue;
    }else{
        ((uchar *)Short)[0] = (uchar)PutValue;
        ((uchar *)Short)[1] = (uchar)(PutValue>>8);
    }
}

//--------------------------------------------------------------------------
// Convert a 16 bit unsigned value from file's native byte order
//--------------------------------------------------------------------------
int Get16u(void * Short)
{
    if (MotorolaOrder){
        return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
    }else{
        return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0];
    }
}

//--------------------------------------------------------------------------
// Convert a 32 bit signed value from file's native byte order
//--------------------------------------------------------------------------
int Get32s(void * Long)
{
    if (MotorolaOrder){
        return  ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16)
              | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 );
    }else{
        return  ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16)
              | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 );
    }
}

//--------------------------------------------------------------------------
// Convert a 32 bit unsigned value to file's native byte order
//--------------------------------------------------------------------------
void Put32u(void * Value, unsigned PutValue)
{
    if (MotorolaOrder){
        ((uchar *)Value)[0] = (uchar)(PutValue>>24);
        ((uchar *)Value)[1] = (uchar)(PutValue>>16);
        ((uchar *)Value)[2] = (uchar)(PutValue>>8);
        ((uchar *)Value)[3] = (uchar)PutValue;
    }else{
        ((uchar *)Value)[0] = (uchar)PutValue;
        ((uchar *)Value)[1] = (uchar)(PutValue>>8);
        ((uchar *)Value)[2] = (uchar)(PutValue>>16);
        ((uchar *)Value)[3] = (uchar)(PutValue>>24);
    }
}

//--------------------------------------------------------------------------
// Convert a 32 bit unsigned value from file's native byte order
//--------------------------------------------------------------------------
unsigned Get32u(void * Long)
{
    return (unsigned)Get32s(Long) & 0xffffffff;
}

//--------------------------------------------------------------------------
// Display a number as one of its many formats
//--------------------------------------------------------------------------
void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount)
{
    int s,n;

    for(n=0;n<16;n++){
        switch(Format){
            case FMT_SBYTE:
            case FMT_BYTE:      printf("%02x",*(uchar *)ValuePtr); s=1;  break;
            case FMT_USHORT:    printf("%d",Get16u(ValuePtr)); s=2;      break;
            case FMT_ULONG:
            case FMT_SLONG:     printf("%d",Get32s(ValuePtr)); s=4;      break;
            case FMT_SSHORT:    printf("%hd",(signed short)Get16u(ValuePtr)); s=2; break;
            case FMT_URATIONAL:
            case FMT_SRATIONAL:
               printf("%d/%d",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr));
               s = 8;
               break;

            case FMT_SINGLE:    printf("%f",(double)*(float *)ValuePtr); s=8; break;
            case FMT_DOUBLE:    printf("%f",*(double *)ValuePtr);        s=8; break;
            default:
                printf("Unknown format %d:", Format);
                return;
        }
        ByteCount -= s;
        if (ByteCount <= 0) break;
        printf(", ");
        ValuePtr = (void *)((char *)ValuePtr + s);

    }
    if (n >= 16) printf("...");
}


//--------------------------------------------------------------------------
// Evaluate number, be it int, rational, or float from directory.
//--------------------------------------------------------------------------
double ConvertAnyFormat(void * ValuePtr, int Format)
{
    double Value;
    Value = 0;

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

        case FMT_USHORT:    Value = Get16u(ValuePtr);          break;
        case FMT_ULONG:     Value = Get32u(ValuePtr);          break;

        case FMT_URATIONAL:
        case FMT_SRATIONAL:
            {
                int Num,Den;
                Num = Get32s(ValuePtr);
                Den = Get32s(4+(char *)ValuePtr);
                if (Den == 0){
                    Value = 0;
                }else{
                    Value = (double)Num/Den;
                }
                break;
            }

        case FMT_SSHORT:    Value = (signed short)Get16u(ValuePtr);  break;
        case FMT_SLONG:     Value = Get32s(ValuePtr);                break;

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

        default:
            ErrNonfatal("Illegal format code %d",Format,0);
    }
    return Value;
}

//--------------------------------------------------------------------------
// Convert a double value into a signed or unsigned rational number.
//--------------------------------------------------------------------------
static void float2urat(double value, unsigned int max, unsigned int *numerator,
                       unsigned int *denominator) {
    if (value <= 0) {
        *numerator = 0;
        *denominator = 1;
        return;
    }

    if (value > max) {
        *numerator = max;
        *denominator = 1;
        return;
    }

    // For values less than 1e-9, scale as much as possible
    if (value < 1e-9) {
        unsigned int n = (unsigned int)(value * max);
        if (n == 0) {
            *numerator = 0;
            *denominator = 1;
        } else {
            *numerator = n;
            *denominator = max;
        }
        return;
    }

    // Try to use a denominator of 1e9, 1e8, ..., until the numerator fits
    unsigned int d;
    for (d = 1000000000; d >= 1; d /= 10) {
        double s = value * d;
        if (s <= max) {
            // Remove the trailing zeros from both.
            unsigned int n = (unsigned int)s;
            while (n % 10 == 0 && d >= 10) {
                n /= 10;
                d /= 10;
            }
            *numerator = n;
            *denominator = d;
            return;
        }
    }

    // Shouldn't reach here because the denominator 1 should work
    // above. But just in case.
    *numerator = 0;
    *denominator = 1;
}

static void ConvertDoubleToURational(double value, unsigned int *numerator,
                                     unsigned int *denominator) {
    float2urat(value, 0xFFFFFFFFU, numerator, denominator);
}

static void ConvertDoubleToSRational(double value, int *numerator,
                                     int *denominator) {
    int negative = 0;

    if (value < 0) {
        value = -value;
        negative = 1;
    }

    unsigned int n, d;
    float2urat(value, 0x7FFFFFFFU, &n, &d);
    *numerator = (int)n;
    *denominator = (int)d;
    if (negative) {
        *numerator = -*numerator;
    }
}

//--------------------------------------------------------------------------
// Process one of the nested EXIF directories.
//--------------------------------------------------------------------------
static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase,
        unsigned ExifLength, int NestingLevel)
{
    int de;
    int a;
    int NumDirEntries;
    unsigned ThumbnailOffset = 0;
    unsigned ThumbnailSize = 0;
    char IndentString[25];

    printf("ProcessExifDir");
    if (NestingLevel > 4){
        ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0);
        return;
    }

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


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

    {
        unsigned char * DirEnd;
        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 exif subdirectory (%d entries)",NumDirEntries,0);
                return;
            }
        }
        if (DumpExifMap){
            printf("Map: %05d-%05d: Directory\n",(int)(DirStart-OffsetBase), (int)(DirEnd+4-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 > UINT32_MAX - ByteCount || OffsetVal+ByteCount > ExifLength){
                // Bogus pointer offset and / or bytecount value
                ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
                continue;
            }
            ValuePtr = OffsetBase+OffsetVal;

            if (OffsetVal > ImageInfo.LargestExifOffset){
                ImageInfo.LargestExifOffset = OffsetVal;
            }

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

        if (Tag == TAG_MAKER_NOTE){
            if (ShowTags){
                printf("%s    Maker note: ",IndentString);
            }
            ProcessMakerNote(ValuePtr, ByteCount, OffsetBase, ExifLength);
            continue;
        }

        if (ShowTags){
            // Show tag name
            for (a=0;;a++){
                if (a >= (int)TAG_TABLE_SIZE){
                    printf("%s", IndentString);
                    printf("    Unknown Tag %04x Value = ", Tag);
                    break;
                }
                if (TagTable[a].Tag == Tag){
                    printf("%s", IndentString);
                    printf("    %s = ",TagTable[a].Desc);
                    break;
                }
            }

            // Show tag value.
            switch(Format){
                case FMT_BYTE:
                    if(ByteCount>1){
                        printf("%.*ls\n", ByteCount/2, (wchar_t *)ValuePtr);
                    }else{
                        PrintFormatNumber(ValuePtr, Format, ByteCount);
                        printf("\n");
                    }
                    break;

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

                case FMT_STRING:
                    // String arrays printed without function call (different from int arrays)
                    {
                          printf("\"%s\"", ValuePtr);
//                        int NoPrint = 0;
//                        printf("\"");
//                        for (a=0;a<ByteCount;a++){
//                            if (ValuePtr[a] >= 32){
//                                putchar(ValuePtr[a]);
//                                NoPrint = 0;
//                            }else{
//                                // Avoiding indicating too many unprintable characters of proprietary
//                                // bits of binary information this program may not know how to parse.
//                                if (!NoPrint && a != ByteCount-1){
//                                    putchar('?');
//                                    NoPrint = 1;
//                                }
//                            }
//                        }
//                        printf("\"\n");
                    }
                    break;

                default:
                    // Handle arrays of numbers later (will there ever be?)
                    PrintFormatNumber(ValuePtr, Format, ByteCount);
                    printf("\n");
            }
        }

        // Extract useful components of tag
        switch(Tag){

            case TAG_MAKE:
                strncpy(ImageInfo.CameraMake, (char *)ValuePtr, ByteCount < 31 ? ByteCount : 31);
                break;

            case TAG_MODEL:
                strncpy(ImageInfo.CameraModel, (char *)ValuePtr, ByteCount < 39 ? ByteCount : 39);
                break;

            case TAG_SUBSEC_TIME:
                strlcpy(ImageInfo.SubSecTime, (char *)ValuePtr, sizeof(ImageInfo.SubSecTime));
                break;

            case TAG_SUBSEC_TIME_ORIG:
                strlcpy(ImageInfo.SubSecTimeOrig, (char *)ValuePtr,
                        sizeof(ImageInfo.SubSecTimeOrig));
                break;

            case TAG_SUBSEC_TIME_DIG:
                strlcpy(ImageInfo.SubSecTimeDig, (char *)ValuePtr,
                        sizeof(ImageInfo.SubSecTimeDig));
                break;

            case TAG_DATETIME_DIGITIZED:
                strlcpy(ImageInfo.DigitizedTime, (char *)ValuePtr,
                        sizeof(ImageInfo.DigitizedTime));

                if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES){
                    ErrNonfatal("More than %d date fields!  This is nuts", MAX_DATE_COPIES, 0);
                    break;
                }
                ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] =
                    (char *)ValuePtr - (char *)OffsetBase;
                break;

            case TAG_DATETIME_ORIGINAL:
                // If we get a DATETIME_ORIGINAL, we use that one.
                strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
                // Fallthru...

            case TAG_DATETIME:
                if (!isdigit(ImageInfo.DateTime[0])){
                    // If we don't already have a DATETIME_ORIGINAL, use whatever
                    // time fields we may have.
                    strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19);
                }

                if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES){
                    ErrNonfatal("More than %d date fields!  This is nuts", MAX_DATE_COPIES, 0);
                    break;
                }
                ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] =
                    (char *)ValuePtr - (char *)OffsetBase;
                break;

            case TAG_WINXP_COMMENT:
                if (ImageInfo.Comments[0]){ // We already have a jpeg comment.
                    // Already have a comment (probably windows comment), skip this one.
                    if (ShowTags) printf("Windows XP commend and other comment in header\n");
                    break; // Already have a windows comment, skip this one.
                }

                if (ByteCount > 1){
                    if (ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE;
                    memcpy(ImageInfo.Comments, ValuePtr, ByteCount);
                    ImageInfo.CommentWidchars = ByteCount/2;
                }
                break;

            case TAG_USERCOMMENT:
                if (ImageInfo.Comments[0]){ // We already have a jpeg comment.
                    // Already have a comment (probably windows comment), skip this one.
                    if (ShowTags) printf("Multiple comments in exif header\n");
                    break; // Already have a windows comment, skip this one.
                }

                // Comment is often padded with trailing spaces.  Remove these first.
                for (a=ByteCount;;){
                    a--;
                    if ((ValuePtr)[a] == ' '){
                        (ValuePtr)[a] = '\0';
                    }else{
                        break;
                    }
                    if (a == 0) break;
                }

                // Copy the comment
                {
                    // We want to set copied comment length (msize) to be the
                    // minimum of:
                    // (1) The space still available in Exif
                    // (2) The given comment length (ByteCount)
                    // (3) MAX_COMMENT_SIZE - 1
                    int msiz = ExifLength - (ValuePtr-OffsetBase);
                    if (msiz > ByteCount) msiz = ByteCount;
                    if (msiz > MAX_COMMENT_SIZE - 1) msiz = MAX_COMMENT_SIZE - 1;
                    if (msiz > 5 && memcmp(ValuePtr, "ASCII", 5) == 0) {
                        for (a = 5; a < 10 && a < msiz; a++) {
                            int c = (ValuePtr)[a];
                            if (c != '\0' && c != ' ') {
                                strncpy(ImageInfo.Comments,
                                        (char *)ValuePtr + a, msiz - a);
                                break;
                            }
                        }
                    } else {
                        strncpy(ImageInfo.Comments, (char *)ValuePtr, msiz);
                    }
                }
                break;

            case TAG_FNUMBER:
                // Simplest way of expressing aperture, so I trust it the most.
                // (overwrite previously computd value if there is one)
                ImageInfo.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 (ImageInfo.ApertureFNumber == 0){
                    ImageInfo.ApertureFNumber
                        = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5);
                }
                break;

            case TAG_FOCALLENGTH:
                // Nice digital cameras actually save the focal length as a function
                // of how farthey are zoomed in.
                ImageInfo.FocalLength.num = Get32u(ValuePtr);
                ImageInfo.FocalLength.denom = Get32u(4+(char *)ValuePtr);
                break;

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

            case TAG_EXPOSURETIME:
                // Simplest way of expressing exposure time, so I trust it most.
                // (overwrite previously computd value if there is one)
                ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format);
                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 (ImageInfo.ExposureTime == 0){
                    ImageInfo.ExposureTime
                        = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2)));
                }
                break;


            case TAG_FLASH:
                ImageInfo.FlashUsed=(int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_ORIENTATION:
                if (NumOrientations >= 2){
                    // Can have another orientation tag for the thumbnail, but if there's
                    // a third one, things are stringae.
                    ErrNonfatal("More than two orientation tags!",0,0);
                    break;
                }
                OrientationPtr[NumOrientations] = ValuePtr;
                OrientationNumFormat[NumOrientations] = Format;
                if (NumOrientations == 0){
                    ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
                }
                if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8){
                    ErrNonfatal("Undefined rotation value %d", ImageInfo.Orientation, 0);
                    ImageInfo.Orientation = 0;
                }
                NumOrientations += 1;
                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.
                a = (int)ConvertAnyFormat(ValuePtr, Format);
                if (ExifImageWidth < a) ExifImageWidth = a;
                break;

            case TAG_FOCAL_PLANE_XRES:
                FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_FOCAL_PLANE_UNITS:
                switch((int)ConvertAnyFormat(ValuePtr, Format)){
                    case 1: FocalplaneUnits = 25.4; break; // inch
                    case 2:
                        // According to the information I was using, 2 means meters.
                        // But looking at the Cannon powershot's files, inches is the only
                        // sensible value.
                        FocalplaneUnits = 25.4;
                        break;

                    case 3: FocalplaneUnits = 10;   break;  // centimeter
                    case 4: FocalplaneUnits = 1;    break;  // millimeter
                    case 5: FocalplaneUnits = .001; break;  // micrometer
                }
                break;

            case TAG_EXPOSURE_BIAS:
                ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_WHITEBALANCE:
                ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_LIGHT_SOURCE:
                ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_METERING_MODE:
                ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_EXPOSURE_PROGRAM:
                ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

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

            case TAG_EXPOSURE_MODE:
                ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_ISO_EQUIVALENT:
                ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_DIGITALZOOMRATIO:
                ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_THUMBNAIL_OFFSET:
                ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format);
                DirWithThumbnailPtrs = DirStart;
                break;

            case TAG_THUMBNAIL_LENGTH:
                ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format);
                ImageInfo.ThumbnailSizeOffset = ValuePtr-OffsetBase;
                break;

            case TAG_EXIF_OFFSET:
                if (ShowTags) printf("%s    Exif Dir:",IndentString);

            case TAG_INTEROP_OFFSET:
                if (Tag == TAG_INTEROP_OFFSET && ShowTags) printf("%s    Interop Dir:",IndentString);
                {
                    unsigned char * SubdirStart;
                    SubdirStart = OffsetBase + Get32u(ValuePtr);
                    if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){
                        ErrNonfatal("Illegal exif or interop ofset directory link",0,0);
                    }else{
                        ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
                    }
                    continue;
                }
                break;

            case TAG_GPSINFO:
                if (ShowTags) printf("%s    GPS info dir:",IndentString);
                {
                    unsigned char * SubdirStart;
                    SubdirStart = OffsetBase + Get32u(ValuePtr);
                    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.
                ImageInfo.FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format);
                break;

            case TAG_DISTANCE_RANGE:
                // Three possible standard values:
                //   1 = macro, 2 = close, 3 = distant
                ImageInfo.DistanceRange = (int)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 char * SubdirStart;
        unsigned Offset;

        if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){
            printf("DirStart %p offset from dirstart %d", DirStart, 2+12*NumDirEntries);
            Offset = Get32u(DirStart+2+12*NumDirEntries);
            if (Offset){
                SubdirStart = OffsetBase + Offset;
                if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase){
                    printf("SubdirStart %p OffsetBase %p ExifLength %d Offset %d",
                        SubdirStart, OffsetBase, ExifLength, Offset);
                    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
                        if (ShowTags) printf("Thumbnail removed with Jhead 1.3 or earlier\n");
                    }else{
                        ErrNonfatal("Illegal subdirectory link",0,0);
                    }
                }else{
                    if (SubdirStart <= OffsetBase+ExifLength){
                        if (ShowTags) printf("%s    Continued directory ",IndentString);
                        ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
                    }
                }
                if (Offset > ImageInfo.LargestExifOffset){
                    ImageInfo.LargestExifOffset = Offset;
                }
            }
        }else{
            // The exif header ends before the last next directory pointer.
        }
    }

    if (ThumbnailOffset){
        ImageInfo.ThumbnailAtEnd = FALSE;

        if (DumpExifMap){
            printf("Map: %05d-%05d: Thumbnail\n",ThumbnailOffset, ThumbnailOffset+ThumbnailSize);
        }

        if (ThumbnailOffset <= ExifLength){
            if (ThumbnailSize > ExifLength-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.
                ThumbnailSize = ExifLength-ThumbnailOffset;
                if (ShowTags) printf("Thumbnail incorrectly placed in header\n");

            }
            // The thumbnail pointer appears to be valid.  Store it.
            ImageInfo.ThumbnailOffset = ThumbnailOffset;
            ImageInfo.ThumbnailSize = ThumbnailSize;

            if (ShowTags){
                printf("Thumbnail size: %d bytes\n",ThumbnailSize);
            }
        }
    }
    printf("returning from ProcessExifDir");
}


//--------------------------------------------------------------------------
// Process a EXIF marker
// Describes all the drivel that most digital cameras include...
//--------------------------------------------------------------------------
void process_EXIF (unsigned char * ExifSection, unsigned int length)
{
    unsigned FirstOffset;

    FocalplaneXRes = 0;
    FocalplaneUnits = 0;
    ExifImageWidth = 0;
    NumOrientations = 0;

    if (ShowTags){
        printf("Exif header %d bytes long\n",length);
    }

    {   // Check the EXIF header component
        static uchar ExifHeader[] = "Exif\0\0";
        if (memcmp(ExifSection+2, ExifHeader,6)){
            ErrNonfatal("Incorrect Exif header",0,0);
            return;
        }
    }

    if (memcmp(ExifSection+8,"II",2) == 0){
        if (ShowTags) printf("Exif section in Intel order\n");
        MotorolaOrder = 0;
    }else{
        if (memcmp(ExifSection+8,"MM",2) == 0){
            if (ShowTags) printf("Exif section in Motorola order\n");
            MotorolaOrder = 1;
        }else{
            ErrNonfatal("Invalid Exif alignment marker.",0,0);
            return;
        }
    }

    // Check the next value for correctness.
    if (Get16u(ExifSection+10) != 0x2a){
        ErrNonfatal("Invalid Exif start (1)",0,0);
        return;
    }

    FirstOffset = Get32u(ExifSection+12);
    if (FirstOffset < 8 || FirstOffset+8 >= length) {
        ErrNonfatal("Invalid offset of first IFD value: %u", FirstOffset, 0);
        return;
    }

    DirWithThumbnailPtrs = NULL;


    // First directory starts 16 bytes in.  All offset are relative to 8 bytes in.
    ProcessExifDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0);

    ImageInfo.ThumbnailAtEnd = ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset ? TRUE : FALSE;
#ifdef SUPERDEBUG
    printf("Thumbnail %s end", (ImageInfo.ThumbnailAtEnd ? "at" : "NOT at"));
#endif
    if (DumpExifMap){
        unsigned a,b;
        printf("Map: %05d- End of exif\n",length-8);
//        for (a=0;a<length-8;a+= 10){
//            printf("Map: %05d ",a);
//            for (b=0;b<10;b++) printf(" %02x",*(ExifSection+8+a+b));
//            printf("\n");
//        }
        for (a = 0; a < length - 8; ++a) {
            unsigned char c = *(ExifSection+8+a);
            unsigned pc = isprint(c) ? c : ' ';
            printf("Map: %4d %02x %c", a, c, pc);
        }
    }


    // Compute the CCD width, in millimeters.
    if (FocalplaneXRes != 0){
        // Note: With some cameras, its not possible to compute this correctly because
        // they don't adjust the indicated focal plane resolution units when using less
        // than maximum resolution, so the CCDWidth value comes out too small.  Nothing
        // that Jhad can do about it - its a camera problem.
        ImageInfo.CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);

        if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0
            && ImageInfo.FocalLength35mmEquiv == 0){
            // Compute 35 mm equivalent focal length based on sensor geometry if we haven't
            // already got it explicitly from a tag.
            ImageInfo.FocalLength35mmEquiv = (int)(
                (double)ImageInfo.FocalLength.num / ImageInfo.FocalLength.denom
                / ImageInfo.CCDWidth * 36 + 0.5);
        }
    }
}

static const TagTable_t* TagToTagTableEntry(unsigned short tag)
{
    unsigned int i;
    for (i = 0; i < TAG_TABLE_SIZE; i++) {
        if (TagTable[i].Tag == tag) {
            printf("found tag %d", tag);
            int format = TagTable[i].Format;
            if (format == 0) {
                printf("tag %s format not defined ***** YOU MUST ADD THE FORMAT TO THE TagTable in exif.c!!!!", TagTable[i].Desc);
                return NULL;
            }
            return &TagTable[i];
        }
    }
    printf("tag %d NOT FOUND", tag);
    return NULL;
}

static void writeExifTagAndData(int tag,
                                int format,
                                long components,
                                long value,
                                int valueInString,
                                char* Buffer,
                                int* DirIndex,
                                int* DataWriteIndex) {
    void* componentsPosition = NULL; // for saving component position

    Put16u(Buffer+ (*DirIndex), tag);                    // Tag
    Put16u(Buffer+(*DirIndex) + 2, format);              // Format
    if (format == FMT_STRING && components == -1) {
        components = strlen((char*)value) + 1;                 // account for null terminator
        if (components & 1) ++components;               // no odd lengths
    } else if (format == FMT_SSHORT && components == -1) {
        // jhead only supports reading one SSHORT anyway
        components = 1;
    }
    if (format == FMT_UNDEFINED && components == -1) {
        // check if this UNDEFINED format is actually ASCII (as it usually is)
        // if so, we can calculate the size
        if(memcmp((char*)value, ExifAsciiPrefix, sizeof(ExifAsciiPrefix)) == 0) {
            components = sizeof(ExifAsciiPrefix) +
                         strlen((char*)value + sizeof(ExifAsciiPrefix)) + 1;
            if (components & 1) ++components;               // no odd lengths
        }
    }
    Put32u(Buffer+(*DirIndex) + 4, components);         // Components
    componentsPosition = Buffer+(*DirIndex) + 4; // components # can change for lists
    printf("# components: %ld", components);
    if (format == FMT_STRING) {
        // short strings can fit right in the long, otherwise have to
        // go in the data area
        if (components <= 4) {
            strcpy(Buffer+(*DirIndex) + 8, (char*)value);
        } else {
            Put32u(Buffer+(*DirIndex) + 8, (*DataWriteIndex)-8);   // Pointer
            printf("copying value %s to %d", (char*)value, (*DataWriteIndex));
            strncpy(Buffer+(*DataWriteIndex), (char*)value, components);
            (*DataWriteIndex) += components;
        }
    } else if ((format == FMT_UNDEFINED) &&
               (memcmp((char*)value, ExifAsciiPrefix, sizeof(ExifAsciiPrefix)) == 0)) {
        // short strings can fit right in the long, otherwise have to
        // go in the data area
        if (components <= 4) {
            memcpy(Buffer+(*DirIndex) + 8, (char*)value, components);
        } else {
            Put32u(Buffer+(*DirIndex) + 8, (*DataWriteIndex)-8);   // Pointer
            printf("copying %s to %d", (char*)value + sizeof(ExifAsciiPrefix), (*DataWriteIndex));
            memcpy(Buffer+(*DataWriteIndex), (char*)value, components);
            (*DataWriteIndex) += components;
        }
    } else if (!valueInString) {
        Put32u(Buffer+(*DirIndex) + 8, value);   // Value
    } else {
        Put32u(Buffer+(*DirIndex) + 8, (*DataWriteIndex)-8);   // Pointer
        // Usually the separator is ',', but sometimes ':' is used, like
        // TAG_GPS_TIMESTAMP.
        char* curElement = strtok((char*)value, ",:");
        int i;

        // (components == -1) Need to handle lists with unknown length too
        for (i = 0; ((i < components) || (components == -1)) && curElement != NULL; i++) {
#ifdef SUPERDEBUG
            printf("processing component %s format %s", curElement, formatStr(format));
#endif
            // elements are separated by commas
            if (format == FMT_URATIONAL) {
                unsigned int numerator;
                unsigned int denominator;
                char* separator = strchr(curElement, '/');
                if (separator) {
                    numerator = atoi(curElement);
                    denominator = atoi(separator + 1);
                } else {
                    double value = atof(curElement);
                    ConvertDoubleToURational(value, &numerator, &denominator);
                }
                Put32u(Buffer+(*DataWriteIndex), numerator);
                Put32u(Buffer+(*DataWriteIndex) + 4, denominator);
                (*DataWriteIndex) += 8;
            } else if (format == FMT_SRATIONAL) {
                int numerator;
                int denominator;
                char* separator = strchr(curElement, '/');
                if (separator) {
                    numerator = atoi(curElement);
                    denominator = atoi(separator + 1);
                } else {
                    double value = atof(curElement);
                    ConvertDoubleToSRational(value, &numerator, &denominator);
                }
                Put32u(Buffer+(*DataWriteIndex), numerator);
                Put32u(Buffer+(*DataWriteIndex) + 4, denominator);
                (*DataWriteIndex) += 8;
            } else if ((components == -1) && ((format == FMT_USHORT) || (format == FMT_SSHORT))) {
                // variable components need to go into data write area
                value = atoi(curElement);
                Put16u(Buffer+(*DataWriteIndex), value);
                (*DataWriteIndex) += 4;
            } else {
                // TODO: doesn't handle multiple components yet -- if more than one, have to put in data write area.
                value = atoi(curElement);
                Put32u(Buffer+(*DirIndex) + 8, value);   // Value
            }
            curElement = strtok(NULL, ",:");
        }
        if (components == -1) Put32u(componentsPosition, i); // update component # for unknowns
    }
    (*DirIndex) += 12;
}

#ifdef SUPERDEBUG
char* formatStr(int format) {
    switch (format) {
        case FMT_BYTE: return "FMT_BYTE"; break;
        case FMT_STRING: return "FMT_STRING"; break;
        case FMT_USHORT: return "FMT_USHORT"; break;
        case FMT_ULONG: return "FMT_ULONG"; break;
        case FMT_URATIONAL: return "FMT_URATIONAL"; break;
        case FMT_SBYTE: return "FMT_SBYTE"; break;
        case FMT_UNDEFINED: return "FMT_UNDEFINED"; break;
        case FMT_SSHORT: return "FMT_SSHORT"; break;
        case FMT_SLONG: return "FMT_SLONG"; break;
        case FMT_SRATIONAL: return "FMT_SRATIONAL"; break;
        case FMT_SINGLE: return "FMT_SINGLE"; break;
        case FMT_DOUBLE: return "FMT_SINGLE"; break;
        default: return "UNKNOWN";
    }
}
#endif

//--------------------------------------------------------------------------
// Create minimal exif header - just date and thumbnail pointers,
// so that date and thumbnail may be filled later.
//--------------------------------------------------------------------------
static void create_EXIF_internal(ExifElement_t* elements, int exifTagCount, int gpsTagCount, int hasDateTimeTag, char* Buffer)
{
    unsigned short NumEntries;
    int DataWriteIndex;
    int DirIndex;
    int DirExifLink = 0;

#ifdef SUPERDEBUG
    ALOGE("create_EXIF %d exif elements, %d gps elements", exifTagCount, gpsTagCount);
#endif

    MotorolaOrder = 0;

    memcpy(Buffer+2, "Exif\0\0II",8);
    Put16u(Buffer+10, 0x2a);

    DataWriteIndex = 16;
    Put32u(Buffer+12, DataWriteIndex-8); // first IFD offset.  Means start 16 bytes in.

    {
        DirIndex = DataWriteIndex;
        NumEntries = 1 + exifTagCount;  // the extra is the thumbnail
        if (gpsTagCount) {
            ++NumEntries;       // allow for the GPS info tag
        }
        if (!hasDateTimeTag) {
            // We have to write extra date time tag. The entry number should be
            // adjusted.
            ++NumEntries;
        }
        DataWriteIndex += 2 + NumEntries*12 + 4;

        Put16u(Buffer+DirIndex, NumEntries); // Number of entries
        DirIndex += 2;

        // Entries go here...
        if (!hasDateTimeTag) {
            // Date/time entry
            char* dateTime = NULL;
            char dateBuf[20];
            if (ImageInfo.numDateTimeTags) {
                // If we had a pre-existing exif header, use time from that.
                dateTime = ImageInfo.DateTime;
            } else {
                // Oterwise, use the file's timestamp.
                FileTimeAsString(dateBuf);
                dateTime = dateBuf;
            }
            writeExifTagAndData(TAG_DATETIME,
                                FMT_STRING,
                                20,
                                (long)(char*)dateBuf,
                                FALSE,
                                Buffer,
                                &DirIndex,
                                &DataWriteIndex);

        }
        if (exifTagCount > 0) {
            int i;
            for (i = 0; i < exifTagCount + gpsTagCount; i++) {
                if (elements[i].GpsTag) {
                    continue;
                }
                const TagTable_t* entry = TagToTagTableEntry(elements[i].Tag);
                if (entry == NULL) {
                    continue;
                }
#ifdef SUPERDEBUG
                ALOGE("create_EXIF saving tag %x value \"%s\"",elements[i].Tag, elements[i].Value);
#endif
                writeExifTagAndData(elements[i].Tag,
                                    entry->Format,
                                    entry->DataLength,
                                    (long)elements[i].Value,
                                    TRUE,
                                    Buffer,
                                    &DirIndex,
                                    &DataWriteIndex);
            }

            if (gpsTagCount) {
                // Link to gps dir entry
                writeExifTagAndData(TAG_GPSINFO,
                                    FMT_ULONG,
                                    1,
                                    DataWriteIndex-8,
                                    FALSE,
                                    Buffer,
                                    &DirIndex,
                                    &DataWriteIndex);
            }

            // Link to exif dir entry
            int exifDirPtr = DataWriteIndex-8;
            if (gpsTagCount) {
                exifDirPtr += 2 + gpsTagCount*12 + 4;
            }
            DirExifLink = DirIndex;
            writeExifTagAndData(TAG_EXIF_OFFSET,
                                FMT_ULONG,
                                1,
                                exifDirPtr,
                                FALSE,
                                Buffer,
                                &DirIndex,
                                &DataWriteIndex);
        }

        // End of directory - contains optional link to continued directory.
        Put32u(Buffer+DirIndex, 0);
        printf("Ending Exif section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
    }


    // GPS Section
    if (gpsTagCount) {
        DirIndex = DataWriteIndex;
        printf("Starting GPS section DirIndex = %d", DirIndex);
        NumEntries = gpsTagCount;
        DataWriteIndex += 2 + NumEntries*12 + 4;

        Put16u(Buffer+DirIndex, NumEntries); // Number of entries
        DirIndex += 2;
        {
            int i;
            for (i = 0; i < exifTagCount + gpsTagCount; i++) {
                if (!elements[i].GpsTag) {
                    continue;
                }
                const TagTable_t* entry = GpsTagToTagTableEntry(elements[i].Tag);
                if (entry == NULL) {
                    continue;
                }
#ifdef SUPERDEBUG
                ALOGE("create_EXIF saving GPS tag %x value \"%s\"",elements[i].Tag, elements[i].Value);
#endif
                writeExifTagAndData(elements[i].Tag,
                                    entry->Format,
                                    entry->DataLength,
                                    (long)elements[i].Value,
                                    TRUE,
                                    Buffer,
                                    &DirIndex,
                                    &DataWriteIndex);
            }
        }

        // End of directory - contains optional link to continued directory.
        Put32u(Buffer+DirIndex, 0);
        printf("Ending GPS section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
    }

    {
        // Overwriting TAG_EXIF_OFFSET which links to this directory
        Put32u(Buffer+DirExifLink+8, DataWriteIndex-8);

        printf("Starting Thumbnail section DirIndex = %d", DirIndex);
        DirIndex = DataWriteIndex;
        NumEntries = 2;
        DataWriteIndex += 2 + NumEntries*12 + 4;

        Put16u(Buffer+DirIndex, NumEntries); // Number of entries
        DirIndex += 2;
        {
            // Link to exif dir entry
            writeExifTagAndData(TAG_THUMBNAIL_OFFSET,
                                FMT_ULONG,
                                1,
                                DataWriteIndex-8,
                                FALSE,
                                Buffer,
                                &DirIndex,
                                &DataWriteIndex);
        }

        {
            // Link to exif dir entry
            writeExifTagAndData(TAG_THUMBNAIL_LENGTH,
                                FMT_ULONG,
                                1,
                                0,
                                FALSE,
                                Buffer,
                                &DirIndex,
                                &DataWriteIndex);
        }

        // End of directory - contains optional link to continued directory.
        Put32u(Buffer+DirIndex, 0);
        printf("Ending Thumbnail section DirIndex = %d DataWriteIndex %d", DirIndex, DataWriteIndex);
    }


    Buffer[0] = (unsigned char)(DataWriteIndex >> 8);
    Buffer[1] = (unsigned char)DataWriteIndex;

    // Remove old exif section, if there was one.
    RemoveSectionType(M_EXIF);

    {
        // Sections need malloced buffers, so do that now, especially because
        // we now know how big it needs to be allocated.
        unsigned char * NewBuf = malloc(DataWriteIndex);
        if (NewBuf == NULL){
            ErrFatal("Could not allocate memory");
        }
        memcpy(NewBuf, Buffer, DataWriteIndex);

        CreateSection(M_EXIF, NewBuf, DataWriteIndex);

        // Re-parse new exif section, now that its in place
        // otherwise, we risk touching data that has already been freed.
        process_EXIF(NewBuf, DataWriteIndex);
    }
}

void create_EXIF(ExifElement_t* elements, int exifTagCount, int gpsTagCount, int hasDateTimeTag)
{
    // It is hard to calculate exact necessary size for editing the exif
    // header dynamically, so we are using the maximum size of EXIF, 64K
    const int EXIF_MAX_SIZE = 1024*64;
    char* Buffer = malloc(EXIF_MAX_SIZE);

    if (Buffer != NULL) {
        create_EXIF_internal(elements, exifTagCount, gpsTagCount, hasDateTimeTag, Buffer);
        free(Buffer);
    } else {
        ErrFatal("Could not allocate memory");
    }
}

//--------------------------------------------------------------------------
// Cler the rotation tag in the exif header to 1.
//--------------------------------------------------------------------------
const char * ClearOrientation(void)
{
    int a;
    if (NumOrientations == 0) return NULL;

    for (a=0;a<NumOrientations;a++){
        switch(OrientationNumFormat[a]){
            case FMT_SBYTE:
            case FMT_BYTE:
                *(uchar *)(OrientationPtr[a]) = 1;
                break;

            case FMT_USHORT:
                Put16u(OrientationPtr[a], 1);
                break;

            case FMT_ULONG:
            case FMT_SLONG:
                memset(OrientationPtr, 0, 4);
                // Can't be bothered to write  generic Put32 if I only use it once.
                if (MotorolaOrder){
                    ((uchar *)OrientationPtr[a])[3] = 1;
                }else{
                    ((uchar *)OrientationPtr[a])[0] = 1;
                }
                break;

            default:
                return NULL;
        }
    }

    return OrientTab[ImageInfo.Orientation];
}



//--------------------------------------------------------------------------
// Remove thumbnail out of the exif image.
//--------------------------------------------------------------------------
int RemoveThumbnail(unsigned char * ExifSection)
{
    if (!DirWithThumbnailPtrs ||
        ImageInfo.ThumbnailOffset == 0 ||
        ImageInfo.ThumbnailSize == 0){
        // No thumbnail, or already deleted it.
        return 0;
    }
    if (ImageInfo.ThumbnailAtEnd == FALSE){
        ErrNonfatal("Thumbnail is not at end of header, can't chop it off", 0, 0);
        return 0;
    }

    {
        int de;
        int NumDirEntries;
        NumDirEntries = Get16u(DirWithThumbnailPtrs);

        for (de=0;de<NumDirEntries;de++){
            int Tag;
            unsigned char * DirEntry;
            DirEntry = DIR_ENTRY_ADDR(DirWithThumbnailPtrs, de);
            Tag = Get16u(DirEntry);
            if (Tag == TAG_THUMBNAIL_LENGTH){
                // Set length to zero.
                if (Get16u(DirEntry+2) != FMT_ULONG){
                    // non standard format encoding.  Can't do it.
                    ErrNonfatal("Can't remove thumbnail", 0, 0);
                    return 0;
                }
                Put32u(DirEntry+8, 0);
            }
        }
    }

    // This is how far the non thumbnail data went.
    return ImageInfo.ThumbnailOffset+8;

}


//--------------------------------------------------------------------------
// Convert exif time to Unix time structure
//--------------------------------------------------------------------------
int Exif2tm(struct tm * timeptr, char * ExifTime)
{
    int a;

    timeptr->tm_wday = -1;

    // Check for format: YYYY:MM:DD HH:MM:SS format.
    // Date and time normally separated by a space, but also seen a ':' there, so
    // skip the middle space with '%*c' so it can be any character.
    a = sscanf(ExifTime, "%d%*c%d%*c%d%*c%d:%d:%d",
            &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday,
            &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec);


    if (a == 6){
        timeptr->tm_isdst = -1;
        timeptr->tm_mon -= 1;      // Adjust for unix zero-based months
        timeptr->tm_year -= 1900;  // Adjust for year starting at 1900
        return TRUE; // worked.
    }

    return FALSE; // Wasn't in Exif date format.
}


//--------------------------------------------------------------------------
// Show the collected image info, displaying camera F-stop and shutter speed
// in a consistent and legible fashion.
//--------------------------------------------------------------------------
void ShowImageInfo(int ShowFileInfo)
{
    if (ShowFileInfo){
        printf("File name    : %s\n",ImageInfo.FileName);
        printf("File size    : %d bytes\n",ImageInfo.FileSize);

        {
            char Temp[20];
            FileTimeAsString(Temp);
            printf("File date    : %s\n",Temp);
        }
    }

    if (ImageInfo.CameraMake[0]){
        printf("Camera make  : %s\n",ImageInfo.CameraMake);
        printf("Camera model : %s\n",ImageInfo.CameraModel);
    }
    if (ImageInfo.DateTime[0]){
        printf("Date/Time    : %s\n",ImageInfo.DateTime);
    }
    printf("Resolution   : %d x %d\n",ImageInfo.Width, ImageInfo.Height);

    if (ImageInfo.Orientation > 1){
        // Only print orientation if one was supplied, and if its not 1 (normal orientation)
        printf("Orientation  : %s\n", OrientTab[ImageInfo.Orientation]);
    }

    if (ImageInfo.IsColor == 0){
        printf("Color/bw     : Black and white\n");
    }

    if (ImageInfo.FlashUsed >= 0){
        if (ImageInfo.FlashUsed & 1){
            printf("Flash used   : Yes");
            switch (ImageInfo.FlashUsed){
	            case 0x5: printf(" (Strobe light not detected)"); break;
	            case 0x7: printf(" (Strobe light detected) "); break;
	            case 0x9: printf(" (manual)"); break;
	            case 0xd: printf(" (manual, return light not detected)"); break;
	            case 0xf: printf(" (manual, return light  detected)"); break;
	            case 0x19:printf(" (auto)"); break;
	            case 0x1d:printf(" (auto, return light not detected)"); break;
	            case 0x1f:printf(" (auto, return light detected)"); break;
	            case 0x41:printf(" (red eye reduction mode)"); break;
	            case 0x45:printf(" (red eye reduction mode return light not detected)"); break;
	            case 0x47:printf(" (red eye reduction mode return light  detected)"); break;
	            case 0x49:printf(" (manual, red eye reduction mode)"); break;
	            case 0x4d:printf(" (manual, red eye reduction mode, return light not detected)"); break;
	            case 0x4f:printf(" (red eye reduction mode, return light detected)"); break;
	            case 0x59:printf(" (auto, red eye reduction mode)"); break;
	            case 0x5d:printf(" (auto, red eye reduction mode, return light not detected)"); break;
	            case 0x5f:printf(" (auto, red eye reduction mode, return light detected)"); break;
            }
        }else{
            printf("Flash used   : No");
            switch (ImageInfo.FlashUsed){
	            case 0x18:printf(" (auto)"); break;
            }
        }
        printf("\n");
    }


    if (ImageInfo.FocalLength.num != 0 && ImageInfo.FocalLength.denom != 0) {
        printf("Focal length : %4.1fmm",(double)ImageInfo.FocalLength.num / ImageInfo.FocalLength.denom);
        if (ImageInfo.FocalLength35mmEquiv){
            printf("  (35mm equivalent: %dmm)", ImageInfo.FocalLength35mmEquiv);
        }
        printf("\n");
    }

    if (ImageInfo.DigitalZoomRatio > 1){
        // Digital zoom used.  Shame on you!
        printf("Digital Zoom : %5.3fx\n", (double)ImageInfo.DigitalZoomRatio);
    }

    if (ImageInfo.CCDWidth){
        printf("CCD width    : %4.2fmm\n",(double)ImageInfo.CCDWidth);
    }

    if (ImageInfo.ExposureTime){
        if (ImageInfo.ExposureTime < 0.010){
            printf("Exposure time: %6.4f s ",(double)ImageInfo.ExposureTime);
        }else{
            printf("Exposure time: %5.3f s ",(double)ImageInfo.ExposureTime);
        }
        if (ImageInfo.ExposureTime <= 0.5){
            printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime));
        }
        printf("\n");
    }
    if (ImageInfo.ApertureFNumber){
        printf("Aperture     : f/%5.3f\n",(double)ImageInfo.ApertureFNumber);
    }
    if (ImageInfo.Distance){
        if (ImageInfo.Distance < 0){
            printf("Focus dist.  : Infinite\n");
        }else{
            printf("Focus dist.  : %4.2fm\n",(double)ImageInfo.Distance);
        }
    }

    if (ImageInfo.ISOequivalent){
        printf("ISO equiv.   : %2d\n",(int)ImageInfo.ISOequivalent);
    }

    if (ImageInfo.ExposureBias){
        // If exposure bias was specified, but set to zero, presumably its no bias at all,
        // so only show it if its nonzero.
        printf("Exposure bias: %4.2f\n",(double)ImageInfo.ExposureBias);
    }

    switch(ImageInfo.Whitebalance) {
        case 1:
            printf("Whitebalance : Manual\n");
            break;
        case 0:
            printf("Whitebalance : Auto\n");
            break;
    }

    //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both
    switch(ImageInfo.LightSource) {
        case 1:
            printf("Light Source : Daylight\n");
            break;
        case 2:
            printf("Light Source : Fluorescent\n");
            break;
        case 3:
            printf("Light Source : Incandescent\n");
            break;
        case 4:
            printf("Light Source : Flash\n");
            break;
        case 9:
            printf("Light Source : Fine weather\n");
            break;
        case 11:
            printf("Light Source : Shade\n");
            break;
        default:; //Quercus: 17-1-2004 There are many more modes for this, check Exif2.2 specs
            // If it just says 'unknown' or we don't know it, then
            // don't bother showing it - it doesn't add any useful information.
    }

    if (ImageInfo.MeteringMode){ // 05-jan-2001 vcs
        switch(ImageInfo.MeteringMode) {
        case 2:
            printf("Metering Mode: center weight\n");
            break;
        case 3:
            printf("Metering Mode: spot\n");
            break;
        case 5:
            printf("Metering Mode: matrix\n");
            break;
        }
    }

    if (ImageInfo.ExposureProgram){ // 05-jan-2001 vcs
        switch(ImageInfo.ExposureProgram) {
        case 1:
            printf("Exposure     : Manual\n");
            break;
        case 2:
            printf("Exposure     : program (auto)\n");
            break;
        case 3:
            printf("Exposure     : aperture priority (semi-auto)\n");
            break;
        case 4:
            printf("Exposure     : shutter priority (semi-auto)\n");
            break;
        case 5:
            printf("Exposure     : Creative Program (based towards depth of field)\n");
            break;
        case 6:
            printf("Exposure     : Action program (based towards fast shutter speed)\n");
            break;
        case 7:
            printf("Exposure     : Portrait Mode\n");
            break;
        case 8:
            printf("Exposure     : LandscapeMode \n");
            break;
        default:
            break;
        }
    }
    switch(ImageInfo.ExposureMode){
        case 0: // Automatic (not worth cluttering up output for)
            break;
        case 1: printf("Exposure Mode: Manual\n");
            break;
        case 2: printf("Exposure Mode: Auto bracketing\n");
            break;
    }

    if (ImageInfo.DistanceRange) {
        printf("Focus range  : ");
        switch(ImageInfo.DistanceRange) {
            case 1:
                printf("macro");
                break;
            case 2:
                printf("close");
                break;
            case 3:
                printf("distant");
                break;
        }
        printf("\n");
    }



    if (ImageInfo.Process != M_SOF0){
        // don't show it if its the plain old boring 'baseline' process, but do
        // show it if its something else, like 'progressive' (used on web sometimes)
        int a;
        for (a=0;;a++){
            if (a >= (int)PROCESS_TABLE_SIZE){
                // ran off the end of the table.
                printf("Jpeg process : Unknown\n");
                break;
            }
            if (ProcessTable[a].Tag == ImageInfo.Process){
                printf("Jpeg process : %s\n",ProcessTable[a].Desc);
                break;
            }
        }
    }

    if (ImageInfo.GpsInfoPresent){
        printf("GPS Latitude : %s\n",ImageInfo.GpsLat);
        printf("GPS Longitude: %s\n",ImageInfo.GpsLong);
        if (ImageInfo.GpsAlt[0]) printf("GPS Altitude : %s\n",ImageInfo.GpsAlt);
    }

    // Print the comment. Print 'Comment:' for each new line of comment.
    if (ImageInfo.Comments[0]){
        int a,c;
        printf("Comment      : ");
        if (!ImageInfo.CommentWidchars){
            for (a=0;a<MAX_COMMENT_SIZE;a++){
                c = ImageInfo.Comments[a];
                if (c == '\0') break;
                if (c == '\n'){
                    // Do not start a new line if the string ends with a carriage return.
                    if (ImageInfo.Comments[a+1] != '\0'){
                        printf("\nComment      : ");
                    }else{
                        printf("\n");
                    }
                }else{
                    putchar(c);
                }
            }
            printf("\n");
        }else{
            printf("%.*ls\n", ImageInfo.CommentWidchars, (wchar_t *)ImageInfo.Comments);
        }
    }
    if (ImageInfo.ThumbnailOffset){
        printf("Map: %05d-%05d: Thumbnail\n",ImageInfo.ThumbnailOffset, ImageInfo.ThumbnailOffset+ImageInfo.ThumbnailSize);
    } else {
        printf("NO thumbnail");
    }
}


//--------------------------------------------------------------------------
// Summarize highlights of image info on one line (suitable for grep-ing)
//--------------------------------------------------------------------------
void ShowConciseImageInfo(void)
{
    printf("\"%s\"",ImageInfo.FileName);

    printf(" %dx%d",ImageInfo.Width, ImageInfo.Height);

    if (ImageInfo.ExposureTime){
        if (ImageInfo.ExposureTime <= 0.5){
            printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime));
        }else{
            printf(" (%3.1f)",ImageInfo.ExposureTime);
        }
    }

    if (ImageInfo.ApertureFNumber){
        printf(" f/%3.1f",(double)ImageInfo.ApertureFNumber);
    }

    if (ImageInfo.FocalLength35mmEquiv){
        printf(" f(35)=%dmm",ImageInfo.FocalLength35mmEquiv);
    }

    if (ImageInfo.FlashUsed >= 0 && ImageInfo.FlashUsed & 1){
        printf(" (flash)");
    }

    if (ImageInfo.IsColor == 0){
        printf(" (bw)");
    }

    printf("\n");
}