|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
This file contains subroutines for use by Jpegtopnm to handle the
|
|
Packit |
78deda |
EXIF header.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The code is adapted from the program Jhead by Matthaias Wandel
|
|
Packit |
78deda |
December 1999 - August 2000, and contributed to the public domain.
|
|
Packit |
78deda |
Bryan Henderson adapted it to Netpbm in September 2001. Bryan
|
|
Packit |
78deda |
added more of Wandel's code, from Jhead 1.9 dated December 2002 in
|
|
Packit |
78deda |
January 2003.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
An EXIF header is a JFIF APP1 marker. It is generated by some
|
|
Packit |
78deda |
digital cameras and contains information about the circumstances of
|
|
Packit |
78deda |
the creation of the image (camera settings, etc.).
|
|
Packit |
78deda |
|
|
Packit |
78deda |
The EXIF header uses the TIFF format, only it contains only tag
|
|
Packit |
78deda |
values and no actual image.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Note that the image format called EXIF is simply JFIF with an EXIF
|
|
Packit |
78deda |
header, i.e. a subformat of JFIF.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
See the EXIF specs at http://exif.org (2001.09.01).
|
|
Packit |
78deda |
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
#include "pm_config.h"
|
|
Packit |
78deda |
#include <stdio.h>
|
|
Packit |
78deda |
#include <stdlib.h>
|
|
Packit |
78deda |
#include <math.h>
|
|
Packit |
78deda |
#include <string.h>
|
|
Packit |
78deda |
#include <time.h>
|
|
Packit |
78deda |
#include <errno.h>
|
|
Packit |
78deda |
#include <limits.h>
|
|
Packit |
78deda |
#include <ctype.h>
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#if MSVCRT
|
|
Packit |
78deda |
#include <sys/utime.h>
|
|
Packit |
78deda |
#else
|
|
Packit |
78deda |
#include <utime.h>
|
|
Packit |
78deda |
#include <sys/types.h>
|
|
Packit |
78deda |
#include <unistd.h>
|
|
Packit |
78deda |
#include <errno.h>
|
|
Packit |
78deda |
#endif
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include "pm_c_util.h"
|
|
Packit |
78deda |
#include "pm.h"
|
|
Packit |
78deda |
#include "nstring.h"
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#include "exif.h"
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static const unsigned char * DirWithThumbnailPtrs;
|
|
Packit |
78deda |
static double FocalplaneXRes;
|
|
Packit |
78deda |
bool HaveXRes;
|
|
Packit |
78deda |
static double FocalplaneUnits;
|
|
Packit |
78deda |
static int ExifImageWidth;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
typedef struct {
|
|
Packit |
78deda |
unsigned short Tag;
|
|
Packit |
78deda |
const char * Desc;
|
|
Packit |
78deda |
} TagTable;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Describes format descriptor */
|
|
Packit |
78deda |
static int const bytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8};
|
|
Packit |
78deda |
#define NUM_FORMATS 12
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define FMT_BYTE 1
|
|
Packit |
78deda |
#define FMT_STRING 2
|
|
Packit |
78deda |
#define FMT_USHORT 3
|
|
Packit |
78deda |
#define FMT_ULONG 4
|
|
Packit |
78deda |
#define FMT_URATIONAL 5
|
|
Packit |
78deda |
#define FMT_SBYTE 6
|
|
Packit |
78deda |
#define FMT_UNDEFINED 7
|
|
Packit |
78deda |
#define FMT_SSHORT 8
|
|
Packit |
78deda |
#define FMT_SLONG 9
|
|
Packit |
78deda |
#define FMT_SRATIONAL 10
|
|
Packit |
78deda |
#define FMT_SINGLE 11
|
|
Packit |
78deda |
#define FMT_DOUBLE 12
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Describes tag values */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_EXIF_OFFSET 0x8769
|
|
Packit |
78deda |
#define TAG_INTEROP_OFFSET 0xa005
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_MAKE 0x010F
|
|
Packit |
78deda |
#define TAG_MODEL 0x0110
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_ORIENTATION 0x0112
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_XRESOLUTION 0x011A
|
|
Packit |
78deda |
#define TAG_YRESOLUTION 0x011B
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_EXPOSURETIME 0x829A
|
|
Packit |
78deda |
#define TAG_FNUMBER 0x829D
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_SHUTTERSPEED 0x9201
|
|
Packit |
78deda |
#define TAG_APERTURE 0x9202
|
|
Packit |
78deda |
#define TAG_MAXAPERTURE 0x9205
|
|
Packit |
78deda |
#define TAG_FOCALLENGTH 0x920A
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_DATETIME_ORIGINAL 0x9003
|
|
Packit |
78deda |
#define TAG_USERCOMMENT 0x9286
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_SUBJECT_DISTANCE 0x9206
|
|
Packit |
78deda |
#define TAG_FLASH 0x9209
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_FOCALPLANEXRES 0xa20E
|
|
Packit |
78deda |
#define TAG_FOCALPLANEUNITS 0xa210
|
|
Packit |
78deda |
#define TAG_EXIF_IMAGEWIDTH 0xA002
|
|
Packit |
78deda |
#define TAG_EXIF_IMAGELENGTH 0xA003
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* the following is added 05-jan-2001 vcs */
|
|
Packit |
78deda |
#define TAG_EXPOSURE_BIAS 0x9204
|
|
Packit |
78deda |
#define TAG_WHITEBALANCE 0x9208
|
|
Packit |
78deda |
#define TAG_METERING_MODE 0x9207
|
|
Packit |
78deda |
#define TAG_EXPOSURE_PROGRAM 0x8822
|
|
Packit |
78deda |
#define TAG_ISO_EQUIVALENT 0x8827
|
|
Packit |
78deda |
#define TAG_COMPRESSION_LEVEL 0x9102
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define TAG_THUMBNAIL_OFFSET 0x0201
|
|
Packit |
78deda |
#define TAG_THUMBNAIL_LENGTH 0x0202
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static TagTable const tagTable[] = {
|
|
Packit |
78deda |
{ 0x100, "ImageWidth"},
|
|
Packit |
78deda |
{ 0x101, "ImageLength"},
|
|
Packit |
78deda |
{ 0x102, "BitsPerSample"},
|
|
Packit |
78deda |
{ 0x103, "Compression"},
|
|
Packit |
78deda |
{ 0x106, "PhotometricInterpretation"},
|
|
Packit |
78deda |
{ 0x10A, "FillOrder"},
|
|
Packit |
78deda |
{ 0x10D, "DocumentName"},
|
|
Packit |
78deda |
{ 0x10E, "ImageDescription"},
|
|
Packit |
78deda |
{ 0x10F, "Make"},
|
|
Packit |
78deda |
{ 0x110, "Model"},
|
|
Packit |
78deda |
{ 0x111, "StripOffsets"},
|
|
Packit |
78deda |
{ 0x112, "Orientation"},
|
|
Packit |
78deda |
{ 0x115, "SamplesPerPixel"},
|
|
Packit |
78deda |
{ 0x116, "RowsPerStrip"},
|
|
Packit |
78deda |
{ 0x117, "StripByteCounts"},
|
|
Packit |
78deda |
{ 0x11A, "XResolution"},
|
|
Packit |
78deda |
{ 0x11B, "YResolution"},
|
|
Packit |
78deda |
{ 0x11C, "PlanarConfiguration"},
|
|
Packit |
78deda |
{ 0x128, "ResolutionUnit"},
|
|
Packit |
78deda |
{ 0x12D, "TransferFunction"},
|
|
Packit |
78deda |
{ 0x131, "Software"},
|
|
Packit |
78deda |
{ 0x132, "DateTime"},
|
|
Packit |
78deda |
{ 0x13B, "Artist"},
|
|
Packit |
78deda |
{ 0x13E, "WhitePoint"},
|
|
Packit |
78deda |
{ 0x13F, "PrimaryChromaticities"},
|
|
Packit |
78deda |
{ 0x156, "TransferRange"},
|
|
Packit |
78deda |
{ 0x200, "JPEGProc"},
|
|
Packit |
78deda |
{ 0x201, "ThumbnailOffset"},
|
|
Packit |
78deda |
{ 0x202, "ThumbnailLength"},
|
|
Packit |
78deda |
{ 0x211, "YCbCrCoefficients"},
|
|
Packit |
78deda |
{ 0x212, "YCbCrSubSampling"},
|
|
Packit |
78deda |
{ 0x213, "YCbCrPositioning"},
|
|
Packit |
78deda |
{ 0x214, "ReferenceBlackWhite"},
|
|
Packit |
78deda |
{ 0x828D, "CFARepeatPatternDim"},
|
|
Packit |
78deda |
{ 0x828E, "CFAPattern"},
|
|
Packit |
78deda |
{ 0x828F, "BatteryLevel"},
|
|
Packit |
78deda |
{ 0x8298, "Copyright"},
|
|
Packit |
78deda |
{ 0x829A, "ExposureTime"},
|
|
Packit |
78deda |
{ 0x829D, "FNumber"},
|
|
Packit |
78deda |
{ 0x83BB, "IPTC/NAA"},
|
|
Packit |
78deda |
{ 0x8769, "ExifOffset"},
|
|
Packit |
78deda |
{ 0x8773, "InterColorProfile"},
|
|
Packit |
78deda |
{ 0x8822, "ExposureProgram"},
|
|
Packit |
78deda |
{ 0x8824, "SpectralSensitivity"},
|
|
Packit |
78deda |
{ 0x8825, "GPSInfo"},
|
|
Packit |
78deda |
{ 0x8827, "ISOSpeedRatings"},
|
|
Packit |
78deda |
{ 0x8828, "OECF"},
|
|
Packit |
78deda |
{ 0x9000, "ExifVersion"},
|
|
Packit |
78deda |
{ 0x9003, "DateTimeOriginal"},
|
|
Packit |
78deda |
{ 0x9004, "DateTimeDigitized"},
|
|
Packit |
78deda |
{ 0x9101, "ComponentsConfiguration"},
|
|
Packit |
78deda |
{ 0x9102, "CompressedBitsPerPixel"},
|
|
Packit |
78deda |
{ 0x9201, "ShutterSpeedValue"},
|
|
Packit |
78deda |
{ 0x9202, "ApertureValue"},
|
|
Packit |
78deda |
{ 0x9203, "BrightnessValue"},
|
|
Packit |
78deda |
{ 0x9204, "ExposureBiasValue"},
|
|
Packit |
78deda |
{ 0x9205, "MaxApertureValue"},
|
|
Packit |
78deda |
{ 0x9206, "SubjectDistance"},
|
|
Packit |
78deda |
{ 0x9207, "MeteringMode"},
|
|
Packit |
78deda |
{ 0x9208, "LightSource"},
|
|
Packit |
78deda |
{ 0x9209, "Flash"},
|
|
Packit |
78deda |
{ 0x920A, "FocalLength"},
|
|
Packit |
78deda |
{ 0x927C, "MakerNote"},
|
|
Packit |
78deda |
{ 0x9286, "UserComment"},
|
|
Packit |
78deda |
{ 0x9290, "SubSecTime"},
|
|
Packit |
78deda |
{ 0x9291, "SubSecTimeOriginal"},
|
|
Packit |
78deda |
{ 0x9292, "SubSecTimeDigitized"},
|
|
Packit |
78deda |
{ 0xA000, "FlashPixVersion"},
|
|
Packit |
78deda |
{ 0xA001, "ColorSpace"},
|
|
Packit |
78deda |
{ 0xA002, "ExifImageWidth"},
|
|
Packit |
78deda |
{ 0xA003, "ExifImageLength"},
|
|
Packit |
78deda |
{ 0xA005, "InteroperabilityOffset"},
|
|
Packit |
78deda |
{ 0xA20B, "FlashEnergy"}, /* 0x920B in TIFF/EP */
|
|
Packit |
78deda |
{ 0xA20C, "SpatialFrequencyResponse"}, /* 0x920C - - */
|
|
Packit |
78deda |
{ 0xA20E, "FocalPlaneXResolution"}, /* 0x920E - - */
|
|
Packit |
78deda |
{ 0xA20F, "FocalPlaneYResolution"}, /* 0x920F - - */
|
|
Packit |
78deda |
{ 0xA210, "FocalPlaneResolutionUnit"}, /* 0x9210 - - */
|
|
Packit |
78deda |
{ 0xA214, "SubjectLocation"}, /* 0x9214 - - */
|
|
Packit |
78deda |
{ 0xA215, "ExposureIndex"}, /* 0x9215 - - */
|
|
Packit |
78deda |
{ 0xA217, "SensingMethod"}, /* 0x9217 - - */
|
|
Packit |
78deda |
{ 0xA300, "FileSource"},
|
|
Packit |
78deda |
{ 0xA301, "SceneType"},
|
|
Packit |
78deda |
{ 0, NULL}
|
|
Packit |
78deda |
} ;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
typedef enum { NORMAL, MOTOROLA } ByteOrder;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static uint16_t
|
|
Packit |
78deda |
get16u(const void * const data,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Convert a 16 bit unsigned value from file's native byte order
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
if (byteOrder == MOTOROLA){
|
|
Packit |
78deda |
return (((const unsigned char *)data)[0] << 8) |
|
|
Packit |
78deda |
((const unsigned char *)data)[1];
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
return (((const unsigned char *)data)[1] << 8) |
|
|
Packit |
78deda |
((const unsigned char *)data)[0];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static int32_t
|
|
Packit |
78deda |
get32s(const void * const data,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Convert a 32 bit signed value from file's native byte order
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
if (byteOrder == MOTOROLA){
|
|
Packit |
78deda |
return
|
|
Packit |
78deda |
(((const char *)data)[0] << 24) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[1] << 16) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[2] << 8 ) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[3] << 0 );
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
return
|
|
Packit |
78deda |
(((const char *)data)[3] << 24) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[2] << 16) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[1] << 8 ) |
|
|
Packit |
78deda |
(((const unsigned char *)data)[0] << 0 );
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static uint32_t
|
|
Packit |
78deda |
get32u(const void * const data,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Convert a 32 bit unsigned value from file's native byte order
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
return (uint32_t)get32s(data, byteOrder) & 0xffffffff;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
printFormatNumber(FILE * const fileP,
|
|
Packit |
78deda |
const void * const ValuePtr,
|
|
Packit |
78deda |
int const Format,
|
|
Packit |
78deda |
int const ByteCount,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Display a number as one of its many formats
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
switch(Format){
|
|
Packit |
78deda |
case FMT_SBYTE:
|
|
Packit |
78deda |
case FMT_BYTE:
|
|
Packit |
78deda |
fprintf(fileP, "%02x\n", *(unsigned char *)ValuePtr);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_USHORT:
|
|
Packit |
78deda |
fprintf(fileP, "%d\n",get16u(ValuePtr, byteOrder));
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_ULONG:
|
|
Packit |
78deda |
case FMT_SLONG:
|
|
Packit |
78deda |
fprintf(fileP, "%d\n",get32s(ValuePtr, byteOrder));
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_SSHORT:
|
|
Packit |
78deda |
fprintf(fileP, "%hd\n",(signed short)get16u(ValuePtr, byteOrder));
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_URATIONAL:
|
|
Packit |
78deda |
case FMT_SRATIONAL:
|
|
Packit |
78deda |
fprintf(fileP, "%d/%d\n",get32s(ValuePtr, byteOrder),
|
|
Packit |
78deda |
get32s(4+(char *)ValuePtr, byteOrder));
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_SINGLE:
|
|
Packit |
78deda |
fprintf(fileP, "%f\n",(double)*(float *)ValuePtr);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_DOUBLE:
|
|
Packit |
78deda |
fprintf(fileP, "%f\n",*(double *)ValuePtr);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
default:
|
|
Packit |
78deda |
fprintf(fileP, "Unknown format %d:", Format);
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
unsigned int a;
|
|
Packit |
78deda |
for (a = 0; a < ByteCount && a < 16; ++a)
|
|
Packit |
78deda |
printf("%02x", ((unsigned char *)ValuePtr)[a]);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static double
|
|
Packit |
78deda |
convertAnyFormat(const void * const ValuePtr,
|
|
Packit |
78deda |
int const Format,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Evaluate number, be it int, rational, or float from directory.
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
double Value;
|
|
Packit |
78deda |
Value = 0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
switch(Format){
|
|
Packit |
78deda |
case FMT_SBYTE:
|
|
Packit |
78deda |
Value = *(signed char *)ValuePtr;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_BYTE:
|
|
Packit |
78deda |
Value = *(unsigned char *)ValuePtr;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_USHORT:
|
|
Packit |
78deda |
Value = get16u(ValuePtr, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_ULONG:
|
|
Packit |
78deda |
Value = get32u(ValuePtr, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_URATIONAL:
|
|
Packit |
78deda |
case FMT_SRATIONAL: {
|
|
Packit |
78deda |
int num, den;
|
|
Packit |
78deda |
num = get32s(ValuePtr, byteOrder);
|
|
Packit |
78deda |
den = get32s(4+(char *)ValuePtr, byteOrder);
|
|
Packit |
78deda |
Value = den == 0 ? 0 : (double)(num/den);
|
|
Packit |
78deda |
} break;
|
|
Packit |
78deda |
case FMT_SSHORT:
|
|
Packit |
78deda |
Value = (signed short)get16u(ValuePtr, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_SLONG:
|
|
Packit |
78deda |
Value = get32s(ValuePtr, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Not sure if this is correct (never seen float used in Exif format) */
|
|
Packit |
78deda |
case FMT_SINGLE:
|
|
Packit |
78deda |
Value = (double)*(float *)ValuePtr;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case FMT_DOUBLE:
|
|
Packit |
78deda |
Value = *(double *)ValuePtr;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
return Value;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
traceTag(int const tag,
|
|
Packit |
78deda |
int const format,
|
|
Packit |
78deda |
const unsigned char * const valuePtr,
|
|
Packit |
78deda |
unsigned int const byteCount,
|
|
Packit |
78deda |
ByteOrder const byteOrder) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Show tag name */
|
|
Packit |
78deda |
unsigned int a;
|
|
Packit |
78deda |
bool found;
|
|
Packit |
78deda |
for (a = 0, found = false; !found; ++a){
|
|
Packit |
78deda |
if (tagTable[a].Tag == 0){
|
|
Packit |
78deda |
fprintf(stderr, " Unknown Tag %04x Value = ", tag);
|
|
Packit |
78deda |
found = true;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (tagTable[a].Tag == tag){
|
|
Packit |
78deda |
fprintf(stderr, " %s = ",tagTable[a].Desc);
|
|
Packit |
78deda |
found = true;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Show tag value. */
|
|
Packit |
78deda |
switch(format){
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case FMT_UNDEFINED:
|
|
Packit |
78deda |
/* Undefined is typically an ascii string. */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case FMT_STRING: {
|
|
Packit |
78deda |
/* String arrays printed without function call
|
|
Packit |
78deda |
(different from int arrays)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
bool noPrint;
|
|
Packit |
78deda |
printf("\"");
|
|
Packit |
78deda |
for (a = 0, noPrint = false; a < byteCount; ++a){
|
|
Packit |
78deda |
if (ISPRINT((valuePtr)[a])){
|
|
Packit |
78deda |
fprintf(stderr, "%c", valuePtr[a]);
|
|
Packit |
78deda |
noPrint = false;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
/* Avoiding indicating too many unprintable characters of
|
|
Packit |
78deda |
proprietary bits of binary information this program may not
|
|
Packit |
78deda |
know how to parse.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if (!noPrint){
|
|
Packit |
78deda |
fprintf(stderr, "?");
|
|
Packit |
78deda |
noPrint = true;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
fprintf(stderr, "\"\n");
|
|
Packit |
78deda |
} break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
default:
|
|
Packit |
78deda |
/* Handle arrays of numbers later (will there ever be?)*/
|
|
Packit |
78deda |
printFormatNumber(stderr, valuePtr, format, byteCount, byteOrder);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Forward declaration for recursion */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
processExifDir(const unsigned char * const ExifData,
|
|
Packit |
78deda |
unsigned int const ExifLength,
|
|
Packit |
78deda |
unsigned int const DirOffset,
|
|
Packit |
78deda |
exif_ImageInfo * const imageInfoP,
|
|
Packit |
78deda |
ByteOrder const byteOrder,
|
|
Packit |
78deda |
bool const wantTagTrace,
|
|
Packit |
78deda |
const unsigned char ** const LastExifRefdP);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
processDirEntry(const unsigned char * const dirEntry,
|
|
Packit |
78deda |
const unsigned char * const exifData,
|
|
Packit |
78deda |
unsigned int const exifLength,
|
|
Packit |
78deda |
ByteOrder const byteOrder,
|
|
Packit |
78deda |
bool const wantTagTrace,
|
|
Packit |
78deda |
exif_ImageInfo * const imageInfoP,
|
|
Packit |
78deda |
unsigned int * const thumbnailOffsetP,
|
|
Packit |
78deda |
unsigned int * const thumbnailSizeP,
|
|
Packit |
78deda |
bool * const haveThumbnailP,
|
|
Packit |
78deda |
const unsigned char ** const lastExifRefdP) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
int const tag = get16u(&dirEntry[0], byteOrder);
|
|
Packit |
78deda |
int const format = get16u(&dirEntry[2], byteOrder);
|
|
Packit |
78deda |
int const components = get32u(&dirEntry[4], byteOrder);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
const unsigned char * valuePtr;
|
|
Packit |
78deda |
/* This actually can point to a variety of things; it must be cast to
|
|
Packit |
78deda |
other types when used. But we use it as a byte-by-byte cursor, so
|
|
Packit |
78deda |
we declare it as a pointer to a generic byte here.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
unsigned int byteCount;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if ((format-1) >= NUM_FORMATS) {
|
|
Packit |
78deda |
/* (-1) catches illegal zero case as unsigned underflows
|
|
Packit |
78deda |
to positive large.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
pm_message("Illegal number format %d for tag %04x", format, tag);
|
|
Packit |
78deda |
return;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
byteCount = components * bytesPerFormat[format];
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (byteCount > 4){
|
|
Packit |
78deda |
unsigned const offsetVal = get32u(&dirEntry[8], byteOrder);
|
|
Packit |
78deda |
/* If its bigger than 4 bytes, the dir entry contains an offset.*/
|
|
Packit |
78deda |
if (offsetVal + byteCount > exifLength){
|
|
Packit |
78deda |
/* Bogus pointer offset and / or bytecount value */
|
|
Packit |
78deda |
pm_message("Illegal pointer offset value in EXIF "
|
|
Packit |
78deda |
"for tag %04x. "
|
|
Packit |
78deda |
"Offset %d bytes %d ExifLen %d\n",
|
|
Packit |
78deda |
tag, offsetVal, byteCount, exifLength);
|
|
Packit |
78deda |
return;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
valuePtr = &exifData[offsetVal];
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
/* 4 bytes or less and value is in the dir entry itself */
|
|
Packit |
78deda |
valuePtr = &dirEntry[8];
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (*lastExifRefdP < valuePtr + byteCount){
|
|
Packit |
78deda |
/* Keep track of last byte in the exif header that was actually
|
|
Packit |
78deda |
referenced. That way, we know where the discardable thumbnail data
|
|
Packit |
78deda |
begins.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
*lastExifRefdP = valuePtr + byteCount;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
traceTag(tag, format, valuePtr, byteCount, byteOrder);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
*haveThumbnailP = (tag == TAG_THUMBNAIL_OFFSET);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Extract useful components of tag */
|
|
Packit |
78deda |
switch (tag){
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_MAKE:
|
|
Packit |
78deda |
STRSCPY(imageInfoP->CameraMake, (const char*)valuePtr);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_MODEL:
|
|
Packit |
78deda |
STRSCPY(imageInfoP->CameraModel, (const char*)valuePtr);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_XRESOLUTION:
|
|
Packit |
78deda |
imageInfoP->XResolution =
|
|
Packit |
78deda |
convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_YRESOLUTION:
|
|
Packit |
78deda |
imageInfoP->YResolution =
|
|
Packit |
78deda |
convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_DATETIME_ORIGINAL:
|
|
Packit |
78deda |
STRSCPY(imageInfoP->DateTime, (const char*)valuePtr);
|
|
Packit |
78deda |
imageInfoP->DatePointer = (const char*)valuePtr;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_USERCOMMENT: {
|
|
Packit |
78deda |
/* Olympus has this padded with trailing spaces. We stop the copy
|
|
Packit |
78deda |
where those start.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
const char * const value = (const char *)valuePtr;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
unsigned int cursor;
|
|
Packit |
78deda |
unsigned int outCursor;
|
|
Packit |
78deda |
unsigned int end;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (end = byteCount; end > 0 && value[end] == ' '; --end);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Skip "ASCII" if it is there */
|
|
Packit |
78deda |
if (end >= 5 && MEMEQ(value, "ASCII", 5))
|
|
Packit |
78deda |
cursor = 5;
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
cursor = 0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Skip consecutive blanks and NULs */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (;
|
|
Packit |
78deda |
cursor < byteCount &&
|
|
Packit |
78deda |
(value[cursor] == '\0' || value[cursor] == ' ');
|
|
Packit |
78deda |
++cursor);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Copy the rest as the comment */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (outCursor = 0;
|
|
Packit |
78deda |
cursor < end && outCursor < MAX_COMMENT-1;
|
|
Packit |
78deda |
++cursor)
|
|
Packit |
78deda |
imageInfoP->Comments[outCursor++] = value[cursor];
|
|
Packit |
78deda |
|
|
Packit |
78deda |
imageInfoP->Comments[outCursor++] = '\0';
|
|
Packit |
78deda |
} break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_FNUMBER:
|
|
Packit |
78deda |
/* Simplest way of expressing aperture, so I trust it the most.
|
|
Packit |
78deda |
(overwrite previously computd value if there is one)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
imageInfoP->ApertureFNumber =
|
|
Packit |
78deda |
(float)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_APERTURE:
|
|
Packit |
78deda |
case TAG_MAXAPERTURE:
|
|
Packit |
78deda |
/* More relevant info always comes earlier, so only use this field if
|
|
Packit |
78deda |
we don't have appropriate aperture information yet.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if (imageInfoP->ApertureFNumber == 0){
|
|
Packit |
78deda |
imageInfoP->ApertureFNumber = (float)
|
|
Packit |
78deda |
exp(convertAnyFormat(valuePtr, format, byteOrder)
|
|
Packit |
78deda |
* log(2) * 0.5);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_FOCALLENGTH:
|
|
Packit |
78deda |
/* Nice digital cameras actually save the focal length
|
|
Packit |
78deda |
as a function of how farthey are zoomed in.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
imageInfoP->FocalLength =
|
|
Packit |
78deda |
(float)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_SUBJECT_DISTANCE:
|
|
Packit |
78deda |
/* Inidcates the distacne the autofocus camera is focused to.
|
|
Packit |
78deda |
Tends to be less accurate as distance increases.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
imageInfoP->Distance =
|
|
Packit |
78deda |
(float)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_EXPOSURETIME:
|
|
Packit |
78deda |
/* Simplest way of expressing exposure time, so I
|
|
Packit |
78deda |
trust it most. (overwrite previously computd value
|
|
Packit |
78deda |
if there is one)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
imageInfoP->ExposureTime =
|
|
Packit |
78deda |
(float)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_SHUTTERSPEED:
|
|
Packit |
78deda |
/* More complicated way of expressing exposure time,
|
|
Packit |
78deda |
so only use this value if we don't already have it
|
|
Packit |
78deda |
from somewhere else.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if (imageInfoP->ExposureTime == 0){
|
|
Packit |
78deda |
imageInfoP->ExposureTime = (float)
|
|
Packit |
78deda |
(1/exp(convertAnyFormat(valuePtr, format, byteOrder)
|
|
Packit |
78deda |
* log(2)));
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_FLASH:
|
|
Packit |
78deda |
if ((int)convertAnyFormat(valuePtr, format, byteOrder) & 0x7){
|
|
Packit |
78deda |
imageInfoP->FlashUsed = TRUE;
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
imageInfoP->FlashUsed = FALSE;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_ORIENTATION:
|
|
Packit |
78deda |
imageInfoP->Orientation =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
if (imageInfoP->Orientation < 1 ||
|
|
Packit |
78deda |
imageInfoP->Orientation > 8){
|
|
Packit |
78deda |
pm_message("Undefined rotation value %d",
|
|
Packit |
78deda |
imageInfoP->Orientation);
|
|
Packit |
78deda |
imageInfoP->Orientation = 0;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_EXIF_IMAGELENGTH:
|
|
Packit |
78deda |
case TAG_EXIF_IMAGEWIDTH:
|
|
Packit |
78deda |
/* Use largest of height and width to deal with images
|
|
Packit |
78deda |
that have been rotated to portrait format.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
ExifImageWidth =
|
|
Packit |
78deda |
MIN(ExifImageWidth,
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder));
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_FOCALPLANEXRES:
|
|
Packit |
78deda |
HaveXRes = TRUE;
|
|
Packit |
78deda |
FocalplaneXRes = convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_FOCALPLANEUNITS:
|
|
Packit |
78deda |
switch((int)convertAnyFormat(valuePtr, format, byteOrder)){
|
|
Packit |
78deda |
case 1: FocalplaneUnits = 25.4; break; /* 1 inch */
|
|
Packit |
78deda |
case 2:
|
|
Packit |
78deda |
/* According to the information I was using, 2
|
|
Packit |
78deda |
means meters. But looking at the Cannon
|
|
Packit |
78deda |
powershot's files, inches is the only
|
|
Packit |
78deda |
sensible value.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
FocalplaneUnits = 25.4;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case 3: FocalplaneUnits = 10; break; /* 1 centimeter*/
|
|
Packit |
78deda |
case 4: FocalplaneUnits = 1; break; /* 1 millimeter*/
|
|
Packit |
78deda |
case 5: FocalplaneUnits = .001; break; /* 1 micrometer*/
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Remaining cases contributed by: Volker C. Schoech
|
|
Packit |
78deda |
(schoech@gmx.de)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_EXPOSURE_BIAS:
|
|
Packit |
78deda |
imageInfoP->ExposureBias =
|
|
Packit |
78deda |
(float) convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_WHITEBALANCE:
|
|
Packit |
78deda |
imageInfoP->Whitebalance =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_METERING_MODE:
|
|
Packit |
78deda |
imageInfoP->MeteringMode =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_EXPOSURE_PROGRAM:
|
|
Packit |
78deda |
imageInfoP->ExposureProgram =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_ISO_EQUIVALENT:
|
|
Packit |
78deda |
imageInfoP->ISOequivalent =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
if ( imageInfoP->ISOequivalent < 50 )
|
|
Packit |
78deda |
imageInfoP->ISOequivalent *= 200;
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_COMPRESSION_LEVEL:
|
|
Packit |
78deda |
imageInfoP->CompressionLevel =
|
|
Packit |
78deda |
(int)convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_THUMBNAIL_OFFSET:
|
|
Packit |
78deda |
*thumbnailOffsetP = (unsigned int)
|
|
Packit |
78deda |
convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_THUMBNAIL_LENGTH:
|
|
Packit |
78deda |
*thumbnailSizeP = (unsigned int)
|
|
Packit |
78deda |
convertAnyFormat(valuePtr, format, byteOrder);
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
case TAG_EXIF_OFFSET:
|
|
Packit |
78deda |
case TAG_INTEROP_OFFSET: {
|
|
Packit |
78deda |
unsigned int const subdirOffset = get32u(valuePtr, byteOrder);
|
|
Packit |
78deda |
if (subdirOffset >= exifLength)
|
|
Packit |
78deda |
pm_message("Illegal exif or interop offset "
|
|
Packit |
78deda |
"directory link. Offset is %u, "
|
|
Packit |
78deda |
"but Exif data is only %u bytes.",
|
|
Packit |
78deda |
subdirOffset, exifLength);
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
processExifDir(exifData, exifLength, subdirOffset,
|
|
Packit |
78deda |
imageInfoP, byteOrder, wantTagTrace,
|
|
Packit |
78deda |
lastExifRefdP);
|
|
Packit |
78deda |
} break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
static void
|
|
Packit |
78deda |
processExifDir(const unsigned char * const exifData,
|
|
Packit |
78deda |
unsigned int const exifLength,
|
|
Packit |
78deda |
unsigned int const dirOffset,
|
|
Packit |
78deda |
exif_ImageInfo * const imageInfoP,
|
|
Packit |
78deda |
ByteOrder const byteOrder,
|
|
Packit |
78deda |
bool const wantTagTrace,
|
|
Packit |
78deda |
const unsigned char ** const lastExifRefdP) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Process one of the nested EXIF directories.
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
const unsigned char * const dirStart = exifData + dirOffset;
|
|
Packit |
78deda |
unsigned int const numDirEntries = get16u(&dirStart[0], byteOrder);
|
|
Packit |
78deda |
unsigned int de;
|
|
Packit |
78deda |
bool haveThumbnail;
|
|
Packit |
78deda |
unsigned int thumbnailOffset;
|
|
Packit |
78deda |
unsigned int thumbnailSize;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
#define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
|
|
Packit |
78deda |
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
const unsigned char * const dirEnd =
|
|
Packit |
78deda |
DIR_ENTRY_ADDR(dirStart, numDirEntries);
|
|
Packit |
78deda |
if (dirEnd + 4 > (exifData + exifLength)){
|
|
Packit |
78deda |
if (dirEnd + 2 == exifData + exifLength ||
|
|
Packit |
78deda |
dirEnd == exifData + exifLength){
|
|
Packit |
78deda |
/* Version 1.3 of jhead would truncate a bit too much.
|
|
Packit |
78deda |
This also caught later on as well.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
/* Note: Files that had thumbnails trimmed with jhead
|
|
Packit |
78deda |
1.3 or earlier might trigger this.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
pm_message("Illegal directory entry size");
|
|
Packit |
78deda |
return;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
*lastExifRefdP = MAX(*lastExifRefdP, dirEnd);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
pm_message("Directory with %d entries", numDirEntries);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
haveThumbnail = false; /* initial value */
|
|
Packit |
78deda |
thumbnailOffset = 0; /* initial value */
|
|
Packit |
78deda |
thumbnailSize = 0; /* initial value */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (de = 0; de < numDirEntries; ++de)
|
|
Packit |
78deda |
processDirEntry(DIR_ENTRY_ADDR(dirStart, de), exifData, exifLength,
|
|
Packit |
78deda |
byteOrder, wantTagTrace, imageInfoP,
|
|
Packit |
78deda |
&thumbnailOffset, &thumbnailSize, &haveThumbnail,
|
|
Packit |
78deda |
lastExifRefdP);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (haveThumbnail)
|
|
Packit |
78deda |
DirWithThumbnailPtrs = dirStart;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
{
|
|
Packit |
78deda |
/* In addition to linking to subdirectories via exif tags,
|
|
Packit |
78deda |
there's also a potential link to another directory at the end
|
|
Packit |
78deda |
of each directory. This has got to be the result of a
|
|
Packit |
78deda |
committee!
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if (DIR_ENTRY_ADDR(dirStart, numDirEntries) + 4 <=
|
|
Packit |
78deda |
exifData + exifLength){
|
|
Packit |
78deda |
unsigned int const subdirOffset =
|
|
Packit |
78deda |
get32u(dirStart + 2 + 12*numDirEntries, byteOrder);
|
|
Packit |
78deda |
if (subdirOffset){
|
|
Packit |
78deda |
const unsigned char * const subdirStart =
|
|
Packit |
78deda |
exifData + subdirOffset;
|
|
Packit |
78deda |
if (subdirStart > exifData + exifLength){
|
|
Packit |
78deda |
if (subdirStart < exifData + exifLength + 20){
|
|
Packit |
78deda |
/* Jhead 1.3 or earlier would crop the whole directory!
|
|
Packit |
78deda |
As Jhead produces this form of format incorrectness,
|
|
Packit |
78deda |
I'll just let it pass silently.
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
printf("Thumbnail removed with "
|
|
Packit |
78deda |
"Jhead 1.3 or earlier\n");
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
pm_message("Illegal subdirectory link");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
if (subdirOffset <= exifLength)
|
|
Packit |
78deda |
processExifDir(exifData, exifLength, subdirOffset,
|
|
Packit |
78deda |
imageInfoP, byteOrder, wantTagTrace,
|
|
Packit |
78deda |
lastExifRefdP);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
/* The exif header ends before the last next directory pointer. */
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (thumbnailSize && thumbnailOffset){
|
|
Packit |
78deda |
if (thumbnailSize + thumbnailOffset <= exifLength){
|
|
Packit |
78deda |
/* The thumbnail pointer appears to be valid. Store it. */
|
|
Packit |
78deda |
imageInfoP->ThumbnailPointer = exifData + thumbnailOffset;
|
|
Packit |
78deda |
imageInfoP->ThumbnailSize = thumbnailSize;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (wantTagTrace){
|
|
Packit |
78deda |
fprintf(stderr, "Thumbnail size: %u bytes\n", thumbnailSize);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
void
|
|
Packit |
78deda |
exif_parse(const unsigned char * const exifData,
|
|
Packit |
78deda |
unsigned int const length,
|
|
Packit |
78deda |
exif_ImageInfo * const imageInfoP,
|
|
Packit |
78deda |
bool const wantTagTrace,
|
|
Packit |
78deda |
const char ** const errorP) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Interpret an EXIF APP1 marker
|
|
Packit |
78deda |
|
|
Packit |
78deda |
'exifData' is the actual Exif data; it does not include the
|
|
Packit |
78deda |
"Exif" identifier and length field that often prefix Exif data.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
'length' is the length of the Exif section.
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
ByteOrder byteOrder;
|
|
Packit |
78deda |
int FirstOffset;
|
|
Packit |
78deda |
const unsigned char * lastExifRefd;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
*errorP = NULL; /* initial assumption */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
fprintf(stderr, "Exif header %d bytes long\n",length);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (MEMEQ(exifData + 0, "II" , 2)) {
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
fprintf(stderr, "Exif header in Intel order\n");
|
|
Packit |
78deda |
byteOrder = NORMAL;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
if (MEMEQ(exifData + 0, "MM", 2)) {
|
|
Packit |
78deda |
if (wantTagTrace)
|
|
Packit |
78deda |
fprintf(stderr, "Exif header in Motorola order\n");
|
|
Packit |
78deda |
byteOrder = MOTOROLA;
|
|
Packit |
78deda |
} else {
|
|
Packit |
78deda |
pm_asprintf(errorP, "Invalid alignment marker in Exif "
|
|
Packit |
78deda |
"data. First two bytes are '%c%c' (0x%02x%02x) "
|
|
Packit |
78deda |
"instead of 'II' or 'MM'.",
|
|
Packit |
78deda |
exifData[0], exifData[1], exifData[0], exifData[1]);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (!*errorP) {
|
|
Packit |
78deda |
unsigned short const start = get16u(exifData + 2, byteOrder);
|
|
Packit |
78deda |
/* Check the next value for correctness. */
|
|
Packit |
78deda |
if (start != 0x002a){
|
|
Packit |
78deda |
pm_asprintf(errorP, "Invalid Exif header start. "
|
|
Packit |
78deda |
"two bytes after the alignment marker "
|
|
Packit |
78deda |
"should be 0x002a, but is 0x%04x",
|
|
Packit |
78deda |
start);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (!*errorP) {
|
|
Packit |
78deda |
FirstOffset = get32u(exifData + 4, byteOrder);
|
|
Packit |
78deda |
if (FirstOffset < 8 || FirstOffset > 16){
|
|
Packit |
78deda |
/* I used to ensure this was set to 8 (website I used
|
|
Packit |
78deda |
indicated its 8) but PENTAX Optio 230 has it set
|
|
Packit |
78deda |
differently, and uses it as offset. (Sept 11 2002)
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
pm_message("Suspicious offset of first IFD value in Exif header");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
imageInfoP->Comments[0] = '\0'; /* Initial value - null string */
|
|
Packit |
78deda |
|
|
Packit |
78deda |
HaveXRes = FALSE; /* Initial assumption */
|
|
Packit |
78deda |
FocalplaneUnits = 0;
|
|
Packit |
78deda |
ExifImageWidth = 0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
lastExifRefd = exifData;
|
|
Packit |
78deda |
DirWithThumbnailPtrs = NULL;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
processExifDir(exifData, length, FirstOffset,
|
|
Packit |
78deda |
imageInfoP, byteOrder, wantTagTrace, &lastExifRefd);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Compute the CCD width, in millimeters. */
|
|
Packit |
78deda |
if (HaveXRes){
|
|
Packit |
78deda |
imageInfoP->HaveCCDWidth = 1;
|
|
Packit |
78deda |
imageInfoP->CCDWidth =
|
|
Packit |
78deda |
(float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes);
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
imageInfoP->HaveCCDWidth = 0;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (wantTagTrace){
|
|
Packit |
78deda |
fprintf(stderr,
|
|
Packit |
78deda |
"Non-settings part of Exif header: %lu bytes\n",
|
|
Packit |
78deda |
(unsigned long)(exifData + length - lastExifRefd));
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|
|
Packit |
78deda |
void
|
|
Packit |
78deda |
exif_showImageInfo(const exif_ImageInfo * const imageInfoP,
|
|
Packit |
78deda |
FILE * const fileP) {
|
|
Packit |
78deda |
/*--------------------------------------------------------------------------
|
|
Packit |
78deda |
Show the collected image info, displaying camera F-stop and shutter
|
|
Packit |
78deda |
speed in a consistent and legible fashion.
|
|
Packit |
78deda |
--------------------------------------------------------------------------*/
|
|
Packit |
78deda |
if (imageInfoP->CameraMake[0]) {
|
|
Packit |
78deda |
fprintf(fileP, "Camera make : %s\n", imageInfoP->CameraMake);
|
|
Packit |
78deda |
fprintf(fileP, "Camera model : %s\n", imageInfoP->CameraModel);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->DateTime[0])
|
|
Packit |
78deda |
fprintf(fileP, "Date/Time : %s\n", imageInfoP->DateTime);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
fprintf(fileP, "Resolution : %f x %f\n",
|
|
Packit |
78deda |
imageInfoP->XResolution, imageInfoP->YResolution);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->Orientation > 1) {
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Only print orientation if one was supplied, and if its not
|
|
Packit |
78deda |
1 (normal orientation)
|
|
Packit |
78deda |
|
|
Packit |
78deda |
1 - The 0th row is at the visual top of the image
|
|
Packit |
78deda |
and the 0th column is the visual left-hand side.
|
|
Packit |
78deda |
2 - The 0th row is at the visual top of the image
|
|
Packit |
78deda |
and the 0th column is the visual right-hand side.
|
|
Packit |
78deda |
3 - The 0th row is at the visual bottom of the image
|
|
Packit |
78deda |
and the 0th column is the visual right-hand side.
|
|
Packit |
78deda |
4 - The 0th row is at the visual bottom of the image
|
|
Packit |
78deda |
and the 0th column is the visual left-hand side.
|
|
Packit |
78deda |
5 - The 0th row is the visual left-hand side of of the image
|
|
Packit |
78deda |
and the 0th column is the visual top.
|
|
Packit |
78deda |
6 - The 0th row is the visual right-hand side of of the image
|
|
Packit |
78deda |
and the 0th column is the visual top.
|
|
Packit |
78deda |
7 - The 0th row is the visual right-hand side of of the image
|
|
Packit |
78deda |
and the 0th column is the visual bottom.
|
|
Packit |
78deda |
8 - The 0th row is the visual left-hand side of of the image
|
|
Packit |
78deda |
and the 0th column is the visual bottom.
|
|
Packit |
78deda |
|
|
Packit |
78deda |
Note: The descriptions here are the same as the name of the
|
|
Packit |
78deda |
command line option to pass to jpegtran to right the image
|
|
Packit |
78deda |
*/
|
|
Packit |
78deda |
static const char * OrientTab[9] = {
|
|
Packit |
78deda |
"Undefined",
|
|
Packit |
78deda |
"Normal", /* 1 */
|
|
Packit |
78deda |
"flip horizontal", /* left right reversed mirror */
|
|
Packit |
78deda |
"rotate 180", /* 3 */
|
|
Packit |
78deda |
"flip vertical", /* upside down mirror */
|
|
Packit |
78deda |
"transpose", /* Flipped about top-left <--> bottom-right axis.*/
|
|
Packit |
78deda |
"rotate 90", /* rotate 90 cw to right it. */
|
|
Packit |
78deda |
"transverse", /* flipped about top-right <--> bottom-left axis */
|
|
Packit |
78deda |
"rotate 270", /* rotate 270 to right it. */
|
|
Packit |
78deda |
};
|
|
Packit |
78deda |
|
|
Packit |
78deda |
fprintf(fileP, "Orientation : %s\n",
|
|
Packit |
78deda |
OrientTab[imageInfoP->Orientation]);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->IsColor == 0)
|
|
Packit |
78deda |
fprintf(fileP, "Color/bw : Black and white\n");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->FlashUsed >= 0)
|
|
Packit |
78deda |
fprintf(fileP, "Flash used : %s\n",
|
|
Packit |
78deda |
imageInfoP->FlashUsed ? "Yes" :"No");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->FocalLength) {
|
|
Packit |
78deda |
fprintf(fileP, "Focal length : %4.1fmm",
|
|
Packit |
78deda |
(double)imageInfoP->FocalLength);
|
|
Packit |
78deda |
if (imageInfoP->HaveCCDWidth){
|
|
Packit |
78deda |
fprintf(fileP, " (35mm equivalent: %dmm)",
|
|
Packit |
78deda |
(int)
|
|
Packit |
78deda |
(imageInfoP->FocalLength/imageInfoP->CCDWidth*36 + 0.5));
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->HaveCCDWidth)
|
|
Packit |
78deda |
fprintf(fileP, "CCD width : %2.4fmm\n",
|
|
Packit |
78deda |
(double)imageInfoP->CCDWidth);
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->ExposureTime) {
|
|
Packit |
78deda |
if (imageInfoP->ExposureTime < 0.010){
|
|
Packit |
78deda |
fprintf(fileP,
|
|
Packit |
78deda |
"Exposure time: %6.4f s ",
|
|
Packit |
78deda |
(double)imageInfoP->ExposureTime);
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
fprintf(fileP,
|
|
Packit |
78deda |
"Exposure time: %5.3f s ",
|
|
Packit |
78deda |
(double)imageInfoP->ExposureTime);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->ExposureTime <= 0.5){
|
|
Packit |
78deda |
fprintf(fileP, " (1/%d)",(int)(0.5 + 1/imageInfoP->ExposureTime));
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->ApertureFNumber){
|
|
Packit |
78deda |
fprintf(fileP, "Aperture : f/%3.1f\n",
|
|
Packit |
78deda |
(double)imageInfoP->ApertureFNumber);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->Distance){
|
|
Packit |
78deda |
if (imageInfoP->Distance < 0){
|
|
Packit |
78deda |
fprintf(fileP, "Focus dist. : Infinite\n");
|
|
Packit |
78deda |
}else{
|
|
Packit |
78deda |
fprintf(fileP, "Focus dist. :%5.2fm\n",
|
|
Packit |
78deda |
(double)imageInfoP->Distance);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->ISOequivalent){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
fprintf(fileP, "ISO equiv. : %2d\n",(int)imageInfoP->ISOequivalent);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->ExposureBias){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
fprintf(fileP, "Exposure bias:%4.2f\n",
|
|
Packit |
78deda |
(double)imageInfoP->ExposureBias);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
if (imageInfoP->Whitebalance){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
switch(imageInfoP->Whitebalance) {
|
|
Packit |
78deda |
case 1:
|
|
Packit |
78deda |
fprintf(fileP, "Whitebalance : sunny\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 2:
|
|
Packit |
78deda |
fprintf(fileP, "Whitebalance : fluorescent\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 3:
|
|
Packit |
78deda |
fprintf(fileP, "Whitebalance : incandescent\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
default:
|
|
Packit |
78deda |
fprintf(fileP, "Whitebalance : cloudy\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->MeteringMode){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
switch(imageInfoP->MeteringMode) {
|
|
Packit |
78deda |
case 2:
|
|
Packit |
78deda |
fprintf(fileP, "Metering Mode: center weight\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 3:
|
|
Packit |
78deda |
fprintf(fileP, "Metering Mode: spot\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 5:
|
|
Packit |
78deda |
fprintf(fileP, "Metering Mode: matrix\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->ExposureProgram){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
switch(imageInfoP->ExposureProgram) {
|
|
Packit |
78deda |
case 2:
|
|
Packit |
78deda |
fprintf(fileP, "Exposure : program (auto)\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 3:
|
|
Packit |
78deda |
fprintf(fileP, "Exposure : aperture priority (semi-auto)\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 4:
|
|
Packit |
78deda |
fprintf(fileP, "Exposure : shutter priority (semi-auto)\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
if (imageInfoP->CompressionLevel){ /* 05-jan-2001 vcs */
|
|
Packit |
78deda |
switch(imageInfoP->CompressionLevel) {
|
|
Packit |
78deda |
case 1:
|
|
Packit |
78deda |
fprintf(fileP, "Jpeg Quality : basic\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 2:
|
|
Packit |
78deda |
fprintf(fileP, "Jpeg Quality : normal\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
case 4:
|
|
Packit |
78deda |
fprintf(fileP, "Jpeg Quality : fine\n");
|
|
Packit |
78deda |
break;
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
/* Print the comment. Print 'Comment:' for each new line of comment. */
|
|
Packit |
78deda |
if (imageInfoP->Comments[0]) {
|
|
Packit |
78deda |
unsigned int a;
|
|
Packit |
78deda |
|
|
Packit |
78deda |
fprintf(fileP, "Comment : ");
|
|
Packit |
78deda |
|
|
Packit |
78deda |
for (a = 0; a < MAX_COMMENT && imageInfoP->Comments[a]; ++a) {
|
|
Packit |
78deda |
char const c = imageInfoP->Comments[a];
|
|
Packit |
78deda |
if (c == '\n'){
|
|
Packit |
78deda |
/* Do not start a new line if the string ends with a cr */
|
|
Packit |
78deda |
if (imageInfoP->Comments[a+1] != '\0')
|
|
Packit |
78deda |
fprintf(fileP, "\nComment : ");
|
|
Packit |
78deda |
else
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
} else
|
|
Packit |
78deda |
putc(c, fileP);
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
fprintf(fileP, "\n");
|
|
Packit |
78deda |
}
|
|
Packit |
78deda |
|
|
Packit |
78deda |
|