about summary refs log tree commit diff
path: root/converter/other/exif.c
diff options
context:
space:
mode:
Diffstat (limited to 'converter/other/exif.c')
-rw-r--r--converter/other/exif.c1030
1 files changed, 1030 insertions, 0 deletions
diff --git a/converter/other/exif.c b/converter/other/exif.c
new file mode 100644
index 00000000..19f108a7
--- /dev/null
+++ b/converter/other/exif.c
@@ -0,0 +1,1030 @@
+/*--------------------------------------------------------------------------
+  This file contains subroutines for use by Jpegtopnm to handle the
+  EXIF header.
+
+  The code is adapted from the program Jhead by Matthaias Wandel
+  December 1999 - August 2000, and contributed to the public domain.
+  Bryan Henderson adapted it to Netpbm in September 2001.  Bryan
+  added more of Wandel's code, from Jhead 1.9 dated December 2002 in
+  January 2003.
+
+  An EXIF header is a JFIF APP1 marker.  It is generated by some
+  digital cameras and contains information about the circumstances of
+  the creation of the image (camera settings, etc.).
+
+  The EXIF header uses the TIFF format, only it contains only tag
+  values and no actual image.
+
+  Note that the image format called EXIF is simply JFIF with an EXIF
+  header, i.e. a subformat of JFIF.
+
+  See the EXIF specs at http://exif.org (2001.09.01).
+
+--------------------------------------------------------------------------*/
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <limits.h>
+#include <ctype.h>
+
+#ifdef _WIN32
+    #include <sys/utime.h>
+#else
+    #include <utime.h>
+    #include <sys/types.h>
+    #include <unistd.h>
+    #include <errno.h>
+#endif
+
+#include "pm_c_util.h"
+#include "pm.h"
+#include "nstring.h"
+
+#include "exif.h"
+
+static unsigned char * LastExifRefd;
+static unsigned char * DirWithThumbnailPtrs;
+static double FocalplaneXRes;
+bool HaveXRes;
+static double FocalplaneUnits;
+static int ExifImageWidth;
+static int MotorolaOrder = 0;
+
+typedef struct {
+    unsigned short Tag;
+    const char * Desc;
+}TagTable_t;
+
+
+/* Describes format descriptor */
+static int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
+#define NUM_FORMATS 12
+
+#define FMT_BYTE       1 
+#define FMT_STRING     2
+#define FMT_USHORT     3
+#define FMT_ULONG      4
+#define FMT_URATIONAL  5
+#define FMT_SBYTE      6
+#define FMT_UNDEFINED  7
+#define FMT_SSHORT     8
+#define FMT_SLONG      9
+#define FMT_SRATIONAL 10
+#define FMT_SINGLE    11
+#define FMT_DOUBLE    12
+
+/* Describes tag values */
+
+#define TAG_EXIF_OFFSET       0x8769
+#define TAG_INTEROP_OFFSET    0xa005
+
+#define TAG_MAKE              0x010F
+#define TAG_MODEL             0x0110
+
+#define TAG_ORIENTATION       0x0112
+
+#define TAG_EXPOSURETIME      0x829A
+#define TAG_FNUMBER           0x829D
+
+#define TAG_SHUTTERSPEED      0x9201
+#define TAG_APERTURE          0x9202
+#define TAG_MAXAPERTURE       0x9205
+#define TAG_FOCALLENGTH       0x920A
+
+#define TAG_DATETIME_ORIGINAL 0x9003
+#define TAG_USERCOMMENT       0x9286
+
+#define TAG_SUBJECT_DISTANCE  0x9206
+#define TAG_FLASH             0x9209
+
+#define TAG_FOCALPLANEXRES    0xa20E
+#define TAG_FOCALPLANEUNITS   0xa210
+#define TAG_EXIF_IMAGEWIDTH   0xA002
+#define TAG_EXIF_IMAGELENGTH  0xA003
+
+/* the following is added 05-jan-2001 vcs */
+#define TAG_EXPOSURE_BIAS     0x9204
+#define TAG_WHITEBALANCE      0x9208
+#define TAG_METERING_MODE     0x9207
+#define TAG_EXPOSURE_PROGRAM  0x8822
+#define TAG_ISO_EQUIVALENT    0x8827
+#define TAG_COMPRESSION_LEVEL 0x9102
+
+#define TAG_THUMBNAIL_OFFSET  0x0201
+#define TAG_THUMBNAIL_LENGTH  0x0202
+
+static TagTable_t const TagTable[] = {
+  {   0x100,   "ImageWidth"},
+  {   0x101,   "ImageLength"},
+  {   0x102,   "BitsPerSample"},
+  {   0x103,   "Compression"},
+  {   0x106,   "PhotometricInterpretation"},
+  {   0x10A,   "FillOrder"},
+  {   0x10D,   "DocumentName"},
+  {   0x10E,   "ImageDescription"},
+  {   0x10F,   "Make"},
+  {   0x110,   "Model"},
+  {   0x111,   "StripOffsets"},
+  {   0x112,   "Orientation"},
+  {   0x115,   "SamplesPerPixel"},
+  {   0x116,   "RowsPerStrip"},
+  {   0x117,   "StripByteCounts"},
+  {   0x11A,   "XResolution"},
+  {   0x11B,   "YResolution"},
+  {   0x11C,   "PlanarConfiguration"},
+  {   0x128,   "ResolutionUnit"},
+  {   0x12D,   "TransferFunction"},
+  {   0x131,   "Software"},
+  {   0x132,   "DateTime"},
+  {   0x13B,   "Artist"},
+  {   0x13E,   "WhitePoint"},
+  {   0x13F,   "PrimaryChromaticities"},
+  {   0x156,   "TransferRange"},
+  {   0x200,   "JPEGProc"},
+  {   0x201,   "ThumbnailOffset"},
+  {   0x202,   "ThumbnailLength"},
+  {   0x211,   "YCbCrCoefficients"},
+  {   0x212,   "YCbCrSubSampling"},
+  {   0x213,   "YCbCrPositioning"},
+  {   0x214,   "ReferenceBlackWhite"},
+  {   0x828D,  "CFARepeatPatternDim"},
+  {   0x828E,  "CFAPattern"},
+  {   0x828F,  "BatteryLevel"},
+  {   0x8298,  "Copyright"},
+  {   0x829A,  "ExposureTime"},
+  {   0x829D,  "FNumber"},
+  {   0x83BB,  "IPTC/NAA"},
+  {   0x8769,  "ExifOffset"},
+  {   0x8773,  "InterColorProfile"},
+  {   0x8822,  "ExposureProgram"},
+  {   0x8824,  "SpectralSensitivity"},
+  {   0x8825,  "GPSInfo"},
+  {   0x8827,  "ISOSpeedRatings"},
+  {   0x8828,  "OECF"},
+  {   0x9000,  "ExifVersion"},
+  {   0x9003,  "DateTimeOriginal"},
+  {   0x9004,  "DateTimeDigitized"},
+  {   0x9101,  "ComponentsConfiguration"},
+  {   0x9102,  "CompressedBitsPerPixel"},
+  {   0x9201,  "ShutterSpeedValue"},
+  {   0x9202,  "ApertureValue"},
+  {   0x9203,  "BrightnessValue"},
+  {   0x9204,  "ExposureBiasValue"},
+  {   0x9205,  "MaxApertureValue"},
+  {   0x9206,  "SubjectDistance"},
+  {   0x9207,  "MeteringMode"},
+  {   0x9208,  "LightSource"},
+  {   0x9209,  "Flash"},
+  {   0x920A,  "FocalLength"},
+  {   0x927C,  "MakerNote"},
+  {   0x9286,  "UserComment"},
+  {   0x9290,  "SubSecTime"},
+  {   0x9291,  "SubSecTimeOriginal"},
+  {   0x9292,  "SubSecTimeDigitized"},
+  {   0xA000,  "FlashPixVersion"},
+  {   0xA001,  "ColorSpace"},
+  {   0xA002,  "ExifImageWidth"},
+  {   0xA003,  "ExifImageLength"},
+  {   0xA005,  "InteroperabilityOffset"},
+  {   0xA20B,  "FlashEnergy"},                 /* 0x920B in TIFF/EP */
+  {   0xA20C,  "SpatialFrequencyResponse"},  /* 0x920C    -  - */
+  {   0xA20E,  "FocalPlaneXResolution"},     /* 0x920E    -  - */
+  {   0xA20F,  "FocalPlaneYResolution"},      /* 0x920F    -  - */
+  {   0xA210,  "FocalPlaneResolutionUnit"},  /* 0x9210    -  - */
+  {   0xA214,  "SubjectLocation"},             /* 0x9214    -  - */
+  {   0xA215,  "ExposureIndex"},            /* 0x9215    -  - */
+  {   0xA217,  "SensingMethod"},            /* 0x9217    -  - */
+  {   0xA300,  "FileSource"},
+  {   0xA301,  "SceneType"},
+  {      0, NULL}
+} ;
+
+
+
+/*--------------------------------------------------------------------------
+   Convert a 16 bit unsigned value from file's native byte order
+--------------------------------------------------------------------------*/
+static int Get16u(void * Short)
+{
+    if (MotorolaOrder){
+        return (((unsigned char *)Short)[0] << 8) | 
+            ((unsigned char *)Short)[1];
+    }else{
+        return (((unsigned char *)Short)[1] << 8) | 
+            ((unsigned char *)Short)[0];
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Convert a 32 bit signed value from file's native byte order
+--------------------------------------------------------------------------*/
+static int Get32s(void * Long)
+{
+    if (MotorolaOrder){
+        return  
+            ((( char *)Long)[0] << 24) | (((unsigned char *)Long)[1] << 16) |
+            (((unsigned char *)Long)[2] << 8 ) | 
+            (((unsigned char *)Long)[3] << 0 );
+    }else{
+        return  
+            ((( char *)Long)[3] << 24) | (((unsigned char *)Long)[2] << 16) |
+            (((unsigned char *)Long)[1] << 8 ) | 
+            (((unsigned char *)Long)[0] << 0 );
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Convert a 32 bit unsigned value from file's native byte order
+--------------------------------------------------------------------------*/
+static unsigned Get32u(void * Long)
+{
+    return (unsigned)Get32s(Long) & 0xffffffff;
+}
+
+/*--------------------------------------------------------------------------
+   Display a number as one of its many formats
+--------------------------------------------------------------------------*/
+static void PrintFormatNumber(FILE * const file, 
+                              void * const ValuePtr, 
+                              int const Format, int const ByteCount)
+{
+    switch(Format){
+    case FMT_SBYTE:
+    case FMT_BYTE:      printf("%02x\n",*(unsigned char *)ValuePtr); break;
+    case FMT_USHORT:    fprintf(file, "%d\n",Get16u(ValuePtr));    break;
+    case FMT_ULONG:     
+    case FMT_SLONG:     fprintf(file, "%d\n",Get32s(ValuePtr));    break;
+    case FMT_SSHORT:    
+        fprintf(file, "%hd\n",(signed short)Get16u(ValuePtr));     break;
+    case FMT_URATIONAL:
+    case FMT_SRATIONAL: 
+        fprintf(file, "%d/%d\n",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr));
+        break;
+    case FMT_SINGLE:    
+        fprintf(file, "%f\n",(double)*(float *)ValuePtr);          break;
+    case FMT_DOUBLE:    fprintf(file, "%f\n",*(double *)ValuePtr); break;
+    default: 
+        fprintf(file, "Unknown format %d:", Format);
+        {
+            int a;
+            for (a=0; a < ByteCount && a < 16; ++a)
+                printf("%02x", ((unsigned char *)ValuePtr)[a]);
+        }
+        fprintf(file, "\n");
+    }
+}
+
+
+/*--------------------------------------------------------------------------
+   Evaluate number, be it int, rational, or float from directory.
+--------------------------------------------------------------------------*/
+static double ConvertAnyFormat(void * ValuePtr, int Format)
+{
+    double Value;
+    Value = 0;
+
+    switch(Format){
+        case FMT_SBYTE:     Value = *(signed char *)ValuePtr;  break;
+        case FMT_BYTE:      Value = *(unsigned char *)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;
+    }
+    return Value;
+}
+
+/*--------------------------------------------------------------------------
+   Process one of the nested EXIF directories.
+--------------------------------------------------------------------------*/
+static void 
+ProcessExifDir(unsigned char *  const ExifData, 
+               unsigned int     const ExifLength,
+               unsigned int     const DirOffset,
+               ImageInfo_t *    const ImageInfoP, 
+               int              const ShowTags,
+               unsigned char ** const LastExifRefdP) {
+
+    unsigned char * const DirStart = ExifData + DirOffset;
+    int de;
+    int a;
+    int NumDirEntries;
+    unsigned ThumbnailOffset = 0;
+    unsigned ThumbnailSize = 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 > (ExifData+ExifLength)){
+            if (DirEnd+2 == ExifData+ExifLength || 
+                DirEnd == ExifData+ExifLength){
+                /* Version 1.3 of jhead would truncate a bit too much.
+                   This also caught later on as well.
+                */
+            }else{
+                /* Note: Files that had thumbnails trimmed with jhead
+                   1.3 or earlier might trigger this.
+                */
+                pm_message("Illegal directory entry size");
+                return;
+            }
+        }
+        if (DirEnd > LastExifRefd) LastExifRefd = DirEnd;
+    }
+
+    if (ShowTags){
+        pm_message("Directory with %d entries",NumDirEntries);
+    }
+
+    for (de=0;de<NumDirEntries;de++){
+        int Tag, Format, Components;
+        unsigned char * ValuePtr;
+            /* This actually can point to a variety of things; it must
+               be cast to other types when used.  But we use it as a
+               byte-by-byte cursor, so we declare it as a pointer to a
+               generic byte here.  
+            */
+        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.  
+            */
+            pm_message("Illegal number format %d for tag %04x", Format, 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 */
+                pm_message("Illegal pointer offset value in EXIF "
+                           "for tag %04x.  "
+                           "Offset %d bytes %d ExifLen %d\n",
+                           Tag, OffsetVal, ByteCount, ExifLength);
+                continue;
+            }
+            ValuePtr = ExifData+OffsetVal;
+        }else{
+            /* 4 bytes or less and value is in the dir entry itself */
+            ValuePtr = DirEntry+8;
+        }
+
+        if (*LastExifRefdP < ValuePtr+ByteCount){
+            /* Keep track of last byte in the exif header that was
+               actually referenced.  That way, we know where the
+               discardable thumbnail data begins.
+            */
+            *LastExifRefdP = ValuePtr+ByteCount;
+        }
+
+        if (ShowTags){
+            /* Show tag name */
+            for (a=0;;a++){
+                if (TagTable[a].Tag == 0){
+                    fprintf(stderr, "  Unknown Tag %04x Value = ", Tag);
+                    break;
+                }
+                if (TagTable[a].Tag == Tag){
+                    fprintf(stderr, "    %s = ",TagTable[a].Desc);
+                    break;
+                }
+            }
+
+            /* 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)
+                    */
+                    {
+                        int NoPrint = 0;
+                        printf("\"");
+                        for (a=0;a<ByteCount;a++){
+                            if (ISPRINT((ValuePtr)[a])){
+                                fprintf(stderr, "%c", (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){
+                                    fprintf(stderr, "?");
+                                    NoPrint = 1;
+                                }
+                            }
+                        }
+                        fprintf(stderr, "\"\n");
+                    }
+                    break;
+
+                default:
+                    /* Handle arrays of numbers later (will there ever be?)*/
+                    PrintFormatNumber(stderr, ValuePtr, Format, ByteCount);
+            }
+        }
+
+        /* Extract useful components of tag */
+        switch(Tag){
+
+            case TAG_MAKE:
+                strncpy(ImageInfoP->CameraMake, (char*)ValuePtr, 31);
+                break;
+
+            case TAG_MODEL:
+                strncpy(ImageInfoP->CameraModel, (char*)ValuePtr, 39);
+                break;
+
+            case TAG_DATETIME_ORIGINAL:
+                strncpy(ImageInfoP->DateTime, (char*)ValuePtr, 19);
+                ImageInfoP->DatePointer = (char*)ValuePtr;
+                break;
+
+            case TAG_USERCOMMENT:
+                /* Olympus has this padded with trailing spaces.
+                   Remove these first. 
+                */
+                for (a=ByteCount;;){
+                    a--;
+                    if (((char*)ValuePtr)[a] == ' '){
+                        ((char*)ValuePtr)[a] = '\0';
+                    }else{
+                        break;
+                    }
+                    if (a == 0) break;
+                }
+
+                /* Copy the comment */
+                if (memcmp(ValuePtr, "ASCII",5) == 0){
+                    for (a=5;a<10;a++){
+                        char c;
+                        c = ((char*)ValuePtr)[a];
+                        if (c != '\0' && c != ' '){
+                            strncpy(ImageInfoP->Comments, (char*)ValuePtr+a, 
+                                    199);
+                            break;
+                        }
+                    }
+                    
+                }else{
+                    strncpy(ImageInfoP->Comments, (char*)ValuePtr, 199);
+                }
+                break;
+
+            case TAG_FNUMBER:
+                /* Simplest way of expressing aperture, so I trust it the most.
+                   (overwrite previously computd value if there is one)
+                   */
+                ImageInfoP->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 (ImageInfoP->ApertureFNumber == 0){
+                    ImageInfoP->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. 
+                */
+
+                ImageInfoP->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.
+                */
+                ImageInfoP->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) 
+                */
+                ImageInfoP->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 (ImageInfoP->ExposureTime == 0){
+                    ImageInfoP->ExposureTime = (float)
+                        (1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2)));
+                }
+                break;
+
+            case TAG_FLASH:
+                if ((int)ConvertAnyFormat(ValuePtr, Format) & 7){
+                    ImageInfoP->FlashUsed = TRUE;
+                }else{
+                    ImageInfoP->FlashUsed = FALSE;
+                }
+                break;
+
+            case TAG_ORIENTATION:
+                ImageInfoP->Orientation = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                if (ImageInfoP->Orientation < 1 || 
+                    ImageInfoP->Orientation > 8){
+                    pm_message("Undefined rotation value %d", 
+                               ImageInfoP->Orientation);
+                    ImageInfoP->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.  
+                */
+                a = (int)ConvertAnyFormat(ValuePtr, Format);
+                if (ExifImageWidth < a) ExifImageWidth = a;
+                break;
+
+            case TAG_FOCALPLANEXRES:
+                HaveXRes = TRUE;
+                FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_FOCALPLANEUNITS:
+                switch((int)ConvertAnyFormat(ValuePtr, Format)){
+                    case 1: FocalplaneUnits = 25.4; break; /* 1 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;  /* 1 centimeter*/
+                    case 4: FocalplaneUnits = 1;    break;  /* 1 millimeter*/
+                    case 5: FocalplaneUnits = .001; break;  /* 1 micrometer*/
+                }
+                break;
+
+                /* Remaining cases contributed by: Volker C. Schoech
+                   (schoech@gmx.de)
+                */
+
+            case TAG_EXPOSURE_BIAS:
+                ImageInfoP->ExposureBias = 
+                    (float) ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_WHITEBALANCE:
+                ImageInfoP->Whitebalance = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_METERING_MODE:
+                ImageInfoP->MeteringMode = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_EXPOSURE_PROGRAM:
+                ImageInfoP->ExposureProgram = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                break;
+
+            case TAG_ISO_EQUIVALENT:
+                ImageInfoP->ISOequivalent = 
+                    (int)ConvertAnyFormat(ValuePtr, Format);
+                if ( ImageInfoP->ISOequivalent < 50 ) 
+                    ImageInfoP->ISOequivalent *= 200;
+                break;
+
+            case TAG_COMPRESSION_LEVEL:
+                ImageInfoP->CompressionLevel = 
+                    (int)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);
+                break;
+
+            case TAG_EXIF_OFFSET:
+            case TAG_INTEROP_OFFSET:
+                {
+                    unsigned int const SubdirOffset  = Get32u(ValuePtr);
+                    if (SubdirOffset >= ExifLength)
+                        pm_message("Illegal exif or interop offset "
+                                   "directory link.  Offset is %u, "
+                                   "but Exif data is only %u bytes.",
+                                   SubdirOffset, ExifLength);
+                    else
+                        ProcessExifDir(ExifData, ExifLength, SubdirOffset, 
+                                       ImageInfoP, ShowTags, LastExifRefdP);
+                    continue;
+                }
+        }
+
+    }
+
+
+    {
+        /* 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!  
+        */
+        if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= 
+            ExifData+ExifLength){
+            unsigned int const SubdirOffset =
+                Get32u(DirStart+2+12*NumDirEntries);
+            if (SubdirOffset){
+                unsigned char * const SubdirStart = ExifData + SubdirOffset;
+                if (SubdirStart > ExifData+ExifLength){
+                    if (SubdirStart < ExifData+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{
+                        pm_message("Illegal subdirectory link");
+                    }
+                }else{
+                    if (SubdirOffset <= ExifLength)
+                        ProcessExifDir(ExifData, ExifLength, SubdirOffset,
+                                       ImageInfoP, ShowTags, LastExifRefdP);
+                }
+            }
+        }else{
+            /* The exif header ends before the last next directory pointer. */
+        }
+    }
+
+    if (ThumbnailSize && ThumbnailOffset){
+        if (ThumbnailSize + ThumbnailOffset <= ExifLength){
+            /* The thumbnail pointer appears to be valid.  Store it. */
+            ImageInfoP->ThumbnailPointer = ExifData + ThumbnailOffset;
+            ImageInfoP->ThumbnailSize = ThumbnailSize;
+
+            if (ShowTags){
+                fprintf(stderr, "Thumbnail size: %d bytes\n",ThumbnailSize);
+            }
+        }
+    }
+}
+
+
+
+void 
+process_EXIF(unsigned char * const ExifData,
+             unsigned int    const length,
+             ImageInfo_t *   const ImageInfoP, 
+             int             const ShowTags,
+             const char **   const errorP) {
+/*--------------------------------------------------------------------------
+  Interpret an EXIF APP1 marker
+
+  'ExifData' is the actual Exif data; it does not include the
+  "Exif" identifier and length field that often prefix Exif data.
+
+  'length' is the length of the Exif section.
+--------------------------------------------------------------------------*/
+    int FirstOffset;
+    unsigned char * LastExifRefd;
+
+    *errorP = NULL;  /* initial assumption */
+
+    if (ShowTags){
+        fprintf(stderr, "Exif header %d bytes long\n",length);
+    }
+
+    if (memcmp(ExifData+0,"II",2) == 0) {
+        if (ShowTags) 
+            fprintf(stderr, "Exif header in Intel order\n");
+        MotorolaOrder = 0;
+    } else {
+        if (memcmp(ExifData+0, "MM", 2) == 0) {
+            if (ShowTags) 
+                fprintf(stderr, "Exif header in Motorola order\n");
+            MotorolaOrder = 1;
+        } else {
+            asprintfN(errorP, "Invalid alignment marker in Exif "
+                      "data.  First two bytes are '%c%c' (0x%02x%02x) "
+                      "instead of 'II' or 'MM'.", 
+                      ExifData[0], ExifData[1], ExifData[0], ExifData[1]);
+        }
+    }
+    if (!*errorP) {
+        unsigned short const start = Get16u(ExifData + 2);
+        /* Check the next value for correctness. */
+        if (start != 0x002a){
+            asprintfN(errorP, "Invalid Exif header start.  "
+                      "two bytes after the alignment marker "
+                      "should be 0x002a, but is 0x%04x",
+                      start);
+        }
+    }
+    if (!*errorP) {
+        FirstOffset = Get32u(ExifData + 4);
+        if (FirstOffset < 8 || FirstOffset > 16){
+            /* I used to ensure this was set to 8 (website I used
+               indicated its 8) but PENTAX Optio 230 has it set
+               differently, and uses it as offset. (Sept 11 2002)
+                */
+            pm_message("Suspicious offset of first IFD value in Exif header");
+        }
+        
+        ImageInfoP->Comments[0] = '\0';  /* Initial value - null string */
+        
+        HaveXRes = FALSE;  /* Initial assumption */
+        FocalplaneUnits = 0;
+        ExifImageWidth = 0;
+        
+        LastExifRefd = ExifData;
+        DirWithThumbnailPtrs = NULL;
+        
+        ProcessExifDir(ExifData, length, FirstOffset, 
+                       ImageInfoP, ShowTags, &LastExifRefd);
+        
+        /* Compute the CCD width, in millimeters. */
+        if (HaveXRes){
+            ImageInfoP->HaveCCDWidth = 1;
+            ImageInfoP->CCDWidth = 
+                    (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
+        } else
+            ImageInfoP->HaveCCDWidth = 0;
+            
+        if (ShowTags){
+            fprintf(stderr, 
+                    "Non-settings part of Exif header: %d bytes\n",
+                    ExifData+length-LastExifRefd);
+        }
+    }
+}
+
+/*--------------------------------------------------------------------------
+   Show the collected image info, displaying camera F-stop and shutter
+   speed in a consistent and legible fashion.
+--------------------------------------------------------------------------*/
+void 
+ShowImageInfo(ImageInfo_t * const ImageInfoP)
+{
+    if (ImageInfoP->CameraMake[0]){
+        fprintf(stderr, "Camera make  : %s\n",ImageInfoP->CameraMake);
+        fprintf(stderr, "Camera model : %s\n",ImageInfoP->CameraModel);
+    }
+    if (ImageInfoP->DateTime[0]){
+        fprintf(stderr, "Date/Time    : %s\n",ImageInfoP->DateTime);
+    }
+    fprintf(stderr, "Resolution   : %d x %d\n",
+            ImageInfoP->Width, ImageInfoP->Height);
+    if (ImageInfoP->Orientation > 1){
+
+        /* Only print orientation if one was supplied, and if its not
+           1 (normal orientation)
+
+           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. */
+        };
+
+        fprintf(stderr, "Orientation  : %s\n", 
+                OrientTab[ImageInfoP->Orientation]);
+    }
+
+    if (ImageInfoP->IsColor == 0){
+        fprintf(stderr, "Color/bw     : Black and white\n");
+    }
+    if (ImageInfoP->FlashUsed >= 0){
+        fprintf(stderr, "Flash used   : %s\n",
+                ImageInfoP->FlashUsed ? "Yes" :"No");
+    }
+    if (ImageInfoP->FocalLength){
+        fprintf(stderr, "Focal length : %4.1fmm",
+                (double)ImageInfoP->FocalLength);
+        if (ImageInfoP->HaveCCDWidth){
+            fprintf(stderr, "  (35mm equivalent: %dmm)",
+                    (int)
+                    (ImageInfoP->FocalLength/ImageInfoP->CCDWidth*36 + 0.5));
+        }
+        fprintf(stderr, "\n");
+    }
+
+    if (ImageInfoP->HaveCCDWidth){
+        fprintf(stderr, "CCD width    : %2.4fmm\n",
+                (double)ImageInfoP->CCDWidth);
+    }
+
+    if (ImageInfoP->ExposureTime){ 
+        if (ImageInfoP->ExposureTime < 0.010){
+            fprintf(stderr, 
+                    "Exposure time: %6.4f s ",
+                    (double)ImageInfoP->ExposureTime);
+        }else{
+            fprintf(stderr, 
+                    "Exposure time: %5.3f s ",
+                    (double)ImageInfoP->ExposureTime);
+        }
+        if (ImageInfoP->ExposureTime <= 0.5){
+            fprintf(stderr, " (1/%d)",(int)(0.5 + 1/ImageInfoP->ExposureTime));
+        }
+        fprintf(stderr, "\n");
+    }
+    if (ImageInfoP->ApertureFNumber){
+        fprintf(stderr, "Aperture     : f/%3.1f\n",
+                (double)ImageInfoP->ApertureFNumber);
+    }
+    if (ImageInfoP->Distance){
+        if (ImageInfoP->Distance < 0){
+            fprintf(stderr, "Focus dist.  : Infinite\n");
+        }else{
+            fprintf(stderr, "Focus dist.  :%5.2fm\n",
+                    (double)ImageInfoP->Distance);
+        }
+    }
+
+
+
+
+
+    if (ImageInfoP->ISOequivalent){ /* 05-jan-2001 vcs */
+        fprintf(stderr, "ISO equiv.   : %2d\n",(int)ImageInfoP->ISOequivalent);
+    }
+    if (ImageInfoP->ExposureBias){ /* 05-jan-2001 vcs */
+        fprintf(stderr, "Exposure bias:%4.2f\n",
+                (double)ImageInfoP->ExposureBias);
+    }
+        
+    if (ImageInfoP->Whitebalance){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->Whitebalance) {
+        case 1:
+            fprintf(stderr, "Whitebalance : sunny\n");
+            break;
+        case 2:
+            fprintf(stderr, "Whitebalance : fluorescent\n");
+            break;
+        case 3:
+            fprintf(stderr, "Whitebalance : incandescent\n");
+            break;
+        default:
+            fprintf(stderr, "Whitebalance : cloudy\n");
+        }
+    }
+    if (ImageInfoP->MeteringMode){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->MeteringMode) {
+        case 2:
+            fprintf(stderr, "Metering Mode: center weight\n");
+            break;
+        case 3:
+            fprintf(stderr, "Metering Mode: spot\n");
+            break;
+        case 5:
+            fprintf(stderr, "Metering Mode: matrix\n");
+            break;
+        }
+    }
+    if (ImageInfoP->ExposureProgram){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->ExposureProgram) {
+        case 2:
+            fprintf(stderr, "Exposure     : program (auto)\n");
+            break;
+        case 3:
+            fprintf(stderr, "Exposure     : aperture priority (semi-auto)\n");
+            break;
+        case 4:
+            fprintf(stderr, "Exposure     : shutter priority (semi-auto)\n");
+            break;
+        }
+    }
+    if (ImageInfoP->CompressionLevel){ /* 05-jan-2001 vcs */
+        switch(ImageInfoP->CompressionLevel) {
+        case 1:
+            fprintf(stderr, "Jpeg Quality  : basic\n");
+            break;
+        case 2:
+            fprintf(stderr, "Jpeg Quality  : normal\n");
+            break;
+        case 4:
+            fprintf(stderr, "Jpeg Quality  : fine\n");
+            break;
+       }
+    }
+
+         
+
+    /* Print the comment. Print 'Comment:' for each new line of comment. */
+    if (ImageInfoP->Comments[0]){
+        int a,c;
+        fprintf(stderr, "Comment      : ");
+        for (a=0;a<MAX_COMMENT;a++){
+            c = ImageInfoP->Comments[a];
+            if (c == '\0') break;
+            if (c == '\n'){
+                /* Do not start a new line if the string ends with a cr */
+                if (ImageInfoP->Comments[a+1] != '\0'){
+                    fprintf(stderr, "\nComment      : ");
+                }else{
+                    fprintf(stderr, "\n");
+                }
+            }else{
+                putc(c, stderr);
+            }
+        }
+        fprintf(stderr, "\n");
+    }
+
+    fprintf(stderr, "\n");
+}
+
+
+
+