#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