From 1a1844cb34b7f8e62b2b736157af98ffdcbf15de Mon Sep 17 00:00:00 2001 From: ganovelli Date: Wed, 28 Dec 2011 13:47:08 +0000 Subject: [PATCH] added bundler importer (and small library for Exif reader) --- wrap/Exif/Readme.txt | 6 + wrap/Exif/exif.pri | 16 + wrap/Exif/include/Exif/exif.h | 915 ++++++++++++++++++ wrap/Exif/include/Exif/jhead.h | 244 +++++ wrap/Exif/src/exif.cpp | 1586 ++++++++++++++++++++++++++++++++ wrap/Exif/src/gpsinfo.cpp | 217 +++++ wrap/Exif/src/iptc.cpp | 205 +++++ wrap/Exif/src/jhead.cpp | 1558 +++++++++++++++++++++++++++++++ wrap/Exif/src/jpgfile.cpp | 669 ++++++++++++++ wrap/Exif/src/makernote.cpp | 184 ++++ wrap/Exif/src/myglob.cpp | 305 ++++++ wrap/Exif/src/paths.cpp | 141 +++ wrap/io_trimesh/import_out.h | 209 +++++ 13 files changed, 6255 insertions(+) create mode 100644 wrap/Exif/Readme.txt create mode 100644 wrap/Exif/exif.pri create mode 100644 wrap/Exif/include/Exif/exif.h create mode 100644 wrap/Exif/include/Exif/jhead.h create mode 100644 wrap/Exif/src/exif.cpp create mode 100644 wrap/Exif/src/gpsinfo.cpp create mode 100644 wrap/Exif/src/iptc.cpp create mode 100644 wrap/Exif/src/jhead.cpp create mode 100644 wrap/Exif/src/jpgfile.cpp create mode 100644 wrap/Exif/src/makernote.cpp create mode 100644 wrap/Exif/src/myglob.cpp create mode 100644 wrap/Exif/src/paths.cpp create mode 100644 wrap/io_trimesh/import_out.h diff --git a/wrap/Exif/Readme.txt b/wrap/Exif/Readme.txt new file mode 100644 index 00000000..fc206e70 --- /dev/null +++ b/wrap/Exif/Readme.txt @@ -0,0 +1,6 @@ +How to read exif metadata from a jpg file: +- add "echo INCLUDEPATH += %rootrel%/code/lib/exif/include>> %filename%" + and "echo SOURCES += %rootrel%\code\lib\exif\*.cpp>> %filename%" to pmake.bat +- include "jhead.h"; +- invoke ::ProcessFile(filename); +- get the available information from the "ImageInfo" global variable of type ImageInfo_t, which is defined in jhead.h. diff --git a/wrap/Exif/exif.pri b/wrap/Exif/exif.pri new file mode 100644 index 00000000..aede032d --- /dev/null +++ b/wrap/Exif/exif.pri @@ -0,0 +1,16 @@ +#Please define the EXIF_DIR variable which locates the glUtils directory in your system. +#eg. EXIF_DIR = ../sandbox/brivio/Exif + +!contains(DEFINES, EXIF_DIR){ + DEFINES += EXIF_DIR + + INCLUDEPATH += $$EXIF_DIR/include + SOURCES += $$EXIF_DIR/src/exif.cpp + SOURCES += $$EXIF_DIR/src/gpsinfo.cpp + SOURCES += $$EXIF_DIR/src/iptc.cpp + SOURCES += $$EXIF_DIR/src/jhead.cpp + SOURCES += $$EXIF_DIR/src/jpgfile.cpp + SOURCES += $$EXIF_DIR/src/makernote.cpp + win32:SOURCES += $$EXIF_DIR/src/myglob.cpp + SOURCES += $$EXIF_DIR/src/paths.cpp +} diff --git a/wrap/Exif/include/Exif/exif.h b/wrap/Exif/include/Exif/exif.h new file mode 100644 index 00000000..d9799751 --- /dev/null +++ b/wrap/Exif/include/Exif/exif.h @@ -0,0 +1,915 @@ +#ifndef EXIF_H +#define EXIF_H + +#include +#include +using namespace std; + +template +class Exif{ +public: + Exif(){ + sections.resize(0); + motorolaOrder = false; + } + + bool readJPEG(const char *filename){ + FILE *fp = fopen(filename, "rb"); // Unix ignores 'b', windows needs it. + //parse the marker stream until SOS or EOI is seen + if(!fp || !readJpegSections(fp)) return false; + fclose(fp); + return true; + } + + //Compute the CCD width, in millimeters + // 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. + T CCDwidthMm() const { + if(!FocalplaneXRes) return -1; + return T(ExifImageWidth * FocalplaneUnits / FocalplaneXRes); + } + + // Compute 35 mm equivalent focal length based on sensor geometry if we haven't + // already got it explicitly from a tag. + T FocalLength35mmEquiv() const { + if( intrinsics.FocalMm && !FocalLength35mmEquiv ) + FocalLength35mmEquiv = T(ImageInfo.FocalLength/ImageInfo.CCDWidth*36 + 0.5); + return FocalLength35mmEquiv; + } + + vcg::Camera vcgCamera() const { + vcg::Camera intrinsics; + return intrinsics; + } + +private: +// JPEG markers consist of one or more 0xFF bytes, followed by a marker +// code byte (which is not an FF). Here are the marker codes of interest +// in this program. +#define M_SOF0 0xC0 // Start Of Frame N +#define M_SOF1 0xC1 // N indicates which compression process +#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 // Start Of Image (beginning of datastream) +#define M_EOI 0xD9 // End Of Image (end of datastream) +#define M_SOS 0xDA // Start Of Scan (begins compressed data) +#define M_JFIF 0xE0 // Jfif marker +#define M_EXIF 0xE1 // Exif marker. Also used for XMP data! +#define M_XMP 0x10E1 // Not a real tag (same value in file as Exif!) +#define M_COM 0xFE // COMment +#define M_DQT 0xDB +#define M_DHT 0xC4 +#define M_DRI 0xDD +#define M_IPTC 0xED // IPTC marker + +// Exif format descriptor stuff +#define MAX_DATE_COPIES 10 +#ifdef _WIN32 + #define PATH_MAX _MAX_PATH + #define SLASH '\\' +#else + #define SLASH '/' +#endif +const int *BytesPerFormat; +#define NUM_FORMATS 12 +#define FMT_BYTE 1 +#define FMT_STRING 2 +#define FMT_USHORT 3 +#define FMT_ULONG 4 +#define FMT_URATIONAL 5 +#define FMT_SBYTE 6 +#define FMT_UNDEFINED 7 +#define FMT_SSHORT 8 +#define FMT_SLONG 9 +#define FMT_SRATIONAL 10 +#define FMT_SINGLE 11 +#define FMT_DOUBLE 12 + +// Describes tag values +#define TAG_INTEROP_INDEX 0x0001 +#define TAG_INTEROP_VERSION 0x0002 +#define TAG_IMAGE_WIDTH 0x0100 +#define TAG_IMAGE_LENGTH 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERP 0x0106 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_SRIP_OFFSET 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_PROC 0x0200 +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 +#define TAG_Y_CB_CR_COEFFICIENTS 0x0211 +#define TAG_Y_CB_CR_SUB_SAMPLING 0x0212 +#define TAG_Y_CB_CR_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +#define TAG_RELATED_IMAGE_WIDTH 0x1001 +#define TAG_RELATED_IMAGE_LENGTH 0x1002 +#define TAG_CFA_REPEAT_PATTERN_DIM 0x828D +#define TAG_CFA_PATTERN1 0x828E +#define TAG_BATTERY_LEVEL 0x828F +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_IPTC_NAA 0x83BB +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTER_COLOR_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITIVITY 0x8824 +#define TAG_GPSINFO 0x8825 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_OECF 0x8828 +#define TAG_EXIF_VERSION 0x9000 +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_DATETIME_DIGITIZED 0x9004 +#define TAG_COMPONENTS_CONFIG 0x9101 +#define TAG_CPRS_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_METERING_MODE 0x9207 +#define TAG_LIGHT_SOURCE 0x9208 +#define TAG_FLASH 0x9209 +#define TAG_FOCALLENGTH 0x920A +#define TAG_MAKER_NOTE 0x927C +#define TAG_USERCOMMENT 0x9286 +#define TAG_SUBSEC_TIME 0x9290 +#define TAG_SUBSEC_TIME_ORIG 0x9291 +#define TAG_SUBSEC_TIME_DIG 0x9292 +#define TAG_WINXP_TITLE 0x9c9b // Windows XP - not part of exif standard. +#define TAG_WINXP_COMMENT 0x9c9c // Windows XP - not part of exif standard. +#define TAG_WINXP_AUTHOR 0x9c9d // Windows XP - not part of exif standard. +#define TAG_WINXP_KEYWORDS 0x9c9e // Windows XP - not part of exif standard. +#define TAG_WINXP_SUBJECT 0x9c9f // Windows XP - not part of exif standard. +#define TAG_FLASH_PIX_VERSION 0xA000 +#define TAG_COLOR_SPACE 0xA001 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 +#define TAG_RELATED_AUDIO_FILE 0xA004 +#define TAG_INTEROP_OFFSET 0xA005 +#define TAG_FLASH_ENERGY 0xA20B +#define TAG_SPATIAL_FREQ_RESP 0xA20C +#define TAG_FOCAL_PLANE_XRES 0xA20E +#define TAG_FOCAL_PLANE_YRES 0xA20F +#define TAG_FOCAL_PLANE_UNITS 0xA210 +#define TAG_SUBJECT_LOCATION 0xA214 +#define TAG_EXPOSURE_INDEX 0xA215 +#define TAG_SENSING_METHOD 0xA217 +#define TAG_FILE_SOURCE 0xA300 +#define TAG_SCENE_TYPE 0xA301 +#define TAG_CFA_PATTERN 0xA302 +#define TAG_CUSTOM_RENDERED 0xA401 +#define TAG_EXPOSURE_MODE 0xA402 +#define TAG_WHITEBALANCE 0xA403 +#define TAG_DIGITALZOOMRATIO 0xA404 +#define TAG_FOCALLENGTH_35MM 0xA405 +#define TAG_SCENE_CAPTURE_TYPE 0xA406 +#define TAG_GAIN_CONTROL 0xA407 +#define TAG_CONTRAST 0xA408 +#define TAG_SATURATION 0xA409 +#define TAG_SHARPNESS 0xA40A +#define TAG_DISTANCE_RANGE 0xA40C + + typedef unsigned int uint; + typedef unsigned char uchar; + static const uint MAX_COMMENT_SIZE = 2000; + + typedef struct Section{ + int type; + uint size; + uchar *data; + }Section; + vector
sections; + + bool motorolaOrder; + + // This structure stores Exif header image elements in a simple manner + // Used to store camera data as extracted from the various ways that it can be + // stored in an exif header + typedef struct { + char FileName [PATH_MAX+1]; + time_t FileDateTime; + unsigned FileSize; + char CameraMake [32]; + char CameraModel [40]; + char DateTime [20]; + int Height, Width; + int Orientation; + int IsColor; + int Process; + int FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + float CCDWidth; + float ExposureBias; + float DigitalZoomRatio; + int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present. + int Whitebalance; + int MeteringMode; + int ExposureProgram; + int ExposureMode; + int ISOequivalent; + int LightSource; + int DistanceRange; + + char Comments[MAX_COMMENT_SIZE]; + int CommentWidchars; // If nonzer, widechar comment, indicates number of chars. + + unsigned ThumbnailOffset; // Exif offset to thumbnail + unsigned ThumbnailSize; // Size of thumbnail. + unsigned LargestExifOffset; // Last exif data referenced (to check if thumbnail is at end) + + char ThumbnailAtEnd; // Exif header ends with the thumbnail + // (we can only modify the thumbnail if its at the end) + int ThumbnailSizeOffset; + + int DateTimeOffsets[MAX_DATE_COPIES]; + int numDateTimeTags; + + int GpsInfoPresent; + char GpsLat[31]; + char GpsLong[31]; + char GpsAlt[20]; + }ImageInfo_t; + + ImageInfo_t ImageInfo; + + uchar *DirWithThumbnailPtrs; + T FocalplaneXRes; + T FocalplaneUnits; + int ExifImageWidth; + void * OrientationPtr[2]; + int OrientationNumFormat[2]; + int NumOrientations; + + // Get 16 bits motorola order (always) for jpeg header stuff. + int Get16m(const void *Short){ return (((uchar*)Short)[0] << 8) | ((uchar*)Short)[1]; } + // Convert a 16 bit unsigned value from file's native byte order + int Get16u(void *Short){ + return (motorolaOrder)? + (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1] : + (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0] ; + } + // Convert a 32 bit unsigned value from file's native byte order + unsigned Get32u(void *Long){ return (unsigned)Get32s(Long) & 0xffffffff; } + // Convert a 32 bit signed value from file's native byte order + int Get32s(void * Long){ + return (motorolaOrder)? + ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 ) : + ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 ) ; + } + + // Evaluate number, be it int, rational, or float from directory. + double ConvertAnyFormat(void * ValuePtr, int Format){ + double 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 = Get32s(ValuePtr); + int Den = Get32s(4+(char *)ValuePtr); + Value = (Den == 0)? 0 : double(Num/Den); + break; + } + case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + // Not sure if this is correct (never seen float used in Exif format) + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + default: + fprintf(stderr, "Illegal format code %d", Format); + } + + return Value; + } + + bool readJpegSections(FILE *fp){ + //parse the marker stream until SOS or EOI is seen + if(fgetc(fp) != 0xff) return false; + if(fgetc(fp) != M_SOI) return false; + + uint status = 0; + while(!status){ + Section sec; + + int marker = 0; + + for(int i=0 ; i<=16 ; i++){ + marker = fgetc(fp); + if(marker) break; + } + if(!marker){ + fprintf(stderr,"too many padding bytes\n"); + status = 1; + continue; + } +/* + for(int a=0;a<=16;a++){ + marker = fgetc(fp); + if(marker != 0xff) break; + if(a >= 16){ + fprintf(stderr, "too many padding bytes\n"); + status = 1; + continue; + } + } +*/ + sec.type = marker; + + //read the length of the section + int lh = fgetc(fp); + int ll = fgetc(fp); + int itemlen = (lh << 8) | ll; + if(itemlen < 2){ + fprintf(stderr, "invalid marker\n"); + status = 1; + continue; + } + sec.size = itemlen; + + sec.data = (uchar*)malloc(itemlen); + //store first two pre-read bytes + sec.data[0] = (uchar)lh; + sec.data[1] = (uchar)ll; + + int got = fread(sec.data+2, 1, itemlen-2, fp); + if(itemlen-2 != got){ //read the whole section + fprintf(stderr, "Premature end of file?\n"); + status = 1; + continue; + } + + switch(marker){ + case M_SOS: //stop before hitting compressed data + status = 2; + continue; + case M_EOI: //in case it's a tables-only JPEG stream + fprintf(stderr, "No image in jpeg!\n"); + status = 1; + continue; + case M_COM: //comment section + //process_COM(data, itemlen); + break; + case M_JFIF: + // Regular jpegs always have this tag, exif images have the exif + // marker instead, althogh ACDsee will write images with both markers. + // This program will re-create this marker on absence of exif marker, + // hence no need to keep the copy from the file. + free(sec.data); + sec.data = NULL; + break; + case M_EXIF: + // There can be different section using the same marker. Ignore all but "Exif" one + if(memcmp(sec.data+2, "Exif", 4) == 0){ + if( !process_EXIF(sec.data, itemlen) ) status = 1; + }else // Oterwise, discard this section + free(sec.data); + sec.data = NULL; + break; + case M_IPTC: + case M_SOF0: + case M_SOF1: + case M_SOF2: + case M_SOF3: + case M_SOF5: + case M_SOF6: + case M_SOF7: + case M_SOF9: + case M_SOF10: + case M_SOF11: + case M_SOF13: + case M_SOF14: + case M_SOF15: + process_SOFn(sec.data, marker); + break; + default: // Skip any other sections + break; + } + } + return (status==2); + } + + /* + Process a COM marker. + we must guard against random junk and varying newline representations. + */ +/* UNUSED + void process_COM(const uchar *data, uint length){ + char Comment[MAX_COMMENT_SIZE+1]; + int nch = 0; + + length = max(length, MAX_COMMENT_SIZE); //truncate if it won't fit in our structure + + for(int a=2 ; a=32) || (ch=='\n') || (ch=='\t') )? char(ch) : '?'; + } + Comment[nch] = '\0'; //null terminate + } +*/ + + // Process exif format directory, as used by Cannon maker note + void ProcessCanonMakerNoteDir(unsigned char * DirStart, unsigned char * OffsetBase, unsigned ExifLength){ + int NumDirEntries; + + NumDirEntries = Get16u(DirStart); + #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) + + uchar *DirEnd; + DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); + if(DirEnd > (OffsetBase+ExifLength)){ + fprintf(stderr, "Illegally sized exif makernote subdir (%d entries)", NumDirEntries); + return; + } + + for(int de=0 ; de= NUM_FORMATS){ + // (-1) catches illegal zero case as unsigned underflows to positive large. + fprintf(stderr, "Illegal number format %d for tag %04x", Format, Tag); + continue; + } + if((unsigned)Components > 0x10000){ + fprintf(stderr, "Illegal number of components %d for tag %04x", Components, Tag); + continue; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4){ + unsigned OffsetVal; + OffsetVal = Get32u(DirEntry+8); + // If its bigger than 4 bytes, the dir entry contains an offset. + if (OffsetVal+ByteCount > ExifLength){ + // Bogus pointer offset and / or bytecount value + fprintf(stderr, "Illegal value pointer for tag %04x", Tag); + continue; + } + ValuePtr = OffsetBase+OffsetVal; + }else{ + // 4 bytes or less and value is in the dir entry itself + ValuePtr = DirEntry+8; + } + + if( (Tag == 1) && (Components > 16) ){ + int IsoCode = Get16u(ValuePtr + 16*sizeof(unsigned short)); + if( (IsoCode >= 16) && (IsoCode <= 24) ) + ImageInfo.ISOequivalent = 50 << (IsoCode-16); + } + + if( (Tag == 4) && (Format == FMT_USHORT) ){ + if(Components > 7){ + int WhiteBalance = Get16u(ValuePtr + 7*sizeof(unsigned short)); + switch(WhiteBalance){ + // 0=Auto, 6=Custom + case 1: ImageInfo.LightSource = 1; break; // Sunny + case 2: ImageInfo.LightSource = 1; break; // Cloudy + case 3: ImageInfo.LightSource = 3; break; // Thungsten + case 4: ImageInfo.LightSource = 2; break; // Fourescent + case 5: ImageInfo.LightSource = 4; break; // Flash + } + } + if( (Components > 19) && (ImageInfo.Distance <= 0) ){ + // Indicates the distance the autofocus camera is focused to. + // Tends to be less accurate as distance increases. + int temp_dist = Get16u(ValuePtr + 19*sizeof(unsigned short)); + ImageInfo.Distance = (temp_dist != 65535)? (float)temp_dist/100 : -1; + } + } + } + } + + // Process maker note - to the limited extent that its supported. + void ProcessMakerNote(unsigned char * ValuePtr, unsigned char * OffsetBase, unsigned ExifLength){ + if(strstr(ImageInfo.CameraMake, "Canon")) + ProcessCanonMakerNoteDir(ValuePtr, OffsetBase, ExifLength); + } + + /* + Process a SOFn marker. This is useful for the image dimensions + JPEG image is data[7] color components, data[2] bits per sample + */ + void process_SOFn(const uchar *data, int marker){ + //data[2] contains the data precision value + ImageInfo.Height = Get16m(data+3); + ImageInfo.Width = Get16m(data+5); + int num_components = data[7]; + ImageInfo.IsColor = (num_components == 3); + ImageInfo.Process = marker; + } + + /* + Process a EXIF marker. + Describes all the drivel that most digital cameras include... + */ + bool process_EXIF(uchar *ExifSection, uint length){ + FocalplaneXRes = 0; + FocalplaneUnits = 0; + ExifImageWidth = 0; + NumOrientations = 0; + + // Check the EXIF header component + static char *ExifHeader = "Exif\0\0"; + if(memcmp(ExifSection+2, ExifHeader,6)){ + fprintf(stderr, "Incorrect Exif header"); + return false; + } + if(memcmp(ExifSection+8,"II",2) == 0) motorolaOrder = false; //Exif section in Intel order + else if(memcmp(ExifSection+8,"MM",2) == 0) motorolaOrder = true; //Exif section in Motorola order + else{ + fprintf(stderr, "Invalid Exif alignment marker"); + return false; + } + // Check the next value for correctness + if(Get16u(ExifSection+10) != 0x2a){ + fprintf(stderr, "Invalid Exif start (1)"); + return false; + } + + const uint FirstOffset = Get32u(ExifSection+12); + if(FirstOffset < 8 || FirstOffset > 16){ + // Usually set to 8, but other values valid too + fprintf(stderr, "Suspicious offset of first IFD value"); + return false; + } + + DirWithThumbnailPtrs = NULL; + + // First directory starts 16 bytes in. All offset are relative to 8 bytes in. + if( !ProcessExifDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0) ) return false; + + ImageInfo.ThumbnailAtEnd = (ImageInfo.ThumbnailOffset >= ImageInfo.LargestExifOffset); + + return true; + } + + // Process one of the nested EXIF directories. + bool ProcessExifDir(uchar *DirStart, uchar *OffsetBase, unsigned ExifLength, int NestingLevel){ + int NumDirEntries; + unsigned ThumbnailOffset = 0; + unsigned ThumbnailSize = 0; + char IndentString[25]; + + if(NestingLevel > 4){ + fprintf(stderr, "Maximum directory nesting exceeded (corrupt exif header)"); + return false; + } + + memset(IndentString, ' ', 25); + IndentString[NestingLevel * 4] = '\0'; + + NumDirEntries = Get16u(DirStart); +#define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) + uchar *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{ + fprintf(stderr, "Illegally sized exif subdirectory (%d entries)", NumDirEntries); + return false; + } + } + + for(int de=0 ; de= NUM_FORMATS){ + // (-1) catches illegal zero case as unsigned underflows to positive large. + fprintf(stderr, "Illegal number format %d for tag %04x", Format, Tag); + continue; + } + + if ((unsigned)Components > 0x10000){ + fprintf(stderr, "Illegal number of components %d for tag %04x", Components, Tag); + continue; + } + + int ByteCount = Components * BytesPerFormat[Format]; + + uchar *ValuePtr; + 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 + fprintf(stderr, "Illegal value pointer for tag %04x", Tag,0); + continue; + } + ValuePtr = OffsetBase+OffsetVal; + if(OffsetVal > ImageInfo.LargestExifOffset) ImageInfo.LargestExifOffset = OffsetVal; + }else{ + // 4 bytes or less and value is in the dir entry itself + ValuePtr = DirEntry+8; + } + + if(Tag == TAG_MAKER_NOTE){ + ProcessMakerNote(ValuePtr, OffsetBase, ExifLength); + continue; + } + + // Extract useful components of tag + switch(Tag){ + 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 we don't already have a DATETIME_ORIGINAL, use whatever time fields we may have. + if(!isdigit(ImageInfo.DateTime[0])) strncpy(ImageInfo.DateTime, (char*)ValuePtr, 19); + if(ImageInfo.numDateTimeTags >= MAX_DATE_COPIES) + fprintf(stderr, "More than %d date fields! This is nuts", MAX_DATE_COPIES, 0); + else + ImageInfo.DateTimeOffsets[ImageInfo.numDateTimeTags++] = (char*)ValuePtr - (char*)OffsetBase; + break; + case TAG_WINXP_COMMENT: + // We already have a jpeg comment (probably windows comment), skip this one. + if(ImageInfo.Comments[0]) break; + if(ByteCount > 1){ + if(ByteCount > MAX_COMMENT_SIZE) ByteCount = MAX_COMMENT_SIZE; + memcpy(ImageInfo.Comments, ValuePtr, ByteCount); + ImageInfo.CommentWidchars = ByteCount/2; + } + break; + case TAG_USERCOMMENT:{ + // We already have a jpeg comment (probably windows comment), skip this one. + if(ImageInfo.Comments[0]) break; + // Comment is often padded with trailing spaces. Remove these first. + int a = ByteCount-1; + while( a && ((ValuePtr)[a]==' ') ) (ValuePtr)[a--] = '\0'; + + // Copy the comment + if(memcmp(ValuePtr, "ASCII",5) == 0){ + for(a=5 ; a<10 ; a++){ + uchar c = (ValuePtr)[a]; + if( (c!='\0') && (c!=' ') ){ + strncpy(ImageInfo.Comments, (char *)ValuePtr+a, 199); + break; + } + } + }else{ + strncpy(ImageInfo.Comments, (char *)ValuePtr, MAX_COMMENT_SIZE-1); + } + 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 = T(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 = T(exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*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 = T(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 = T(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 = T(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 = T(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0))); + 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 stringae. + fprintf(stderr, "More than two orientation tags!"); + break; + } + OrientationPtr[NumOrientations] = ValuePtr; + OrientationNumFormat[NumOrientations] = Format; + if(NumOrientations == 0) + ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + if(ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8){ + fprintf(stderr, "Undefined rotation value %d", ImageInfo.Orientation); + ImageInfo.Orientation = 0; + } + NumOrientations += 1; + break; + case TAG_EXIF_IMAGELENGTH: + case TAG_EXIF_IMAGEWIDTH:{ + // Use largest of height and width to deal with images that have been + // rotated to portrait format. + int 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); + // Fixes strange encoding on some older digicams. + if( ImageInfo.ISOequivalent < 50 ) ImageInfo.ISOequivalent *= 200; + 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: + case TAG_INTEROP_OFFSET:{ + uchar *SubdirStart = OffsetBase + Get32u(ValuePtr); + if(SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength) + fprintf(stderr, "Illegal exif or interop offset directory link"); + else + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); + break; + } + case TAG_GPSINFO:{ + uchar *SubdirStart = OffsetBase + Get32u(ValuePtr); + if(SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength) + fprintf(stderr, "Illegal GPS directory link"); + else + ;//ProcessGpsInfo(SubdirStart, ByteCount, OffsetBase, ExifLength); + 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; + } + } + + // 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 + }else{ + fprintf(stderr, "Illegal subdirectory link"); + } + }else{ + if(SubdirStart <= OffsetBase+ExifLength) + 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(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; + } + // The thumbnail pointer appears to be valid. Store it. + ImageInfo.ThumbnailOffset = ThumbnailOffset; + ImageInfo.ThumbnailSize = ThumbnailSize; + } + } + } + +}; + +#endif \ No newline at end of file diff --git a/wrap/Exif/include/Exif/jhead.h b/wrap/Exif/include/Exif/jhead.h new file mode 100644 index 00000000..42940439 --- /dev/null +++ b/wrap/Exif/include/Exif/jhead.h @@ -0,0 +1,244 @@ +//-------------------------------------------------------------------------- +// Include file for jhead program. +// +// This include file only defines stuff that goes across modules. +// I like to keep the definitions for macros and structures as close to +// where they get used as possible, so include files only get stuff that +// gets used in more than one file. +//-------------------------------------------------------------------------- +#define _CRT_SECURE_NO_DEPRECATE 1 + +#include +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------- + +#ifdef _WIN32 + #include +#else + #include + #include + #include + #include + #include +#endif + + +typedef unsigned char uchar; + +#ifndef TRUE + #define TRUE 1 + #define FALSE 0 +#endif + +#define MAX_COMMENT_SIZE 2000 + +#ifdef _WIN32 + #define PATH_MAX _MAX_PATH + #define SLASH '\\' +#else + #define SLASH '/' +#endif + + +//-------------------------------------------------------------------------- +// This structure is used to store jpeg file sections in memory. +typedef struct { + uchar * Data; + int Type; + unsigned Size; +}Section_t; + +extern int ExifSectionIndex; + +extern int DumpExifMap; + +#define MAX_DATE_COPIES 10 + +//-------------------------------------------------------------------------- +// This structure stores Exif header image elements in a simple manner +// Used to store camera data as extracted from the various ways that it can be +// stored in an exif header +typedef struct { + char FileName [PATH_MAX+1]; + time_t FileDateTime; + unsigned FileSize; + char CameraMake [32]; + char CameraModel [40]; + char DateTime [20]; + int Height, Width; + int Orientation; + int IsColor; + int Process; + int FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + float CCDWidth; + float ExposureBias; + float DigitalZoomRatio; + int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present. + int Whitebalance; + int MeteringMode; + int ExposureProgram; + int ExposureMode; + int ISOequivalent; + int LightSource; + int DistanceRange; + + double FocalplaneXRes; + double FocalplaneUnits; + int ExifImageWidth; + + char Comments[MAX_COMMENT_SIZE]; + int CommentWidchars; // If nonzer, widechar comment, indicates number of chars. + + unsigned ThumbnailOffset; // Exif offset to thumbnail + unsigned ThumbnailSize; // Size of thumbnail. + unsigned LargestExifOffset; // Last exif data referenced (to check if thumbnail is at end) + + char ThumbnailAtEnd; // Exif header ends with the thumbnail + // (we can only modify the thumbnail if its at the end) + int ThumbnailSizeOffset; + + int DateTimeOffsets[MAX_DATE_COPIES]; + int numDateTimeTags; + + int GpsInfoPresent; + char GpsLat[31]; + char GpsLong[31]; + char GpsAlt[20]; +}ImageInfo_t; + + + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +// jpgfile.c functions +//typedef +enum { + READ_METADATA = 1, + READ_IMAGE = 2, + READ_ALL = 3 +};//ReadMode_t; +typedef int ReadMode_t; + + +// prototypes for jhead.c functions +void ErrFatal(const char * msg); +void ErrNonfatal(const char * msg, int a1, int a2); +void FileTimeAsString(char * TimeStr); + +void ProcessFile(const char * FileName); + +// Prototypes for exif.c functions. +int Exif2tm(struct tm * timeptr, char * ExifTime); +void process_EXIF (unsigned char * CharBuf, unsigned int length); +int RemoveThumbnail(unsigned char * ExifSection); +void ShowImageInfo(int ShowFileInfo); +void ShowConciseImageInfo(void); +const char * ClearOrientation(void); +void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount); +double ConvertAnyFormat(void * ValuePtr, int Format); +int Get16u(void * Short); +unsigned Get32u(void * Long); +int Get32s(void * Long); +void Put32u(void * Value, unsigned PutValue); +void create_EXIF(void); + +//-------------------------------------------------------------------------- +// Exif format descriptor stuff +extern const int BytesPerFormat[]; +#define NUM_FORMATS 12 + +#define FMT_BYTE 1 +#define FMT_STRING 2 +#define FMT_USHORT 3 +#define FMT_ULONG 4 +#define FMT_URATIONAL 5 +#define FMT_SBYTE 6 +#define FMT_UNDEFINED 7 +#define FMT_SSHORT 8 +#define FMT_SLONG 9 +#define FMT_SRATIONAL 10 +#define FMT_SINGLE 11 +#define FMT_DOUBLE 12 + + +// makernote.c prototypes +extern void ProcessMakerNote(unsigned char * DirStart, int ByteCount, + unsigned char * OffsetBase, unsigned ExifLength); + +// gpsinfo.c prototypes +void ProcessGpsInfo(unsigned char * ValuePtr, int ByteCount, + unsigned char * OffsetBase, unsigned ExifLength); + +// iptc.c prototpyes +void show_IPTC (unsigned char * CharBuf, unsigned int length); +void ShowXmp(Section_t XmpSection); + +// Prototypes for myglob.c module +#ifdef _WIN32 +void MyGlob(const char * Pattern , void (*FileFuncParm)(const char * FileName)); +void SlashToNative(char * Path); +#endif + +// Prototypes for paths.c module +int EnsurePathExists(const char * FileName); +void CatPath(char * BasePath, const char * FilePath); + +// Prototypes from jpgfile.c +int ReadJpegSections (FILE * infile, ReadMode_t ReadMode); +void DiscardData(void); +void DiscardAllButExif(void); +int ReadJpegFile(const char * FileName, ReadMode_t ReadMode); +int ReplaceThumbnail(const char * ThumbFileName); +int SaveThumbnail(char * ThumbFileName); +int RemoveSectionType(int SectionType); +int RemoveUnknownSections(void); +void WriteJpegFile(const char * FileName); +Section_t * FindSection(int SectionType); +Section_t * CreateSection(int SectionType, unsigned char * Data, int size); +void ResetJpgfile(void); + + +// Variables from jhead.c used by exif.c +extern ImageInfo_t ImageInfo; +extern int ShowTags; + +//-------------------------------------------------------------------------- +// JPEG markers consist of one or more 0xFF bytes, followed by a marker +// code byte (which is not an FF). Here are the marker codes of interest +// in this program. (See jdmarker.c for a more complete list.) +//-------------------------------------------------------------------------- + +#define M_SOF0 0xC0 // Start Of Frame N +#define M_SOF1 0xC1 // N indicates which compression process +#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use +#define M_SOF3 0xC3 +#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers +#define M_SOF6 0xC6 +#define M_SOF7 0xC7 +#define M_SOF9 0xC9 +#define M_SOF10 0xCA +#define M_SOF11 0xCB +#define M_SOF13 0xCD +#define M_SOF14 0xCE +#define M_SOF15 0xCF +#define M_SOI 0xD8 // Start Of Image (beginning of datastream) +#define M_EOI 0xD9 // End Of Image (end of datastream) +#define M_SOS 0xDA // Start Of Scan (begins compressed data) +#define M_JFIF 0xE0 // Jfif marker +#define M_EXIF 0xE1 // Exif marker. Also used for XMP data! +#define M_XMP 0x10E1 // Not a real tag (same value in file as Exif!) +#define M_COM 0xFE // COMment +#define M_DQT 0xDB +#define M_DHT 0xC4 +#define M_DRI 0xDD +#define M_IPTC 0xED // IPTC marker diff --git a/wrap/Exif/src/exif.cpp b/wrap/Exif/src/exif.cpp new file mode 100644 index 00000000..ce38f64d --- /dev/null +++ b/wrap/Exif/src/exif.cpp @@ -0,0 +1,1586 @@ +//-------------------------------------------------------------------------- +// Program to pull the information out of various types of EXIF digital +// camera files and show it in a reasonably consistent way +// +// This module parses the very complicated exif structures. +// +// Matthias Wandel +//-------------------------------------------------------------------------- +#include "../include/Exif/jhead.h" + +#include + +static unsigned char * DirWithThumbnailPtrs; +/* +static double FocalplaneXRes; +static double FocalplaneUnits; +static int ExifImageWidth; +*/ +static int MotorolaOrder = 0; + +// for fixing the rotation. +static void * OrientationPtr[2]; +static int OrientationNumFormat[2]; +int NumOrientations = 0; + +typedef struct { + unsigned short Tag; + const char * Desc; +}TagTable_t; + + +//-------------------------------------------------------------------------- +// Table of Jpeg encoding process names +static const TagTable_t ProcessTable[] = { + { M_SOF0, "Baseline"}, + { M_SOF1, "Extended sequential"}, + { M_SOF2, "Progressive"}, + { M_SOF3, "Lossless"}, + { M_SOF5, "Differential sequential"}, + { M_SOF6, "Differential progressive"}, + { M_SOF7, "Differential lossless"}, + { M_SOF9, "Extended sequential, arithmetic coding"}, + { M_SOF10, "Progressive, arithmetic coding"}, + { M_SOF11, "Lossless, arithmetic coding"}, + { M_SOF13, "Differential sequential, arithmetic coding"}, + { M_SOF14, "Differential progressive, arithmetic coding"}, + { M_SOF15, "Differential lossless, arithmetic coding"}, +}; + +#define PROCESS_TABLE_SIZE (int)(sizeof(ProcessTable) / sizeof(TagTable_t)) + +// 1 - "The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side." +// 2 - "The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side." +// 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side." +// 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side." + +// 5 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual top." +// 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top." +// 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom." +// 8 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual bottom." + +// Note: The descriptions here are the same as the name of the command line +// option to pass to jpegtran to right the image + +static const char * OrientTab[9] = { + "Undefined", + "Normal", // 1 + "flip horizontal", // left right reversed mirror + "rotate 180", // 3 + "flip vertical", // upside down mirror + "transpose", // Flipped about top-left <--> bottom-right axis. + "rotate 90", // rotate 90 cw to right it. + "transverse", // flipped about top-right <--> bottom-left axis + "rotate 270", // rotate 270 to right it. +}; + +const int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; + +//-------------------------------------------------------------------------- +// Describes tag values + +#define TAG_INTEROP_INDEX 0x0001 +#define TAG_INTEROP_VERSION 0x0002 +#define TAG_IMAGE_WIDTH 0x0100 +#define TAG_IMAGE_LENGTH 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERP 0x0106 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_SRIP_OFFSET 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_PROC 0x0200 +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 +#define TAG_Y_CB_CR_COEFFICIENTS 0x0211 +#define TAG_Y_CB_CR_SUB_SAMPLING 0x0212 +#define TAG_Y_CB_CR_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +#define TAG_RELATED_IMAGE_WIDTH 0x1001 +#define TAG_RELATED_IMAGE_LENGTH 0x1002 +#define TAG_CFA_REPEAT_PATTERN_DIM 0x828D +#define TAG_CFA_PATTERN1 0x828E +#define TAG_BATTERY_LEVEL 0x828F +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_IPTC_NAA 0x83BB +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTER_COLOR_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITIVITY 0x8824 +#define TAG_GPSINFO 0x8825 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_OECF 0x8828 +#define TAG_EXIF_VERSION 0x9000 +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_DATETIME_DIGITIZED 0x9004 +#define TAG_COMPONENTS_CONFIG 0x9101 +#define TAG_CPRS_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_METERING_MODE 0x9207 +#define TAG_LIGHT_SOURCE 0x9208 +#define TAG_FLASH 0x9209 +#define TAG_FOCALLENGTH 0x920A +#define TAG_MAKER_NOTE 0x927C +#define TAG_USERCOMMENT 0x9286 +#define TAG_SUBSEC_TIME 0x9290 +#define TAG_SUBSEC_TIME_ORIG 0x9291 +#define TAG_SUBSEC_TIME_DIG 0x9292 + +#define TAG_WINXP_TITLE 0x9c9b // Windows XP - not part of exif standard. +#define TAG_WINXP_COMMENT 0x9c9c // Windows XP - not part of exif standard. +#define TAG_WINXP_AUTHOR 0x9c9d // Windows XP - not part of exif standard. +#define TAG_WINXP_KEYWORDS 0x9c9e // Windows XP - not part of exif standard. +#define TAG_WINXP_SUBJECT 0x9c9f // Windows XP - not part of exif standard. + +#define TAG_FLASH_PIX_VERSION 0xA000 +#define TAG_COLOR_SPACE 0xA001 +#define TAG_EXIF_IMAGEWIDTH 0xA002 +#define TAG_EXIF_IMAGELENGTH 0xA003 +#define TAG_RELATED_AUDIO_FILE 0xA004 +#define TAG_INTEROP_OFFSET 0xA005 +#define TAG_FLASH_ENERGY 0xA20B +#define TAG_SPATIAL_FREQ_RESP 0xA20C +#define TAG_FOCAL_PLANE_XRES 0xA20E +#define TAG_FOCAL_PLANE_YRES 0xA20F +#define TAG_FOCAL_PLANE_UNITS 0xA210 +#define TAG_SUBJECT_LOCATION 0xA214 +#define TAG_EXPOSURE_INDEX 0xA215 +#define TAG_SENSING_METHOD 0xA217 +#define TAG_FILE_SOURCE 0xA300 +#define TAG_SCENE_TYPE 0xA301 +#define TAG_CFA_PATTERN 0xA302 +#define TAG_CUSTOM_RENDERED 0xA401 +#define TAG_EXPOSURE_MODE 0xA402 +#define TAG_WHITEBALANCE 0xA403 +#define TAG_DIGITALZOOMRATIO 0xA404 +#define TAG_FOCALLENGTH_35MM 0xA405 +#define TAG_SCENE_CAPTURE_TYPE 0xA406 +#define TAG_GAIN_CONTROL 0xA407 +#define TAG_CONTRAST 0xA408 +#define TAG_SATURATION 0xA409 +#define TAG_SHARPNESS 0xA40A +#define TAG_DISTANCE_RANGE 0xA40C + +static const TagTable_t TagTable[] = { + { 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_X_RESOLUTION, "XResolution"}, + { TAG_Y_RESOLUTION, "YResolution"}, + { TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"}, + { TAG_RESOLUTION_UNIT, "ResolutionUnit"}, + { 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_EXIF_IMAGEWIDTH, "ExifImageWidth"}, + { TAG_EXIF_IMAGELENGTH, "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_SCENE_CAPTURE_TYPE, "SceneCaptureType"}, + { TAG_GAIN_CONTROL, "GainControl"}, + { TAG_CONTRAST, "Contrast"}, + { TAG_SATURATION, "Saturation"}, + { TAG_SHARPNESS, "Sharpness"}, + { TAG_DISTANCE_RANGE, "SubjectDistanceRange"}, +} ; + +#define TAG_TABLE_SIZE (int)(sizeof(TagTable) / sizeof(TagTable_t)) + + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value to file's native byte order +//-------------------------------------------------------------------------- +static void Put16u(void * Short, unsigned short PutValue) +{ + if (MotorolaOrder){ + ((uchar *)Short)[0] = (uchar)(PutValue>>8); + ((uchar *)Short)[1] = (uchar)PutValue; + }else{ + ((uchar *)Short)[0] = (uchar)PutValue; + ((uchar *)Short)[1] = (uchar)(PutValue>>8); + } +} + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +int 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 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 to file's native byte order +//-------------------------------------------------------------------------- +void Put32u(void * Value, unsigned PutValue) +{ + if (MotorolaOrder){ + ((uchar *)Value)[0] = (uchar)(PutValue>>24); + ((uchar *)Value)[1] = (uchar)(PutValue>>16); + ((uchar *)Value)[2] = (uchar)(PutValue>>8); + ((uchar *)Value)[3] = (uchar)PutValue; + }else{ + ((uchar *)Value)[0] = (uchar)PutValue; + ((uchar *)Value)[1] = (uchar)(PutValue>>8); + ((uchar *)Value)[2] = (uchar)(PutValue>>16); + ((uchar *)Value)[3] = (uchar)(PutValue>>24); + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +unsigned Get32u(void * Long) +{ + return (unsigned)Get32s(Long) & 0xffffffff; +} + +//-------------------------------------------------------------------------- +// Display a number as one of its many formats +//-------------------------------------------------------------------------- +void 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: + 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 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{ + Value = (double)Num/Den; + } + break; + } + + case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + + // Not sure if this is correct (never seen float used in Exif format) + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + + default: + ErrNonfatal("Illegal format code %d",Format,0); + } + return Value; +} + +//-------------------------------------------------------------------------- +// Process one of the nested EXIF directories. +//-------------------------------------------------------------------------- +static void 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){ + ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0); + 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{ + ErrNonfatal("Illegally sized exif subdirectory (%d entries)",NumDirEntries,0); + return; + } + } + if (DumpExifMap){ + printf("Map: %05d-%05d: Directory\n",(int)(DirStart-OffsetBase), (int)(DirEnd+4-OffsetBase)); + } + + + } + + if (ShowTags){ + printf("(dir has %d entries)\n",NumDirEntries); + } + + for (de=0;de= NUM_FORMATS) { + // (-1) catches illegal zero case as unsigned underflows to positive large. + ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); + continue; + } + + if ((unsigned)Components > 0x10000){ + ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag); + continue; + } + + ByteCount = Components * BytesPerFormat[Format]; + + if (ByteCount > 4){ + unsigned OffsetVal; + OffsetVal = Get32u(DirEntry+8); + // If its bigger than 4 bytes, the dir entry contains an offset. + if (OffsetVal+ByteCount > ExifLength){ + // Bogus pointer offset and / or bytecount value + ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); + continue; + } + ValuePtr = OffsetBase+OffsetVal; + + if (OffsetVal > ImageInfo.LargestExifOffset){ + ImageInfo.LargestExifOffset = OffsetVal; + } + + if (DumpExifMap){ + printf("Map: %05d-%05d: 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; + } + + 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(IndentString); + printf(" Unknown Tag %04x Value = ", Tag); + break; + } + if (TagTable[a].Tag == Tag){ + printf(IndentString); + printf(" %s = ",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= 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"); + } + } + + // Extract useful components of tag + switch(Tag){ + + 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(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){ + ErrNonfatal("More than %d date fields! This is nuts", MAX_DATE_COPIES, 0); + 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.CommentWidchars = 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 + if (memcmp(ValuePtr, "ASCII",5) == 0){ + for (a=5;a<10;a++){ + int c; + c = (ValuePtr)[a]; + if (c != '\0' && c != ' '){ + strncpy(ImageInfo.Comments, (char *)ValuePtr+a, 199); + break; + } + } + }else{ + strncpy(ImageInfo.Comments, (char *)ValuePtr, MAX_COMMENT_SIZE-1); + } + 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)*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.0))); + } + 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 stringae. + ErrNonfatal("More than two orientation tags!",0,0); + break; + } + OrientationPtr[NumOrientations] = ValuePtr; + OrientationNumFormat[NumOrientations] = Format; + if (NumOrientations == 0){ + ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + } + if (ImageInfo.Orientation < 0 || ImageInfo.Orientation > 8){ + ErrNonfatal("Undefined rotation value %d", ImageInfo.Orientation, 0); + ImageInfo.Orientation = 0; + } + NumOrientations += 1; + break; + + case TAG_EXIF_IMAGELENGTH: + case TAG_EXIF_IMAGEWIDTH: + // Use largest of height and width to deal with images that have been + // rotated to portrait format. + a = (int)ConvertAnyFormat(ValuePtr, Format); + if (ImageInfo.ExifImageWidth < a) ImageInfo.ExifImageWidth = a; + break; + + case TAG_FOCAL_PLANE_XRES: + ImageInfo.FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCAL_PLANE_UNITS: + switch((int)ConvertAnyFormat(ValuePtr, Format)){ + case 1: ImageInfo.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. + ImageInfo.FocalplaneUnits = 25.4; + break; + + case 3: ImageInfo.FocalplaneUnits = 10; break; // centimeter + case 4: ImageInfo.FocalplaneUnits = 1; break; // millimeter + case 5: ImageInfo.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); + if ( ImageInfo.ISOequivalent < 50 ){ + // Fixes strange encoding on some older digicams. + ImageInfo.ISOequivalent *= 200; + } + 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){ + ErrNonfatal("Illegal exif or interop ofset directory link",0,0); + }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){ + ErrNonfatal("Illegal GPS directory link",0,0); + }else{ + ProcessGpsInfo(SubdirStart, ByteCount, 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; + } + } + + + { + // 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{ + ErrNonfatal("Illegal subdirectory link",0,0); + } + }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: %d bytes\n",ThumbnailSize); + } + } + } +} + + +//-------------------------------------------------------------------------- +// Process a EXIF marker +// Describes all the drivel that most digital cameras include... +//-------------------------------------------------------------------------- +void process_EXIF (unsigned char * ExifSection, unsigned int length) +{ + int FirstOffset; + + ImageInfo.FocalplaneXRes = 0; + ImageInfo.FocalplaneUnits = 0; + ImageInfo.ExifImageWidth = 0; + NumOrientations = 0; + + if (ShowTags){ + printf("Exif header %d bytes long\n",length); + } + + { // Check the EXIF header component + static uchar ExifHeader[] = "Exif\0\0"; + if (memcmp(ExifSection+2, ExifHeader,6)){ + ErrNonfatal("Incorrect Exif header",0,0); + 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{ + ErrNonfatal("Invalid Exif alignment marker.",0,0); + return; + } + } + + // Check the next value for correctness. + if (Get16u(ExifSection+10) != 0x2a){ + ErrNonfatal("Invalid Exif start (1)",0,0); + return; + } + + FirstOffset = Get32u(ExifSection+12); + if (FirstOffset < 8 || FirstOffset > 16){ + // Usually set to 8, but other values valid too. + ErrNonfatal("Suspicious offset of first IFD value",0,0); + } + + DirWithThumbnailPtrs = NULL; + + + // First directory starts 16 bytes in. All offset are relative to 8 bytes in. + ProcessExifDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0); + + 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> 8); + Buffer[1] = (unsigned char)DataWriteIndex; + + // Remove old exif section, if there was one. + RemoveSectionType(M_EXIF); + + { + // Sections need malloced buffers, so do that now, especially because + // we now know how big it needs to be allocated. + unsigned char * NewBuf = (unsigned char *)malloc(DataWriteIndex); + if (NewBuf == NULL){ + ErrFatal("Could not allocate memory"); + } + memcpy(NewBuf, Buffer, DataWriteIndex); + + CreateSection(M_EXIF, NewBuf, DataWriteIndex); + + // Re-parse new exif section, now that its in place + // otherwise, we risk touching data that has already been freed. + process_EXIF(NewBuf, DataWriteIndex); + } +} + +//-------------------------------------------------------------------------- +// Cler the rotation tag in the exif header to 1. +//-------------------------------------------------------------------------- +const char * ClearOrientation(void) +{ + int a; + if (NumOrientations == 0) return NULL; + + for (a=0;atm_wday = -1; + + // Check for format: YYYY:MM:DD HH:MM:SS format. + // Date and time normally separated by a space, but also seen a ':' there, so + // skip the middle space with '%*c' so it can be any character. + a = sscanf(ExifTime, "%d%*c%d%*c%d%*c%d:%d:%d", + &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday, + &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec); + + + if (a == 6){ + timeptr->tm_isdst = -1; + timeptr->tm_mon -= 1; // Adjust for unix zero-based months + timeptr->tm_year -= 1900; // Adjust for year starting at 1900 + return TRUE; // worked. + } + + return FALSE; // Wasn't in Exif date format. +} + + +//-------------------------------------------------------------------------- +// Show the collected image info, displaying camera F-stop and shutter speed +// in a consistent and legible fashion. +//-------------------------------------------------------------------------- +void ShowImageInfo(int ShowFileInfo) +{ + if (ShowFileInfo){ + printf("File name : %s\n",ImageInfo.FileName); + printf("File size : %d bytes\n",ImageInfo.FileSize); + + { + char Temp[20]; + FileTimeAsString(Temp); + printf("File date : %s\n",Temp); + } + } + + if (ImageInfo.CameraMake[0]){ + printf("Camera make : %s\n",ImageInfo.CameraMake); + printf("Camera model : %s\n",ImageInfo.CameraModel); + } + if (ImageInfo.DateTime[0]){ + printf("Date/Time : %s\n",ImageInfo.DateTime); + } + printf("Resolution : %d x %d\n",ImageInfo.Width, ImageInfo.Height); + + if (ImageInfo.Orientation > 1){ + // Only print orientation if one was supplied, and if its not 1 (normal orientation) + printf("Orientation : %s\n", OrientTab[ImageInfo.Orientation]); + } + + if (ImageInfo.IsColor == 0){ + printf("Color/bw : Black and white\n"); + } + + if (ImageInfo.FlashUsed >= 0){ + if (ImageInfo.FlashUsed & 1){ + printf("Flash used : Yes"); + switch (ImageInfo.FlashUsed){ + case 0x5: printf(" (Strobe light not detected)"); break; + case 0x7: printf(" (Strobe light detected) "); break; + case 0x9: printf(" (manual)"); break; + case 0xd: printf(" (manual, return light not detected)"); break; + case 0xf: printf(" (manual, return light detected)"); break; + case 0x19:printf(" (auto)"); break; + case 0x1d:printf(" (auto, return light not detected)"); break; + case 0x1f:printf(" (auto, return light detected)"); break; + case 0x41:printf(" (red eye reduction mode)"); break; + case 0x45:printf(" (red eye reduction mode return light not detected)"); break; + case 0x47:printf(" (red eye reduction mode return light detected)"); break; + case 0x49:printf(" (manual, red eye reduction mode)"); break; + case 0x4d:printf(" (manual, red eye reduction mode, return light not detected)"); break; + case 0x4f:printf(" (red eye reduction mode, return light detected)"); break; + case 0x59:printf(" (auto, red eye reduction mode)"); break; + case 0x5d:printf(" (auto, red eye reduction mode, return light not detected)"); break; + case 0x5f:printf(" (auto, red eye reduction mode, return light detected)"); break; + } + }else{ + printf("Flash used : No"); + switch (ImageInfo.FlashUsed){ + case 0x18:printf(" (auto)"); break; + } + } + printf("\n"); + } + + + if (ImageInfo.FocalLength){ + printf("Focal length : %4.1fmm",(double)ImageInfo.FocalLength); + if (ImageInfo.FocalLength35mmEquiv){ + printf(" (35mm equivalent: %dmm)", ImageInfo.FocalLength35mmEquiv); + } + printf("\n"); + } + + if (ImageInfo.DigitalZoomRatio > 1){ + // Digital zoom used. Shame on you! + printf("Digital Zoom : %1.3fx\n", (double)ImageInfo.DigitalZoomRatio); + } + + if (ImageInfo.CCDWidth){ + printf("CCD width : %4.2fmm\n",(double)ImageInfo.CCDWidth); + } + + if (ImageInfo.ExposureTime){ + if (ImageInfo.ExposureTime < 0.010){ + printf("Exposure time: %6.4f s ",(double)ImageInfo.ExposureTime); + }else{ + printf("Exposure time: %5.3f s ",(double)ImageInfo.ExposureTime); + } + if (ImageInfo.ExposureTime <= 0.5){ + printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime)); + } + printf("\n"); + } + if (ImageInfo.ApertureFNumber){ + printf("Aperture : f/%3.1f\n",(double)ImageInfo.ApertureFNumber); + } + if (ImageInfo.Distance){ + if (ImageInfo.Distance < 0){ + printf("Focus dist. : Infinite\n"); + }else{ + printf("Focus dist. : %4.2fm\n",(double)ImageInfo.Distance); + } + } + + if (ImageInfo.ISOequivalent){ + printf("ISO equiv. : %2d\n",(int)ImageInfo.ISOequivalent); + } + + if (ImageInfo.ExposureBias){ + // If exposure bias was specified, but set to zero, presumably its no bias at all, + // so only show it if its nonzero. + printf("Exposure bias: %4.2f\n",(double)ImageInfo.ExposureBias); + } + + switch(ImageInfo.Whitebalance) { + case 1: + printf("Whitebalance : Manual\n"); + break; + case 0: + printf("Whitebalance : Auto\n"); + break; + } + + //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both + switch(ImageInfo.LightSource) { + case 1: + printf("Light Source : Daylight\n"); + break; + case 2: + printf("Light Source : Fluorescent\n"); + break; + case 3: + printf("Light Source : Incandescent\n"); + break; + case 4: + printf("Light Source : Flash\n"); + break; + case 9: + printf("Light Source : Fine weather\n"); + break; + case 11: + printf("Light Source : Shade\n"); + break; + default:; //Quercus: 17-1-2004 There are many more modes for this, check Exif2.2 specs + // If it just says 'unknown' or we don't know it, then + // don't bother showing it - it doesn't add any useful information. + } + + if (ImageInfo.MeteringMode){ // 05-jan-2001 vcs + switch(ImageInfo.MeteringMode) { + case 2: + printf("Metering Mode: center weight\n"); + break; + case 3: + printf("Metering Mode: spot\n"); + break; + case 5: + printf("Metering Mode: matrix\n"); + break; + } + } + + if (ImageInfo.ExposureProgram){ // 05-jan-2001 vcs + switch(ImageInfo.ExposureProgram) { + case 1: + printf("Exposure : Manual\n"); + break; + case 2: + printf("Exposure : program (auto)\n"); + break; + case 3: + printf("Exposure : aperture priority (semi-auto)\n"); + break; + case 4: + printf("Exposure : shutter priority (semi-auto)\n"); + break; + case 5: + printf("Exposure : Creative Program (based towards depth of field)\n"); + break; + case 6: + printf("Exposure : Action program (based towards fast shutter speed)\n"); + break; + case 7: + printf("Exposure : Portrait Mode\n"); + break; + case 8: + printf("Exposure : LandscapeMode \n"); + break; + default: + break; + } + } + switch(ImageInfo.ExposureMode){ + case 0: // Automatic (not worth cluttering up output for) + break; + case 1: printf("Exposure Mode: Manual\n"); + break; + case 2: printf("Exposure Mode: Auto bracketing\n"); + break; + } + + if (ImageInfo.DistanceRange) { + printf("Focus range : "); + switch(ImageInfo.DistanceRange) { + case 1: + printf("macro"); + break; + case 2: + printf("close"); + break; + case 3: + printf("distant"); + break; + } + printf("\n"); + } + + + + if (ImageInfo.Process != M_SOF0){ + // don't show it if its the plain old boring 'baseline' process, but do + // show it if its something else, like 'progressive' (used on web sometimes) + int a; + for (a=0;;a++){ + if (a >= PROCESS_TABLE_SIZE){ + // ran off the end of the table. + printf("Jpeg process : Unknown\n"); + break; + } + if (ProcessTable[a].Tag == ImageInfo.Process){ + printf("Jpeg process : %s\n",ProcessTable[a].Desc); + break; + } + } + } + + if (ImageInfo.GpsInfoPresent){ + printf("GPS Latitude : %s\n",ImageInfo.GpsLat); + printf("GPS Longitude: %s\n",ImageInfo.GpsLong); + if (ImageInfo.GpsAlt[0]) printf("GPS Altitude : %s\n",ImageInfo.GpsAlt); + } + + // Print the comment. Print 'Comment:' for each new line of comment. + if (ImageInfo.Comments[0]){ + int a,c; + printf("Comment : "); + if (!ImageInfo.CommentWidchars){ + for (a=0;a= 0 && ImageInfo.FlashUsed & 1){ + printf(" (flash)"); + } + + if (ImageInfo.IsColor == 0){ + printf(" (bw)"); + } + + printf("\n"); +} diff --git a/wrap/Exif/src/gpsinfo.cpp b/wrap/Exif/src/gpsinfo.cpp new file mode 100644 index 00000000..04b07456 --- /dev/null +++ b/wrap/Exif/src/gpsinfo.cpp @@ -0,0 +1,217 @@ +//-------------------------------------------------------------------------- +// Parsing of GPS info from exif header. +// +// Matthias Wandel, Dec 1999 - Dec 2002 +//-------------------------------------------------------------------------- +#include "../include/Exif/jhead.h" + +#define MAX_GPS_TAG 0x1e + + +#define TAG_GPS_LAT_REF 1 +#define TAG_GPS_LAT 2 +#define TAG_GPS_LONG_REF 3 +#define TAG_GPS_LONG 4 +#define TAG_GPS_ALT_REF 5 +#define TAG_GPS_ALT 6 + + +static const char * GpsTags[MAX_GPS_TAG+1]= { + "VersionID ",//0x00 + "LatitudeRef ",//0x01 + "Latitude ",//0x02 + "LongitudeRef ",//0x03 + "Longitude ",//0x04 + "AltitudeRef ",//0x05 + "Altitude ",//0x06 + "TimeStamp ",//0x07 + "Satellites ",//0x08 + "Status ",//0x09 + "MeasureMode ",//0x0A + "DOP ",//0x0B + "SpeedRef ",//0x0C + "Speed ",//0x0D + "TrackRef ",//0x0E + "Track ",//0x0F + "ImgDirectionRef ",//0x10 + "ImgDirection ",//0x11 + "MapDatum ",//0x12 + "DestLatitudeRef ",//0x13 + "DestLatitude ",//0x14 + "DestLongitudeRef",//0x15 + "DestLongitude ",//0x16 + "DestBearingRef ",//0x17 + "DestBearing ",//0x18 + "DestDistanceRef ",//0x19 + "DestDistance ",//0x1A + "ProcessingMethod",//0x1B + "AreaInformation ",//0x1C + "DateStamp ",//0x1D + "Differential ",//0x1E +}; + +//-------------------------------------------------------------------------- +// Process GPS info directory +//-------------------------------------------------------------------------- +void ProcessGpsInfo(unsigned char * DirStart, int ByteCountUnused, unsigned char * OffsetBase, unsigned ExifLength) +{ + int de; + unsigned a; + int NumDirEntries; + + NumDirEntries = Get16u(DirStart); + #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) + + if (ShowTags){ + printf("(dir has %d entries)\n",NumDirEntries); + } + + ImageInfo.GpsInfoPresent = TRUE; + strcpy(ImageInfo.GpsLat, "? ?"); + strcpy(ImageInfo.GpsLong, "? ?"); + ImageInfo.GpsAlt[0] = 0; + + for (de=0;de OffsetBase+ExifLength){ + ErrNonfatal("GPS info directory goes past end of exif",0,0); + return; + } + + 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. + ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag); + continue; + } + + ComponentSize = BytesPerFormat[Format]; + ByteCount = Components * ComponentSize; + + 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 + ErrNonfatal("Illegal value pointer for tag %04x", Tag,0); + continue; + } + ValuePtr = OffsetBase+OffsetVal; + }else{ + // 4 bytes or less and value is in the dir entry itself + ValuePtr = DirEntry+8; + } + + switch(Tag){ + char FmtString[21]; + char TempString[50]; + double Values[3]; + + case TAG_GPS_LAT_REF: + ImageInfo.GpsLat[0] = ValuePtr[0]; + break; + + case TAG_GPS_LONG_REF: + ImageInfo.GpsLong[0] = ValuePtr[0]; + break; + + case TAG_GPS_LAT: + case TAG_GPS_LONG: + if (Format != FMT_URATIONAL){ + ErrNonfatal("Inappropriate format (%d) for GPS coordinates!", Format, 0); + } + strcpy(FmtString, "%0.0fd %0.0fm %0.0fs"); + for (a=0;a<3;a++){ + int den, digits; + + den = Get32s(ValuePtr+4+a*ComponentSize); + digits = 0; + while (den > 1 && digits <= 6){ + den = den / 10; + digits += 1; + } + if (digits > 6) digits = 6; + FmtString[1+a*7] = (char)('2'+digits+(digits ? 1 : 0)); + FmtString[3+a*7] = (char)('0'+digits); + + Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format); + } + + sprintf(TempString, FmtString, Values[0], Values[1], Values[2]); + + if (Tag == TAG_GPS_LAT){ + strncpy(ImageInfo.GpsLat+2, TempString, 29); + }else{ + strncpy(ImageInfo.GpsLong+2, TempString, 29); + } + break; + + case TAG_GPS_ALT_REF: + ImageInfo.GpsAlt[0] = (char)(ValuePtr[0] ? '-' : ' '); + break; + + case TAG_GPS_ALT: + sprintf(ImageInfo.GpsAlt + 1, "%.2fm", + ConvertAnyFormat(ValuePtr, Format)); + break; + } + + if (ShowTags){ + // Show tag value. + if (Tag < MAX_GPS_TAG){ + printf(" GPS%s =", GpsTags[Tag]); + }else{ + // Show unknown tag + printf(" Illegal GPS tag %04x=", Tag); + } + + switch(Format){ + case FMT_UNDEFINED: + // Undefined is typically an ascii string. + + case FMT_STRING: + // String arrays printed without function call (different from int arrays) + { + printf("\""); + for (a=0;a= 32){ + if (ZeroSkipped){ + printf("?"); + ZeroSkipped = 0; + } + putchar(ValuePtr[a]); + }else{ + if (ValuePtr[a] == 0){ + ZeroSkipped = 1; + } + } + } + printf("\"\n"); + } + break; + + default: + // Handle arrays of numbers later (will there ever be?) + for (a=0;;){ + PrintFormatNumber(ValuePtr+a*ComponentSize, Format, ByteCount); + if (++a >= Components) break; + printf(", "); + } + printf("\n"); + } + } + } +} + + diff --git a/wrap/Exif/src/iptc.cpp b/wrap/Exif/src/iptc.cpp new file mode 100644 index 00000000..f62cddda --- /dev/null +++ b/wrap/Exif/src/iptc.cpp @@ -0,0 +1,205 @@ +//-------------------------------------------------------------------------- +// Process IPTC data and XMP data. +//-------------------------------------------------------------------------- +#include "../include/Exif/jhead.h" + +// IPTC entry types known to Jhead (there's many more defined) +#define IPTC_RECORD_VERSION 0x00 +#define IPTC_SUPLEMENTAL_CATEGORIES 0x14 +#define IPTC_KEYWORDS 0x19 +#define IPTC_CAPTION 0x78 +#define IPTC_AUTHOR 0x7A +#define IPTC_HEADLINE 0x69 +#define IPTC_SPECIAL_INSTRUCTIONS 0x28 +#define IPTC_CATEGORY 0x0F +#define IPTC_BYLINE 0x50 +#define IPTC_BYLINE_TITLE 0x55 +#define IPTC_CREDIT 0x6E +#define IPTC_SOURCE 0x73 +#define IPTC_COPYRIGHT_NOTICE 0x74 +#define IPTC_OBJECT_NAME 0x05 +#define IPTC_CITY 0x5A +#define IPTC_STATE 0x5F +#define IPTC_COUNTRY 0x65 +#define IPTC_TRANSMISSION_REFERENCE 0x67 +#define IPTC_DATE 0x37 +#define IPTC_COPYRIGHT 0x0A +#define IPTC_COUNTRY_CODE 0x64 +#define IPTC_REFERENCE_SERVICE 0x2D +#define IPTC_TIME_CREATED 0x3C +#define IPTC_SUB_LOCATION 0x5C +#define IPTC_IMAGE_TYPE 0x82 + +//-------------------------------------------------------------------------- +// Process and display IPTC marker. +// +// IPTC block consists of: +// - Marker: 1 byte (0xED) +// - Block length: 2 bytes +// - IPTC Signature: 14 bytes ("Photoshop 3.0\0") +// - 8BIM Signature 4 bytes ("8BIM") +// - IPTC Block start 2 bytes (0x04, 0x04) +// - IPTC Header length 1 byte +// - IPTC header Header is padded to even length, counting the length byte +// - Length 4 bytes +// - IPTC Data which consists of a number of entries, each of which has the following format: +// - Signature 2 bytes (0x1C02) +// - Entry type 1 byte (for defined entry types, see #defines above) +// - entry length 2 bytes +// - entry data 'entry length' bytes +// +//-------------------------------------------------------------------------- +void show_IPTC (unsigned char* Data, unsigned int itemlen) +{ + const char IptcSig1[] = "Photoshop 3.0"; + const char IptcSig2[] = "8BIM"; + const char IptcSig3[] = {0x04, 0x04}; + + unsigned char * pos = Data + sizeof(short); // position data pointer after length field + unsigned char * maxpos = Data+itemlen; + char headerLen = 0; + + if (itemlen < 25) goto corrupt; + + // Check IPTC signatures + if (memcmp(pos, IptcSig1, sizeof(IptcSig1)-1) != 0) goto badsig; + pos += sizeof(IptcSig1); // move data pointer to the next field + + if (memcmp(pos, IptcSig2, sizeof(IptcSig2)-1) != 0) goto badsig; + pos += sizeof(IptcSig2)-1; // move data pointer to the next field + + if (memcmp(pos, IptcSig3, sizeof(IptcSig3)) != 0){ +badsig: + if (ShowTags){ + ErrNonfatal("IPTC type signature mismatch\n",0,0); + } + return; + } + pos += sizeof(IptcSig3); // move data pointer to the next field + + if (pos >= maxpos) goto corrupt; + + // IPTC section found + + // Skip header + headerLen = *pos++; // get header length and move data pointer to the next field + pos += headerLen + 1 - (headerLen % 2); // move data pointer to the next field (Header is padded to even length, counting the length byte) + + if (pos+4 >= maxpos) goto corrupt; + + // Get length (from motorola format) + //length = (*pos << 24) | (*(pos+1) << 16) | (*(pos+2) << 8) | *(pos+3); + + pos += 4; // move data pointer to the next field + + printf("======= IPTC data: =======\n"); + + // Now read IPTC data + while (pos < (Data + itemlen-5)) { + short signature; + unsigned char type = 0; + short length = 0; + char * description = NULL; + + if (pos+5 > maxpos) goto corrupt; + + signature = (*pos << 8) + (*(pos+1)); + pos += 2; + + if (signature != 0x1C02){ + break; + } + + type = *pos++; + length = (*pos << 8) + (*(pos+1)); + pos += 2; // Skip tag length + + if (pos+length > maxpos) goto corrupt; + // Process tag here + switch (type) { + case IPTC_RECORD_VERSION: + printf("Record vers. : %d\n", (*pos << 8) + (*(pos+1))); + break; + + case IPTC_SUPLEMENTAL_CATEGORIES: description = (char*)"SuplementalCategories"; break; + case IPTC_KEYWORDS: description = (char*)"Keywords"; break; + case IPTC_CAPTION: description = (char*)"Caption"; break; + case IPTC_AUTHOR: description = (char*)"Author"; break; + case IPTC_HEADLINE: description = (char*)"Headline"; break; + case IPTC_SPECIAL_INSTRUCTIONS: description = (char*)"Spec. Instr."; break; + case IPTC_CATEGORY: description = (char*)"Category"; break; + case IPTC_BYLINE: description = (char*)"Byline"; break; + case IPTC_BYLINE_TITLE: description = (char*)"Byline Title"; break; + case IPTC_CREDIT: description = (char*)"Credit"; break; + case IPTC_SOURCE: description = (char*)"Source"; break; + case IPTC_COPYRIGHT_NOTICE: description = (char*)"(C)Notice"; break; + case IPTC_OBJECT_NAME: description = (char*)"Object Name"; break; + case IPTC_CITY: description = (char*)"City"; break; + case IPTC_STATE: description = (char*)"State"; break; + case IPTC_COUNTRY: description = (char*)"Country"; break; + case IPTC_TRANSMISSION_REFERENCE: description = (char*)"OriginalTransmissionReference"; break; + case IPTC_DATE: description = (char*)"DateCreated"; break; + case IPTC_COPYRIGHT: description = (char*)"(C)Flag"; break; + case IPTC_REFERENCE_SERVICE: description = (char*)"Country Code"; break; + case IPTC_COUNTRY_CODE: description = (char*)"Ref. Service"; break; + case IPTC_TIME_CREATED: description = (char*)"Time Created"; break; + case IPTC_SUB_LOCATION: description = (char*)"Sub Location"; break; + case IPTC_IMAGE_TYPE: description = (char*)"Image type"; break; + + default: + if (ShowTags){ + printf("Unrecognised IPTC tag: %d\n", type ); + } + break; + } + if (description != NULL) { + char TempBuf[32]; + memset(TempBuf, 0, sizeof(TempBuf)); + memset(TempBuf, ' ', 14); + memcpy(TempBuf, description, strlen(description)); + strcat(TempBuf, ":"); + printf("%s %*.*s\n", TempBuf, length, length, pos); + } + pos += length; + } + return; +corrupt: + ErrNonfatal("Pointer corruption in IPTC\n",0,0); +} + + + +//-------------------------------------------------------------------------- +// Dump contents of XMP section +//-------------------------------------------------------------------------- +void ShowXmp(Section_t XmpSection) +{ + unsigned char * Data; + char OutLine[101]; + int OutLineChars; + int NonBlank; + unsigned a; + NonBlank = 0; + Data = XmpSection.Data; + OutLineChars = 0; + + + for (a=0;a= 32 && Data[a] < 128){ + OutLine[OutLineChars++] = Data[a]; + if (Data[a] != ' ') NonBlank |= 1; + }else{ + if (Data[a] != '\n'){ + OutLine[OutLineChars++] = '?'; + } + } + if (Data[a] == '\n' || OutLineChars >= 100){ + OutLine[OutLineChars] = 0; + if (NonBlank){ + puts(OutLine); + } + NonBlank = (NonBlank & 1) << 1; + OutLineChars = 0; + } + } +} diff --git a/wrap/Exif/src/jhead.cpp b/wrap/Exif/src/jhead.cpp new file mode 100644 index 00000000..a63aac23 --- /dev/null +++ b/wrap/Exif/src/jhead.cpp @@ -0,0 +1,1558 @@ +//-------------------------------------------------------------------------- +// Program to pull the information out of various types of EXIF digital +// camera files and show it in a reasonably consistent way +// +// Version 2.86 +// +// Compiling under Windows: +// Make sure you have Microsoft's compiler on the path, then run make.bat +// +// Dec 1999 - Mar 2009 +// +// by Matthias Wandel www.sentex.net/~mwandel +//-------------------------------------------------------------------------- +#include "../include/Exif/jhead.h" + +#include + +#define JHEAD_VERSION "2.87" + +// This #define turns on features that are too very specific to +// how I organize my photos. Best to ignore everything inside #ifdef MATTHIAS +//#define MATTHIAS + +#ifdef _WIN32 + #include +#endif + +static int FilesMatched; +static int FileSequence; + +static const char * CurrentFile; + +//static const char * progname; // program name for error messages + +//-------------------------------------------------------------------------- +// Command line options flags +static int TrimExif = FALSE; // Cut off exif beyond interesting data. +static int RenameToDate = 0; // 1=rename, 2=rename all. +#ifdef _WIN32 +static int RenameAssociatedFiles = FALSE; +#endif +static char * strftime_args = NULL; // Format for new file name. +static int Exif2FileTime = FALSE; +static int DoModify = FALSE; +static int DoReadAction = FALSE; + int ShowTags = FALSE; // Do not show raw by default. +static int Quiet = FALSE; // Be quiet on success (like unix programs) + int DumpExifMap = FALSE; +static int ShowConcise = FALSE; +static int CreateExifSection = FALSE; +static char * ApplyCommand = NULL; // Apply this command to all images. +static char * FilterModel = NULL; +static int ExifOnly = FALSE; +static int PortraitOnly = FALSE; +static time_t ExifTimeAdjust = 0; // Timezone adjust +static time_t ExifTimeSet = 0; // Set exif time to a value. +static char DateSet[11]; +static unsigned DateSetChars = 0; +static unsigned FileTimeToExif = FALSE; + +static int DeleteComments = FALSE; +static int DeleteExif = FALSE; +static int DeleteIptc = FALSE; +static int DeleteXmp = FALSE; +static int DeleteUnknown = FALSE; +static char * ThumbSaveName = NULL; // If not NULL, use this string to make up + // the filename to store the thumbnail to. + +static char * ThumbInsertName = NULL; // If not NULL, use this string to make up + // the filename to retrieve the thumbnail from. + +static int RegenThumbnail = FALSE; + +static char * ExifXferScrFile = NULL;// Extract Exif header from this file, and + // put it into the Jpegs processed. + +static int EditComment = FALSE; // Invoke an editor for editing the comment +static int SupressNonFatalErrors = FALSE; // Wether or not to pint warnings on recoverable errors + +static char * CommentSavefileName = NULL; // Save comment to this file. +static char * CommentInsertfileName = NULL; // Insert comment from this file. +static char * CommentInsertLiteral = NULL; // Insert this comment (from command line) + +static int AutoRotate = FALSE; +static int ZeroRotateTagOnly = FALSE; + +static int ShowFileInfo = TRUE; // Indicates to show standard file info + // (file name, file size, file date) + + +#ifdef MATTHIAS + // This #ifdef to take out less than elegant stuff for editing + // the comments in a JPEG. The programs rdjpgcom and wrjpgcom + // included with Linux distributions do a better job. + + static char * AddComment = NULL; // Add this tag. + static char * RemComment = NULL; // Remove this tag + static int AutoResize = FALSE; +#endif // MATTHIAS + +//-------------------------------------------------------------------------- +// Error exit handler +//-------------------------------------------------------------------------- +void ErrFatal(const char * msg) +{ + fprintf(stderr,"\nError : %s\n", msg); + if (CurrentFile) fprintf(stderr,"in file '%s'\n",CurrentFile); + exit(EXIT_FAILURE); +} + +//-------------------------------------------------------------------------- +// Report non fatal errors. Now that microsoft.net modifies exif headers, +// there's corrupted ones, and there could be more in the future. +//-------------------------------------------------------------------------- +void ErrNonfatal(const char * msg, int a1, int a2) +{ + if (SupressNonFatalErrors) return; + + fprintf(stderr,"\nNonfatal Error : "); + if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile); + fprintf(stderr, msg, a1, a2); + fprintf(stderr, "\n"); +} + + +//-------------------------------------------------------------------------- +// Invoke an editor for editing a string. +//-------------------------------------------------------------------------- +static int FileEditComment(char * TempFileName, char * Comment, int CommentSize) +{ + FILE * file; + int a; + char QuotedPath[PATH_MAX+10]; + + file = fopen(TempFileName, "w"); + if (file == NULL){ + fprintf(stderr, "Can't create file '%s'\n",TempFileName); + ErrFatal("could not create temporary file"); + } + fwrite(Comment, CommentSize, 1, file); + + fclose(file); + + fflush(stdout); // So logs are contiguous. + + { + char * Editor; + Editor = getenv("EDITOR"); + if (Editor == NULL){ +#ifdef _WIN32 + Editor = (char*)"notepad"; +#else + Editor = "vi"; +#endif + } + if (strlen(Editor) > PATH_MAX) ErrFatal("env too long"); + + sprintf(QuotedPath, "%s \"%s\"",Editor, TempFileName); + a = system(QuotedPath); + } + + if (a != 0){ + perror("Editor failed to launch"); + exit(-1); + } + + file = fopen(TempFileName, "r"); + if (file == NULL){ + ErrFatal("could not open temp file for read"); + } + + // Read the file back in. + CommentSize = fread(Comment, 1, 999, file); + + fclose(file); + + unlink(TempFileName); + + return CommentSize; +} + +//-------------------------------------------------------------------------- +// Escape an argument such that it is interpreted literally by the shell +// (returns the number of written characters) +//-------------------------------------------------------------------------- +static int shellescape(char* to, const char* from) +{ + int i, j; + i = j = 0; + + // Enclosing characters in double quotes preserves the literal value of + // all characters within the quotes, with the exception of $, `, and \. + to[j++] = '"'; + while(from[i]) + { +#ifdef _WIN32 + // Under WIN32, there isn't really anything dangerous you can do with + // escape characters, plus windows users aren't as sercurity paranoid. + // Hence, no need to do fancy escaping. + to[j++] = from[i++]; +#else + switch(from[i]) { + case '"': + case '$': + case '`': + case '\\': + to[j++] = '\\'; + // Fallthru... + default: + to[j++] = from[i++]; + } +#endif + if (j >= PATH_MAX) ErrFatal("max path exceeded"); + } + to[j++] = '"'; + return j; +} + + +//-------------------------------------------------------------------------- +// Apply the specified command to the JPEG file. +//-------------------------------------------------------------------------- +static void DoCommand(const char * FileName, int ShowIt) +{ + int a,e; + char ExecString[PATH_MAX*3]; + char TempName[PATH_MAX+10]; + int TempUsed = FALSE; + + e = 0; + + // Generate an unused temporary file name in the destination directory + // (a is the number of characters to copy from FileName) + a = strlen(FileName)-1; + while(a > 0 && FileName[a-1] != SLASH) a--; + memcpy(TempName, FileName, a); + strcpy(TempName+a, "XXXXXX"); + mktemp(TempName); + if(!TempName[0]) { + ErrFatal("Cannot find available temporary file name"); + } + + + + // Build the exec string. &i and &o in the exec string get replaced by input and output files. + for (a=0;;a++){ + if (ApplyCommand[a] == '&'){ + if (ApplyCommand[a+1] == 'i'){ + // Input file. + e += shellescape(ExecString+e, FileName); + a += 1; + continue; + } + if (ApplyCommand[a+1] == 'o'){ + // Needs an output file distinct from the input file. + e += shellescape(ExecString+e, TempName); + a += 1; + TempUsed = TRUE; + continue; + } + } + ExecString[e++] = ApplyCommand[a]; + if (ApplyCommand[a] == 0) break; + } + + if (ShowIt) printf("Cmd:%s\n",ExecString); + + errno = 0; + a = system(ExecString); + + if (a || errno){ + // A command can however fail without errno getting set or system returning an error. + if (errno) perror("system"); + ErrFatal("Problem executing specified command"); + } + + if (TempUsed){ + // Don't delete original file until we know a new one was created by the command. + struct stat dummy; + if (stat(TempName, &dummy) == 0){ + unlink(FileName); + rename(TempName, FileName); + }else{ + ErrFatal("specified command did not produce expected output file"); + } + } +} + +//-------------------------------------------------------------------------- +// check if this file should be skipped based on contents. +//-------------------------------------------------------------------------- +static int CheckFileSkip(void) +{ + // I sometimes add code here to only process images based on certain + // criteria - for example, only to convert non progressive Jpegs to progressives, etc.. + + if (FilterModel){ + // Filtering processing by camera model. + // This feature is useful when pictures from multiple cameras are colated, + // the its found that one of the cameras has the time set incorrectly. + if (strstr(ImageInfo.CameraModel, FilterModel) == NULL){ + // Skip. + return TRUE; + } + } + + if (ExifOnly){ + // Filtering by EXIF only. Skip all files that have no Exif. + if (FindSection(M_EXIF) == NULL){ + return TRUE; + } + } + + if (PortraitOnly == 1){ + if (ImageInfo.Width > ImageInfo.Height) return TRUE; + } + + if (PortraitOnly == -1){ + if (ImageInfo.Width < ImageInfo.Height) return TRUE; + } + + return FALSE; +} + +//-------------------------------------------------------------------------- +// Subsititute original name for '&i' if present in specified name. +// This to allow specifying relative names when manipulating multiple files. +//-------------------------------------------------------------------------- +static void RelativeName(char * OutFileName, const char * NamePattern, const char * OrigName) +{ + // If the filename contains substring "&i", then substitute the + // filename for that. This gives flexibility in terms of processing + // multiple files at a time. + const char * Subst = strstr(NamePattern, "&i"); + if (Subst){ + strncpy(OutFileName, NamePattern, Subst-NamePattern); + OutFileName[Subst-NamePattern] = 0; + strncat(OutFileName, OrigName, PATH_MAX); + strncat(OutFileName, Subst+2, PATH_MAX); + }else{ + strncpy(OutFileName, NamePattern, PATH_MAX); + } +} + + +#ifdef _WIN32 +//-------------------------------------------------------------------------- +// Rename associated files +//-------------------------------------------------------------------------- +void RenameAssociated(const char * FileName, char * NewBaseName) +{ + int a; + int PathLen; + int ExtPos; + char FilePattern[_MAX_PATH+1]; + char NewName[_MAX_PATH+1]; + struct _finddata_t finddata; + long find_handle; + + for(ExtPos = strlen(FileName);FileName[ExtPos-1] != '.';){ + if (--ExtPos == 0) return; // No extension! + } + + memcpy(FilePattern, FileName, ExtPos); + FilePattern[ExtPos] = '*'; + FilePattern[ExtPos+1] = '\0'; + + for(PathLen = strlen(FileName);FileName[PathLen-1] != SLASH;){ + if (--PathLen == 0) break; + } + + find_handle = _findfirst(FilePattern, &finddata); + + for (;;){ + if (find_handle == -1) break; + + // Eliminate the obvious patterns. + if (!memcmp(finddata.name, ".",2)) goto next_file; + if (!memcmp(finddata.name, "..",3)) goto next_file; + if (finddata.attrib & _A_SUBDIR) goto next_file; + + strncpy(FilePattern+PathLen, finddata.name, PATH_MAX-PathLen); // full name with path + + strcpy(NewName, NewBaseName); + for(a = strlen(finddata.name);finddata.name[a] != '.';){ + if (--a == 0) goto next_file; + } + strncat(NewName, finddata.name+a, _MAX_PATH-strlen(NewName)); // add extension to new name + + if (rename(FilePattern, NewName) == 0){ + if (!Quiet){ + printf("%s --> %s\n",FilePattern, NewName); + } + } + + next_file: + if (_findnext(find_handle, &finddata) != 0) break; + } + _findclose(find_handle); +} +#endif + +//-------------------------------------------------------------------------- +// Handle renaming of files by date. +//-------------------------------------------------------------------------- +static void DoFileRenaming(const char * FileName) +{ + int NumAlpha = 0; + int NumDigit = 0; + int PrefixPart = 0; // Where the actual filename starts. + int ExtensionPart; // Where the file extension starts. + int a; + struct tm tm; + char NewBaseName[PATH_MAX*2]; + int AddLetter = 0; + char NewName[PATH_MAX+2]; + + ExtensionPart = strlen(FileName); + for (a=0;FileName[a];a++){ + if (FileName[a] == SLASH){ + // Don't count path component. + NumAlpha = 0; + NumDigit = 0; + PrefixPart = a+1; + } + + if (FileName[a] == '.') ExtensionPart = a; // Remember where extension starts. + + if (isalpha(FileName[a])) NumAlpha += 1; // Tally up alpha vs. digits to judge wether to rename. + if (isdigit(FileName[a])) NumDigit += 1; + } + + if (RenameToDate <= 1){ + // If naming isn't forced, ensure name is mostly digits, or leave it alone. + if (NumAlpha > 8 || NumDigit < 4){ + return; + } + } + + if (!Exif2tm(&tm, ImageInfo.DateTime)){ + printf("File '%s' contains no exif date stamp. Using file date\n",FileName); + // Use file date/time instead. + tm = *localtime(&ImageInfo.FileDateTime); + } + + + strncpy(NewBaseName, FileName, PATH_MAX); // Get path component of name. + + if (strftime_args){ + // Complicated scheme for flexibility. Just pass the args to strftime. + time_t UnixTime; + + char *s; + char pattern[PATH_MAX+20]; + int n = ExtensionPart - PrefixPart; + + // Call mktime to get weekday and such filled in. + UnixTime = mktime(&tm); + if ((int)UnixTime == -1){ + printf("Could not convert %s to unix time",ImageInfo.DateTime); + return; + } + + // Substitute "%f" for the original name (minus path & extension) + pattern[PATH_MAX-1]=0; + strncpy(pattern, strftime_args, PATH_MAX-1); + while ((s = strstr(pattern, "%f")) && strlen(pattern) + n < PATH_MAX-1){ + memmove(s + n, s + 2, strlen(s+2) + 1); + memmove(s, FileName + PrefixPart, n); + } + + { + // Sequential number renaming part. + // '%i' type pattern becomes sequence number. + int ppos = -1; + for (a=0;pattern[a];a++){ + if (pattern[a] == '%'){ + ppos = a; + }else if (pattern[a] == 'i'){ + if (ppos >= 0 && a= PATH_MAX) ErrFatal("str overflow"); + memmove(pattern+ppos+nl, pattern+a+1, l+1); + memcpy(pattern+ppos, num, nl); + break; + } + }else if (!isdigit(pattern[a])){ + ppos = -1; + } + } + } + strftime(NewName, PATH_MAX, pattern, &tm); + }else{ + // My favourite scheme. + sprintf(NewName, "%02d%02d-%02d%02d%02d", + tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + } + + NewBaseName[PrefixPart] = 0; + CatPath(NewBaseName, NewName); + + AddLetter = isdigit(NewBaseName[strlen(NewBaseName)-1]); + for (a=0;;a++){ + char NewName[PATH_MAX+10]; + char NameExtra[3]; + struct stat dummy; + + if (a){ + // Generate a suffix for the file name if previous choice of names is taken. + // depending on wether the name ends in a letter or digit, pick the opposite to separate + // it. This to avoid using a separator character - this because any good separator + // is before the '.' in ascii, and so sorting the names would put the later name before + // the name without suffix, causing the pictures to more likely be out of order. + if (AddLetter){ + NameExtra[0] = (char)('a'-1+a); // Try a,b,c,d... for suffix if it ends in a number. + }else{ + NameExtra[0] = (char)('0'-1+a); // Try 0,1,2,3... for suffix if it ends in a latter. + } + NameExtra[1] = 0; + }else{ + NameExtra[0] = 0; + } + + sprintf(NewName, "%s%s.jpg", NewBaseName, NameExtra); + + if (!strcmp(FileName, NewName)) break; // Skip if its already this name. + + if (!EnsurePathExists(NewBaseName)){ + break; + } + + + if (stat(NewName, &dummy)){ + // This name does not pre-exist. + if (rename(FileName, NewName) == 0){ + printf("%s --> %s\n",FileName, NewName); +#ifdef _WIN32 + if (RenameAssociatedFiles){ + sprintf(NewName, "%s%s", NewBaseName, NameExtra); + RenameAssociated(FileName, NewName); + } +#endif + }else{ + printf("Error: Couldn't rename '%s' to '%s'\n",FileName, NewName); + } + break; + + } + + if (a > 25 || (!AddLetter && a > 9)){ + printf("Possible new names for for '%s' already exist\n",FileName); + break; + } + } +} + +//-------------------------------------------------------------------------- +// Rotate the image and its thumbnail +//-------------------------------------------------------------------------- +static int DoAutoRotate(const char * FileName) +{ + if (ImageInfo.Orientation >= 2 && ImageInfo.Orientation <= 8){ + const char * Argument; + Argument = ClearOrientation(); + + if (!ZeroRotateTagOnly){ + char RotateCommand[PATH_MAX*2+50]; + if (Argument == NULL){ + ErrFatal("Orientation screwup"); + } + + sprintf(RotateCommand, "jpegtran -trim -%s -outfile &o &i", Argument); + ApplyCommand = RotateCommand; + DoCommand(FileName, FALSE); + ApplyCommand = NULL; + + // Now rotate the thumbnail, if there is one. + if (ImageInfo.ThumbnailOffset && + ImageInfo.ThumbnailSize && + ImageInfo.ThumbnailAtEnd){ + // Must have a thumbnail that exists and is modifieable. + + char ThumbTempName_in[PATH_MAX+5]; + char ThumbTempName_out[PATH_MAX+5]; + + strcpy(ThumbTempName_in, FileName); + strcat(ThumbTempName_in, ".thi"); + strcpy(ThumbTempName_out, FileName); + strcat(ThumbTempName_out, ".tho"); + SaveThumbnail(ThumbTempName_in); + sprintf(RotateCommand,"jpegtran -trim -%s -outfile \"%s\" \"%s\"", + Argument, ThumbTempName_out, ThumbTempName_in); + + if (system(RotateCommand) == 0){ + // Put the thumbnail back in the header + ReplaceThumbnail(ThumbTempName_out); + } + + unlink(ThumbTempName_in); + unlink(ThumbTempName_out); + } + } + return TRUE; + } + return FALSE; +} + +//-------------------------------------------------------------------------- +// Regenerate the thumbnail using mogrify +//-------------------------------------------------------------------------- +static int RegenerateThumbnail(const char * FileName) +{ + char ThumbnailGenCommand[PATH_MAX*2+50]; + if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){ + // There is no thumbnail, or the thumbnail is not at the end. + return FALSE; + } + + sprintf(ThumbnailGenCommand, "mogrify -thumbnail %dx%d \"%s\"", + RegenThumbnail, RegenThumbnail, FileName); + + if (system(ThumbnailGenCommand) == 0){ + // Put the thumbnail back in the header + return ReplaceThumbnail(FileName); + }else{ + ErrFatal("Unable to run 'mogrify' command"); + return FALSE; + } +} + +//-------------------------------------------------------------------------- +// Set file time as exif time. +//-------------------------------------------------------------------------- +void FileTimeAsString(char * TimeStr) +{ + struct tm ts; + ts = *localtime(&ImageInfo.FileDateTime); + strftime(TimeStr, 20, "%Y:%m:%d %H:%M:%S", &ts); +} + +//-------------------------------------------------------------------------- +// Do selected operations to one file at a time. +//-------------------------------------------------------------------------- +void ProcessFile(const char * FileName) +{ + int Modified = FALSE; + ReadMode_t ReadMode; + + if (strlen(FileName) >= PATH_MAX-1){ + // Protect against buffer overruns in strcpy / strcat's on filename + ErrFatal("filename too long"); + } + + ReadMode = READ_METADATA; + CurrentFile = FileName; + FilesMatched = 1; + + ResetJpgfile(); + + // Start with an empty image information structure. + memset(&ImageInfo, 0, sizeof(ImageInfo)); + ImageInfo.FlashUsed = -1; + ImageInfo.MeteringMode = -1; + ImageInfo.Whitebalance = -1; + + // Store file date/time. + { + struct stat st; + if (stat(FileName, &st) >= 0){ + ImageInfo.FileDateTime = st.st_mtime; + ImageInfo.FileSize = st.st_size; + }else{ + ErrFatal("No such file"); + } + } + + if (DoModify || RenameToDate || Exif2FileTime){ + if (access(FileName, 2 /*W_OK*/)){ + printf("Skipping readonly file '%s'\n",FileName); + return; + } + } + + strncpy(ImageInfo.FileName, FileName, PATH_MAX); + + + if (ApplyCommand || AutoRotate){ + // Applying a command is special - the headers from the file have to be + // pre-read, then the command executed, and then the image part of the file read. + + if (!ReadJpegFile(FileName, READ_METADATA)) return; + + #ifdef MATTHIAS + if (AutoResize){ + // Automatic resize computation - to customize for each run... + if (AutoResizeCmdStuff() == 0){ + DiscardData(); + return; + } + } + #endif // MATTHIAS + + + if (CheckFileSkip()){ + DiscardData(); + return; + } + + DiscardAllButExif(); + + if (AutoRotate){ + if (DoAutoRotate(FileName)){ + Modified = TRUE; + } + }else{ + struct stat dummy; + DoCommand(FileName, Quiet ? FALSE : TRUE); + + if (stat(FileName, &dummy)){ + // The file is not there anymore. Perhaps the command + // was a delete or a move. So we are all done. + return; + } + Modified = TRUE; + } + ReadMode = READ_IMAGE; // Don't re-read exif section again on next read. + + }else if (ExifXferScrFile){ + char RelativeExifName[PATH_MAX+1]; + + // Make a relative name. + RelativeName(RelativeExifName, ExifXferScrFile, FileName); + + if(!ReadJpegFile(RelativeExifName, READ_METADATA)) return; + + DiscardAllButExif(); // Don't re-read exif section again on next read. + + Modified = TRUE; + ReadMode = READ_IMAGE; + } + + if (DoModify){ + ReadMode = ReadMode | READ_IMAGE; + } + + if (!ReadJpegFile(FileName, ReadMode)) return; + + if (CheckFileSkip()){ + DiscardData(); + return; + } + + FileSequence += 1; // Count files processed. + + if (ShowConcise){ + ShowConciseImageInfo(); + }else{ +//PB line changed, it was: if (!(DoModify || DoReadAction) || ShowTags){ + if (!(DoModify || DoReadAction) && ShowTags){ + ShowImageInfo(ShowFileInfo); + { + // if IPTC section is present, show it also. + Section_t * IptcSection; + IptcSection = FindSection(M_IPTC); + + if (IptcSection){ + show_IPTC(IptcSection->Data, IptcSection->Size); + } + } + printf("\n"); + } + } + + if (ThumbSaveName){ + char OutFileName[PATH_MAX+1]; + // Make a relative name. + RelativeName(OutFileName, ThumbSaveName, FileName); + + if (SaveThumbnail(OutFileName)){ + printf("Created: '%s'\n", OutFileName); + } + } + + if (CreateExifSection){ + // Make a new minimal exif section + create_EXIF(); + Modified = TRUE; + } + + if (RegenThumbnail){ + if (RegenerateThumbnail(FileName)){ + Modified = TRUE; + } + } + + if (ThumbInsertName){ + char ThumbFileName[PATH_MAX+1]; + // Make a relative name. + RelativeName(ThumbFileName, ThumbInsertName, FileName); + + if (ReplaceThumbnail(ThumbFileName)){ + Modified = TRUE; + } + }else if (TrimExif){ + // Deleting thumbnail is just replacing it with a null thumbnail. + if (ReplaceThumbnail(NULL)){ + Modified = TRUE; + } + } + + if ( + EditComment || CommentInsertfileName || CommentInsertLiteral){ + + Section_t * CommentSec; + char Comment[MAX_COMMENT_SIZE+1]; + int CommentSize; + + CommentSec = FindSection(M_COM); + + if (CommentSec == NULL){ + unsigned char * DummyData; + + DummyData = (uchar *) malloc(3); + DummyData[0] = 0; + DummyData[1] = 2; + DummyData[2] = 0; + CommentSec = CreateSection(M_COM, DummyData, 2); + } + + CommentSize = CommentSec->Size-2; + if (CommentSize > MAX_COMMENT_SIZE){ + fprintf(stderr, "Truncating comment at %d chars\n",MAX_COMMENT_SIZE); + CommentSize = MAX_COMMENT_SIZE; + } + + if (CommentInsertfileName){ + // Read a new comment section from file. + char CommentFileName[PATH_MAX+1]; + FILE * CommentFile; + + // Make a relative name. + RelativeName(CommentFileName, CommentInsertfileName, FileName); + + CommentFile = fopen(CommentFileName,"r"); + if (CommentFile == NULL){ + printf("Could not open '%s'\n",CommentFileName); + }else{ + // Read it in. + // Replace the section. + CommentSize = fread(Comment, 1, 999, CommentFile); + fclose(CommentFile); + if (CommentSize < 0) CommentSize = 0; + } + }else if (CommentInsertLiteral){ + strncpy(Comment, CommentInsertLiteral, MAX_COMMENT_SIZE); + CommentSize = strlen(Comment); + }else{ + memcpy(Comment, (char *)CommentSec->Data+2, CommentSize); + char EditFileName[PATH_MAX+5]; + strcpy(EditFileName, FileName); + strcat(EditFileName, ".txt"); + + CommentSize = FileEditComment(EditFileName, Comment, CommentSize); + } + + if (strcmp(Comment, (char *)CommentSec->Data+2)){ + // Discard old comment section and put a new one in. + int size; + size = CommentSize+2; + free(CommentSec->Data); + CommentSec->Size = size; + CommentSec->Data = (uchar*)malloc(size); + CommentSec->Data[0] = (uchar)(size >> 8); + CommentSec->Data[1] = (uchar)(size); + memcpy((CommentSec->Data)+2, Comment, size-2); + Modified = TRUE; + } + if (!Modified){ + printf("Comment not modified\n"); + } + } + + + if (CommentSavefileName){ + Section_t * CommentSec; + CommentSec = FindSection(M_COM); + + if (CommentSec != NULL){ + char OutFileName[PATH_MAX+1]; + FILE * CommentFile; + + // Make a relative name. + RelativeName(OutFileName, CommentSavefileName, FileName); + + CommentFile = fopen(OutFileName,"w"); + + if (CommentFile){ + fwrite((char *)CommentSec->Data+2, CommentSec->Size-2 ,1, CommentFile); + fclose(CommentFile); + }else{ + ErrFatal("Could not write comment file"); + } + }else{ + printf("File '%s' contains no comment section\n",FileName); + } + } + + if (ExifTimeAdjust || ExifTimeSet || DateSetChars || FileTimeToExif){ + if (ImageInfo.numDateTimeTags){ + struct tm tm; + time_t UnixTime; + char TempBuf[50]; + int a; + Section_t * ExifSection; + if (ExifTimeSet){ + // A time to set was specified. + UnixTime = ExifTimeSet; + }else{ + if (FileTimeToExif){ + FileTimeAsString(ImageInfo.DateTime); + } + if (DateSetChars){ + memcpy(ImageInfo.DateTime, DateSet, DateSetChars); + a = 1970; + sscanf(DateSet, "%d", &a); + if (a < 1970){ + strcpy(TempBuf, ImageInfo.DateTime); + goto skip_unixtime; + } + } + // A time offset to adjust by was specified. + if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime; + + // Convert to unix 32 bit time value, add offset, and convert back. + UnixTime = mktime(&tm); + if ((int)UnixTime == -1) goto badtime; + UnixTime += ExifTimeAdjust; + } + tm = *localtime(&UnixTime); + + // Print to temp buffer first to avoid putting null termination in destination. + // snprintf() would do the trick, hbut not available everywhere (like FreeBSD 4.4) + sprintf(TempBuf, "%04d:%02d:%02d %02d:%02d:%02d", + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + +skip_unixtime: + ExifSection = FindSection(M_EXIF); + + for (a = 0; a < ImageInfo.numDateTimeTags; a++) { + uchar * Pointer; + Pointer = ExifSection->Data+ImageInfo.DateTimeOffsets[a]+8; + memcpy(Pointer, TempBuf, 19); + } + memcpy(ImageInfo.DateTime, TempBuf, 19); + + Modified = TRUE; + }else{ + printf("File '%s' contains no Exif timestamp to change\n", FileName); + } + } + + if (DeleteComments){ + if (RemoveSectionType(M_COM)) Modified = TRUE; + } + if (DeleteExif){ + if (RemoveSectionType(M_EXIF)) Modified = TRUE; + } + if (DeleteIptc){ + if (RemoveSectionType(M_IPTC)) Modified = TRUE; + } + if (DeleteXmp){ + if (RemoveSectionType(M_XMP)) Modified = TRUE; + } + if (DeleteUnknown){ + if (RemoveUnknownSections()) Modified = TRUE; + } + + + if (Modified){ + char BackupName[PATH_MAX+5]; + struct stat buf; + + if (!Quiet) printf("Modified: %s\n",FileName); + + strcpy(BackupName, FileName); + strcat(BackupName, ".t"); + + // Remove any .old file name that may pre-exist + unlink(BackupName); + + // Rename the old file. + rename(FileName, BackupName); + + // Write the new file. + WriteJpegFile(FileName); + + // Copy the access rights from original file + if (stat(BackupName, &buf) == 0){ + // set Unix access rights and time to new file + struct utimbuf mtime; + chmod(FileName, buf.st_mode); + + mtime.actime = buf.st_mtime; + mtime.modtime = buf.st_mtime; + + utime(FileName, &mtime); + } + + // Now that we are done, remove original file. + unlink(BackupName); + } + + + if (Exif2FileTime){ + // Set the file date to the date from the exif header. + if (ImageInfo.numDateTimeTags){ + // Converte the file date to Unix time. + struct tm tm; + time_t UnixTime; + struct utimbuf mtime; + if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime; + + UnixTime = mktime(&tm); + if ((int)UnixTime == -1){ + goto badtime; + } + + mtime.actime = UnixTime; + mtime.modtime = UnixTime; + + if (utime(FileName, &mtime) != 0){ + printf("Error: Could not change time of file '%s'\n",FileName); + }else{ + if (!Quiet) printf("%s\n",FileName); + } + }else{ + printf("File '%s' contains no Exif timestamp\n", FileName); + } + } + + // Feature to rename image according to date and time from camera. + // I use this feature to put images from multiple digicams in sequence. + + if (RenameToDate){ + DoFileRenaming(FileName); + } +//PB DiscardData(); + return; +badtime: + printf("Error: Time '%s': cannot convert to Unix time\n",ImageInfo.DateTime); + DiscardData(); +} + +#if 0 //disabled because it is never called +//-------------------------------------------------------------------------- +// complain about bad state of the command line. +//-------------------------------------------------------------------------- +static void Usage (void) +{ + printf("Jhead is a program for manipulating settings and thumnails in Exif jpeg headers\n" + "used by most Digital Cameras. v"JHEAD_VERSION" Matthias Wandel, Mar 02 2009.\n" + "http://www.sentex.net/~mwandel/jhead\n" + "\n"); + + printf("Usage: %s [options] files\n", progname); + printf("Where:\n" + " files path/filenames with or without wildcards\n" + + "[options] are:\n" + "\nGENERAL METADATA:\n" + " -te Transfer exif header from another image file \n" + " Uses same name mangling as '-st' option\n" + " -dc Delete comment field (as left by progs like Photoshop & Compupic)\n" + " -de Strip Exif section (smaller JPEG file, but lose digicam info)\n" + " -di Delete IPTC section (from Photoshop, or Picasa)\n" + " -dx Deletex XMP section\n" + " -du Delete non image sections except for Exif and comment sections\n" + " -purejpg Strip all unnecessary data from jpeg (combines -dc -de and -du)\n" + " -mkexif Create new minimal exif section (overwrites pre-existing exif)\n" + " -ce Edit comment field. Uses environment variable 'editor' to\n" + " determine which editor to use. If editor not set, uses VI\n" + " under Unix and notepad with windows\n" + " -cs Save comment section to a file\n" + " -ci Insert comment section from a file. -cs and -ci use same naming\n" + " scheme as used by the -st option\n" + " -cl string Insert literal comment string\n" + + "\nDATE / TIME MANIPULATION:\n" + " -ft Set file modification time to Exif time\n" + " -dsft Set Exif time to file modification time\n" + " -n[format-string]\n" + " Rename files according to date. Uses exif date if present, file\n" + " date otherwise. If the optional format-string is not supplied,\n" + " the format is mmdd-hhmmss. If a format-string is given, it is\n" + " is passed to the 'strftime' function for formatting\n" + " In addition to strftime format codes:\n" + " '%%f' as part of the string will include the original file name\n" + " '%%i' will include a sequence number, starting from 1. You can\n" + " You can specify '%%03i' for example to get leading zeros.\n" + " This feature is useful for ordering files from multiple digicams to\n" + " sequence of taking. Only renames files whose names are mostly\n" + " numerical (as assigned by digicam)\n" + " The '.jpg' is automatically added to the end of the name. If the\n" + " destination name already exists, a letter or digit is added to \n" + " the end of the name to make it unique.\n" + " The new name may include a path as part of the name. If this path\n" + " does not exist, it will be created\n" + " -nf[format-string]\n" + " Same as -n, but rename regardless of original name\n" + " -a (Windows only) Rename files with same name but different extension\n" + " Use together with -n to rename .AVI files from exif in .THM files\n" + " for example\n" + " -ta<+|->h[:mm[:ss]]\n" + " Adjust time by h:mm backwards or forwards. Useful when having\n" + " taken pictures with the wrong time set on the camera, such as when\n" + " traveling across time zones or DST changes. Dates can be adjusted\n" + " by offsetting by 24 hours or more. For large date adjustments,\n" + " use the -da option\n" + " -da-\n" + " Adjust date by large amounts. This is used to fix photos from\n" + " cameras where the date got set back to the default camera date\n" + " by accident or battery removal.\n" + " To deal with different months and years having different numbers of\n" + " days, a simple date-month-year offset would result in unexpected\n" + " results. Instead, the difference is specified as desired date\n" + " minus original date. Date is specified as yyyy:mm:dd or as date\n" + " and time in the format yyyy:mm:dd/hh:mm:ss\n" + " -ts