/*------------------------------------------------------------- tiio_jpg_exif.cpp Based on source code of a public domain software "Exif Jpeg header manipulation tool (jhead)" by Matthias Wandel. For now it is used only for obtaining resolution values. http://www.sentex.net/~mwandel/jhead/ -------------------------------------------------------------*/ #include "tiio_jpg_exif.h" #include #include // for debug #define ShowTags 0 #define DumpExifMap 0 namespace { typedef unsigned char uchar; const int BytesPerFormat[] = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; //-------------------------------------------------------------------------- // Describes tag values const int TAG_X_RESOLUTION = 0x011A; const int TAG_Y_RESOLUTION = 0x011B; const int TAG_RESOLUTION_UNIT = 0x0128; #ifdef ReadAllTags const int TAG_INTEROP_INDEX = 0x0001; const int TAG_INTEROP_VERSION = 0x0002; const int TAG_IMAGE_WIDTH = 0x0100; const int TAG_IMAGE_LENGTH = 0x0101; const int TAG_BITS_PER_SAMPLE = 0x0102; const int TAG_COMPRESSION = 0x0103; const int TAG_PHOTOMETRIC_INTERP = 0x0106; const int TAG_FILL_ORDER = 0x010A; const int TAG_DOCUMENT_NAME = 0x010D; const int TAG_IMAGE_DESCRIPTION = 0x010E; const int TAG_MAKE = 0x010F; const int TAG_MODEL = 0x0110; const int TAG_SRIP_OFFSET = 0x0111; const int TAG_ORIENTATION = 0x0112; const int TAG_SAMPLES_PER_PIXEL = 0x0115; const int TAG_ROWS_PER_STRIP = 0x0116; const int TAG_STRIP_BYTE_COUNTS = 0x0117; const int TAG_PLANAR_CONFIGURATION = 0x011C; const int TAG_TRANSFER_FUNCTION = 0x012D; const int TAG_SOFTWARE = 0x0131; const int TAG_DATETIME = 0x0132; const int TAG_ARTIST = 0x013B; const int TAG_WHITE_POINT = 0x013E; const int TAG_PRIMARY_CHROMATICITIES = 0x013F; const int TAG_TRANSFER_RANGE = 0x0156; const int TAG_JPEG_PROC = 0x0200; const int TAG_THUMBNAIL_OFFSET = 0x0201; const int TAG_THUMBNAIL_LENGTH = 0x0202; const int TAG_Y_CB_CR_COEFFICIENTS = 0x0211; const int TAG_Y_CB_CR_SUB_SAMPLING = 0x0212; const int TAG_Y_CB_CR_POSITIONING = 0x0213; const int TAG_REFERENCE_BLACK_WHITE = 0x0214; const int TAG_RELATED_IMAGE_WIDTH = 0x1001; const int TAG_RELATED_IMAGE_LENGTH = 0x1002; const int TAG_CFA_REPEAT_PATTERN_DIM = 0x828D; const int TAG_CFA_PATTERN1 = 0x828E; const int TAG_BATTERY_LEVEL = 0x828F; const int TAG_COPYRIGHT = 0x8298; const int TAG_EXPOSURETIME = 0x829A; const int TAG_FNUMBER = 0x829D; const int TAG_IPTC_NAA = 0x83BB; const int TAG_EXIF_OFFSET = 0x8769; const int TAG_INTER_COLOR_PROFILE = 0x8773; const int TAG_EXPOSURE_PROGRAM = 0x8822; const int TAG_SPECTRAL_SENSITIVITY = 0x8824; const int TAG_GPSINFO = 0x8825; const int TAG_ISO_EQUIVALENT = 0x8827; const int TAG_OECF = 0x8828; const int TAG_EXIF_VERSION = 0x9000; const int TAG_DATETIME_ORIGINAL = 0x9003; const int TAG_DATETIME_DIGITIZED = 0x9004; const int TAG_COMPONENTS_CONFIG = 0x9101; const int TAG_CPRS_BITS_PER_PIXEL = 0x9102; const int TAG_SHUTTERSPEED = 0x9201; const int TAG_APERTURE = 0x9202; const int TAG_BRIGHTNESS_VALUE = 0x9203; const int TAG_EXPOSURE_BIAS = 0x9204; const int TAG_MAXAPERTURE = 0x9205; const int TAG_SUBJECT_DISTANCE = 0x9206; const int TAG_METERING_MODE = 0x9207; const int TAG_LIGHT_SOURCE = 0x9208; const int TAG_FLASH = 0x9209; const int TAG_FOCALLENGTH = 0x920A; const int TAG_SUBJECTAREA = 0x9214; const int TAG_MAKER_NOTE = 0x927C; const int TAG_USERCOMMENT = 0x9286; const int TAG_SUBSEC_TIME = 0x9290; const int TAG_SUBSEC_TIME_ORIG = 0x9291; const int TAG_SUBSEC_TIME_DIG = 0x9292; const int TAG_WINXP_TITLE = 0x9c9b; // Windows XP - not part of exif standard. const int TAG_WINXP_COMMENT = 0x9c9c; // Windows XP - not part of exif standard. const int TAG_WINXP_AUTHOR = 0x9c9d; // Windows XP - not part of exif standard. const int TAG_WINXP_KEYWORDS = 0x9c9e; // Windows XP - not part of exif standard. const int TAG_WINXP_SUBJECT = 0x9c9f; // Windows XP - not part of exif standard. const int TAG_FLASH_PIX_VERSION = 0xA000; const int TAG_COLOR_SPACE = 0xA001; const int TAG_PIXEL_X_DIMENSION = 0xA002; const int TAG_PIXEL_Y_DIMENSION = 0xA003; const int TAG_RELATED_AUDIO_FILE = 0xA004; const int TAG_INTEROP_OFFSET = 0xA005; const int TAG_FLASH_ENERGY = 0xA20B; const int TAG_SPATIAL_FREQ_RESP = 0xA20C; const int TAG_FOCAL_PLANE_XRES = 0xA20E; const int TAG_FOCAL_PLANE_YRES = 0xA20F; const int TAG_FOCAL_PLANE_UNITS = 0xA210; const int TAG_SUBJECT_LOCATION = 0xA214; const int TAG_EXPOSURE_INDEX = 0xA215; const int TAG_SENSING_METHOD = 0xA217; const int TAG_FILE_SOURCE = 0xA300; const int TAG_SCENE_TYPE = 0xA301; const int TAG_CFA_PATTERN = 0xA302; const int TAG_CUSTOM_RENDERED = 0xA401; const int TAG_EXPOSURE_MODE = 0xA402; const int TAG_WHITEBALANCE = 0xA403; const int TAG_DIGITALZOOMRATIO = 0xA404; const int TAG_FOCALLENGTH_35MM = 0xA405; const int TAG_SCENE_CAPTURE_TYPE = 0xA406; const int TAG_GAIN_CONTROL = 0xA407; const int TAG_CONTRAST = 0xA408; const int TAG_SATURATION = 0xA409; const int TAG_SHARPNESS = 0xA40A; const int TAG_DISTANCE_RANGE = 0xA40C; const int TAG_IMAGE_UNIQUE_ID = 0xA420; #endif typedef struct { unsigned short Tag; const char *Desc; } TagTable_t; const TagTable_t TagTable[] = { {TAG_X_RESOLUTION, "XResolution"}, {TAG_Y_RESOLUTION, "YResolution"}, {TAG_RESOLUTION_UNIT, "ResolutionUnit"}, #ifdef ReadAllTags {TAG_INTEROP_INDEX, "InteropIndex"}, {TAG_INTEROP_VERSION, "InteropVersion"}, {TAG_IMAGE_WIDTH, "ImageWidth"}, {TAG_IMAGE_LENGTH, "ImageLength"}, {TAG_BITS_PER_SAMPLE, "BitsPerSample"}, {TAG_COMPRESSION, "Compression"}, {TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation"}, {TAG_FILL_ORDER, "FillOrder"}, {TAG_DOCUMENT_NAME, "DocumentName"}, {TAG_IMAGE_DESCRIPTION, "ImageDescription"}, {TAG_MAKE, "Make"}, {TAG_MODEL, "Model"}, {TAG_SRIP_OFFSET, "StripOffsets"}, {TAG_ORIENTATION, "Orientation"}, {TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"}, {TAG_ROWS_PER_STRIP, "RowsPerStrip"}, {TAG_STRIP_BYTE_COUNTS, "StripByteCounts"}, {TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"}, {TAG_TRANSFER_FUNCTION, "TransferFunction"}, {TAG_SOFTWARE, "Software"}, {TAG_DATETIME, "DateTime"}, {TAG_ARTIST, "Artist"}, {TAG_WHITE_POINT, "WhitePoint"}, {TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"}, {TAG_TRANSFER_RANGE, "TransferRange"}, {TAG_JPEG_PROC, "JPEGProc"}, {TAG_THUMBNAIL_OFFSET, "ThumbnailOffset"}, {TAG_THUMBNAIL_LENGTH, "ThumbnailLength"}, {TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"}, {TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"}, {TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"}, {TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"}, {TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth"}, {TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength"}, {TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim"}, {TAG_CFA_PATTERN1, "CFAPattern"}, {TAG_BATTERY_LEVEL, "BatteryLevel"}, {TAG_COPYRIGHT, "Copyright"}, {TAG_EXPOSURETIME, "ExposureTime"}, {TAG_FNUMBER, "FNumber"}, {TAG_IPTC_NAA, "IPTC/NAA"}, {TAG_EXIF_OFFSET, "ExifOffset"}, {TAG_INTER_COLOR_PROFILE, "InterColorProfile"}, {TAG_EXPOSURE_PROGRAM, "ExposureProgram"}, {TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"}, {TAG_GPSINFO, "GPS Dir offset"}, {TAG_ISO_EQUIVALENT, "ISOSpeedRatings"}, {TAG_OECF, "OECF"}, {TAG_EXIF_VERSION, "ExifVersion"}, {TAG_DATETIME_ORIGINAL, "DateTimeOriginal"}, {TAG_DATETIME_DIGITIZED, "DateTimeDigitized"}, {TAG_COMPONENTS_CONFIG, "ComponentsConfiguration"}, {TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel"}, {TAG_SHUTTERSPEED, "ShutterSpeedValue"}, {TAG_APERTURE, "ApertureValue"}, {TAG_BRIGHTNESS_VALUE, "BrightnessValue"}, {TAG_EXPOSURE_BIAS, "ExposureBiasValue"}, {TAG_MAXAPERTURE, "MaxApertureValue"}, {TAG_SUBJECT_DISTANCE, "SubjectDistance"}, {TAG_METERING_MODE, "MeteringMode"}, {TAG_LIGHT_SOURCE, "LightSource"}, {TAG_FLASH, "Flash"}, {TAG_FOCALLENGTH, "FocalLength"}, {TAG_MAKER_NOTE, "MakerNote"}, {TAG_USERCOMMENT, "UserComment"}, {TAG_SUBSEC_TIME, "SubSecTime"}, {TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal"}, {TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized"}, {TAG_WINXP_TITLE, "Windows-XP Title"}, {TAG_WINXP_COMMENT, "Windows-XP comment"}, {TAG_WINXP_AUTHOR, "Windows-XP author"}, {TAG_WINXP_KEYWORDS, "Windows-XP keywords"}, {TAG_WINXP_SUBJECT, "Windows-XP subject"}, {TAG_FLASH_PIX_VERSION, "FlashPixVersion"}, {TAG_COLOR_SPACE, "ColorSpace"}, {TAG_PIXEL_X_DIMENSION, "ExifImageWidth"}, {TAG_PIXEL_Y_DIMENSION, "ExifImageLength"}, {TAG_RELATED_AUDIO_FILE, "RelatedAudioFile"}, {TAG_INTEROP_OFFSET, "InteroperabilityOffset"}, {TAG_FLASH_ENERGY, "FlashEnergy"}, {TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse"}, {TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution"}, {TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution"}, {TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit"}, {TAG_SUBJECT_LOCATION, "SubjectLocation"}, {TAG_EXPOSURE_INDEX, "ExposureIndex"}, {TAG_SENSING_METHOD, "SensingMethod"}, {TAG_FILE_SOURCE, "FileSource"}, {TAG_SCENE_TYPE, "SceneType"}, {TAG_CFA_PATTERN, "CFA Pattern"}, {TAG_CUSTOM_RENDERED, "CustomRendered"}, {TAG_EXPOSURE_MODE, "ExposureMode"}, {TAG_WHITEBALANCE, "WhiteBalance"}, {TAG_DIGITALZOOMRATIO, "DigitalZoomRatio"}, {TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm"}, {TAG_SUBJECTAREA, "SubjectArea"}, {TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"}, {TAG_GAIN_CONTROL, "GainControl"}, {TAG_CONTRAST, "Contrast"}, {TAG_SATURATION, "Saturation"}, {TAG_SHARPNESS, "Sharpness"}, {TAG_DISTANCE_RANGE, "SubjectDistanceRange"}, {TAG_IMAGE_UNIQUE_ID, "ImageUniqueId"}, #endif }; const int TAG_TABLE_SIZE = (sizeof(TagTable) / sizeof(TagTable_t)); const int TRUE = 1; const int FALSE = 0; } // namespace //-------------------------------------------------------------------------- // Convert a 16 bit unsigned value from file's native byte order //-------------------------------------------------------------------------- int JpgExifReader::Get16u(void *Short) { if (MotorolaOrder) { return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; } else { return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0]; } } //-------------------------------------------------------------------------- // Convert a 32 bit signed value from file's native byte order //-------------------------------------------------------------------------- int JpgExifReader::Get32s(void *Long) { if (MotorolaOrder) { return (((char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) | (((uchar *)Long)[2] << 8) | (((uchar *)Long)[3] << 0); } else { return (((char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) | (((uchar *)Long)[1] << 8) | (((uchar *)Long)[0] << 0); } } //-------------------------------------------------------------------------- // Convert a 32 bit unsigned value from file's native byte order //-------------------------------------------------------------------------- unsigned JpgExifReader::Get32u(void *Long) { return (unsigned)Get32s(Long) & 0xffffffff; } //-------------------------------------------------------------------------- // Display a number as one of its many formats //-------------------------------------------------------------------------- void JpgExifReader::PrintFormatNumber(void *ValuePtr, int Format, int ByteCount) { int s, n; for (n = 0; n < 16; n++) { switch (Format) { case FMT_SBYTE: case FMT_BYTE: printf("%02x", *(uchar *)ValuePtr); s = 1; break; case FMT_USHORT: printf("%d", Get16u(ValuePtr)); s = 2; break; case FMT_ULONG: case FMT_SLONG: printf("%d", Get32s(ValuePtr)); s = 4; break; case FMT_SSHORT: printf("%hd", (signed short)Get16u(ValuePtr)); s = 2; break; case FMT_URATIONAL: printf("%u/%u", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr)); s = 8; break; case FMT_SRATIONAL: printf("%d/%d", Get32s(ValuePtr), Get32s(4 + (char *)ValuePtr)); s = 8; break; case FMT_SINGLE: printf("%f", (double)*(float *)ValuePtr); s = 8; break; case FMT_DOUBLE: printf("%f", *(double *)ValuePtr); s = 8; break; default: printf("Unknown format %d:", Format); return; } ByteCount -= s; if (ByteCount <= 0) break; printf(", "); ValuePtr = (void *)((char *)ValuePtr + s); } if (n >= 16) printf("..."); } //-------------------------------------------------------------------------- // Evaluate number, be it int, rational, or float from directory. //-------------------------------------------------------------------------- double JpgExifReader::ConvertAnyFormat(void *ValuePtr, int Format) { double Value; Value = 0; switch (Format) { case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; case FMT_BYTE: Value = *(uchar *)ValuePtr; break; case FMT_USHORT: Value = Get16u(ValuePtr); break; case FMT_ULONG: Value = Get32u(ValuePtr); break; case FMT_URATIONAL: case FMT_SRATIONAL: { int Num, Den; Num = Get32s(ValuePtr); Den = Get32s(4 + (char *)ValuePtr); if (Den == 0) { Value = 0; } else { if (Format == FMT_SRATIONAL) { Value = (double)Num / Den; } else { Value = (double)(unsigned)Num / (double)(unsigned)Den; } } break; } case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; case FMT_SLONG: Value = Get32s(ValuePtr); break; // Not sure if this is correct (never seen float used in Exif format) case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; case FMT_DOUBLE: Value = *(double *)ValuePtr; break; default: std::cout << "Illegal format code " << Format << " in Exif header" << std::endl; } return Value; } //-------------------------------------------------------------------------- void JpgExifReader::process_EXIF(unsigned char *ExifSection, unsigned int length) { unsigned int FirstOffset; #ifdef ReadAllTags FocalplaneXRes = 0; FocalplaneUnits = 0; ExifImageWidth = 0; NumOrientations = 0; DirWithThumbnailPtrs = NULL; #endif if (ShowTags) printf("Exif header %u bytes long\n", length); { // Check the EXIF header component static uchar ExifHeader[] = "Exif\0\0"; if (memcmp(ExifSection + 2, ExifHeader, 6)) { std::cout << "Incorrect Exif header" << std::endl; return; } } if (memcmp(ExifSection + 8, "II", 2) == 0) { if (ShowTags) printf("Exif section in Intel order\n"); MotorolaOrder = 0; } else { if (memcmp(ExifSection + 8, "MM", 2) == 0) { if (ShowTags) printf("Exif section in Motorola order\n"); MotorolaOrder = 1; } else { std::cout << "Invalid Exif alignment marker." << std::endl; return; } } // Check the next value for correctness. if (Get16u(ExifSection + 10) != 0x2a) { std::cout << "Invalid Exif start (1)" << std::endl; return; } FirstOffset = Get32u(ExifSection + 12); if (FirstOffset < 8 || FirstOffset > 16) { if (FirstOffset < 16 || FirstOffset > length - 16) { std::cout << "invalid offset for first Exif IFD value" << std::endl; return; } // Usually set to 8, but other values valid too. std::cout << "Suspicious offset of first Exif IFD value" << std::endl; } // First directory starts 16 bytes in. All offset are relative to 8 bytes in. ProcessExifDir(ExifSection + 8 + FirstOffset, ExifSection + 8, length - 8, 0); #ifdef ReadAllTags ImageInfo.ThumbnailAtEnd = ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset ? TRUE : FALSE; if (DumpExifMap) { unsigned a, b; printf("Map: %05d- End of exif\n", length - 8); for (a = 0; a < length - 8; a += 10) { printf("Map: %05d ", a); for (b = 0; b < 10; b++) printf(" %02x", *(ExifSection + 8 + a + b)); printf("\n"); } } // Compute the CCD width, in millimeters. if (FocalplaneXRes != 0 && ExifImageWidth != 0) { // Note: With some cameras, its not possible to compute this correctly // because // they don't adjust the indicated focal plane resolution units when using // less // than maximum resolution, so the CCDWidth value comes out too small. // Nothing // that Jhad can do about it - its a camera problem. ImageInfo.CCDWidth = (float)(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); if (ImageInfo.FocalLength && ImageInfo.FocalLength35mmEquiv == 0) { // Compute 35 mm equivalent focal length based on sensor geometry if we // haven't // already got it explicitly from a tag. ImageInfo.FocalLength35mmEquiv = (int)(ImageInfo.FocalLength / ImageInfo.CCDWidth * 36 + 0.5); } } #endif } //-------------------------------------------------------------------------- // Process one of the nested EXIF directories. //-------------------------------------------------------------------------- void JpgExifReader::ProcessExifDir(unsigned char *DirStart, unsigned char *OffsetBase, unsigned ExifLength, int NestingLevel) { int de; int a; int NumDirEntries; unsigned ThumbnailOffset = 0; unsigned ThumbnailSize = 0; char IndentString[25]; if (NestingLevel > 4) { std::cout << "Maximum Exif directory nesting exceeded (corrupt Exif header)" << std::endl; return; } memset(IndentString, ' ', 25); IndentString[NestingLevel * 4] = '\0'; NumDirEntries = Get16u(DirStart); #define DIR_ENTRY_ADDR(Start, Entry) (Start + 2 + 12 * (Entry)) { unsigned char *DirEnd; DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); if (DirEnd + 4 > (OffsetBase + ExifLength)) { if (DirEnd + 2 == OffsetBase + ExifLength || DirEnd == OffsetBase + ExifLength) { // Version 1.3 of jhead would truncate a bit too much. // This also caught later on as well. } else { std::cout << "Illegally sized Exif subdirectory (" << NumDirEntries << "entries)" << std::endl; return; } } if (DumpExifMap) { printf("Map: %05u-%05u: Directory\n", (int)(DirStart - OffsetBase), (int)(DirEnd + 4 - OffsetBase)); } } if (ShowTags) { printf("(dir has %d entries)\n", NumDirEntries); } for (de = 0; de < NumDirEntries; de++) { int Tag, Format, Components; unsigned char *ValuePtr; int ByteCount; unsigned char *DirEntry; DirEntry = DIR_ENTRY_ADDR(DirStart, de); Tag = Get16u(DirEntry); Format = Get16u(DirEntry + 2); Components = Get32u(DirEntry + 4); if ((Format - 1) >= NUM_FORMATS) { // (-1) catches illegal zero case as unsigned underflows to positive // large. std::cout << "Illegal number format " << Format << " for tag " << Tag << " in Exif" << std::endl; continue; } if ((unsigned)Components > 0x10000) { std::cout << "Too many components " << Components << " for tag " << Tag << " in Exif"; continue; } ByteCount = Components * BytesPerFormat[Format]; if (ByteCount > 4) { unsigned OffsetVal; OffsetVal = Get32u(DirEntry + 8); // If its bigger than 4 bytes, the dir entry contains an offset. if (OffsetVal + ByteCount > ExifLength) { // Bogus pointer offset and / or bytecount value std::cout << "Illegal value pointer for tag " << Tag << " in Exif"; continue; } ValuePtr = OffsetBase + OffsetVal; #ifdef ReadAllTags if (OffsetVal > ImageInfo.LargestExifOffset) { ImageInfo.LargestExifOffset = OffsetVal; } #endif if (DumpExifMap) { printf("Map: %05u-%05u: Data for tag %04x\n", OffsetVal, OffsetVal + ByteCount, Tag); } } else { // 4 bytes or less and value is in the dir entry itself ValuePtr = DirEntry + 8; } #ifdef ReadAllTags if (Tag == TAG_MAKER_NOTE) { if (ShowTags) { printf("%s Maker note: ", IndentString); } // ProcessMakerNote(ValuePtr, ByteCount, OffsetBase, ExifLength); continue; } if (ShowTags) { // Show tag name for (a = 0;; a++) { if (a >= TAG_TABLE_SIZE) { printf("%s Unknown Tag %04x Value = ", IndentString, Tag); break; } if (TagTable[a].Tag == Tag) { printf("%s %s = ", IndentString, TagTable[a].Desc); break; } } // Show tag value. switch (Format) { case FMT_BYTE: if (ByteCount > 1) { printf("%.*ls\n", ByteCount / 2, (wchar_t *)ValuePtr); } else { PrintFormatNumber(ValuePtr, Format, ByteCount); printf("\n"); } break; case FMT_UNDEFINED: // Undefined is typically an ascii string. case FMT_STRING: // String arrays printed without function call (different from int // arrays) { int NoPrint = 0; printf("\""); for (a = 0; a < ByteCount; a++) { if (ValuePtr[a] >= 32) { putchar(ValuePtr[a]); NoPrint = 0; } else { // Avoiding indicating too many unprintable characters of // proprietary // bits of binary information this program may not know how to // parse. if (!NoPrint && a != ByteCount - 1) { putchar('?'); NoPrint = 1; } } } printf("\"\n"); } break; default: // Handle arrays of numbers later (will there ever be?) PrintFormatNumber(ValuePtr, Format, ByteCount); printf("\n"); } } #endif // Extract useful components of tag switch (Tag) { #ifdef ReadAllTags case TAG_MAKE: strncpy(ImageInfo.CameraMake, (char *)ValuePtr, ByteCount < 31 ? ByteCount : 31); break; case TAG_MODEL: strncpy(ImageInfo.CameraModel, (char *)ValuePtr, ByteCount < 39 ? ByteCount : 39); break; case TAG_DATETIME_ORIGINAL: // If we get a DATETIME_ORIGINAL, we use that one. strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); // Fallthru... case TAG_DATETIME_DIGITIZED: case TAG_DATETIME: if (!isdigit(static_cast(ImageInfo.DateTime[0]))) { // If we don't already have a DATETIME_ORIGINAL, use whatever // time fields we may have. strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); } if (ImageInfo.numDateTimeTags >= MAX_DATE_COPIES) { std::cout << "More than " << MAX_DATE_COPIES << " date fields in Exif. This is nuts" << std::endl; break; } ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] = (char *)ValuePtr - (char *)OffsetBase; break; case TAG_WINXP_COMMENT: if (ImageInfo.Comments[0]) { // We already have a jpeg comment. // Already have a comment (probably windows comment), skip this one. if (ShowTags) printf("Windows XP commend and other comment in header\n"); break; // Already have a windows comment, skip this one. } if (ByteCount > 1) { if (ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE; memcpy(ImageInfo.Comments, ValuePtr, ByteCount); ImageInfo.CommentWidthchars = ByteCount / 2; } break; case TAG_USERCOMMENT: if (ImageInfo.Comments[0]) { // We already have a jpeg comment. // Already have a comment (probably windows comment), skip this one. if (ShowTags) printf("Multiple comments in exif header\n"); break; // Already have a windows comment, skip this one. } // Comment is often padded with trailing spaces. Remove these first. for (a = ByteCount;;) { a--; if ((ValuePtr)[a] == ' ') { (ValuePtr)[a] = '\0'; } else { break; } if (a == 0) break; } // Copy the comment { int msiz = ExifLength - (ValuePtr - OffsetBase); if (msiz > ByteCount) msiz = ByteCount; if (msiz > MAX_COMMENT_SIZE - 1) msiz = MAX_COMMENT_SIZE - 1; if (msiz > 5 && memcmp(ValuePtr, "ASCII", 5) == 0) { for (a = 5; a < 10 && a < msiz; a++) { int c = (ValuePtr)[a]; if (c != '\0' && c != ' ') { strncpy(ImageInfo.Comments, (char *)ValuePtr + a, msiz - a); break; } } } else { strncpy(ImageInfo.Comments, (char *)ValuePtr, msiz); } } break; case TAG_FNUMBER: // Simplest way of expressing aperture, so I trust it the most. // (overwrite previously computd value if there is one) ImageInfo.ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_APERTURE: case TAG_MAXAPERTURE: // More relevant info always comes earlier, so only use this field if we // don't // have appropriate aperture information yet. if (ImageInfo.ApertureFNumber == 0) { ImageInfo.ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format) * log(2) * 0.5); } break; case TAG_FOCALLENGTH: // Nice digital cameras actually save the focal length as a function // of how farthey are zoomed in. ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_SUBJECT_DISTANCE: // Inidcates the distacne the autofocus camera is focused to. // Tends to be less accurate as distance increases. ImageInfo.Distance = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURETIME: // Simplest way of expressing exposure time, so I trust it most. // (overwrite previously computd value if there is one) ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_SHUTTERSPEED: // More complicated way of expressing exposure time, so only use // this value if we don't already have it from somewhere else. if (ImageInfo.ExposureTime == 0) { ImageInfo.ExposureTime = (float)(1 / exp(ConvertAnyFormat(ValuePtr, Format) * log(2))); } break; case TAG_FLASH: ImageInfo.FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_ORIENTATION: if (NumOrientations >= 2) { // Can have another orientation tag for the thumbnail, but if there's // a third one, things are strange. std::cout << "More than two orientation in Exif" << std::endl; break; } OrientationPtr[NumOrientations] = ValuePtr; OrientationNumFormat[NumOrientations] = Format; if (NumOrientations == 0) { ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format); } if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8) { std::cout << "Undefined rotation value " << ImageInfo.Orientation << " in Exif" << std::endl; } NumOrientations += 1; break; case TAG_PIXEL_Y_DIMENSION: case TAG_PIXEL_X_DIMENSION: // Use largest of height and width to deal with images that have been // rotated to portrait format. a = (int)ConvertAnyFormat(ValuePtr, Format); if (ExifImageWidth < a) ExifImageWidth = a; break; case TAG_FOCAL_PLANE_XRES: FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); break; case TAG_FOCAL_PLANE_UNITS: switch ((int)ConvertAnyFormat(ValuePtr, Format)) { case 1: FocalplaneUnits = 25.4; break; // inch case 2: // According to the information I was using, 2 means meters. // But looking at the Cannon powershot's files, inches is the only // sensible value. FocalplaneUnits = 25.4; break; case 3: FocalplaneUnits = 10; break; // centimeter case 4: FocalplaneUnits = 1; break; // millimeter case 5: FocalplaneUnits = .001; break; // micrometer } break; case TAG_EXPOSURE_BIAS: ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_WHITEBALANCE: ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_LIGHT_SOURCE: ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_METERING_MODE: ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURE_PROGRAM: ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_EXPOSURE_INDEX: if (ImageInfo.ISOequivalent == 0) { // Exposure index and ISO equivalent are often used interchangeably, // so we will do the same in jhead. // http://photography.about.com/library/glossary/bldef_ei.htm ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); } break; case TAG_EXPOSURE_MODE: ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_ISO_EQUIVALENT: ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); break; case TAG_DIGITALZOOMRATIO: ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format); break; case TAG_THUMBNAIL_OFFSET: ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); DirWithThumbnailPtrs = DirStart; break; case TAG_THUMBNAIL_LENGTH: ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); ImageInfo.ThumbnailSizeOffset = ValuePtr - OffsetBase; break; case TAG_EXIF_OFFSET: if (ShowTags) printf("%s Exif Dir:", IndentString); case TAG_INTEROP_OFFSET: if (Tag == TAG_INTEROP_OFFSET && ShowTags) printf("%s Interop Dir:", IndentString); { unsigned char *SubdirStart; SubdirStart = OffsetBase + Get32u(ValuePtr); if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) { std::cout << "Illegal Exif or interop ofset directory link" << std::endl; } else { ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel + 1); } continue; } break; case TAG_GPSINFO: if (ShowTags) printf("%s GPS info dir:", IndentString); { unsigned char *SubdirStart; SubdirStart = OffsetBase + Get32u(ValuePtr); if (SubdirStart < OffsetBase || SubdirStart > OffsetBase + ExifLength) { std::cout << "Illegal GPS directory link in Exif" << std::endl; } else { // ProcessGpsInfo(SubdirStart, OffsetBase, ExifLength); } continue; } break; case TAG_FOCALLENGTH_35MM: // The focal length equivalent 35 mm is a 2.2 tag (defined as of April // 2002) // if its present, use it to compute equivalent focal length instead of // computing it from sensor geometry and actual focal length. ImageInfo.FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format); break; case TAG_DISTANCE_RANGE: // Three possible standard values: // 1 = macro, 2 = close, 3 = distant ImageInfo.DistanceRange = (int)ConvertAnyFormat(ValuePtr, Format); break; #endif case TAG_X_RESOLUTION: if (NestingLevel == 0) { // Only use the values from the top level directory ImageInfo.xResolution = (float)ConvertAnyFormat(ValuePtr, Format); // if yResolution has not been set, use the value of xResolution if (ImageInfo.yResolution == 0.0) ImageInfo.yResolution = ImageInfo.xResolution; } break; case TAG_Y_RESOLUTION: if (NestingLevel == 0) { // Only use the values from the top level directory ImageInfo.yResolution = (float)ConvertAnyFormat(ValuePtr, Format); // if xResolution has not been set, use the value of yResolution if (ImageInfo.xResolution == 0.0) ImageInfo.xResolution = ImageInfo.yResolution; } break; case TAG_RESOLUTION_UNIT: if (NestingLevel == 0) { // Only use the values from the top level directory ImageInfo.ResolutionUnit = (int)ConvertAnyFormat(ValuePtr, Format); } break; } } #ifdef ReadAllTags { // In addition to linking to subdirectories via exif tags, // there's also a potential link to another directory at the end of each // directory. this has got to be the result of a committee! unsigned char *SubdirStart; unsigned Offset; if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase + ExifLength) { Offset = Get32u(DirStart + 2 + 12 * NumDirEntries); if (Offset) { SubdirStart = OffsetBase + Offset; if (SubdirStart > OffsetBase + ExifLength || SubdirStart < OffsetBase) { if (SubdirStart > OffsetBase && SubdirStart < OffsetBase + ExifLength + 20) { // Jhead 1.3 or earlier would crop the whole directory! // As Jhead produces this form of format incorrectness, // I'll just let it pass silently if (ShowTags) printf("Thumbnail removed with Jhead 1.3 or earlier\n"); } else { std::cout << "Illegal subdirectory link in Exif header" << std::endl; } } else { if (SubdirStart <= OffsetBase + ExifLength) { if (ShowTags) printf("%s Continued directory ", IndentString); ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel + 1); } } if (Offset > ImageInfo.LargestExifOffset) { ImageInfo.LargestExifOffset = Offset; } } } else { // The exif header ends before the last next directory pointer. } } if (ThumbnailOffset) { ImageInfo.ThumbnailAtEnd = FALSE; if (DumpExifMap) { printf("Map: %05d-%05d: Thumbnail\n", ThumbnailOffset, ThumbnailOffset + ThumbnailSize); } if (ThumbnailOffset <= ExifLength) { if (ThumbnailSize > ExifLength - ThumbnailOffset) { // If thumbnail extends past exif header, only save the part that // actually exists. Canon's EOS viewer utility will do this - the // thumbnail extracts ok with this hack. ThumbnailSize = ExifLength - ThumbnailOffset; if (ShowTags) printf("Thumbnail incorrectly placed in header\n"); } // The thumbnail pointer appears to be valid. Store it. ImageInfo.ThumbnailOffset = ThumbnailOffset; ImageInfo.ThumbnailSize = ThumbnailSize; if (ShowTags) { printf("Thumbnail size: %u bytes\n", ThumbnailSize); } } } #endif }