From 2b2e88c44df8fe21456de2f8d21a89d172943f8f Mon Sep 17 00:00:00 2001 From: giraffedata Date: Tue, 21 Mar 2023 02:11:23 +0000 Subject: cleanup git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@4521 9d0c8265-081b-0410-96cb-a4ca84ce46f8 --- converter/other/exif.c | 689 +++++++++++++++++++++++--------------------- converter/other/exif.h | 79 ++--- converter/other/jpegtopnm.c | 2 +- 3 files changed, 412 insertions(+), 358 deletions(-) diff --git a/converter/other/exif.c b/converter/other/exif.c index 208eac59..c02aa9e3 100644 --- a/converter/other/exif.c +++ b/converter/other/exif.c @@ -7,20 +7,40 @@ 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.). +/* + 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 @@ -42,6 +62,7 @@ #include "pm_c_util.h" #include "pm.h" +#include "mallocvar.h" #include "nstring.h" #include "exif.h" @@ -262,58 +283,69 @@ 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 --------------------------------------------------------------------------*/ + 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_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)); + 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)); + pm_asprintf(&retval, "%d/%d", + get32s(valueP, byteOrder), + get32s(4+(char *)valueP, + byteOrder)); break; case FMT_SINGLE: - fprintf(fileP, "%f\n",(double)*(float *)valuePtr); + 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", ((const 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. --------------------------------------------------------------------------*/ @@ -359,62 +391,85 @@ convertAnyFormat(const void * const valuePtr, +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; +} + + + 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; + tagValue = stringTraceValue(value, valueSz); - 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"); } 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); } @@ -422,13 +477,13 @@ traceTag(int const tag, /* 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); +processIfd(const unsigned char * const exifData, + unsigned int const exifLength, + unsigned int const dirOffset, + exif_ifd * const ifdP, + ByteOrder const byteOrder, + bool const wantTagTrace, + const unsigned char ** const lastExifRefdP); static void @@ -437,7 +492,7 @@ processDirEntry(const unsigned char * const dirEntry, unsigned int const exifLength, ByteOrder const byteOrder, bool const wantTagTrace, - exif_ImageInfo * const imageInfoP, + exif_ifd * const ifdP, unsigned int * const thumbnailOffsetP, unsigned int * const thumbnailSizeP, bool * const haveThumbnailP, @@ -452,7 +507,7 @@ 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; if ((format-1) >= NUM_FORMATS) { /* (-1) catches illegal zero case as unsigned underflows @@ -462,17 +517,17 @@ processDirEntry(const unsigned char * const dirEntry, return; } - byteCount = components * bytesPerFormat[format]; + valueSz = components * bytesPerFormat[format]; - if (byteCount > 4) { + 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,16 +536,16 @@ processDirEntry(const unsigned char * const dirEntry, valuePtr = &dirEntry[8]; } - if (*lastExifRefdP < valuePtr + byteCount) { + if (*lastExifRefdP < valuePtr + valueSz) { /* 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; + *lastExifRefdP = valuePtr + valueSz; } if (wantTagTrace) - traceTag(tag, format, valuePtr, byteCount, byteOrder); + traceTag(tag, format, valuePtr, valueSz, byteOrder); *haveThumbnailP = (tag == TAG_THUMBNAIL_OFFSET); @@ -498,26 +553,25 @@ processDirEntry(const unsigned char * const dirEntry, switch (tag) { case TAG_MAKE: - STRSCPY(imageInfoP->CameraMake, (const char*)valuePtr); + STRSCPY(ifdP->cameraMake, (const char*)valuePtr); break; case TAG_MODEL: - STRSCPY(imageInfoP->CameraModel, (const char*)valuePtr); + STRSCPY(ifdP->cameraModel, (const char*)valuePtr); break; case TAG_XRESOLUTION: - imageInfoP->XResolution = - convertAnyFormat(valuePtr, format, byteOrder); + ifdP->xResolution = + numericValue(valuePtr, format, byteOrder); break; case TAG_YRESOLUTION: - imageInfoP->YResolution = - convertAnyFormat(valuePtr, format, byteOrder); + ifdP->yResolution = + numericValue(valuePtr, format, byteOrder); break; case TAG_DATETIME_ORIGINAL: - STRSCPY(imageInfoP->DateTime, (const char*)valuePtr); - imageInfoP->DatePointer = (const char*)valuePtr; + STRSCPY(ifdP->dateTime, (const char*)valuePtr); break; case TAG_USERCOMMENT: { @@ -530,7 +584,7 @@ processDirEntry(const unsigned char * const dirEntry, unsigned int outCursor; unsigned int end; - for (end = byteCount; end > 0 && value[end] == ' '; --end); + for (end = valueSz; end > 0 && value[end] == ' '; --end); /* Skip "ASCII" if it is there */ if (end >= 5 && memeq(value, "ASCII", 5)) @@ -541,7 +595,7 @@ processDirEntry(const unsigned char * const dirEntry, /* Skip consecutive blanks and NULs */ for (; - cursor < byteCount && + cursor < valueSz && (value[cursor] == '\0' || value[cursor] == ' '); ++cursor); @@ -550,17 +604,17 @@ processDirEntry(const unsigned char * const dirEntry, for (outCursor = 0; cursor < end && outCursor < MAX_COMMENT-1; ++cursor) - imageInfoP->Comments[outCursor++] = value[cursor]; + ifdP->comments[outCursor++] = value[cursor]; - imageInfoP->Comments[outCursor++] = '\0'; + ifdP->comments[outCursor++] = '\0'; } 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, byteOrder); + ifdP->apertureFNumber = + (float)numericValue(valuePtr, format, byteOrder); break; case TAG_APERTURE: @@ -568,9 +622,9 @@ processDirEntry(const unsigned char * const dirEntry, /* 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, byteOrder) + if (ifdP->apertureFNumber == 0) { + ifdP->apertureFNumber = (float) + exp(numericValue(valuePtr, format, byteOrder) * log(2) * 0.5); } break; @@ -580,16 +634,16 @@ processDirEntry(const unsigned char * const dirEntry, as a function of how farthey are zoomed in. */ - imageInfoP->FocalLength = - (float)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->focalLength = + (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); + ifdP->distance = + (float)numericValue(valuePtr, format, byteOrder); break; case TAG_EXPOSURETIME: @@ -597,8 +651,8 @@ processDirEntry(const unsigned char * const dirEntry, trust it most. (overwrite previously computd value if there is one) */ - imageInfoP->ExposureTime = - (float)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->exposureTime = + (float)numericValue(valuePtr, format, byteOrder); break; case TAG_SHUTTERSPEED: @@ -606,29 +660,29 @@ processDirEntry(const unsigned char * const dirEntry, 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) + if (ifdP->exposureTime == 0) { + ifdP->exposureTime = (float) + (1/exp(numericValue(valuePtr, format, byteOrder) * log(2))); } break; case TAG_FLASH: - if ((int)convertAnyFormat(valuePtr, format, byteOrder) & 0x7) { - imageInfoP->FlashUsed = TRUE; + if ((int)numericValue(valuePtr, format, byteOrder) & 0x7) { + ifdP->flashUsed = TRUE; }else{ - imageInfoP->FlashUsed = FALSE; + ifdP->flashUsed = FALSE; } break; case TAG_ORIENTATION: - imageInfoP->Orientation = - (int)convertAnyFormat(valuePtr, format, byteOrder); - if (imageInfoP->Orientation < 1 || - imageInfoP->Orientation > 8) { + ifdP->orientation = + (int)numericValue(valuePtr, format, byteOrder); + if (ifdP->orientation < 1 || + ifdP->orientation > 8) { pm_message("Undefined rotation value %d", - imageInfoP->Orientation); - imageInfoP->Orientation = 0; + ifdP->orientation); + ifdP->orientation = 0; } break; @@ -639,16 +693,16 @@ processDirEntry(const unsigned char * const dirEntry, */ exifImageWidth = MIN(exifImageWidth, - (int)convertAnyFormat(valuePtr, format, byteOrder)); + (int)numericValue(valuePtr, format, byteOrder)); break; case TAG_FOCALPLANEXRES: haveXRes = true; - focalplaneXRes = convertAnyFormat(valuePtr, format, byteOrder); + focalplaneXRes = numericValue(valuePtr, format, byteOrder); break; case TAG_FOCALPLANEUNITS: - switch((int)convertAnyFormat(valuePtr, format, byteOrder)) { + switch((int)numericValue(valuePtr, format, byteOrder)) { case 1: focalplaneUnits = 25.4; break; /* 1 inch */ case 2: /* According to the information I was using, 2 @@ -670,45 +724,45 @@ processDirEntry(const unsigned char * const dirEntry, */ case TAG_EXPOSURE_BIAS: - imageInfoP->ExposureBias = - (float) convertAnyFormat(valuePtr, format, byteOrder); + ifdP->exposureBias = + (float) numericValue(valuePtr, format, byteOrder); break; case TAG_WHITEBALANCE: - imageInfoP->Whitebalance = - (int)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->whiteBalance = + (int)numericValue(valuePtr, format, byteOrder); break; case TAG_METERING_MODE: - imageInfoP->MeteringMode = - (int)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->meteringMode = + (int)numericValue(valuePtr, format, byteOrder); break; case TAG_EXPOSURE_PROGRAM: - imageInfoP->ExposureProgram = - (int)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->exposureProgram = + (int)numericValue(valuePtr, format, byteOrder); break; case TAG_ISO_EQUIVALENT: - imageInfoP->ISOequivalent = - (int)convertAnyFormat(valuePtr, format, byteOrder); - if ( imageInfoP->ISOequivalent < 50 ) - imageInfoP->ISOequivalent *= 200; + ifdP->isoEquivalent = + (int)numericValue(valuePtr, format, byteOrder); + if ( ifdP->isoEquivalent < 50 ) + ifdP->isoEquivalent *= 200; break; case TAG_COMPRESSION_LEVEL: - imageInfoP->CompressionLevel = - (int)convertAnyFormat(valuePtr, format, byteOrder); + ifdP->compressionLevel = + (int)numericValue(valuePtr, format, byteOrder); break; case TAG_THUMBNAIL_OFFSET: *thumbnailOffsetP = (unsigned int) - convertAnyFormat(valuePtr, format, byteOrder); + numericValue(valuePtr, format, byteOrder); break; case TAG_THUMBNAIL_LENGTH: *thumbnailSizeP = (unsigned int) - convertAnyFormat(valuePtr, format, byteOrder); + numericValue(valuePtr, format, byteOrder); break; case TAG_EXIF_OFFSET: @@ -720,9 +774,9 @@ processDirEntry(const unsigned char * const dirEntry, "but Exif data is only %u bytes.", subdirOffset, exifLength); else - processExifDir(exifData, exifLength, subdirOffset, - imageInfoP, byteOrder, wantTagTrace, - lastExifRefdP); + processIfd(exifData, exifLength, subdirOffset, + ifdP, byteOrder, wantTagTrace, + lastExifRefdP); } break; } } @@ -730,15 +784,15 @@ processDirEntry(const unsigned char * const dirEntry, 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) { +processIfd(const unsigned char * const exifData, + unsigned int const exifLength, + unsigned int const dirOffset, + exif_ifd * const ifdP, + ByteOrder const byteOrder, + bool const wantTagTrace, + const unsigned char ** const lastExifRefdP) { /*-------------------------------------------------------------------------- - Process one of the nested EXIF directories. + Process one of the nested EXIF IFDs (Image File Directory). --------------------------------------------------------------------------*/ const unsigned char * const dirStart = exifData + dirOffset; unsigned int const numDirEntries = get16u(&dirStart[0], byteOrder); @@ -778,7 +832,7 @@ processExifDir(const unsigned char * const exifData, for (de = 0, haveThumbnail = false; de < numDirEntries; ++de) processDirEntry(DIR_ENTRY_ADDR(dirStart, de), exifData, exifLength, - byteOrder, wantTagTrace, imageInfoP, + byteOrder, wantTagTrace, ifdP, &thumbnailOffset, &thumbnailSize, &haveThumbnail, lastExifRefdP); @@ -786,10 +840,8 @@ processExifDir(const unsigned char * const exifData, 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! + /* Recursively process the next directory in the chain, if there is + one */ if (DIR_ENTRY_ADDR(dirStart, numDirEntries) + 4 <= exifData + exifLength) { @@ -805,16 +857,16 @@ processExifDir(const unsigned char * const exifData, I'll just let it pass silently. */ if (wantTagTrace) - printf("Thumbnail removed with " - "Jhead 1.3 or earlier\n"); + pm_message("Thumbnail removed with " + "Jhead 1.3 or earlier"); } else { pm_message("Illegal subdirectory link"); } } else { if (subdirOffset <= exifLength) - processExifDir(exifData, exifLength, subdirOffset, - imageInfoP, byteOrder, wantTagTrace, - lastExifRefdP); + processIfd(exifData, exifLength, subdirOffset, + ifdP, byteOrder, wantTagTrace, + lastExifRefdP); } } } else { @@ -825,8 +877,8 @@ processExifDir(const unsigned char * const exifData, if (thumbnailSize && thumbnailOffset) { if (thumbnailSize + thumbnailOffset <= exifLength) { /* The thumbnail pointer appears to be valid. Store it. */ - imageInfoP->ThumbnailPointer = exifData + thumbnailOffset; - imageInfoP->ThumbnailSize = thumbnailSize; + ifdP->thumbnail = exifData + thumbnailOffset; + ifdP->thumbnailSize = thumbnailSize; if (wantTagTrace) { fprintf(stderr, "Thumbnail size: %u bytes\n", thumbnailSize); @@ -896,7 +948,8 @@ exif_parse(const unsigned char * const exifData, pm_message("Suspicious offset of first IFD value in Exif header"); } - imageInfoP->Comments[0] = '\0'; /* Initial value - null string */ + imageInfoP->mainImage.comments[0] = '\0'; + /* Initial value - null string */ haveXRes = FALSE; /* Initial assumption */ focalplaneUnits = 0; @@ -905,16 +958,17 @@ exif_parse(const unsigned char * const exifData, lastExifRefd = exifData; dirWithThumbnailPtrs = NULL; - processExifDir(exifData, length, firstOffset, - imageInfoP, byteOrder, wantTagTrace, &lastExifRefd); + processIfd(exifData, length, firstOffset, + &imageInfoP->mainImage, byteOrder, wantTagTrace, + &lastExifRefd); /* Compute the CCD width, in millimeters. */ - if (haveXRes) { - imageInfoP->HaveCCDWidth = 1; - imageInfoP->CCDWidth = + if (haveXRes && exifImageWidth) { + imageInfoP->mainImage.haveCCDWidth = 1; + imageInfoP->mainImage.ccdWidth = (float)(exifImageWidth * focalplaneUnits / focalplaneXRes); } else - imageInfoP->HaveCCDWidth = 0; + imageInfoP->mainImage.haveCCDWidth = 0; if (wantTagTrace) { fprintf(stderr, @@ -926,47 +980,25 @@ exif_parse(const unsigned char * const exifData, -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); +static void +showIfd(const exif_ifd * const ifdP) { + + if (ifdP->cameraMake[0]) { + pm_message("Camera make : %s", ifdP->cameraMake); + pm_message("Camera model : %s", ifdP->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) { - - /* Print orientation only 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 + if (ifdP->dateTime[0]) + pm_message("Date/Time : %s", ifdP->dateTime); + + pm_message("Resolution : %f x %f", + ifdP->xResolution, ifdP->yResolution); + + if (ifdP->orientation > 1) { + /* 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. */ static const char * orientTab[9] = { "Undefined", @@ -980,144 +1012,157 @@ exif_showImageInfo(const exif_ImageInfo * const imageInfoP, "rotate 270", /* rotate 270 to right it. */ }; - fprintf(fileP, "Orientation : %s\n", - orientTab[imageInfoP->Orientation]); + pm_message("Camera orientation : %s", + orientTab[ifdP->orientation]); } - if (imageInfoP->IsColor == 0) - fprintf(fileP, "Color/bw : Black and white\n"); + if (ifdP->isColor == 0) + pm_message("Color/bw : Black and white"); - if (imageInfoP->FlashUsed >= 0) - fprintf(fileP, "Flash used : %s\n", - imageInfoP->FlashUsed ? "Yes" :"No"); + if (ifdP->flashUsed >= 0) + pm_message("Flash used : %s", + ifdP->flashUsed ? "Yes" :"No"); - 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->focalLength) { + const char * mm35equiv; + + if (ifdP->haveCCDWidth) { + pm_asprintf(&mm35equiv, " (35mm equivalent: %dmm)", + (int) (ifdP->focalLength/ifdP->ccdWidth*36 + 0.5)); + } else + mm35equiv = pm_strdup(""); + + pm_message("Focal length : %4.1fmm %s", + (double)ifdP->focalLength, mm35equiv); + + pm_strfree(mm35equiv); } - if (imageInfoP->HaveCCDWidth) - fprintf(fileP, "CCD width : %2.4fmm\n", - (double)imageInfoP->CCDWidth); + if (ifdP->haveCCDWidth) + pm_message("CCD width : %2.4fmm", (double)ifdP->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->exposureTime) { + const char * timeDisp; + const char * recipDisp; + + if (ifdP->exposureTime < 0.010) { + pm_asprintf(&timeDisp, "%6.4f s", (double)ifdP->exposureTime); + } else { + pm_asprintf(&timeDisp, "%5.3f s", (double)ifdP->exposureTime); } - fprintf(fileP, "\n"); + if (ifdP->exposureTime <= 0.5) { + pm_asprintf(&recipDisp, " (1/%d)", + (int)(0.5 + 1/ifdP->exposureTime)); + } else + recipDisp = pm_strdup(""); + + pm_message("Exposure time: %s %s", timeDisp, recipDisp); + + pm_strfree(recipDisp); + pm_strfree(timeDisp); } - if (imageInfoP->ApertureFNumber) { - fprintf(fileP, "Aperture : f/%3.1f\n", - (double)imageInfoP->ApertureFNumber); + if (ifdP->apertureFNumber) { + pm_message("Aperture : f/%3.1f", (double)ifdP->apertureFNumber); } - 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->distance) { + if (ifdP->distance < 0) + pm_message("Focus dist. : Infinite"); + else + pm_message("Focus dist. :%5.2fm", (double)ifdP->distance); } - if (imageInfoP->ISOequivalent) { /* 05-jan-2001 vcs */ - fprintf(fileP, "ISO equiv. : %2d\n",(int)imageInfoP->ISOequivalent); - } - if (imageInfoP->ExposureBias) { /* 05-jan-2001 vcs */ - fprintf(fileP, "Exposure bias:%4.2f\n", - (double)imageInfoP->ExposureBias); - } + if (ifdP->isoEquivalent) + pm_message("ISO equiv. : %2d",(int)ifdP->isoEquivalent); - 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->exposureBias) + pm_message("Exposure bias:%4.2f", (double)ifdP->exposureBias); + + if (ifdP->whiteBalance) { + const char * whiteBalanceDisp; + + switch(ifdP->whiteBalance) { + 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->meteringMode) { + const char * meteringModeDisp; + + switch(ifdP->meteringMode) { + case 2: meteringModeDisp = "center weight"; break; + case 3: meteringModeDisp = "spot"; break; + case 5: meteringModeDisp = "matrix"; 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->exposureProgram) { + const char * exposureDisp; + + switch(ifdP->exposureProgram) { + case 2: exposureDisp = "program (auto)"; break; + case 3: exposureDisp = "aperture priority (semi-auto)"; break; + case 4: exposureDisp = "shutter priority (semi-auto)"; 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->compressionLevel) { + const char * jpegQualityDisp; + + switch(ifdP->compressionLevel) { + case 1: jpegQualityDisp = "basic"; break; + case 2: jpegQualityDisp = "normal"; break; + case 4: jpegQualityDisp = "fine"; 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; - - fprintf(fileP, "Comment : "); - - 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 (ifdP->comments[0]) { + char * buffer; + + MALLOCARRAY(buffer, strlen(ifdP->comments) + 1); + + 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"); } +} + + + +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. +--------------------------------------------------------------------------*/ - fprintf(fileP, "\n"); + showIfd(&imageInfoP->mainImage); } diff --git a/converter/other/exif.h b/converter/other/exif.h index 1a703694..d06a1185 100644 --- a/converter/other/exif.h +++ b/converter/other/exif.h @@ -12,50 +12,59 @@ typedef struct { /*-------------------------------------------------------------------------- - A structure of this type 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. +--------------------------------------------------------------------------*/ + 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 * thumbnail; /* Pointer at the thumbnail */ + unsigned thumbnailSize; /* Size of thumbnail. */ +} exif_ifd; + + +typedef struct { +/*-------------------------------------------------------------------------- + A structure of this type contains the information from an EXIF header. --------------------------------------------------------------------------*/ - 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; + exif_ifd mainImage; /* aka IFD0 */ + exif_ifd thumbnailImage; /* aka IFD1 */ } 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); #endif diff --git a/converter/other/jpegtopnm.c b/converter/other/jpegtopnm.c index b7c94fde..35b7b9d0 100644 --- a/converter/other/jpegtopnm.c +++ b/converter/other/jpegtopnm.c @@ -671,7 +671,7 @@ printExifInfo(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); } -- cgit 1.4.1