From 1b6e51a266008348ad93ed8b6ac9ec91b5024fea Mon Sep 17 00:00:00 2001 From: giraffedata Date: Sat, 25 Mar 2023 00:22:30 +0000 Subject: Release 10.86.38 git-svn-id: http://svn.code.sf.net/p/netpbm/code/stable@4534 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- converter/other/exif.c | 1413 +++++++++++++++++++++++++------------------ converter/other/exif.h | 95 +-- converter/other/jpegtopnm.c | 2 +- doc/HISTORY | 5 + version.mk | 2 +- 5 files changed, 893 insertions(+), 624 deletions(-) diff --git a/converter/other/exif.c b/converter/other/exif.c index 1bfe4b2b..d1eb517f 100644 --- a/converter/other/exif.c +++ b/converter/other/exif.c @@ -8,19 +8,41 @@ 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.). + Bryan fundamentally rewrote it in March 2023 because it wasn't properly + dealing with the main image vs thumbnail IFDs. +--------------------------------------------------------------------------*/ + +/* + N.B. "EXIF" refers to a whole image file format; some people think it is + just the format of the camera model, orientation, etc. data within the + image file. EXIF is a subset of JFIF; an EXIF file is JFIF file + containing an EXIF header in the form of a JFIF APP1 marker. - The EXIF header uses the TIFF format, only it contains only tag - values and no actual image. + An EXIF header is generated by some digital cameras and contains information + about the circumstances of the creation of the image (camera settings, + etc.). - Note that the image format called EXIF is simply JFIF with an EXIF - header, i.e. a subformat of JFIF. + 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). ---------------------------------------------------------------------------*/ + The basic format of the EXIF header is a sequence of IFDs (directories). I + believe the first IFD is always for the main image and the 2nd IFD is for a + thumbnail image and is not present if there is no thumbnail image in the + file. + + A directory is a sequence of tag/value pairs. + + Each IFD can contain SubIFD, as the value of an EXIF Offset or Interop + Offset tag. + +*/ + + #include "pm_config.h" #include #include @@ -30,6 +52,7 @@ #include #include #include +#include #if MSVCRT #include @@ -42,20 +65,28 @@ #include "pm_c_util.h" #include "pm.h" +#include "mallocvar.h" #include "nstring.h" #include "exif.h" -static const unsigned char * DirWithThumbnailPtrs; -static double FocalplaneXRes; -bool HaveXRes; -static double FocalplaneUnits; -static int ExifImageWidth; + + +enum Orientation { + ORIENT_NORMAL, + ORIENT_FLIP_HORIZ, /* left right reversed mirror */ + ORIENT_ROTATE_180, /* upside down */ + ORIENT_FLIP_VERT, /* upside down mirror */ + ORIENT_TRANSPOSE, /* Flipped about top-left <--> bottom-right axis*/ + ORIENT_ROTATE_90, /* rotate 90 cw to right it */ + ORIENT_TRANSVERSE, /* flipped about top-right <--> bottom-left axis */ + ORIENT_ROTATE_270 /* rotate 270 to right it */ +}; typedef struct { - unsigned short Tag; - const char * Desc; -} TagTable; + unsigned short tag; + const char * desc; +} TagTableEntry; @@ -63,7 +94,7 @@ typedef struct { static int const bytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; #define NUM_FORMATS 12 -#define FMT_BYTE 1 +#define FMT_BYTE 1 #define FMT_STRING 2 #define FMT_USHORT 3 #define FMT_ULONG 4 @@ -119,7 +150,7 @@ static int const bytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; #define TAG_THUMBNAIL_OFFSET 0x0201 #define TAG_THUMBNAIL_LENGTH 0x0202 -static TagTable const tagTable[] = { +static TagTableEntry const tagTable[] = { { 0x100, "ImageWidth"}, { 0x101, "ImageLength"}, { 0x102, "BitsPerSample"}, @@ -207,7 +238,7 @@ static TagTable const tagTable[] = { -typedef enum { NORMAL, MOTOROLA } ByteOrder; +typedef enum { ORDER_NORMAL, ORDER_MOTOROLA } ByteOrder; @@ -217,11 +248,11 @@ get16u(const void * const data, /*-------------------------------------------------------------------------- Convert a 16 bit unsigned value from file's native byte order --------------------------------------------------------------------------*/ - if (byteOrder == MOTOROLA){ - return (((const unsigned char *)data)[0] << 8) | + if (byteOrder == ORDER_MOTOROLA) { + return (((const unsigned char *)data)[0] << 8) | ((const unsigned char *)data)[1]; - }else{ - return (((const unsigned char *)data)[1] << 8) | + } else { + return (((const unsigned char *)data)[1] << 8) | ((const unsigned char *)data)[0]; } } @@ -234,17 +265,17 @@ get32s(const void * const data, /*-------------------------------------------------------------------------- Convert a 32 bit signed value from file's native byte order --------------------------------------------------------------------------*/ - if (byteOrder == MOTOROLA){ - return + if (byteOrder == ORDER_MOTOROLA) { + return (((const char *)data)[0] << 24) | (((const unsigned char *)data)[1] << 16) | - (((const unsigned char *)data)[2] << 8 ) | + (((const unsigned char *)data)[2] << 8 ) | (((const unsigned char *)data)[3] << 0 ); } else { - return + return (((const char *)data)[3] << 24) | (((const unsigned char *)data)[2] << 16) | - (((const unsigned char *)data)[1] << 8 ) | + (((const unsigned char *)data)[1] << 8 ) | (((const unsigned char *)data)[0] << 0 ); } } @@ -262,100 +293,150 @@ get32u(const void * const data, -static void -printFormatNumber(FILE * const fileP, - const void * const ValuePtr, - int const Format, - int const ByteCount, - ByteOrder const byteOrder) { +static const char * +numberTraceValue(const void * const valueP, + int const format, + unsigned int const byteCt, + ByteOrder const byteOrder) { /*-------------------------------------------------------------------------- - Display a number as one of its many formats + Format for display a number represented in any of the numeric formats --------------------------------------------------------------------------*/ - switch(Format){ + const char * retval; + + switch(format) { case FMT_SBYTE: case FMT_BYTE: - fprintf(fileP, "%02x\n", *(unsigned char *)ValuePtr); + pm_asprintf(&retval, "%02x", *(unsigned char *)valueP); break; case FMT_USHORT: - fprintf(fileP, "%d\n",get16u(ValuePtr, byteOrder)); + pm_asprintf(&retval, "%d",get16u(valueP, byteOrder)); break; - case FMT_ULONG: + case FMT_ULONG: case FMT_SLONG: - fprintf(fileP, "%d\n",get32s(ValuePtr, byteOrder)); + pm_asprintf(&retval, "%d",get32s(valueP, byteOrder)); break; - case FMT_SSHORT: - fprintf(fileP, "%hd\n",(signed short)get16u(ValuePtr, byteOrder)); + case FMT_SSHORT: + pm_asprintf(&retval, "%hd",(signed short)get16u(valueP, byteOrder)); break; case FMT_URATIONAL: - case FMT_SRATIONAL: - fprintf(fileP, "%d/%d\n",get32s(ValuePtr, byteOrder), - get32s(4+(char *)ValuePtr, byteOrder)); + case FMT_SRATIONAL: + pm_asprintf(&retval, "%d/%d", + get32s(valueP, byteOrder), + get32s(4+(char *)valueP, + byteOrder)); break; - case FMT_SINGLE: - fprintf(fileP, "%f\n",(double)*(float *)ValuePtr); + case FMT_SINGLE: + pm_asprintf(&retval, "%f",(double)*(float *)valueP); break; case FMT_DOUBLE: - fprintf(fileP, "%f\n",*(double *)ValuePtr); + pm_asprintf(&retval, "%f",*(double *)valueP); break; - default: - fprintf(fileP, "Unknown format %d:", Format); - { - unsigned int a; - for (a = 0; a < ByteCount && a < 16; ++a) - printf("%02x", ((unsigned char *)ValuePtr)[a]); + default: { + char * hex; + + MALLOCARRAY(hex, byteCt*2 + 1); + if (!hex) + retval = pm_strsol; + else { + unsigned int i; + for (i = 0; i < byteCt && i < 16; ++i) { + sprintf(&hex[i*2], "%02x", + ((const unsigned char *)valueP)[i]); + } + pm_asprintf(&retval, "Unknown format %d: %s", format, hex); } - fprintf(fileP, "\n"); } + } + return retval; } static double -convertAnyFormat(const void * const ValuePtr, - int const Format, - ByteOrder const byteOrder) { +numericValue(const void * const valuePtr, + int const format, + ByteOrder const byteOrder) { /*-------------------------------------------------------------------------- Evaluate number, be it int, rational, or float from directory. --------------------------------------------------------------------------*/ - double Value; - Value = 0; + double value; - switch(Format){ + switch(format) { case FMT_SBYTE: - Value = *(signed char *)ValuePtr; + value = *(signed char *)valuePtr; break; case FMT_BYTE: - Value = *(unsigned char *)ValuePtr; + value = *(unsigned char *)valuePtr; break; case FMT_USHORT: - Value = get16u(ValuePtr, byteOrder); + value = get16u(valuePtr, byteOrder); break; case FMT_ULONG: - Value = get32u(ValuePtr, byteOrder); + value = get32u(valuePtr, byteOrder); break; case FMT_URATIONAL: case FMT_SRATIONAL: { int num, den; - num = get32s(ValuePtr, byteOrder); - den = get32s(4+(char *)ValuePtr, byteOrder); - Value = den == 0 ? 0 : (double)(num/den); + num = get32s(valuePtr, byteOrder); + den = get32s(4+(char *)valuePtr, byteOrder); + value = den == 0 ? 0 : (double)(num/den); } break; case FMT_SSHORT: - Value = (signed short)get16u(ValuePtr, byteOrder); + value = (signed short)get16u(valuePtr, byteOrder); break; case FMT_SLONG: - Value = get32s(ValuePtr, byteOrder); + value = get32s(valuePtr, byteOrder); break; /* Not sure if this is correct (never seen float used in Exif format) */ case FMT_SINGLE: - Value = (double)*(float *)ValuePtr; + value = (double)*(float *)valuePtr; break; case FMT_DOUBLE: - Value = *(double *)ValuePtr; + value = *(double *)valuePtr; break; } - return Value; + return value; +} + + + +static const char * +stringTraceValue(const unsigned char * const value, + unsigned int const valueSz) { + + const char * retval; + char * buffer; + + MALLOCARRAY(buffer, valueSz + 1); + if (!buffer) + retval = pm_strsol; + else { + unsigned int i; + bool noPrint; + /* We're in a sequence of unprintable characters. We put one + '?' in the value for the whole sequence. + */ + unsigned int outCursor; + + outCursor = 0; /* initial value */ + + for (i = 0, noPrint = false; i < valueSz; ++i) { + if (ISPRINT(value[i])) { + buffer[outCursor++] = value[i]; + noPrint = false; + } else { + if (!noPrint) { + buffer[outCursor++] = '?'; + noPrint = true; + } + } + } + buffer[outCursor++] = '\0'; + + retval = buffer; + } + return retval; } @@ -363,72 +444,186 @@ convertAnyFormat(const void * const ValuePtr, static void traceTag(int const tag, int const format, - const unsigned char * const valuePtr, - unsigned int const byteCount, + const unsigned char * const value, + unsigned int const valueSz, ByteOrder const byteOrder) { - - /* Show tag name */ - unsigned int a; - bool found; - for (a = 0, found = false; !found; ++a){ - if (tagTable[a].Tag == 0){ - fprintf(stderr, " Unknown Tag %04x Value = ", tag); - found = true; - } - if (tagTable[a].Tag == tag){ - fprintf(stderr, " %s = ",tagTable[a].Desc); - found = true; - } + + const char * tagNm; + const char * tagValue; + unsigned int i; + + for (i = 0, tagNm = NULL; tagTable[i].tag; ++i) { + if (tagTable[i].tag == tag) + tagNm = pm_strdup(tagTable[i].desc); } + if (!tagNm) + pm_asprintf(&tagNm, "Unknown Tag %04x", tag); + /* Show tag value. */ - switch(format){ + switch (format) { case FMT_UNDEFINED: /* Undefined is typically an ascii string. */ - case FMT_STRING: { - /* String arrays printed without function call - (different from int arrays) - */ - bool noPrint; - printf("\""); - for (a = 0, noPrint = false; a < byteCount; ++a){ - if (ISPRINT((valuePtr)[a])){ - fprintf(stderr, "%c", valuePtr[a]); - noPrint = false; - } 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 = true; - } - } - } - fprintf(stderr, "\"\n"); + tagValue = stringTraceValue(value, valueSz); } break; default: /* Handle arrays of numbers later (will there ever be?)*/ - printFormatNumber(stderr, valuePtr, format, byteCount, byteOrder); + tagValue = numberTraceValue(value, format, valueSz, byteOrder); } + pm_message("%s = \"%s\"", tagNm, tagValue); + + pm_strfree(tagValue); + pm_strfree(tagNm); } +static void +initializeIfd(exif_ifd * const ifdP) { + + ifdP->cameraMake = NULL; + ifdP->cameraModel = NULL; + ifdP->dateTime = NULL; + ifdP->xResolutionP = NULL; + ifdP->yResolutionP = NULL; + ifdP->orientationP = NULL; + ifdP->isColorP = NULL; + ifdP->flashP = NULL; + ifdP->focalLengthP = NULL; + ifdP->exposureTimeP = NULL; + ifdP->shutterSpeedP = NULL; + ifdP->apertureFNumberP = NULL; + ifdP->distanceP = NULL; + ifdP->exposureBiasP = NULL; + ifdP->whiteBalanceP = NULL; + ifdP->meteringModeP = NULL; + ifdP->exposureProgramP = NULL; + ifdP->isoEquivalentP = NULL; + ifdP->compressionLevelP = NULL; + ifdP->comments = NULL; + ifdP->thumbnailOffsetP = NULL; + ifdP->thumbnailLengthP = NULL; + ifdP->exifImageLengthP = NULL; + ifdP->exifImageWidthP = NULL; + ifdP->focalPlaneXResP = NULL; + ifdP->focalPlaneUnitsP = NULL; + ifdP->thumbnail = NULL; +} + + + +static void +freeIfPresent(const void * const arg) { + + if (arg) + free((void *)arg); +} + + + +static void +strfreeIfPresent(const char * const arg) { + + if (arg) + pm_strfree(arg); +} + + + +static void +terminateIfd(exif_ifd * const ifdP) { + + strfreeIfPresent(ifdP->cameraMake ); + strfreeIfPresent(ifdP->cameraModel ); + strfreeIfPresent(ifdP->dateTime ); + strfreeIfPresent(ifdP->comments ); + freeIfPresent(ifdP->xResolutionP ); + freeIfPresent(ifdP->yResolutionP ); + freeIfPresent(ifdP->orientationP ); + freeIfPresent(ifdP->isColorP ); + freeIfPresent(ifdP->flashP ); + freeIfPresent(ifdP->focalLengthP ); + freeIfPresent(ifdP->exposureTimeP ); + freeIfPresent(ifdP->shutterSpeedP ); + freeIfPresent(ifdP->apertureFNumberP ); + freeIfPresent(ifdP->distanceP ); + freeIfPresent(ifdP->exposureBiasP ); + freeIfPresent(ifdP->whiteBalanceP ); + freeIfPresent(ifdP->meteringModeP ); + freeIfPresent(ifdP->exposureProgramP ); + freeIfPresent(ifdP->isoEquivalentP ); + freeIfPresent(ifdP->compressionLevelP ); + freeIfPresent(ifdP->thumbnailOffsetP ); + freeIfPresent(ifdP->thumbnailLengthP ); + freeIfPresent(ifdP->exifImageLengthP ); + freeIfPresent(ifdP->exifImageWidthP ); + freeIfPresent(ifdP->focalPlaneXResP ); + freeIfPresent(ifdP->focalPlaneUnitsP ); +} + + + +static const char * +commentValue(const unsigned char * const valuePtr, + unsigned int const valueSz) { + + /* Olympus has this padded with trailing spaces. We stop the copy + where those start. + */ + const char * const value = (const char *)valuePtr; + + const char * retval; + char * buffer; /* malloc'ed */ + unsigned int cursor; + unsigned int end; + + for (end = valueSz; end > 0 && value[end] == ' '; --end); + + /* Skip "ASCII" if it is there */ + if (end >= 5 && memeq(value, "ASCII", 5)) + cursor = 5; + else + cursor = 0; + + /* Skip consecutive blanks and NULs */ + + for (; + cursor < valueSz && + (value[cursor] == '\0' || value[cursor] == ' '); + ++cursor); + + /* Copy the rest as the comment */ + + MALLOCARRAY(buffer, end - cursor + 1); + if (!buffer) + retval = pm_strsol; + else { + unsigned int outCursor; + for (outCursor = 0; cursor < end; ++cursor) + buffer[outCursor++] = value[cursor]; + + buffer[outCursor++] = '\0'; + + retval = buffer; + } + return retval; +} + + /* Forward declaration for recursion */ -static void -processExifDir(const unsigned char * const ExifData, - unsigned int const ExifLength, - unsigned int const DirOffset, - exif_ImageInfo * const imageInfoP, - ByteOrder const byteOrder, - bool const wantTagTrace, - const unsigned char ** const LastExifRefdP); +static void +processIfd(const unsigned char * const exifData, + unsigned int const exifLength, + const unsigned char * const ifdData, + ByteOrder const byteOrder, + bool const wantTagTrace, + exif_ifd * const ifdP, + const unsigned char ** const nextIfdPP, + const char ** const errorP); static void @@ -437,11 +632,8 @@ processDirEntry(const unsigned char * const dirEntry, unsigned int const exifLength, ByteOrder const byteOrder, bool const wantTagTrace, - exif_ImageInfo * const imageInfoP, - unsigned int * const thumbnailOffsetP, - unsigned int * const thumbnailSizeP, - bool * const haveThumbnailP, - const unsigned char ** const lastExifRefdP) { + exif_ifd * const ifdP, + const char ** const errorP) { int const tag = get16u(&dirEntry[0], byteOrder); int const format = get16u(&dirEntry[2], byteOrder); @@ -452,27 +644,29 @@ processDirEntry(const unsigned char * const dirEntry, 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. */ - unsigned int byteCount; + unsigned int valueSz; + + *errorP = NULL; /* initial assumption */ if ((format-1) >= NUM_FORMATS) { /* (-1) catches illegal zero case as unsigned underflows - to positive large. + to positive large. */ pm_message("Illegal number format %d for tag %04x", format, tag); return; } - - byteCount = components * bytesPerFormat[format]; - if (byteCount > 4){ + valueSz = components * bytesPerFormat[format]; + + if (valueSz > 4) { unsigned const offsetVal = get32u(&dirEntry[8], byteOrder); /* If its bigger than 4 bytes, the dir entry contains an offset.*/ - if (offsetVal + byteCount > exifLength){ + if (offsetVal + valueSz > 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); + tag, offsetVal, valueSz, exifLength); return; } valuePtr = &exifData[offsetVal]; @@ -481,366 +675,361 @@ processDirEntry(const unsigned char * const dirEntry, 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 (wantTagTrace) - traceTag(tag, format, valuePtr, byteCount, byteOrder); - - *haveThumbnailP = (tag == TAG_THUMBNAIL_OFFSET); - + traceTag(tag, format, valuePtr, valueSz, byteOrder); + /* TODO: Need to deal with nonterminated strings in tag value */ + /* TODO: Deal with repeated tag */ /* Extract useful components of tag */ - switch (tag){ - + switch (tag) { case TAG_MAKE: - STRSCPY(imageInfoP->CameraMake, (const char*)valuePtr); + ifdP->cameraMake = pm_strdup((const char*)valuePtr); break; case TAG_MODEL: - STRSCPY(imageInfoP->CameraModel, (const char*)valuePtr); + ifdP->cameraModel = pm_strdup((const char*)valuePtr); break; case TAG_XRESOLUTION: - imageInfoP->XResolution = - convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->xResolutionP); + *ifdP->xResolutionP = numericValue(valuePtr, format, byteOrder); break; case TAG_YRESOLUTION: - imageInfoP->YResolution = - convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->yResolutionP); + *ifdP->yResolutionP = numericValue(valuePtr, format, byteOrder); break; case TAG_DATETIME_ORIGINAL: - STRSCPY(imageInfoP->DateTime, (const char*)valuePtr); - imageInfoP->DatePointer = (const char*)valuePtr; + ifdP->dateTime = pm_strdup((const char*)valuePtr); break; - case TAG_USERCOMMENT: { - /* Olympus has this padded with trailing spaces. We stop the copy - where those start. - */ - const char * const value = (const char *)valuePtr; - - unsigned int cursor; - unsigned int outCursor; - unsigned int end; - - for (end = byteCount; end > 0 && value[end] == ' '; --end); - - /* Skip "ASCII" if it is there */ - if (end >= 5 && MEMEQ(value, "ASCII", 5)) - cursor = 5; - else - cursor = 0; - - /* Skip consecutive blanks and NULs */ - - for (; - cursor < byteCount && - (value[cursor] == '\0' || value[cursor] == ' '); - ++cursor); - - /* Copy the rest as the comment */ - - for (outCursor = 0; - cursor < end && outCursor < MAX_COMMENT-1; - ++cursor) - imageInfoP->Comments[outCursor++] = value[cursor]; - - imageInfoP->Comments[outCursor++] = '\0'; - } break; + case TAG_USERCOMMENT: + ifdP->comments = commentValue(valuePtr, valueSz); + break; case TAG_FNUMBER: /* Simplest way of expressing aperture, so I trust it the most. - (overwrite previously computd value if there is one) + (replace any existing value, as it will be based on a less useful + tag that came earlier in the IFD). */ - imageInfoP->ApertureFNumber = - (float)convertAnyFormat(valuePtr, format, byteOrder); + if (ifdP->apertureFNumberP) + free(ifdP->apertureFNumberP); + + MALLOCVAR_NOFAIL(ifdP->apertureFNumberP); + *ifdP->apertureFNumberP = + (float)numericValue(valuePtr, format, byteOrder); 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 we already have aperture information, it probably came from an + FNUMBER tag and is superior, so we leave it alone */ - if (imageInfoP->ApertureFNumber == 0){ - imageInfoP->ApertureFNumber = (float) - exp(convertAnyFormat(valuePtr, format, byteOrder) + if (!ifdP->apertureFNumberP) { + MALLOCVAR_NOFAIL(ifdP->apertureFNumberP); + *ifdP->apertureFNumberP = (float) + exp(numericValue(valuePtr, format, byteOrder) * 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. + as a function of how farthey are zoomed in. */ - imageInfoP->FocalLength = - (float)convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->focalLengthP); + *ifdP->focalLengthP = + (float)numericValue(valuePtr, format, byteOrder); 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, byteOrder); + MALLOCVAR_NOFAIL(ifdP->distanceP); + *ifdP->distanceP = + (float)numericValue(valuePtr, format, byteOrder); 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, byteOrder); + MALLOCVAR_NOFAIL(ifdP->exposureTimeP); + *ifdP->exposureTimeP = + (float)numericValue(valuePtr, format, byteOrder); 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, byteOrder) - * log(2))); - } + MALLOCVAR_NOFAIL(ifdP->shutterSpeedP); + *ifdP->shutterSpeedP = + 1 << (unsigned int)numericValue(valuePtr, format, byteOrder); break; - case TAG_FLASH: - if ((int)convertAnyFormat(valuePtr, format, byteOrder) & 0x7){ - imageInfoP->FlashUsed = TRUE; - }else{ - imageInfoP->FlashUsed = FALSE; - } - break; + case TAG_FLASH: { + unsigned int const tagValue = + (unsigned int)numericValue(valuePtr, format, byteOrder); + + MALLOCVAR_NOFAIL(ifdP->flashP); + + *ifdP->flashP = ((tagValue & 0x7) != 0); + + } break; - case TAG_ORIENTATION: - imageInfoP->Orientation = - (int)convertAnyFormat(valuePtr, format, byteOrder); - if (imageInfoP->Orientation < 1 || - imageInfoP->Orientation > 8){ - pm_message("Undefined rotation value %d", - imageInfoP->Orientation); - imageInfoP->Orientation = 0; + case TAG_ORIENTATION: { + unsigned int const tagValue = + (unsigned int)numericValue(valuePtr, format, byteOrder); + + if (tagValue < 1 || tagValue > 8) + pm_asprintf(errorP, "Unrecognized orientation value %d", + tagValue); + else { + MALLOCVAR_NOFAIL(ifdP->orientationP); + + switch (tagValue) { + case 1: *ifdP->orientationP = ORIENT_NORMAL; break; + case 2: *ifdP->orientationP = ORIENT_FLIP_HORIZ; break; + case 3: *ifdP->orientationP = ORIENT_ROTATE_180; break; + case 4: *ifdP->orientationP = ORIENT_FLIP_VERT; break; + case 5: *ifdP->orientationP = ORIENT_TRANSPOSE; break; + case 6: *ifdP->orientationP = ORIENT_ROTATE_90; break; + case 7: *ifdP->orientationP = ORIENT_TRANSVERSE; break; + case 8: *ifdP->orientationP = ORIENT_ROTATE_270; break; + default: + assert(false); + } } - break; + } break; case TAG_EXIF_IMAGELENGTH: + MALLOCVAR_NOFAIL(ifdP->exifImageLengthP); + *ifdP->exifImageLengthP = + (unsigned int)numericValue(valuePtr, format, byteOrder); + break; + case TAG_EXIF_IMAGEWIDTH: - /* Use largest of height and width to deal with images - that have been rotated to portrait format. - */ - ExifImageWidth = - MIN(ExifImageWidth, - (int)convertAnyFormat(valuePtr, format, byteOrder)); + MALLOCVAR_NOFAIL(ifdP->exifImageWidthP); + *ifdP->exifImageWidthP = + (unsigned int)numericValue(valuePtr, format, byteOrder); break; case TAG_FOCALPLANEXRES: - HaveXRes = TRUE; - FocalplaneXRes = convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->focalPlaneXResP); + *ifdP->focalPlaneXResP = numericValue(valuePtr, format, byteOrder); break; - case TAG_FOCALPLANEUNITS: - switch((int)convertAnyFormat(valuePtr, format, byteOrder)){ - 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 TAG_FOCALPLANEUNITS: { + int const tagValue = (int)numericValue(valuePtr, format, byteOrder); - case 3: FocalplaneUnits = 10; break; /* 1 centimeter*/ - case 4: FocalplaneUnits = 1; break; /* 1 millimeter*/ - case 5: FocalplaneUnits = .001; break; /* 1 micrometer*/ + if (tagValue < 1 || tagValue > 5) { + pm_asprintf(errorP, "Unrecognized FOCALPLANEUNITS value %d. " + "We know only 1, 2, 3, 4, and 5", + tagValue); + } else { + MALLOCVAR_NOFAIL(ifdP->focalPlaneUnitsP); + + switch (tagValue) { + case 1: *ifdP->focalPlaneUnitsP = 25.4; break; /* 1 inch */ + case 2: *ifdP->focalPlaneUnitsP = 100.0; break; /* 1 meter */ + case 3: *ifdP->focalPlaneUnitsP = 10.0; break; /* 1 centimeter*/ + case 4: *ifdP->focalPlaneUnitsP = 1.0; break; /* 1 millimeter*/ + case 5: *ifdP->focalPlaneUnitsP = .001; break; /* 1 micrometer*/ + } + /* According to the information I was using, 2 means meters. But + looking at the Cannon powershot's files, inches is the only + sensible value. + */ } - break; + } break; /* Remaining cases contributed by: Volker C. Schoech (schoech@gmx.de) */ case TAG_EXPOSURE_BIAS: - imageInfoP->ExposureBias = - (float) convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->exposureBiasP); + *ifdP->exposureBiasP = + (float) numericValue(valuePtr, format, byteOrder); break; case TAG_WHITEBALANCE: - imageInfoP->Whitebalance = - (int)convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->whiteBalanceP); + *ifdP->whiteBalanceP = (int)numericValue(valuePtr, format, byteOrder); break; case TAG_METERING_MODE: - imageInfoP->MeteringMode = - (int)convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->meteringModeP); + *ifdP->meteringModeP = (int)numericValue(valuePtr, format, byteOrder); break; case TAG_EXPOSURE_PROGRAM: - imageInfoP->ExposureProgram = - (int)convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->exposureProgramP); + *ifdP->exposureProgramP = + (int)numericValue(valuePtr, format, byteOrder); break; - case TAG_ISO_EQUIVALENT: - imageInfoP->ISOequivalent = - (int)convertAnyFormat(valuePtr, format, byteOrder); - if ( imageInfoP->ISOequivalent < 50 ) - imageInfoP->ISOequivalent *= 200; - break; + case TAG_ISO_EQUIVALENT: { + int const tagValue = (int)numericValue(valuePtr, format, byteOrder); + + MALLOCVAR_NOFAIL(ifdP->isoEquivalentP); + if (tagValue < 50) + *ifdP->isoEquivalentP = tagValue * 200; + else + *ifdP->isoEquivalentP = tagValue; + } break; case TAG_COMPRESSION_LEVEL: - imageInfoP->CompressionLevel = - (int)convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->compressionLevelP); + *ifdP->compressionLevelP = + (int)numericValue(valuePtr, format, byteOrder); break; case TAG_THUMBNAIL_OFFSET: - *thumbnailOffsetP = (unsigned int) - convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->thumbnailOffsetP); + *ifdP->thumbnailOffsetP = (unsigned int) + numericValue(valuePtr, format, byteOrder); break; case TAG_THUMBNAIL_LENGTH: - *thumbnailSizeP = (unsigned int) - convertAnyFormat(valuePtr, format, byteOrder); + MALLOCVAR_NOFAIL(ifdP->thumbnailLengthP); + *ifdP->thumbnailLengthP = (unsigned int) + numericValue(valuePtr, format, byteOrder); break; case TAG_EXIF_OFFSET: case TAG_INTEROP_OFFSET: { - unsigned int const subdirOffset = get32u(valuePtr, byteOrder); - if (subdirOffset >= exifLength) - pm_message("Illegal exif or interop offset " + unsigned int const subIfdOffset = get32u(valuePtr, byteOrder); + if (subIfdOffset + 4 > exifLength) + pm_message("Invalid exif or interop offset " "directory link. Offset is %u, " "but Exif data is only %u bytes.", - subdirOffset, exifLength); - else - processExifDir(exifData, exifLength, subdirOffset, - imageInfoP, byteOrder, wantTagTrace, - lastExifRefdP); + subIfdOffset, exifLength); + else { + /* Process the chain of IFDs starting at 'subIfdOffset'. + Merge whatever tags are in them into *ifdP + */ + const unsigned char * nextIfdP; + + for (nextIfdP = exifData + subIfdOffset; nextIfdP;) { + const char * error; + + if (wantTagTrace) + pm_message("Processing subIFD"); + + processIfd(exifData, exifLength, exifData + subIfdOffset, + byteOrder, wantTagTrace, + ifdP, &nextIfdP, &error); + + if (error) { + pm_asprintf(errorP, "Failed to process " + "ExifOffset/InteropOffset tag. %s", error); + pm_strfree(error); + } + } + } } break; } } -static void -processExifDir(const unsigned char * const exifData, - unsigned int const exifLength, - unsigned int const dirOffset, - exif_ImageInfo * const imageInfoP, - ByteOrder const byteOrder, - bool const wantTagTrace, - const unsigned char ** const lastExifRefdP) { +static void +locateNextIfd(const unsigned char * const exifData, + unsigned int const exifLength, + ByteOrder const byteOrder, + const unsigned char * const nextIfdLinkPtr, + const unsigned char ** const nextIfdPP, + const char ** const errorP) { + + if (nextIfdLinkPtr + 4 > exifData + exifLength) + pm_asprintf(errorP, "EXIF header ends before next-IFD link"); + else { + if (nextIfdLinkPtr + 4 <= exifData + exifLength) { + unsigned int const nextIfdLink = + get32u(nextIfdLinkPtr, byteOrder); + /* Offset in EXIF header of next IFD, or zero if none */ + + if (nextIfdLink) { + if (nextIfdLink + 4 >= exifLength) { + pm_asprintf(errorP, "Next IFD link is %u, " + "but EXIF header is only %u bytes long", + nextIfdLink, exifLength); + } else + *nextIfdPP = exifData + nextIfdLink; + } else + *nextIfdPP = NULL; + } + } +} + + + +static void +processIfd(const unsigned char * const exifData, + unsigned int const exifLength, + const unsigned char * const ifdData, + ByteOrder const byteOrder, + bool const wantTagTrace, + exif_ifd * const ifdP, + const unsigned char ** const nextIfdPP, + const char ** const errorP) { /*-------------------------------------------------------------------------- - Process one of the nested EXIF directories. + Process one EXIF IFD (Image File Directory). + + The text of the IFD is at 'ifdData'. + + The text of the EXIF header of which the IFD is part is the 'exifLength' + bytes at 'exifData'. + + Return the contents of the directory (tags) as *ifdP. + + Return as *nextIfdPP a pointer into the EXIF header at 'exifData' + to the next IFD in the chain, or NULL if this is the last IFD. --------------------------------------------------------------------------*/ - const unsigned char * const dirStart = exifData + dirOffset; - unsigned int const numDirEntries = get16u(&dirStart[0], byteOrder); + unsigned int const numDirEntries = get16u(&ifdData[0], byteOrder); + unsigned int de; - bool haveThumbnail; - unsigned int thumbnailOffset; - unsigned int thumbnailSize; - - #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) - - { - const unsigned char * const 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; - } - } - *lastExifRefdP = MAX(*lastExifRefdP, dirEnd); - } + + *errorP = NULL; /* initial value */ + + #define DIR_ENTRY_ADDR(Start, Entry) (Start + 2 + 12*(Entry)) if (wantTagTrace) - pm_message("Directory with %d entries", numDirEntries); - - haveThumbnail = false; /* initial value */ - thumbnailOffset = 0; /* initial value */ - thumbnailSize = 0; /* initial value */ - - for (de = 0; de < numDirEntries; ++de) - processDirEntry(DIR_ENTRY_ADDR(dirStart, de), exifData, exifLength, - byteOrder, wantTagTrace, imageInfoP, - &thumbnailOffset, &thumbnailSize, &haveThumbnail, - lastExifRefdP); - - if (haveThumbnail) - DirWithThumbnailPtrs = dirStart; - - { - /* 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, byteOrder); - if (subdirOffset){ - const 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 (wantTagTrace) - 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, byteOrder, wantTagTrace, - lastExifRefdP); - } - } - }else{ - /* The exif header ends before the last next directory pointer. */ + pm_message("Processing IFD at %p with %u entries", + ifdData, numDirEntries); + + for (de = 0; de < numDirEntries && !*errorP; ++de) { + const char * error; + processDirEntry(DIR_ENTRY_ADDR(ifdData, de), exifData, exifLength, + byteOrder, wantTagTrace, ifdP, + &error); + + if (error) { + pm_asprintf(errorP, "Failed to process tag %u. %s", + de, error); + pm_strfree(error); } } - if (thumbnailSize && thumbnailOffset){ - if (thumbnailSize + thumbnailOffset <= exifLength){ - /* The thumbnail pointer appears to be valid. Store it. */ - imageInfoP->ThumbnailPointer = exifData + thumbnailOffset; - imageInfoP->ThumbnailSize = thumbnailSize; + locateNextIfd(exifData, exifLength, byteOrder, + DIR_ENTRY_ADDR(ifdData, numDirEntries), + nextIfdPP, errorP); - if (wantTagTrace){ - fprintf(stderr, "Thumbnail size: %u bytes\n", thumbnailSize); - } + if (ifdP->thumbnailOffsetP && ifdP->thumbnailLengthP) { + if (*ifdP->thumbnailOffsetP + *ifdP->thumbnailLengthP <= exifLength) { + /* The thumbnail pointer appears to be valid. Store it. */ + ifdP->thumbnail = exifData + *ifdP->thumbnailOffsetP; + ifdP->thumbnailSize = *ifdP->thumbnailLengthP; } } + if (wantTagTrace) + pm_message("Done processing IFD at %p", ifdData); } -void +void exif_parse(const unsigned char * const exifData, unsigned int const length, - exif_ImageInfo * const imageInfoP, + exif_ImageInfo * const imageInfoP, bool const wantTagTrace, const char ** const errorP) { /*-------------------------------------------------------------------------- @@ -852,34 +1041,33 @@ exif_parse(const unsigned char * const exifData, 'length' is the length of the Exif section. --------------------------------------------------------------------------*/ ByteOrder byteOrder; - int FirstOffset; - const unsigned char * lastExifRefd; + unsigned int firstOffset; *errorP = NULL; /* initial assumption */ if (wantTagTrace) - fprintf(stderr, "Exif header %d bytes long\n",length); + pm_message("Exif header %u bytes long", length); - if (MEMEQ(exifData + 0, "II" , 2)) { - if (wantTagTrace) - fprintf(stderr, "Exif header in Intel order\n"); - byteOrder = NORMAL; + if (memeq(exifData + 0, "II" , 2)) { + if (wantTagTrace) + pm_message("Exif header in Intel order"); + byteOrder = ORDER_NORMAL; } else { - if (MEMEQ(exifData + 0, "MM", 2)) { - if (wantTagTrace) - fprintf(stderr, "Exif header in Motorola order\n"); - byteOrder = MOTOROLA; + if (memeq(exifData + 0, "MM", 2)) { + if (wantTagTrace) + pm_message("Exif header in Motorola order"); + byteOrder = ORDER_MOTOROLA; } else { pm_asprintf(errorP, "Invalid alignment marker in Exif " "data. First two bytes are '%c%c' (0x%02x%02x) " - "instead of 'II' or 'MM'.", + "instead of 'II' or 'MM'.", exifData[0], exifData[1], exifData[0], exifData[1]); } } if (!*errorP) { unsigned short const start = get16u(exifData + 2, byteOrder); /* Check the next value for correctness. */ - if (start != 0x002a){ + if (start != 0x002a) { pm_asprintf(errorP, "Invalid Exif header start. " "two bytes after the alignment marker " "should be 0x002a, but is 0x%04x", @@ -887,237 +1075,288 @@ exif_parse(const unsigned char * const exifData, } } if (!*errorP) { - FirstOffset = get32u(exifData + 4, byteOrder); - if (FirstOffset < 8 || FirstOffset > 16){ + const char * error; + const unsigned char * nextIfdP; + + firstOffset = get32u(exifData + 4, byteOrder); + 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 + indicated it's 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, byteOrder, wantTagTrace, &lastExifRefd); - - /* Compute the CCD width, in millimeters. */ - if (HaveXRes){ - imageInfoP->HaveCCDWidth = 1; - imageInfoP->CCDWidth = - (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); - } else - imageInfoP->HaveCCDWidth = 0; - - if (wantTagTrace){ - fprintf(stderr, - "Non-settings part of Exif header: %lu bytes\n", - (unsigned long)(exifData + length - lastExifRefd)); + + initializeIfd(&imageInfoP->mainImage); + initializeIfd(&imageInfoP->thumbnailImage); + + if (wantTagTrace) + pm_message("Processing main image IFD (IFD0)"); + + processIfd(exifData, length, exifData + firstOffset, byteOrder, + wantTagTrace, + &imageInfoP->mainImage, &nextIfdP, &error); + + if (error) { + pm_asprintf(errorP, "Failed to process main image IFD. %s", + error); + pm_strfree(error); + } + + if (nextIfdP) { + const char * error; + + if (wantTagTrace) + pm_message("Processing thumbnail IFD (IFD1)"); + + processIfd(exifData, length, nextIfdP, byteOrder, + wantTagTrace, + &imageInfoP->thumbnailImage, &nextIfdP, &error); + + if (error) { + pm_asprintf(errorP, + "Failed to process thumbnail image IFD. %s", + error); + pm_strfree(error); + } else { + if (nextIfdP) { + pm_message("Ignoring third IFD in EXIF header because " + "We understand only two -- one for the main " + "image and one for the thumbnail"); + } + } + } + if (!*errorP) { + /* Compute the CCD width, in millimeters. */ + if (imageInfoP->mainImage.focalPlaneXResP && + imageInfoP->mainImage.focalPlaneUnitsP && + imageInfoP->mainImage.exifImageWidthP && + imageInfoP->mainImage.exifImageLengthP) { + + unsigned int const maxDim = + MAX(*imageInfoP->mainImage.exifImageWidthP, + *imageInfoP->mainImage.exifImageLengthP); + + MALLOCVAR_NOFAIL(imageInfoP->ccdWidthP); + *imageInfoP->ccdWidthP = + (float)(maxDim * + *imageInfoP->mainImage.focalPlaneUnitsP / + *imageInfoP->mainImage.focalPlaneXResP); + } else + imageInfoP->ccdWidthP = NULL; } } } -void -exif_showImageInfo(const exif_ImageInfo * const imageInfoP, - FILE * const fileP) { -/*-------------------------------------------------------------------------- - Show the collected image info, displaying camera F-stop and shutter - speed in a consistent and legible fashion. ---------------------------------------------------------------------------*/ - if (imageInfoP->CameraMake[0]) { - fprintf(fileP, "Camera make : %s\n", imageInfoP->CameraMake); - fprintf(fileP, "Camera model : %s\n", imageInfoP->CameraModel); - } - if (imageInfoP->DateTime[0]) - fprintf(fileP, "Date/Time : %s\n", imageInfoP->DateTime); - - fprintf(fileP, "Resolution : %f x %f\n", - imageInfoP->XResolution, imageInfoP->YResolution); - - 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(fileP, "Orientation : %s\n", - OrientTab[imageInfoP->Orientation]); - } +static void +showIfd(const exif_ifd * const ifdP) { - if (imageInfoP->IsColor == 0) - fprintf(fileP, "Color/bw : Black and white\n"); + if (ifdP->cameraMake) + pm_message("Camera make : %s", ifdP->cameraMake); - if (imageInfoP->FlashUsed >= 0) - fprintf(fileP, "Flash used : %s\n", - imageInfoP->FlashUsed ? "Yes" :"No"); + if (ifdP->cameraModel) + pm_message("Camera model : %s", ifdP->cameraModel); - if (imageInfoP->FocalLength) { - fprintf(fileP, "Focal length : %4.1fmm", - (double)imageInfoP->FocalLength); - if (imageInfoP->HaveCCDWidth){ - fprintf(fileP, " (35mm equivalent: %dmm)", - (int) - (imageInfoP->FocalLength/imageInfoP->CCDWidth*36 + 0.5)); - } - fprintf(fileP, "\n"); - } + if (ifdP->dateTime) + pm_message("Date/Time : %s", ifdP->dateTime); - if (imageInfoP->HaveCCDWidth) - fprintf(fileP, "CCD width : %2.4fmm\n", - (double)imageInfoP->CCDWidth); - - if (imageInfoP->ExposureTime) { - if (imageInfoP->ExposureTime < 0.010){ - fprintf(fileP, - "Exposure time: %6.4f s ", - (double)imageInfoP->ExposureTime); - }else{ - fprintf(fileP, - "Exposure time: %5.3f s ", - (double)imageInfoP->ExposureTime); - } - if (imageInfoP->ExposureTime <= 0.5){ - fprintf(fileP, " (1/%d)",(int)(0.5 + 1/imageInfoP->ExposureTime)); + if (ifdP->xResolutionP && ifdP->yResolutionP) + pm_message("Resolution : %f x %f", + *ifdP->xResolutionP, *ifdP->yResolutionP); + + if (ifdP->orientationP) { + /* Note that orientation is usually understood to be the orientation + of the camera, not of the image. The top, bottom, left, and right + sides of an image are defined in the JFIF format. + + But values such as "flip horizontal" make no sense for that. + */ + + const char * orientDisp; + + switch (*ifdP->orientationP) { + case ORIENT_NORMAL: orientDisp = "Normal"; break; + case ORIENT_FLIP_HORIZ: orientDisp = "Flip horizontal"; break; + case ORIENT_ROTATE_180: orientDisp = "Rotate 180"; break; + case ORIENT_FLIP_VERT: orientDisp = "Flip vertical"; break; + case ORIENT_TRANSPOSE: orientDisp = "Transpose"; break; + case ORIENT_ROTATE_90: orientDisp = "Rotate 90"; break; + case ORIENT_TRANSVERSE: orientDisp = "Transverse"; break; + case ORIENT_ROTATE_270: orientDisp = "Rotate 270"; break; } - fprintf(fileP, "\n"); - } - if (imageInfoP->ApertureFNumber){ - fprintf(fileP, "Aperture : f/%3.1f\n", - (double)imageInfoP->ApertureFNumber); + + pm_message("Orientation : %s", orientDisp); } - if (imageInfoP->Distance){ - if (imageInfoP->Distance < 0){ - fprintf(fileP, "Focus dist. : Infinite\n"); - }else{ - fprintf(fileP, "Focus dist. :%5.2fm\n", - (double)imageInfoP->Distance); + + if (ifdP->isColorP) + pm_message("Color/bw : %s", + *ifdP->isColorP ? "Color" : "Black and white"); + + if (ifdP->flashP) + pm_message("Flash used : %s", + *ifdP->flashP ? "Yes" :"No"); + + if (ifdP->exposureTimeP) { + const char * timeDisp; + const char * recipDisp; + + if (*ifdP->exposureTimeP < 0.010) { + pm_asprintf(&timeDisp, "%6.4f s", *ifdP->exposureTimeP); + } else { + pm_asprintf(&timeDisp, "%5.3f s", *ifdP->exposureTimeP); } + /* We've seen the EXPOSURETIME tag be present but contain zero. + I don't know why. + */ + if (*ifdP->exposureTimeP <= 0.5 && *ifdP->exposureTimeP > 0) { + pm_asprintf(&recipDisp, " (1/%d)", + (int)(0.5 + 1 / *ifdP->exposureTimeP)); + } else + recipDisp = pm_strdup(""); + + pm_message("Exposure time: %s %s", timeDisp, recipDisp); + + pm_strfree(recipDisp); + pm_strfree(timeDisp); } + if (ifdP->shutterSpeedP) + pm_message("Shutter speed: 1/%u", *ifdP->shutterSpeedP); - if (imageInfoP->ISOequivalent){ /* 05-jan-2001 vcs */ - fprintf(fileP, "ISO equiv. : %2d\n",(int)imageInfoP->ISOequivalent); + if (ifdP->apertureFNumberP) { + pm_message("Aperture : f/%3.1f", *ifdP->apertureFNumberP); } - if (imageInfoP->ExposureBias){ /* 05-jan-2001 vcs */ - fprintf(fileP, "Exposure bias:%4.2f\n", - (double)imageInfoP->ExposureBias); + if (ifdP->distanceP) { + if (*ifdP->distanceP < 0) + pm_message("Focus dist. : Infinite"); + else + pm_message("Focus dist. :%5.2fm", *ifdP->distanceP); } - - if (imageInfoP->Whitebalance){ /* 05-jan-2001 vcs */ - switch(imageInfoP->Whitebalance) { - case 1: - fprintf(fileP, "Whitebalance : sunny\n"); - break; - case 2: - fprintf(fileP, "Whitebalance : fluorescent\n"); - break; - case 3: - fprintf(fileP, "Whitebalance : incandescent\n"); - break; - default: - fprintf(fileP, "Whitebalance : cloudy\n"); + + if (ifdP->isoEquivalentP) + pm_message("ISO equiv. : %2d", *ifdP->isoEquivalentP); + + if (ifdP->exposureBiasP) + pm_message("Exposure bias: %4.2f", *ifdP->exposureBiasP); + + if (ifdP->whiteBalanceP) { + const char * whiteBalanceDisp; + + switch(*ifdP->whiteBalanceP) { + case 1: whiteBalanceDisp = "sunny"; break; + case 2: whiteBalanceDisp = "fluorescent"; break; + case 3: whiteBalanceDisp = "incandescent"; break; + default: whiteBalanceDisp = "cloudy"; break; } + pm_message("Whitebalance : %s", whiteBalanceDisp); } - if (imageInfoP->MeteringMode){ /* 05-jan-2001 vcs */ - switch(imageInfoP->MeteringMode) { - case 2: - fprintf(fileP, "Metering Mode: center weight\n"); - break; - case 3: - fprintf(fileP, "Metering Mode: spot\n"); - break; - case 5: - fprintf(fileP, "Metering Mode: matrix\n"); - break; + if (ifdP->meteringModeP) { + const char * meteringModeDisp; + + switch(*ifdP->meteringModeP) { + case 2: meteringModeDisp = "center weight"; break; + case 3: meteringModeDisp = "spot"; break; + case 5: meteringModeDisp = "matrix"; break; + default: meteringModeDisp = "?"; break; } + pm_message("Metering Mode: %s", meteringModeDisp); } - if (imageInfoP->ExposureProgram){ /* 05-jan-2001 vcs */ - switch(imageInfoP->ExposureProgram) { - case 2: - fprintf(fileP, "Exposure : program (auto)\n"); - break; - case 3: - fprintf(fileP, "Exposure : aperture priority (semi-auto)\n"); - break; - case 4: - fprintf(fileP, "Exposure : shutter priority (semi-auto)\n"); - break; + if (ifdP->exposureProgramP) { + const char * exposureDisp; + + switch(*ifdP->exposureProgramP) { + case 2: exposureDisp = "program (auto)"; break; + case 3: exposureDisp = "aperture priority (semi-auto)"; break; + case 4: exposureDisp = "shutter priority (semi-auto)"; break; + default: exposureDisp = "?"; break; } + pm_message("Exposure : %s", exposureDisp); } - if (imageInfoP->CompressionLevel){ /* 05-jan-2001 vcs */ - switch(imageInfoP->CompressionLevel) { - case 1: - fprintf(fileP, "Jpeg Quality : basic\n"); - break; - case 2: - fprintf(fileP, "Jpeg Quality : normal\n"); - break; - case 4: - fprintf(fileP, "Jpeg Quality : fine\n"); - break; + if (ifdP->compressionLevelP) { + const char * jpegQualityDisp; + + switch(*ifdP->compressionLevelP) { + case 1: jpegQualityDisp = "basic"; break; + case 2: jpegQualityDisp = "normal"; break; + case 4: jpegQualityDisp = "fine"; break; + default: jpegQualityDisp = "?"; break; } + pm_message("Jpeg Quality : %s", jpegQualityDisp); } - /* Print the comment. Print 'Comment:' for each new line of comment. */ - if (imageInfoP->Comments[0]) { - unsigned int a; + if (ifdP->comments) { + char * buffer; - fprintf(fileP, "Comment : "); + MALLOCARRAY(buffer, strlen(ifdP->comments) + 1); - for (a = 0; a < MAX_COMMENT && imageInfoP->Comments[a]; ++a) { - char const c = imageInfoP->Comments[a]; - if (c == '\n'){ - /* Do not start a new line if the string ends with a cr */ - if (imageInfoP->Comments[a+1] != '\0') - fprintf(fileP, "\nComment : "); - else - fprintf(fileP, "\n"); - } else - putc(c, fileP); + if (!buffer) + pm_message("Out of memory allocating a buffer for %u " + "characters of comments", + (unsigned)strlen(ifdP->comments)); + else { + unsigned int i; + unsigned int outCursor; + + strcpy(buffer, "Comment: "); /* Permanently in buffer */ + + outCursor = 10; /* initial value */ + + for (i = 0; ifdP->comments[i]; ++i) { + char const c = ifdP->comments[i]; + if (c == '\n') { + buffer[outCursor++] = '\0'; + pm_message("%s", buffer); + outCursor = 10; + } else + buffer[outCursor++] = c; + } + if (outCursor > 10) + pm_message("%s", buffer); + + free(buffer); } - fprintf(fileP, "\n"); } +} - fprintf(fileP, "\n"); + + +void +exif_showImageInfo(const exif_ImageInfo * const imageInfoP) { +/*-------------------------------------------------------------------------- + Show the collected image info, displaying camera F-stop and shutter + speed in a consistent and legible fashion. +--------------------------------------------------------------------------*/ + showIfd(&imageInfoP->mainImage); + + if (imageInfoP->mainImage.focalLengthP) { + const char * mm35equiv; + + if (imageInfoP->ccdWidthP) { + pm_asprintf(&mm35equiv, " (35mm equivalent: %dmm)", + (int) (*imageInfoP->mainImage.focalLengthP / + *imageInfoP->ccdWidthP * 36 + 0.5)); + } else + mm35equiv = pm_strdup(""); + + pm_message("Focal length : %4.1fmm %s", + *imageInfoP->mainImage.focalLengthP, mm35equiv); + + pm_strfree(mm35equiv); + } + + if (imageInfoP->ccdWidthP) + pm_message("CCD width : %2.4fmm", *imageInfoP->ccdWidthP); } + +void +exif_terminateImageInfo(exif_ImageInfo * const imageInfoP) { + + terminateIfd(&imageInfoP->mainImage); + terminateIfd(&imageInfoP->thumbnailImage); +} + + + diff --git a/converter/other/exif.h b/converter/other/exif.h index 57eb745b..37dcf240 100644 --- a/converter/other/exif.h +++ b/converter/other/exif.h @@ -10,52 +10,77 @@ #define PATH_MAX _MAX_PATH #endif +typedef struct { /*-------------------------------------------------------------------------- - This structure stores Exif header image elements in a simple manner - Used to store camera data as extracted from the various ways that it can be - stored in an exif header + A structure of this type contains the information from an EXIF header + Image File Directory (IFD). + + It appears that some of these members are possible only for certain kinds of + IFD (e.g. ThumbnailSize does not appear in a legal IFD for a main image), + but we recognize all tags in all IFDs all the same. --------------------------------------------------------------------------*/ + /* In all of the following members, a null pointer means "not present," + which normally means the tag from which the information comes was + not present in the IFD. + + The EXIF format might require certain tags to be present, but we + don't. + */ + const char * cameraMake; + const char * cameraModel; + const char * dateTime; + float * xResolutionP; + float * yResolutionP; + int * orientationP; + int * isColorP; + int * flashP; + float * focalLengthP; + float * exposureTimeP; + unsigned int * shutterSpeedP; /* e.g. 128 for 1/128 second */ + float * apertureFNumberP; + float * distanceP; + float * exposureBiasP; + int * whiteBalanceP; + int * meteringModeP; + int * exposureProgramP; + int * isoEquivalentP; + int * compressionLevelP; + const char * comments; + unsigned int * thumbnailOffsetP; + unsigned int * thumbnailLengthP; + unsigned int * exifImageLengthP; + unsigned int * exifImageWidthP; + double * focalPlaneXResP; + double * focalPlaneUnitsP; + + const unsigned char * thumbnail; /* Pointer at the thumbnail */ + unsigned thumbnailSize; /* Size of thumbnail. */ +} exif_ifd; + + typedef struct { - char CameraMake [32]; - char CameraModel [40]; - char DateTime [20]; - float XResolution; - float YResolution; - int Orientation; - int IsColor; - int FlashUsed; - float FocalLength; - float ExposureTime; - float ApertureFNumber; - float Distance; - int HaveCCDWidth; /* boolean */ - float CCDWidth; - float ExposureBias; - int Whitebalance; - int MeteringMode; - int ExposureProgram; - int ISOequivalent; - int CompressionLevel; - char Comments[MAX_COMMENT]; - - const unsigned char * ThumbnailPointer; /* Pointer at the thumbnail */ - unsigned ThumbnailSize; /* Size of thumbnail. */ - - const char * DatePointer; +/*-------------------------------------------------------------------------- + A structure of this type contains the information from an EXIF header. +--------------------------------------------------------------------------*/ + exif_ifd mainImage; /* aka IFD0 */ + exif_ifd thumbnailImage; /* aka IFD1 */ + float * ccdWidthP; /* NULL means none */ } exif_ImageInfo; /* Prototypes for exif.c functions. */ -void -exif_parse(const unsigned char * const exifSection, +void +exif_parse(const unsigned char * const exifSection, unsigned int const length, - exif_ImageInfo * const imageInfoP, + exif_ImageInfo * const imageInfoP, bool const wantTagTrace, const char ** const errorP); -void -exif_showImageInfo(const exif_ImageInfo * const imageInfoP, - FILE * const fileP); +void +exif_showImageInfo(const exif_ImageInfo * const imageInfoP); + +void +exif_terminateImageInfo(exif_ImageInfo * const imageInfoP); #endif diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c index 98552c00..6357e859 100644 --- a/converter/other/jpegtopnm.c +++ b/converter/other/jpegtopnm.c @@ -666,7 +666,7 @@ print_exif_info(struct jpeg_marker_struct const marker) { pm_message("EXIF header is invalid. %s", error); pm_strfree(error); } else - exif_showImageInfo(&imageInfo, stderr); + exif_showImageInfo(&imageInfo); } diff --git a/doc/HISTORY b/doc/HISTORY index 1164daca..55a5e9db 100644 --- a/doc/HISTORY +++ b/doc/HISTORY @@ -4,6 +4,11 @@ Netpbm. CHANGE HISTORY -------------- +23.03.25 BJH Release 10.86.38 + + jpegtopnm: Many fixes to -dumpexif. Always broken. + (-dumpexif was new in Netpbm 9.18 (September 2001)) + 23.03.14 BJH Release 10.86.37 pamtopng: fix -chroma option: always rejected. Always broken. diff --git a/version.mk b/version.mk index 6a91ce52..7aac55ea 100644 --- a/version.mk +++ b/version.mk @@ -1,3 +1,3 @@ NETPBM_MAJOR_RELEASE = 10 NETPBM_MINOR_RELEASE = 86 -NETPBM_POINT_RELEASE = 37 +NETPBM_POINT_RELEASE = 38 -- cgit 1.4.1