added bundler importer (and small library for Exif reader)
This commit is contained in:
parent
d256abfaaf
commit
1a1844cb34
wrap
|
@ -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.
|
|
@ -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
|
||||||
|
}
|
|
@ -0,0 +1,915 @@
|
||||||
|
#ifndef EXIF_H
|
||||||
|
#define EXIF_H
|
||||||
|
|
||||||
|
#include <vcg/math/camera.h>
|
||||||
|
#include <vector>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
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<T> vcgCamera() const {
|
||||||
|
vcg::Camera<T> 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<Section> 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<length ; a++){
|
||||||
|
uchar ch = data[a];
|
||||||
|
if( (ch=='\r') && (data[a+1]=='\n') ) continue; //remove cr followed by lf
|
||||||
|
Comment[nch++] = ( (ch>=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<NumDirEntries ; de++){
|
||||||
|
int Tag, Format, Components;
|
||||||
|
uchar *ValuePtr;
|
||||||
|
int ByteCount;
|
||||||
|
uchar *DirEntry;
|
||||||
|
DirEntry = DIR_ENTRY_ADDR(DirStart, de);
|
||||||
|
|
||||||
|
Tag = Get16u(DirEntry);
|
||||||
|
Format = Get16u(DirEntry+2);
|
||||||
|
Components = Get32u(DirEntry+4);
|
||||||
|
|
||||||
|
if((Format-1) >= NUM_FORMATS){
|
||||||
|
// (-1) catches illegal zero case as unsigned underflows to positive large.
|
||||||
|
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<NumDirEntries ; de++){
|
||||||
|
int Tag, Format, Components;
|
||||||
|
uchar *DirEntry;
|
||||||
|
DirEntry = DIR_ENTRY_ADDR(DirStart, de);
|
||||||
|
|
||||||
|
Tag = Get16u(DirEntry);
|
||||||
|
Format = Get16u(DirEntry+2);
|
||||||
|
Components = Get32u(DirEntry+4);
|
||||||
|
|
||||||
|
if((Format-1) >= NUM_FORMATS){
|
||||||
|
// (-1) catches illegal zero case as unsigned underflows to positive large.
|
||||||
|
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
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <sys/utime.h>
|
||||||
|
#else
|
||||||
|
#include <utime.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#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
|
File diff suppressed because it is too large
Load Diff
|
@ -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<NumDirEntries;de++){
|
||||||
|
unsigned Tag, Format, Components;
|
||||||
|
unsigned char * ValuePtr;
|
||||||
|
int ComponentSize;
|
||||||
|
unsigned ByteCount;
|
||||||
|
unsigned char * DirEntry;
|
||||||
|
DirEntry = DIR_ENTRY_ADDR(DirStart, de);
|
||||||
|
|
||||||
|
if (DirEntry+12 > 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<ByteCount;a++){
|
||||||
|
int ZeroSkipped = 0;
|
||||||
|
if (ValuePtr[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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<XmpSection.Size;a++){
|
||||||
|
if (Data[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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,669 @@
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Program to pull the information out of various types of EXIF digital
|
||||||
|
// camera files and show it in a reasonably consistent way
|
||||||
|
//
|
||||||
|
// This module handles basic Jpeg file handling
|
||||||
|
//
|
||||||
|
// Matthias Wandel
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
#include "../include/Exif/jhead.h"
|
||||||
|
|
||||||
|
// Storage for simplified info extracted from file.
|
||||||
|
ImageInfo_t ImageInfo;
|
||||||
|
|
||||||
|
|
||||||
|
static Section_t * Sections = NULL;
|
||||||
|
static int SectionsAllocated;
|
||||||
|
static int SectionsRead;
|
||||||
|
static int HaveAll;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define PSEUDO_IMAGE_MARKER 0x123; // Extra value.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Get 16 bits motorola order (always) for jpeg header stuff.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
static int Get16m(const void * Short)
|
||||||
|
{
|
||||||
|
return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Process a COM marker.
|
||||||
|
// We want to print out the marker contents as legible text;
|
||||||
|
// we must guard against random junk and varying newline representations.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
static void process_COM (const uchar * Data, int length)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
char Comment[MAX_COMMENT_SIZE+1];
|
||||||
|
int nch;
|
||||||
|
int a;
|
||||||
|
|
||||||
|
nch = 0;
|
||||||
|
|
||||||
|
if (length > MAX_COMMENT_SIZE) length = MAX_COMMENT_SIZE; // Truncate if it won't fit in our structure.
|
||||||
|
|
||||||
|
for (a=2;a<length;a++){
|
||||||
|
ch = Data[a];
|
||||||
|
|
||||||
|
if (ch == '\r' && Data[a+1] == '\n') continue; // Remove cr followed by lf.
|
||||||
|
|
||||||
|
if (ch >= 32 || ch == '\n' || ch == '\t'){
|
||||||
|
Comment[nch++] = (char)ch;
|
||||||
|
}else{
|
||||||
|
Comment[nch++] = '?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Comment[nch] = '\0'; // Null terminate
|
||||||
|
|
||||||
|
if (ShowTags){
|
||||||
|
printf("COM marker comment: %s\n",Comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(ImageInfo.Comments,Comment);
|
||||||
|
ImageInfo.CommentWidchars = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Process a SOFn marker. This is useful for the image dimensions
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
static void process_SOFn (const uchar * Data, int marker)
|
||||||
|
{
|
||||||
|
int data_precision, num_components;
|
||||||
|
|
||||||
|
data_precision = Data[2];
|
||||||
|
ImageInfo.Height = Get16m(Data+3);
|
||||||
|
ImageInfo.Width = Get16m(Data+5);
|
||||||
|
num_components = Data[7];
|
||||||
|
|
||||||
|
if (num_components == 3){
|
||||||
|
ImageInfo.IsColor = 1;
|
||||||
|
}else{
|
||||||
|
ImageInfo.IsColor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInfo.Process = marker;
|
||||||
|
|
||||||
|
if (ShowTags){
|
||||||
|
printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",
|
||||||
|
ImageInfo.Width, ImageInfo.Height, num_components, data_precision);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Check sections array to see if it needs to be increased in size.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void CheckSectionsAllocated(void)
|
||||||
|
{
|
||||||
|
if (SectionsRead > SectionsAllocated){
|
||||||
|
ErrFatal("allocation screwup");
|
||||||
|
}
|
||||||
|
if (SectionsRead >= SectionsAllocated){
|
||||||
|
SectionsAllocated += SectionsAllocated/2;
|
||||||
|
Sections = (Section_t *)realloc(Sections, sizeof(Section_t)*SectionsAllocated);
|
||||||
|
if (Sections == NULL){
|
||||||
|
ErrFatal("could not allocate data for entire image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Parse the marker stream until SOS or EOI is seen;
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int ReadJpegSections (FILE * infile, ReadMode_t ReadMode)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
int HaveCom = FALSE;
|
||||||
|
|
||||||
|
a = fgetc(infile);
|
||||||
|
|
||||||
|
if (a != 0xff || fgetc(infile) != M_SOI){
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
for(;;){
|
||||||
|
int itemlen;
|
||||||
|
int marker = 0;
|
||||||
|
int ll,lh, got;
|
||||||
|
uchar * Data;
|
||||||
|
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
|
||||||
|
for (a=0;a<=16;a++){
|
||||||
|
marker = fgetc(infile);
|
||||||
|
if (marker != 0xff) break;
|
||||||
|
|
||||||
|
if (a >= 16){
|
||||||
|
fprintf(stderr,"too many padding bytes\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Sections[SectionsRead].Type = marker;
|
||||||
|
|
||||||
|
// Read the length of the section.
|
||||||
|
lh = fgetc(infile);
|
||||||
|
ll = fgetc(infile);
|
||||||
|
|
||||||
|
itemlen = (lh << 8) | ll;
|
||||||
|
|
||||||
|
if (itemlen < 2){
|
||||||
|
ErrFatal("invalid marker");
|
||||||
|
}
|
||||||
|
|
||||||
|
Sections[SectionsRead].Size = itemlen;
|
||||||
|
|
||||||
|
Data = (uchar *)malloc(itemlen);
|
||||||
|
if (Data == NULL){
|
||||||
|
ErrFatal("Could not allocate memory");
|
||||||
|
}
|
||||||
|
Sections[SectionsRead].Data = Data;
|
||||||
|
|
||||||
|
// Store first two pre-read bytes.
|
||||||
|
Data[0] = (uchar)lh;
|
||||||
|
Data[1] = (uchar)ll;
|
||||||
|
|
||||||
|
got = fread(Data+2, 1, itemlen-2, infile); // Read the whole section.
|
||||||
|
if (got != itemlen-2){
|
||||||
|
ErrFatal("Premature end of file?");
|
||||||
|
}
|
||||||
|
SectionsRead += 1;
|
||||||
|
|
||||||
|
switch(marker){
|
||||||
|
|
||||||
|
case M_SOS: // stop before hitting compressed data
|
||||||
|
// If reading entire image is requested, read the rest of the data.
|
||||||
|
if (ReadMode & READ_IMAGE){
|
||||||
|
int cp, ep, size;
|
||||||
|
// Determine how much file is left.
|
||||||
|
cp = ftell(infile);
|
||||||
|
fseek(infile, 0, SEEK_END);
|
||||||
|
ep = ftell(infile);
|
||||||
|
fseek(infile, cp, SEEK_SET);
|
||||||
|
|
||||||
|
size = ep-cp;
|
||||||
|
Data = (uchar *)malloc(size);
|
||||||
|
if (Data == NULL){
|
||||||
|
ErrFatal("could not allocate data for entire image");
|
||||||
|
}
|
||||||
|
|
||||||
|
got = fread(Data, 1, size, infile);
|
||||||
|
if (got != size){
|
||||||
|
ErrFatal("could not read the rest of the image");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
Sections[SectionsRead].Data = Data;
|
||||||
|
Sections[SectionsRead].Size = size;
|
||||||
|
Sections[SectionsRead].Type = PSEUDO_IMAGE_MARKER;
|
||||||
|
SectionsRead ++;
|
||||||
|
HaveAll = 1;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
case M_EOI: // in case it's a tables-only JPEG stream
|
||||||
|
fprintf(stderr,"No image in jpeg!\n");
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
case M_COM: // Comment section
|
||||||
|
if (HaveCom || ((ReadMode & READ_METADATA) == 0)){
|
||||||
|
// Discard this section.
|
||||||
|
free(Sections[--SectionsRead].Data);
|
||||||
|
}else{
|
||||||
|
process_COM(Data, itemlen);
|
||||||
|
HaveCom = TRUE;
|
||||||
|
}
|
||||||
|
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(Sections[--SectionsRead].Data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case M_EXIF:
|
||||||
|
// There can be different section using the same marker.
|
||||||
|
if (ReadMode & READ_METADATA){
|
||||||
|
if (memcmp(Data+2, "Exif", 4) == 0){
|
||||||
|
process_EXIF(Data, itemlen);
|
||||||
|
break;
|
||||||
|
}else if (memcmp(Data+2, "http:", 5) == 0){
|
||||||
|
Sections[SectionsRead-1].Type = M_XMP; // Change tag for internal purposes.
|
||||||
|
if (ShowTags){
|
||||||
|
printf("Image cotains XMP section, %d bytes long\n", itemlen);
|
||||||
|
if (ShowTags){
|
||||||
|
ShowXmp(Sections[SectionsRead-1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Oterwise, discard this section.
|
||||||
|
free(Sections[--SectionsRead].Data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case M_IPTC:
|
||||||
|
if (ReadMode & READ_METADATA){
|
||||||
|
if (ShowTags){
|
||||||
|
printf("Image cotains IPTC section, %d bytes long\n", itemlen);
|
||||||
|
}
|
||||||
|
// Note: We just store the IPTC section. Its relatively straightforward
|
||||||
|
// and we don't act on any part of it, so just display it at parse time.
|
||||||
|
}else{
|
||||||
|
free(Sections[--SectionsRead].Data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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(Data, marker);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Skip any other sections.
|
||||||
|
if (ShowTags){
|
||||||
|
printf("Jpeg section marker 0x%02x size %d\n",marker, itemlen);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Discard read data.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void DiscardData(void)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
|
||||||
|
for (a=0;a<SectionsRead;a++){
|
||||||
|
free(Sections[a].Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(&ImageInfo, 0, sizeof(ImageInfo));
|
||||||
|
SectionsRead = 0;
|
||||||
|
HaveAll = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Read image data.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int ReadJpegFile(const char * FileName, ReadMode_t ReadMode)
|
||||||
|
{
|
||||||
|
FILE * infile;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
infile = fopen(FileName, "rb"); // Unix ignores 'b', windows needs it.
|
||||||
|
|
||||||
|
if (infile == NULL) {
|
||||||
|
fprintf(stderr, "can't open '%s'\n", FileName);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan the JPEG headers.
|
||||||
|
ret = ReadJpegSections(infile, ReadMode);
|
||||||
|
if (!ret){
|
||||||
|
fprintf(stderr,"Not JPEG: %s\n",FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(infile);
|
||||||
|
|
||||||
|
if (ret == FALSE){
|
||||||
|
DiscardData();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Replace or remove exif thumbnail
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int SaveThumbnail(char * ThumbFileName)
|
||||||
|
{
|
||||||
|
FILE * ThumbnailFile;
|
||||||
|
|
||||||
|
if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailSize == 0){
|
||||||
|
fprintf(stderr,"Image contains no thumbnail\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(ThumbFileName, "-") == 0){
|
||||||
|
// A filename of '-' indicates thumbnail goes to stdout.
|
||||||
|
// This doesn't make much sense under Windows, so this feature is unix only.
|
||||||
|
ThumbnailFile = stdout;
|
||||||
|
}else{
|
||||||
|
ThumbnailFile = fopen(ThumbFileName,"wb");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ThumbnailFile){
|
||||||
|
uchar * ThumbnailPointer;
|
||||||
|
Section_t * ExifSection;
|
||||||
|
ExifSection = FindSection(M_EXIF);
|
||||||
|
ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
|
||||||
|
|
||||||
|
fwrite(ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile);
|
||||||
|
fclose(ThumbnailFile);
|
||||||
|
return TRUE;
|
||||||
|
}else{
|
||||||
|
ErrFatal("Could not write thumbnail file");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Replace or remove exif thumbnail
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int ReplaceThumbnail(const char * ThumbFileName)
|
||||||
|
{
|
||||||
|
FILE * ThumbnailFile;
|
||||||
|
int ThumbLen, NewExifSize;
|
||||||
|
Section_t * ExifSection;
|
||||||
|
uchar * ThumbnailPointer;
|
||||||
|
|
||||||
|
if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){
|
||||||
|
if (ThumbFileName == NULL){
|
||||||
|
// Delete of nonexistent thumbnail (not even pointers present)
|
||||||
|
// No action, no error.
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding or removing of thumbnail is not possible - that would require rearranging
|
||||||
|
// of the exif header, which is risky, and jhad doesn't know how to do.
|
||||||
|
fprintf(stderr,"Image contains no thumbnail to replace - add is not possible\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ThumbFileName){
|
||||||
|
ThumbnailFile = fopen(ThumbFileName,"rb");
|
||||||
|
|
||||||
|
if (ThumbnailFile == NULL){
|
||||||
|
ErrFatal("Could not read thumbnail file");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get length
|
||||||
|
fseek(ThumbnailFile, 0, SEEK_END);
|
||||||
|
|
||||||
|
ThumbLen = ftell(ThumbnailFile);
|
||||||
|
fseek(ThumbnailFile, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (ThumbLen + ImageInfo.ThumbnailOffset > 0x10000-20){
|
||||||
|
ErrFatal("Thumbnail is too large to insert into exif header");
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (ImageInfo.ThumbnailSize == 0){
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThumbLen = 0;
|
||||||
|
ThumbnailFile = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExifSection = FindSection(M_EXIF);
|
||||||
|
|
||||||
|
NewExifSize = ImageInfo.ThumbnailOffset+8+ThumbLen;
|
||||||
|
ExifSection->Data = (uchar *)realloc(ExifSection->Data, NewExifSize);
|
||||||
|
|
||||||
|
ThumbnailPointer = ExifSection->Data+ImageInfo.ThumbnailOffset+8;
|
||||||
|
|
||||||
|
if (ThumbnailFile){
|
||||||
|
fread(ThumbnailPointer, ThumbLen, 1, ThumbnailFile);
|
||||||
|
fclose(ThumbnailFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageInfo.ThumbnailSize = ThumbLen;
|
||||||
|
|
||||||
|
Put32u(ExifSection->Data+ImageInfo.ThumbnailSizeOffset+8, ThumbLen);
|
||||||
|
|
||||||
|
ExifSection->Data[0] = (uchar)(NewExifSize >> 8);
|
||||||
|
ExifSection->Data[1] = (uchar)NewExifSize;
|
||||||
|
ExifSection->Size = NewExifSize;
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Discard everything but the exif and comment sections.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void DiscardAllButExif(void)
|
||||||
|
{
|
||||||
|
Section_t ExifKeeper;
|
||||||
|
Section_t CommentKeeper;
|
||||||
|
Section_t IptcKeeper;
|
||||||
|
Section_t XmpKeeper;
|
||||||
|
int a;
|
||||||
|
|
||||||
|
memset(&ExifKeeper, 0, sizeof(ExifKeeper));
|
||||||
|
memset(&CommentKeeper, 0, sizeof(CommentKeeper));
|
||||||
|
memset(&IptcKeeper, 0, sizeof(IptcKeeper));
|
||||||
|
memset(&XmpKeeper, 0, sizeof(IptcKeeper));
|
||||||
|
|
||||||
|
for (a=0;a<SectionsRead;a++){
|
||||||
|
if (Sections[a].Type == M_EXIF && ExifKeeper.Type == 0){
|
||||||
|
ExifKeeper = Sections[a];
|
||||||
|
}else if (Sections[a].Type == M_XMP && XmpKeeper.Type == 0){
|
||||||
|
XmpKeeper = Sections[a];
|
||||||
|
}else if (Sections[a].Type == M_COM && CommentKeeper.Type == 0){
|
||||||
|
CommentKeeper = Sections[a];
|
||||||
|
}else if (Sections[a].Type == M_IPTC && IptcKeeper.Type == 0){
|
||||||
|
IptcKeeper = Sections[a];
|
||||||
|
}else{
|
||||||
|
free(Sections[a].Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SectionsRead = 0;
|
||||||
|
if (ExifKeeper.Type){
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
Sections[SectionsRead++] = ExifKeeper;
|
||||||
|
}
|
||||||
|
if (CommentKeeper.Type){
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
Sections[SectionsRead++] = CommentKeeper;
|
||||||
|
}
|
||||||
|
if (IptcKeeper.Type){
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
Sections[SectionsRead++] = IptcKeeper;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (XmpKeeper.Type){
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
Sections[SectionsRead++] = XmpKeeper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Write image data back to disk.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void WriteJpegFile(const char * FileName)
|
||||||
|
{
|
||||||
|
FILE * outfile;
|
||||||
|
int a;
|
||||||
|
|
||||||
|
if (!HaveAll){
|
||||||
|
ErrFatal("Can't write back - didn't read all");
|
||||||
|
}
|
||||||
|
|
||||||
|
outfile = fopen(FileName,"wb");
|
||||||
|
if (outfile == NULL){
|
||||||
|
ErrFatal("Could not open file for write");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial static jpeg marker.
|
||||||
|
fputc(0xff,outfile);
|
||||||
|
fputc(0xd8,outfile);
|
||||||
|
|
||||||
|
if (Sections[0].Type != M_EXIF && Sections[0].Type != M_JFIF){
|
||||||
|
// The image must start with an exif or jfif marker. If we threw those away, create one.
|
||||||
|
static uchar JfifHead[18] = {
|
||||||
|
0xff, M_JFIF,
|
||||||
|
0x00, 0x10, 'J' , 'F' , 'I' , 'F' , 0x00, 0x01,
|
||||||
|
0x01, 0x01, 0x01, 0x2C, 0x01, 0x2C, 0x00, 0x00
|
||||||
|
};
|
||||||
|
fwrite(JfifHead, 18, 1, outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all the misc sections
|
||||||
|
for (a=0;a<SectionsRead-1;a++){
|
||||||
|
fputc(0xff,outfile);
|
||||||
|
fputc((unsigned char)Sections[a].Type, outfile);
|
||||||
|
fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the remaining image data.
|
||||||
|
fwrite(Sections[a].Data, Sections[a].Size, 1, outfile);
|
||||||
|
|
||||||
|
fclose(outfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Check if image has exif header.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
Section_t * FindSection(int SectionType)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
|
||||||
|
for (a=0;a<SectionsRead;a++){
|
||||||
|
if (Sections[a].Type == SectionType){
|
||||||
|
return &Sections[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Could not be found.
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Remove a certain type of section.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int RemoveSectionType(int SectionType)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
for (a=0;a<SectionsRead-1;a++){
|
||||||
|
if (Sections[a].Type == SectionType){
|
||||||
|
// Free up this section
|
||||||
|
free (Sections[a].Data);
|
||||||
|
// Move succeding sections back by one to close space in array.
|
||||||
|
memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
|
||||||
|
SectionsRead -= 1;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Remove sectons not part of image and not exif or comment sections.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
int RemoveUnknownSections(void)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
int Modified = FALSE;
|
||||||
|
for (a=0;a<SectionsRead-1;){
|
||||||
|
switch(Sections[a].Type){
|
||||||
|
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:
|
||||||
|
case M_SOI:
|
||||||
|
case M_EOI:
|
||||||
|
case M_SOS:
|
||||||
|
case M_JFIF:
|
||||||
|
case M_EXIF:
|
||||||
|
case M_XMP:
|
||||||
|
case M_COM:
|
||||||
|
case M_DQT:
|
||||||
|
case M_DHT:
|
||||||
|
case M_DRI:
|
||||||
|
case M_IPTC:
|
||||||
|
// keep.
|
||||||
|
a++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unknown. Delete.
|
||||||
|
free (Sections[a].Data);
|
||||||
|
// Move succeding sections back by one to close space in array.
|
||||||
|
memmove(Sections+a, Sections+a+1, sizeof(Section_t) * (SectionsRead-a));
|
||||||
|
SectionsRead -= 1;
|
||||||
|
Modified = TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Add a section (assume it doesn't already exist) - used for
|
||||||
|
// adding comment sections and exif sections
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
Section_t * CreateSection(int SectionType, unsigned char * Data, int Size)
|
||||||
|
{
|
||||||
|
Section_t * NewSection;
|
||||||
|
int a;
|
||||||
|
int NewIndex;
|
||||||
|
NewIndex = 2;
|
||||||
|
|
||||||
|
if (SectionType == M_EXIF) NewIndex = 0; // Exif alwas goes first!
|
||||||
|
|
||||||
|
// Insert it in third position - seems like a safe place to put
|
||||||
|
// things like comments.
|
||||||
|
|
||||||
|
if (SectionsRead < NewIndex){
|
||||||
|
ErrFatal("Too few sections!");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckSectionsAllocated();
|
||||||
|
for (a=SectionsRead;a>NewIndex;a--){
|
||||||
|
Sections[a] = Sections[a-1];
|
||||||
|
}
|
||||||
|
SectionsRead += 1;
|
||||||
|
|
||||||
|
NewSection = Sections+NewIndex;
|
||||||
|
|
||||||
|
NewSection->Type = SectionType;
|
||||||
|
NewSection->Size = Size;
|
||||||
|
NewSection->Data = Data;
|
||||||
|
|
||||||
|
return NewSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Initialisation.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void ResetJpgfile(void)
|
||||||
|
{
|
||||||
|
if (Sections == NULL){
|
||||||
|
Sections = (Section_t *)malloc(sizeof(Section_t)*5);
|
||||||
|
SectionsAllocated = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionsRead = 0;
|
||||||
|
HaveAll = 0;
|
||||||
|
}
|
|
@ -0,0 +1,184 @@
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Parse some maker specific onformation.
|
||||||
|
// (Very limited right now - add maker specific stuff to this module)
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
#include "../include/Exif/jhead.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Process exif format directory, as used by Cannon maker note
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void ProcessCanonMakerNoteDir(unsigned char * DirStart, unsigned char * OffsetBase,
|
||||||
|
unsigned ExifLength)
|
||||||
|
{
|
||||||
|
int de;
|
||||||
|
int a;
|
||||||
|
int NumDirEntries;
|
||||||
|
|
||||||
|
NumDirEntries = Get16u(DirStart);
|
||||||
|
#define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry))
|
||||||
|
|
||||||
|
{
|
||||||
|
unsigned char * DirEnd;
|
||||||
|
DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
|
||||||
|
if (DirEnd > (OffsetBase+ExifLength)){
|
||||||
|
ErrNonfatal("Illegally sized exif makernote subdir (%d entries)",NumDirEntries,0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DumpExifMap){
|
||||||
|
printf("Map: %05d-%05d: Directory (makernote)\n",(int)(DirStart-OffsetBase), (int)(DirEnd-OffsetBase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowTags){
|
||||||
|
printf("(dir has %d entries)\n",NumDirEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (de=0;de<NumDirEntries;de++){
|
||||||
|
int Tag, Format, Components;
|
||||||
|
unsigned char * ValuePtr;
|
||||||
|
int ByteCount;
|
||||||
|
unsigned char * DirEntry;
|
||||||
|
DirEntry = DIR_ENTRY_ADDR(DirStart, de);
|
||||||
|
|
||||||
|
Tag = Get16u(DirEntry);
|
||||||
|
Format = Get16u(DirEntry+2);
|
||||||
|
Components = Get32u(DirEntry+4);
|
||||||
|
|
||||||
|
if ((Format-1) >= NUM_FORMATS) {
|
||||||
|
// (-1) catches illegal zero case as unsigned underflows to positive large.
|
||||||
|
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 (DumpExifMap){
|
||||||
|
printf("Map: %05d-%05d: Data for makernote tag %04x\n",OffsetVal, OffsetVal+ByteCount, Tag);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// 4 bytes or less and value is in the dir entry itself
|
||||||
|
ValuePtr = DirEntry+8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ShowTags){
|
||||||
|
// Show tag name
|
||||||
|
printf(" Canon maker tag %04x Value = ", Tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show tag value.
|
||||||
|
switch(Format){
|
||||||
|
|
||||||
|
case FMT_UNDEFINED:
|
||||||
|
// Undefined is typically an ascii string.
|
||||||
|
|
||||||
|
case FMT_STRING:
|
||||||
|
// String arrays printed without function call (different from int arrays)
|
||||||
|
if (ShowTags){
|
||||||
|
printf("\"");
|
||||||
|
for (a=0;a<ByteCount;a++){
|
||||||
|
int ZeroSkipped = 0;
|
||||||
|
if (ValuePtr[a] >= 32){
|
||||||
|
if (ZeroSkipped){
|
||||||
|
printf("?");
|
||||||
|
ZeroSkipped = 0;
|
||||||
|
}
|
||||||
|
putchar(ValuePtr[a]);
|
||||||
|
}else{
|
||||||
|
if (ValuePtr[a] == 0){
|
||||||
|
ZeroSkipped = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("\"\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (ShowTags){
|
||||||
|
PrintFormatNumber(ValuePtr, Format, ByteCount);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
if (temp_dist != 65535){
|
||||||
|
ImageInfo.Distance = (float)temp_dist/100;
|
||||||
|
}else{
|
||||||
|
ImageInfo.Distance = -1 /* infinity */;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Show generic maker note - just hex bytes.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void ShowMakerNoteGeneric(unsigned char * ValuePtr, int ByteCount)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
for (a=0;a<ByteCount;a++){
|
||||||
|
if (a > 10){
|
||||||
|
printf("...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
printf(" %02x",ValuePtr[a]);
|
||||||
|
}
|
||||||
|
printf(" (%d bytes)", ByteCount);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
// Process maker note - to the limited extent that its supported.
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
void ProcessMakerNote(unsigned char * ValuePtr, int ByteCount,
|
||||||
|
unsigned char * OffsetBase, unsigned ExifLength)
|
||||||
|
{
|
||||||
|
if (strstr(ImageInfo.CameraMake, "Canon")){
|
||||||
|
ProcessCanonMakerNoteDir(ValuePtr, OffsetBase, ExifLength);
|
||||||
|
}else{
|
||||||
|
if (ShowTags){
|
||||||
|
ShowMakerNoteGeneric(ValuePtr, ByteCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,305 @@
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Module to do recursive directory file matching under windows.
|
||||||
|
//
|
||||||
|
// Tries to do pattern matching to produce similar results as Unix, but using
|
||||||
|
// the Windows _findfirst to do all the pattern matching.
|
||||||
|
//
|
||||||
|
// Also hadles recursive directories - "**" path component expands into
|
||||||
|
// any levels of subdirectores (ie c:\**\*.c matches ALL .c files on drive c:)
|
||||||
|
//
|
||||||
|
// Matthias Wandel Nov 5 2000
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <io.h>
|
||||||
|
#include "../include/Exif/jhead.h"
|
||||||
|
|
||||||
|
#define TRUE 1
|
||||||
|
#define FALSE 0
|
||||||
|
|
||||||
|
//#define DEBUGGING
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char * Name;
|
||||||
|
int attrib;
|
||||||
|
}FileEntry;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Dummy function to show operation.
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
void ShowName(const char * FileName)
|
||||||
|
{
|
||||||
|
printf(" %s\n",FileName);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Simple path splicing (assumes no '\' in either part)
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
static void SplicePath(char * dest, const char * p1, const char * p2)
|
||||||
|
{
|
||||||
|
int l;
|
||||||
|
l = strlen(p1);
|
||||||
|
if (!l){
|
||||||
|
strcpy(dest, p2);
|
||||||
|
}else{
|
||||||
|
if (l+strlen(p2) > _MAX_PATH-2){
|
||||||
|
fprintf(stderr,"Path too long\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
memcpy(dest, p1, l+1);
|
||||||
|
if (dest[l-1] != '\\' && dest[l-1] != ':'){
|
||||||
|
dest[l++] = '\\';
|
||||||
|
}
|
||||||
|
strcpy(dest+l, p2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Qsort compare function
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
int CompareFunc(const void * f1, const void * f2)
|
||||||
|
{
|
||||||
|
return strcmp(((FileEntry *)f1)->Name,((FileEntry *)f2)->Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Decide how a particular pattern should be handled, and call function for each.
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
void MyGlob(const char * Pattern , void (*FileFuncParm)(const char * FileName))
|
||||||
|
{
|
||||||
|
char BasePattern[_MAX_PATH];
|
||||||
|
char MatchPattern[_MAX_PATH];
|
||||||
|
char PatCopy[_MAX_PATH*2+1];
|
||||||
|
|
||||||
|
int a;
|
||||||
|
|
||||||
|
int MatchFiles, MatchDirs;
|
||||||
|
int BaseEnd, PatternEnd;
|
||||||
|
int SawPat;
|
||||||
|
int RecurseAt;
|
||||||
|
|
||||||
|
strcpy(PatCopy, Pattern);
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
printf("Called with '%s'\n",Pattern);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DoRecursion:
|
||||||
|
MatchFiles = FALSE;
|
||||||
|
MatchDirs = TRUE;
|
||||||
|
BaseEnd = 0;
|
||||||
|
PatternEnd = 0;
|
||||||
|
|
||||||
|
SawPat = FALSE;
|
||||||
|
RecurseAt = -1;
|
||||||
|
|
||||||
|
// Split the path into base path and pattern to match against using findfirst.
|
||||||
|
for (a=0;;a++){
|
||||||
|
if (PatCopy[a] == '*' || PatCopy[a] == '?'){
|
||||||
|
SawPat = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PatCopy[a] == '*' && PatCopy[a+1] == '*'){
|
||||||
|
if (a == 0 || PatCopy[a-1] == '\\' || PatCopy[a-1] == ':'){
|
||||||
|
if (PatCopy[a+2] == '\\' || PatCopy[a+2] == '\0'){
|
||||||
|
// x\**\y ---> x\y x\*\**\y
|
||||||
|
RecurseAt = a;
|
||||||
|
if (PatCopy[a+2]){
|
||||||
|
memcpy(PatCopy+a, PatCopy+a+3, strlen(PatCopy)-a-1);
|
||||||
|
}else{
|
||||||
|
PatCopy[a+1] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PatCopy[a] == '\\' || (PatCopy[a] == ':' && PatCopy[a+1] != '\\')){
|
||||||
|
PatternEnd = a;
|
||||||
|
if (SawPat) break; // Findfirst can only match one level of wildcard at a time.
|
||||||
|
BaseEnd = a+1;
|
||||||
|
}
|
||||||
|
if (PatCopy[a] == '\0'){
|
||||||
|
PatternEnd = a;
|
||||||
|
MatchFiles = TRUE;
|
||||||
|
MatchDirs = FALSE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!SawPat){
|
||||||
|
// No pattern. This should refer to a file.
|
||||||
|
FileFuncParm(PatCopy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(BasePattern, PatCopy, BaseEnd);
|
||||||
|
BasePattern[BaseEnd] = 0;
|
||||||
|
|
||||||
|
strncpy(MatchPattern, PatCopy, PatternEnd);
|
||||||
|
MatchPattern[PatternEnd] = 0;
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
printf("Base:%s Pattern:%s Files:%d dirs:%d\n",BasePattern, MatchPattern, MatchFiles, MatchDirs);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{
|
||||||
|
FileEntry * FileList = NULL;
|
||||||
|
int NumAllocated = 0;
|
||||||
|
int NumHave = 0;
|
||||||
|
|
||||||
|
struct _finddata_t finddata;
|
||||||
|
long find_handle;
|
||||||
|
|
||||||
|
find_handle = _findfirst(MatchPattern, &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){
|
||||||
|
if (!MatchDirs) goto next_file;
|
||||||
|
}else{
|
||||||
|
if (!MatchFiles) goto next_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add it to the list.
|
||||||
|
if (NumAllocated <= NumHave){
|
||||||
|
NumAllocated = NumAllocated+10+NumAllocated/2;
|
||||||
|
FileList = (FileEntry*)realloc(FileList, NumAllocated * sizeof(FileEntry));
|
||||||
|
if (FileList == NULL) goto nomem;
|
||||||
|
}
|
||||||
|
a = strlen(finddata.name);
|
||||||
|
FileList[NumHave].Name = (char*)malloc(a+1);
|
||||||
|
if (FileList[NumHave].Name == NULL){
|
||||||
|
nomem:
|
||||||
|
printf("malloc failure\n");
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
memcpy(FileList[NumHave].Name, finddata.name, a+1);
|
||||||
|
FileList[NumHave].attrib = finddata.attrib;
|
||||||
|
NumHave++;
|
||||||
|
|
||||||
|
next_file:
|
||||||
|
if (_findnext(find_handle, &finddata) != 0) break;
|
||||||
|
}
|
||||||
|
_findclose(find_handle);
|
||||||
|
|
||||||
|
// Sort the list...
|
||||||
|
qsort(FileList, NumHave, sizeof(FileEntry), CompareFunc);
|
||||||
|
|
||||||
|
|
||||||
|
// Use the list.
|
||||||
|
for (a=0;a<NumHave;a++){
|
||||||
|
char CombinedName[_MAX_PATH*2+1];
|
||||||
|
if (FileList[a].attrib & _A_SUBDIR){
|
||||||
|
if (MatchDirs){
|
||||||
|
// Need more directories.
|
||||||
|
SplicePath(CombinedName, BasePattern, FileList[a].Name);
|
||||||
|
strncat(CombinedName, PatCopy+PatternEnd, _MAX_PATH*2-strlen(CombinedName));
|
||||||
|
MyGlob(CombinedName,FileFuncParm);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if (MatchFiles){
|
||||||
|
// We need files at this level.
|
||||||
|
SplicePath(CombinedName, BasePattern, FileList[a].Name);
|
||||||
|
FileFuncParm(CombinedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(FileList[a].Name);
|
||||||
|
}
|
||||||
|
free(FileList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(RecurseAt >= 0){
|
||||||
|
strcpy(MatchPattern, PatCopy+RecurseAt);
|
||||||
|
PatCopy[RecurseAt] = 0;
|
||||||
|
strncpy(PatCopy+RecurseAt, "*\\**\\", _MAX_PATH*2-RecurseAt);
|
||||||
|
strncat(PatCopy, MatchPattern, _MAX_PATH*2-strlen(PatCopy));
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
printf("Recurse with '%s'\n",PatCopy);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// As this function context is no longer needed, we can just goto back
|
||||||
|
// to the top of it to avoid adding another context on the stack.
|
||||||
|
goto DoRecursion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Flip slashes to native OS representation (for Windows)
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
void SlashToNative(char * Path)
|
||||||
|
{
|
||||||
|
int a;
|
||||||
|
for (a=0;Path[a];a++){
|
||||||
|
if (Path[a] == '/') Path[a] = SLASH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef DEBUGGING
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// The main program.
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
int main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
int argn;
|
||||||
|
char * arg;
|
||||||
|
int Subdirs = 0;
|
||||||
|
|
||||||
|
for (argn=1;argn<argc;argn++){
|
||||||
|
arg = argv[argn];
|
||||||
|
if (arg[0] != '-') break; // Filenames from here on.
|
||||||
|
if (!strcmp(arg,"-r")){
|
||||||
|
printf("do recursive\n");
|
||||||
|
Subdirs = 1;
|
||||||
|
}else{
|
||||||
|
fprintf(stderr, "Argument '%s' not understood\n",arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (argn == argc){
|
||||||
|
fprintf(stderr,"Error: Must supply a file name\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;argn<argc;argn++){
|
||||||
|
MyGlob(argv[argn], ShowName);
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
non-recursive test cases:
|
||||||
|
|
||||||
|
e:\make*\*
|
||||||
|
\make*\*
|
||||||
|
e:*\*.c
|
||||||
|
\*\*.c
|
||||||
|
\*
|
||||||
|
c:*.c
|
||||||
|
c:\*
|
||||||
|
..\*.c
|
||||||
|
|
||||||
|
|
||||||
|
recursive test cases:
|
||||||
|
**
|
||||||
|
**\*.c
|
||||||
|
c:\**\*.c
|
||||||
|
c:**\*.c
|
||||||
|
.\**
|
||||||
|
..\**
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Module to do path manipulation for file moving of jhead.
|
||||||
|
//
|
||||||
|
// Matthias Wandel Feb 2 2009
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <direct.h> // for mkdir under windows.
|
||||||
|
#define mkdir(dir,mode) mkdir(dir)
|
||||||
|
#undef S_ISDIR
|
||||||
|
#define S_ISDIR(a) (a & _S_IFDIR)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "../include/Exif/jhead.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Ensure that a path exists
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
int EnsurePathExists(const char * FileName)
|
||||||
|
{
|
||||||
|
char NewPath[PATH_MAX*2];
|
||||||
|
int a;
|
||||||
|
int LastSlash = 0;
|
||||||
|
|
||||||
|
//printf("\nEnsure exists:%s\n",FileName);
|
||||||
|
|
||||||
|
// Extract the path component of the file name.
|
||||||
|
strcpy(NewPath, FileName);
|
||||||
|
a = strlen(NewPath);
|
||||||
|
for (;;){
|
||||||
|
a--;
|
||||||
|
if (a == 0){
|
||||||
|
NewPath[0] = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (NewPath[a] == SLASH){
|
||||||
|
struct stat dummy;
|
||||||
|
NewPath[a] = 0;
|
||||||
|
if (stat(NewPath, &dummy) == 0){
|
||||||
|
if (S_ISDIR(dummy.st_mode)){
|
||||||
|
// Break out of loop, and go forward along path making
|
||||||
|
// the directories.
|
||||||
|
if (LastSlash == 0){
|
||||||
|
// Full path exists. No need to create any directories.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}else{
|
||||||
|
// Its a file.
|
||||||
|
fprintf(stderr,"Can't create path '%s' due to file conflict\n",NewPath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (LastSlash == 0) LastSlash = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now work forward.
|
||||||
|
//printf("Existing First dir: '%s' a = %d\n",NewPath,a);
|
||||||
|
|
||||||
|
for(;FileName[a];a++){
|
||||||
|
if (FileName[a] == SLASH || a == 0){
|
||||||
|
if (a == LastSlash) break;
|
||||||
|
NewPath[a] = FileName[a];
|
||||||
|
//printf("make dir '%s'\n",NewPath);
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (NewPath[1] == ':' && strlen(NewPath) == 2) continue;
|
||||||
|
#endif
|
||||||
|
if (mkdir(NewPath,0777)){
|
||||||
|
fprintf(stderr,"Could not create directory '%s'\n",NewPath);
|
||||||
|
// Failed to create directory.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
// Make a new path out of a base path, and a filename.
|
||||||
|
// Basepath is the base path, and FilePath is a filename, or path + filename.
|
||||||
|
//--------------------------------------------------------------------------------
|
||||||
|
void CatPath(char * BasePath, const char * FilePath)
|
||||||
|
{
|
||||||
|
int l;
|
||||||
|
l = strlen(BasePath);
|
||||||
|
|
||||||
|
if (FilePath[1] == ':'){
|
||||||
|
// Its a windows absolute path.
|
||||||
|
l = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FilePath[0] == SLASH || FilePath[0] == '.' || l == 0){
|
||||||
|
// Its an absolute path, or there was no base path.
|
||||||
|
strcpy(BasePath, FilePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BasePath[l-1] != SLASH){
|
||||||
|
BasePath[l++] = SLASH;
|
||||||
|
BasePath[l] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(BasePath, FilePath);
|
||||||
|
|
||||||
|
// Note that the combined path may contains things like "foo/../bar". We assume
|
||||||
|
// that the filesystem will take care of these.
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
char Path1[] = "ztest\\cdir\\foo.jpg";
|
||||||
|
char Path2[] = "zxtest\\cdir\\foo.jpg";
|
||||||
|
char Path3[] = "\\tzest\\cdir\\foo.jpg";
|
||||||
|
|
||||||
|
char BasePath[100];
|
||||||
|
|
||||||
|
main()
|
||||||
|
{
|
||||||
|
EnsurePathExists(Path1);
|
||||||
|
EnsurePathExists(Path2);
|
||||||
|
EnsurePathExists(Path3);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CatPath(BasePath, "hello.txt");
|
||||||
|
CatPath(BasePath, "world\\hello.txt");
|
||||||
|
CatPath(BasePath, "\\hello.txt");
|
||||||
|
CatPath(BasePath, "c:\\hello.txt");
|
||||||
|
CatPath(BasePath, "c:\\world\\hello.txt");
|
||||||
|
CatPath(BasePath, "c:\\abresl\\hello.txt");
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
|
@ -0,0 +1,209 @@
|
||||||
|
/****************************************************************************
|
||||||
|
* VCGLib o o *
|
||||||
|
* Visual and Computer Graphics Library o o *
|
||||||
|
* _ O _ *
|
||||||
|
* Copyright(C) 2004 \/)\/ *
|
||||||
|
* Visual Computing Lab /\/| *
|
||||||
|
* ISTI - Italian National Research Council | *
|
||||||
|
* \ *
|
||||||
|
* All rights reserved. *
|
||||||
|
* *
|
||||||
|
* This program is free software; you can redistribute it and/or modify *
|
||||||
|
* it under the terms of the GNU General Public License as published by *
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or *
|
||||||
|
* (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This program is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
|
||||||
|
* for more details. *
|
||||||
|
* *
|
||||||
|
****************************************************************************/
|
||||||
|
#ifndef __VCGLIB_IMPORTERBUNDLER
|
||||||
|
#define __VCGLIB_IMPORTERBUNDLER
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <wrap/callback.h>
|
||||||
|
#include <wrap/io_trimesh/io_mask.h>
|
||||||
|
#include <wrap/Exif/include/Exif/jhead.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace vcg {
|
||||||
|
namespace tri {
|
||||||
|
namespace io {
|
||||||
|
|
||||||
|
struct Correspondence{
|
||||||
|
Correspondence(unsigned int id_img_,unsigned int key_,float x_,float y_):id_img(id_img_),key(key_),x(x_),y(y_){};
|
||||||
|
unsigned int id_img,key;
|
||||||
|
float x;
|
||||||
|
float y;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<Correspondence> CorrVec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
This class encapsulate a filter for opening bundler file
|
||||||
|
*/
|
||||||
|
template <class OpenMeshType>
|
||||||
|
class ImporterOUT
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
typedef typename OpenMeshType::VertexPointer VertexPointer;
|
||||||
|
typedef typename OpenMeshType::ScalarType ScalarType;
|
||||||
|
typedef typename OpenMeshType::VertexType VertexType;
|
||||||
|
typedef typename OpenMeshType::FaceType FaceType;
|
||||||
|
typedef typename OpenMeshType::VertexIterator VertexIterator;
|
||||||
|
typedef typename OpenMeshType::FaceIterator FaceIterator;
|
||||||
|
typedef typename OpenMeshType::EdgeIterator EdgeIterator;
|
||||||
|
|
||||||
|
static char *readline(FILE *fp, int max=100){
|
||||||
|
int i=0;
|
||||||
|
char c, *str = new char[max];
|
||||||
|
fscanf(fp, "%c", &c);
|
||||||
|
while( (c!=10) && (c!=13) && (i<max) ){
|
||||||
|
str[i++] = c;
|
||||||
|
fscanf(fp, "%c", &c);
|
||||||
|
}
|
||||||
|
str[i] = '\0'; //end of string
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
static bool ReadHeader(FILE *fp,unsigned int &num_cams, unsigned int &num_points){
|
||||||
|
char *line;
|
||||||
|
line = readline(fp); if( (!line) || (0!=strcmp("# Bundle file v0.3", line)) ) return false;
|
||||||
|
line = readline(fp); if(!line) return false;
|
||||||
|
sscanf(line, "%d %d", &num_cams, &num_points);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadHeader(const char * filename,unsigned int &num_cams, unsigned int &num_points){
|
||||||
|
FILE *fp = fopen(s.c_str(), "r");
|
||||||
|
if(!fp) return false;
|
||||||
|
ReadHeader(fp);
|
||||||
|
fclose(fp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int Open( OpenMeshType &m, std::vector<Shot<ScalarType> > & shots,
|
||||||
|
std::vector<std::string > & image_filenames,
|
||||||
|
const char * filename,const char * filename_images, CallBackPos *cb=0)
|
||||||
|
{
|
||||||
|
unsigned int num_cams,num_points;
|
||||||
|
|
||||||
|
FILE *fp = fopen(filename,"r");
|
||||||
|
if(!fp) return false;
|
||||||
|
ReadHeader(fp, num_cams, num_points);
|
||||||
|
char *line;
|
||||||
|
|
||||||
|
ReadImagesFilenames(filename_images,image_filenames);
|
||||||
|
|
||||||
|
shots.resize(num_cams);
|
||||||
|
for(int i = 0; i < num_cams;++i)
|
||||||
|
{
|
||||||
|
float f, k1, k2;
|
||||||
|
float R[16]={0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,1};
|
||||||
|
vcg::Point3f t;
|
||||||
|
|
||||||
|
line = readline(fp); if(!line) return false; sscanf(line, "%f %f %f", &f, &k1, &k2);
|
||||||
|
|
||||||
|
line = readline(fp); if(!line) return false; sscanf(line, "%f %f %f", &(R[0]), &(R[1]), &(R[2])); R[3] = 0;
|
||||||
|
line = readline(fp); if(!line) return false; sscanf(line, "%f %f %f", &(R[4]), &(R[5]), &(R[6])); R[7] = 0;
|
||||||
|
line = readline(fp); if(!line) return false; sscanf(line, "%f %f %f", &(R[8]), &(R[9]), &(R[10])); R[11] = 0;
|
||||||
|
|
||||||
|
line = readline(fp); if(!line) return false; sscanf(line, "%f %f %f", &(t[0]), &(t[1]), &(t[2]));
|
||||||
|
|
||||||
|
vcg::Matrix44f mat = vcg::Matrix44<vcg::Shotf::ScalarType>::Construct<float>(R);
|
||||||
|
|
||||||
|
vcg::Matrix33f Rt = vcg::Matrix33f( vcg::Matrix44f(mat), 3);
|
||||||
|
Rt.Transpose();
|
||||||
|
|
||||||
|
vcg::Point3f pos = Rt * vcg::Point3f(t[0], t[1], t[2]);
|
||||||
|
|
||||||
|
shots[i].Extrinsics.SetTra(vcg::Point3<vcg::Shotf::ScalarType>::Construct<float>(-pos[0],-pos[1],-pos[2]));
|
||||||
|
shots[i].Extrinsics.SetRot(mat);
|
||||||
|
|
||||||
|
shots[i].Intrinsics.FocalMm = f;
|
||||||
|
shot.Intrinsics.k[0] = 0.0;//k1; To be uncommented when distortion is taken into account reliably
|
||||||
|
shot.Intrinsics.k[1] = 0.0;//k2;
|
||||||
|
AddIntrinsics(shots[i],image_filenames[i].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// load all correspondences
|
||||||
|
OpenMeshType::template PerVertexAttributeHandle<CorrVec> ch = vcg::tri::Allocator<OpenMeshType>::GetPerVertexAttribute<CorrVec>(m,"correspondences");
|
||||||
|
if(!vcg::tri::Allocator<OpenMeshType>::IsValidHandle(m,ch))
|
||||||
|
ch = vcg::tri::Allocator<OpenMeshType>::AddPerVertexAttribute<CorrVec>(m,"correspondences");
|
||||||
|
|
||||||
|
OpenMeshType::VertexIterator vi = vcg::tri::Allocator<OpenMeshType>::AddVertices(m,num_points);
|
||||||
|
for(int i = 0; i < num_points;++i,++vi){
|
||||||
|
float x,y,z;
|
||||||
|
unsigned int r,g,b,i_cam, key_sift,n_corr;
|
||||||
|
fscanf(fp,"%f %f %f ",&x,&y,&z);
|
||||||
|
(*vi).P() = vcg::Point3<OpenMeshType::ScalarType>(x,y,z);
|
||||||
|
fscanf(fp,"%d %d %d ",&r,&g,&b);
|
||||||
|
(*vi).C() = vcg::Color4b(r,g,b,255);
|
||||||
|
|
||||||
|
fscanf(fp,"%d ",&n_corr);
|
||||||
|
for(int j = 0; j < n_corr; ++j){
|
||||||
|
fscanf(fp,"%d %d %f %f ",&i_cam,&key_sift,&x,&y);
|
||||||
|
Correspondence corr(i_cam,key_sift,x,y);
|
||||||
|
ch[i].push_back(corr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vcg::tri::UpdateBounding<OpenMeshType>::Box(m);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return (shots.size() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Open( OpenMeshType &m, std::vector<Shot<ScalarType> > shots, const char * filename_out,const char * filename_list, CallBackPos *cb=0){
|
||||||
|
QStringList image_files;
|
||||||
|
ReadHeader(filename_out);
|
||||||
|
ReadImagesFilenames(QString(filename_list),list,num_cams);
|
||||||
|
return Open( m, shots,filename, image_files, cb);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ReadImagesFilenames(const char * filename,std::vector<std::string> &image_filenames)
|
||||||
|
{
|
||||||
|
char line[1000],name[1000];
|
||||||
|
|
||||||
|
FILE * fi = fopen(filename,"r");
|
||||||
|
if (!fi) return false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while(!feof(fi)){
|
||||||
|
fgets (line , 1000, fi);
|
||||||
|
sscanf(line,"%s",&name[0]);
|
||||||
|
std::string n(name);
|
||||||
|
image_filenames.push_back(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(fi);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool AddIntrinsics(vcg::Shotf &shot, const char * image_file)
|
||||||
|
{
|
||||||
|
::ProcessFile(image_file);
|
||||||
|
shot.Intrinsics.ViewportPx = vcg::Point2i(ImageInfo.Width, ImageInfo.Height);
|
||||||
|
shot.Intrinsics.CenterPx = vcg::Point2f(float(ImageInfo.Width/2.0), float(ImageInfo.Height/2.0));
|
||||||
|
|
||||||
|
shot.Intrinsics.PixelSizeMm = vcg::Point2f(1,1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}; // end class
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // end namespace tri
|
||||||
|
} // end namespace io
|
||||||
|
} // end namespace vcg
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue