diff --git a/makefile-win32 b/makefile-win32 index e87fa91..cb06df7 100644 --- a/makefile-win32 +++ b/makefile-win32 @@ -18,8 +18,8 @@ OBJECTS_IMGCOMP = $(OBJ)\main.obj $(OBJ)\compare.obj $(OBJ)\dir_win.obj \ $(OBJ)\jpeg2mem.obj $(OBJ)\jpgfile.obj $(OBJ)\exif.obj \ $(OBJ)\start_raspistill.obj $(OBJ)\util.obj -$(OBJECTS_IMGCOMP): $(@B).c imgcomp.h - $(CC) /Fo$(OBJ)\ $(CFLAGS) $(@B).c +$(OBJECTS_IMGCOMP): src\$(@B).c imgcomp.h + $(CC) /Fo$(OBJ)\ $(CFLAGS) src\$(@B).c imgcomp.exe: $(OBJECTS_IMGCOMP) $(LINKER) $(LINKCON) -OUT:imgcomp.exe libjpeg\libjpeg.lib $(OBJECTS_IMGCOMP) diff --git a/src/blink_camera_led.c b/src/blink_camera_led.c new file mode 100644 index 0000000..1c4c8d6 --- /dev/null +++ b/src/blink_camera_led.c @@ -0,0 +1,111 @@ +// +// This program based on "How to access GPIO registers from C-code on the Raspberry-Pi +// Example program + +#define BCM2708_PERI_BASE 0x20000000 +#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) /* GPIO controller */ + +#include +#include +#include +#include +#include + +#define PAGE_SIZE (4*1024) +#define BLOCK_SIZE (4*1024) + +int mem_fd; +void *gpio_map; + +// I/O access +volatile unsigned *gpio; + +// GPIO setup macros. Always use INP_GPIO(x) before using OUT_GPIO(x) or SET_GPIO_ALT(x,y) +#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3)) +#define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3)) +#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3)) + +#define GPIO_SET *(gpio+7) // sets bits which are 1 ignores bits which are 0 +#define GPIO_CLR *(gpio+10) // clears bits which are 1 ignores bits which are 0 + +#define GPIO_SET2 *(gpio+8) // same as GPI_SET macro, but for GPIO 32 and higher +#define GPIO_CLR2 *(gpio+11) // same as GPI_SET macro, but for GPIO 32 and higher + +#define GET_GPIO(g) (*(gpio+13)&(1< bit is 1 <- port is HIGH=3.3V + printf("Button pressed!\n"); + else // port is LOW=0V + printf("Button released!\n"); +} + +int main(int argc, char **argv) +{ + int g,rep; + + // Set up gpi pointer for direct register access + setup_io(); + + // Switch GPIO 7..11 to output mode + + /************************************************************************\ + * You are about to change the GPIO settings of your computer. * + * Mess this up and it will stop working! * + * It might be a good idea to 'sync' before running this program * + * so at least you still have your code changes written to the SD-card! * + \************************************************************************/ + + // Set GPIO pins 7-11 to output + INP_GPIO(32); // must use INP_GPIO before we can use OUT_GPIO + OUT_GPIO(32); + + // Turn on the camera LED once for 300 miliseconds + GPIO_SET2 = 1; + usleep(300000); + GPIO_CLR2 = 1; + + return 0; + +} // main + + +// +// Set up a memory regions to access GPIO +// +void setup_io() +{ + // open /dev/mem + if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) { + printf("can't open /dev/mem \n"); + exit(-1); + } + + /* mmap GPIO */ + gpio_map = mmap( + NULL, //Any adddress in our space will do + BLOCK_SIZE, //Map length + PROT_READ|PROT_WRITE,// Enable reading & writting to mapped memory + MAP_SHARED, //Shared with other processes + mem_fd, //File to map + GPIO_BASE //Offset to GPIO peripheral + ); + + close(mem_fd); //No need to keep mem_fd open after mmap + + if (gpio_map == MAP_FAILED) { + printf("mmap error %d\n", (int)gpio_map);//errno also set! + exit(-1); + } + + // Always use volatile pointer! + gpio = (volatile unsigned *)gpio_map; + + +} // setup_io diff --git a/src/compare.c b/src/compare.c new file mode 100644 index 0000000..cffd2bc --- /dev/null +++ b/src/compare.c @@ -0,0 +1,578 @@ +#include +#include +#include +#include +#include +#include +#include "imgcomp.h" + +int NewestAverageBright; +int NightMode; + +typedef struct { + int w, h; + unsigned char values[1]; +}ImgMap_t; + +static ImgMap_t * DiffVal = NULL; +static ImgMap_t * WeightMap = NULL; + +static TriggerInfo_t SearchDiffMaxWindow(Region_t Region, int threshold); + +//---------------------------------------------------------------------------------------- +// Calculate average brightness of an image. +//---------------------------------------------------------------------------------------- +static double AverageBright(MemImage_t * pic, Region_t Region, ImgMap_t* WeightMap) +{ + double baverage; + int DetectionPixels; + int row; + int rowbytes = pic->width*3; + DetectionPixels = 0; + + // Compute average brightnesses. + baverage = 0; + for (row=Region.y1;rowpixels+rowbytes*row; + px = &WeightMap->values[pic->width*row]; + for (col=Region.x1;colvalues[0])*width*height); + WeightMap->w = width; WeightMap->h = height; + + memset(WeightMap->values, 0, sizeof(WeightMap->values)*width*height); + + Reg = Regions.DetectReg; + if (Reg.x2 > width) Reg.x2 = width; + if (Reg.y2 > height) Reg.y2 = height; + printf("fill %d-%d,%d-%d\n",Reg.x1, Reg.x2, Reg.y1, Reg.y2); + for (row=Reg.y1;rowvalues[row*width+Reg.x1], 1, Reg.x2-Reg.x1); + } + + for (r=0;r width) Reg.x2 = width; + if (Reg.y2 > height) Reg.y2 = height; + printf("clear %d-%d,%d-%d\n",Reg.x1, Reg.x2, Reg.y1, Reg.y2); + for (row=Reg.y1;rowvalues[row*width+Reg.x1], 0, Reg.x2-Reg.x1); + } + } + + Reg = Regions.DetectReg; + for (row=Reg.y1;rowvalues[row*width+r]); + } + printf("\n"); + } +} + +//---------------------------------------------------------------------------------------- +// Load an image which indicates regions to use and exclude. +//---------------------------------------------------------------------------------------- +void ProcessDiffMap(MemImage_t * MapPic) +{ + int width, height, row, col; + int firstrow, lastrow; + int numred, numblue; + width = MapPic->width; + height = MapPic->height; + + // Allocate the detection region. + WeightMap = malloc(offsetof(ImgMap_t, values) + + sizeof(DiffVal->values[0])*width*height); + WeightMap->w = width; + WeightMap->h = height; + + numred = numblue = 0; + firstrow = -1; + lastrow = 0; + + for (row=0;rowvalues[width*row]; + img = &MapPic->pixels[width*3*row]; + for (col=0;col 100 && b > (r+g)*2){ + // Blue. Ignore. + *map = 0; + numblue += 1; + }else{ + if (firstrow == -1) firstrow = row; + lastrow = row; + *map = 1; + if (r > 100 && r > (g+b)*2){ + // Red. Weigh 2x. + *map = 2; + numred += 1; + } + } + map += 1; + } + } + + printf("Map: Ignore %5.1f%% 3x:%5.1f%%\n",numblue*100.0/(width*height), + numred*100.0/(width*height)); + + Regions.DetectReg.y1 = firstrow; + Regions.DetectReg.y2 = lastrow+1; + + for (row=0;rowvalues[row*width+r] == 0){ + putchar('-'); + }else{ + putchar(WeightMap->values[row*width+r]+'0'); + } + } + printf("\n"); + } + + Regions.DetectReg.y1 = firstrow; + Regions.DetectReg.y2 = lastrow; +} + + + +//---------------------------------------------------------------------------------------- +// Compare two images in memory +//---------------------------------------------------------------------------------------- +TriggerInfo_t ComparePix(MemImage_t * pic1, MemImage_t * pic2, char * DebugImgName) +{ + int width, height, bPerRow; + int row, col; + MemImage_t * DiffOut = NULL; + int DiffHist[256]; + int a; + int DetectionPixels; + int m1i, m2i; + Region_t MainReg; + TriggerInfo_t RetVal; + RetVal.x = RetVal.y = 0; + RetVal.DiffLevel = -1; + + if (Verbosity){ + printf("\ncompare pictures %dx%d %d\n", pic1->width, pic1->height, pic1->components); + } + + if (pic1->width != pic2->width || pic1->height != pic2->height + || pic1->components != pic2->components){ + fprintf(stderr, "pic types mismatch!\n"); + return RetVal; + } + width = pic1->width; + height = pic1->height; + bPerRow = width * 3; + + if (DiffVal != NULL && (width != DiffVal->w || height != DiffVal->h)){ + // DiffVal allocated size is wrong. + free(DiffVal); + free(WeightMap); + DiffVal = NULL; + } + if (DiffVal == NULL){ + DiffVal = malloc(offsetof(ImgMap_t, values) + sizeof(DiffVal->values[0])*width*height); + DiffVal->w = width; DiffVal->h = height; + if (!WeightMap){ + FillWeightMap(width,height); + }else{ + if (WeightMap->w != width || WeightMap->h != height){ + fprintf(stderr,"diff map image size mismatch\n"); + exit(-1); + } + } + } + + memset(DiffVal->values, 0, sizeof(DiffVal->values)*width*height); + + if (DebugImgName){ + int data_size; + data_size = height * bPerRow; + DiffOut = malloc(data_size+offsetof(MemImage_t, pixels)); + memcpy(DiffOut, pic1, offsetof(MemImage_t, pixels)); + memset(DiffOut->pixels, 0, data_size); + } + memset(DiffHist, 0, sizeof(DiffHist)); + + MainReg = Regions.DetectReg; + if (MainReg.y2 > height) MainReg.y2 = height; + if (MainReg.x2 > width) MainReg.x2 = width; + if (MainReg.x2 < MainReg.x1 || MainReg.y2 < MainReg.y1){ + fprintf(stderr, "Negative region, or region outside of image\n"); + return RetVal; + } + + DetectionPixels = (MainReg.x2-MainReg.x1) * (MainReg.y2-MainReg.y1); + if (DetectionPixels < 1000){ + fprintf(stderr, "Too few pixels in region\n"); + return RetVal; + } + + if (Verbosity > 0){ + printf("Detection region is %d-%d, %d-%d\n",MainReg.x1, MainReg.x2, MainReg.y1, MainReg.y2); + } + + // Compute brightness multipliers. + { + double b1average, b2average; + double m1, m2; + b1average = AverageBright(pic1, MainReg, WeightMap); + b2average = AverageBright(pic2, MainReg, WeightMap); + + NewestAverageBright = (int)(b2average+0.5); + + if (Verbosity > 0){ + printf("average bright: %f %f\n",b1average, b2average); + } + if (b1average < 0.5) b1average = 0.5; // Avoid possible division by zero. + if (b2average < 0.5) b2average = 0.5; + + NightMode = 0; + if (b1average < 15 || b2average < 15){ + if (Verbosity > 0) printf("Using night mode diff\n"); + NightMode = 1; + } + + m1 = 80/b1average; + m2 = 80/b2average; + + { + double maxm = m1 > m2 ? m1 : m2; + if (maxm > 4.0){ + // Don't allow multiplier to get bigger than 4. Otherwise, for dark images + // we just end up multiplying pixel noise! + m1 = m1 * 4 / maxm; + m2 = m2 * 4 / maxm; + }else if (maxm < 1.0){ + // And there's no point in scaling down both images either. + m1 = m1 * 1 / maxm; + m2 = m2 * 1 / maxm; + } + } + + if (Verbosity) printf("Brightness adjust multipliers: m1 = %5.2f m2=%5.2f\n",m1,m2); + + if (!NightMode || b2average > b1average){ + m1i = (int)(m1*256+0.5); + m2i = (int)(m2*256+0.5); + }else{ + // Swap images and multipliers so that pic2 is the brighter one. + // this makes giving it slack easier. + MemImage_t * temp; + temp = pic1; pic1=pic2; pic2=temp; + m1i = (int)(m2*256+0.5); // m1 should always be bigger. + m2i = (int)(m1*256+0.5); + if (Verbosity) printf("swapped images m1i=%d m2i=%d\n",m1i,m2i); + } + } + + // Compute differences + for (row=MainReg.y1;rowpixels+row*bPerRow; + p2 = pic2->pixels+row*bPerRow; + diffrow = &DiffVal->values[width*row]; + ExRow = &WeightMap->values[width*row]; + if (DebugImgName) pd = DiffOut->pixels+row*bPerRow; + + if (NightMode){ + for (col=MainReg.x1;col m2i. + // Try it without multiplication difference. + dcomp = b2*m1i-b1*m1i; + if (dcomp > 0){ + // if difference now positive, the whole difference seen may + // be because m1i > m2i. So call it zero. + dcomp = 0; + } + } + + dcomp = dcomp >> 9; + + if (DebugImgName){ + pd[1] = pd[4] = dcomp > 0 ? dcomp : 0; // Green = img2 brighter + pd[0] = pd[3] = dcomp < 0 ? -dcomp : 0; // Red = img1 brigher. + pd[2] = pd[5] = 0; + } + + if (dcomp < 0) dcomp = -dcomp; + if (dcomp > 255) dcomp = 255;// put it in a histogram. + DiffHist[dcomp] += 4; // Did four pixels, so add 4. + diffrow[col] = dcomp; + } + + pd += 6; + p1 += 6; + p2 += 6; + } + row+=2; + }else{ + for (col=MainReg.x1;col> 8; // Remove the *256 from fixed point aritmetic multiply + + if (dcomp >= 256) dcomp = 255;// put it in a histogram. + DiffHist[dcomp] += 1; + diffrow[col] = dcomp; + + + if (DebugImgName){ + // Save the difference image, scaled 4x. + if (dr > 256) dr = 256; + if (dg > 256) dg = 256; + if (db > 256) db = 256; + pd[0] = dr; + pd[1] = dg; + pd[2] = db;; + } + } + + pd += 3; + p1 += 3; + p2 += 3; + } + row++; + } + } + + if (DebugImgName){ + WritePpmFile(DebugImgName,DiffOut); + free(DiffOut); + } + + + + if (Verbosity > 1){ + for (a=0;a<60;a+= 2){ + printf("%3d:%5d,%5d\n",a,DiffHist[a], DiffHist[a+1]); + } + } + + { + // Try to gauge the difference noise (assuming two thirds of the image + // has not changed) + int cumsum = 0; + int threshold; + int twothirds = DetectionPixels*2/3; + TriggerInfo_t Trigger; + + for (a=0;a<256;a++){ + if (cumsum >= twothirds) break; + cumsum += DiffHist[a]; + } + + threshold = a*3+12; + if (threshold < 30) threshold = 30; + if (threshold > 80) threshold = 80; + + if (Verbosity) printf("2/3 of image is below %d diff. Using %d threshold\n",a, threshold); + + cumsum = 0; + for (a=threshold;a<256;a++){ + cumsum += DiffHist[a] * (a-threshold); + } + if (Verbosity) printf("Above threshold: %d\n",cumsum); + + cumsum = (cumsum * 100) / DetectionPixels; + if (Verbosity) printf("Normalized diff: %d\n",cumsum); + + // Try to gauge the difference noise (assuming two thirds of the image + // has not changed) + + Trigger = SearchDiffMaxWindow(MainReg, threshold); + return Trigger; + } +} + +//---------------------------------------------------------------------------------------- +// Search for an nxn window with the maximum differences in it. +//---------------------------------------------------------------------------------------- +static TriggerInfo_t SearchDiffMaxWindow(Region_t Region, int threshold) +{ + int row,col; + TriggerInfo_t retval; + + // Scale down by this factor before applying windowing algorithm to look for max localized change + #define SCALEF 4 + #define ROOF(x) ((x+SCALEF-1)/SCALEF) + + // these determine the window over over which to look for the change (after scaling) + const int wind_w = 4, wind_h = 4; + + static int * Diff4 = NULL; + static int * Diff4b = NULL; + static int width4, height4; + if (width4 != ROOF(DiffVal->w) || height4 != ROOF(DiffVal->h) || Diff4 == NULL){ + // Size has changed. Reallocate. + free(Diff4); + width4 = ROOF(DiffVal->w); + height4 = ROOF(DiffVal->h); + Diff4 = malloc(sizeof(int)*width4*height4); + Diff4b = malloc(sizeof(int)*width4*height4); + } + + // Compute scaled down array of differences. + { + int width = DiffVal->w; + memset(Diff4, 0, sizeof(int)*width4*height4); + for (row=Region.y1;rowvalues[width*row]; + ExRow = &WeightMap->values[width*row]; + + width4row = &Diff4[width4*(row/SCALEF)]; + for (col=Region.x1;col 0){ + width4row[col/SCALEF] += d; + if (ExRow[col] > 1){ + // Double weight region + width4row[col/SCALEF] += d; + } + } + } + } + } + + if (Verbosity > 1){ + // Show the array. + for (row=0;row= wind_h){ + int * subtrow = &Diff4[width4*(row-wind_h)]; + for (col=0;col= wind_w) s-= srcrow[col-wind_w]; + destrow[col] = s; + if (s > maxval){ + maxval = s; + maxc = col; + maxr = row; + } + } + } + if (Verbosity) printf("max v=%d at r=%d,c=%d\n",maxval/100, maxr, maxc); + retval.x = maxc * SCALEF - wind_w * SCALEF / 2; + retval.y = maxr * SCALEF - wind_h * SCALEF / 2; + if (retval.x < 0) retval.x = 0; + if (retval.y < 0) retval.y = 0; + retval.DiffLevel = maxval / 100; + } + + if (Verbosity > 1){ + // Show the array. + printf("Window sums\n"); + for (row=0;row +#include +#include +#include "readdir_win.h" +// Copied from: +// http://www.opensource.apple.com/source/apache_mod_php/apache_mod_php-4.3/php/win32/readdir.c + +/********************************************************************** + * Implement dirent-style opendir/readdir/closedir on Window 95/NT + * + * Functions defined are opendir(), readdir() and closedir() with the + * same prototypes as the normal dirent.h implementation. + * + * Does not implement telldir(), seekdir(), rewinddir() or scandir(). + * The dirent struct is compatible with Unix, except that d_ino is + * always 1 and d_off is made up as we go along. + * + * The DIR typedef is not compatible with Unix. + **********************************************************************/ + +DIR *opendir(const char *dir) +{ + DIR *dp; + char *filespec; + long handle; + int index; + + filespec = malloc(strlen(dir) + 2 + 1); + strcpy(filespec, dir); + index = strlen(filespec) - 1; + if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\')) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + dp = (DIR *) malloc(sizeof(DIR)); + dp->offset = 0; + dp->finished = 0; + dp->dir = _strdup(dir); + + if ((handle = _findfirst(filespec, &(dp->fileinfo))) < 0) { + if (errno == ENOENT) + dp->finished = 1; + else + return NULL; + } + dp->handle = handle; + free(filespec); + + return dp; +} + +struct dirent *readdir(DIR *dp) +{ + if (!dp || dp->finished) + return NULL; + + if (dp->offset != 0) { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) { + dp->finished = 1; + return NULL; + } + } + dp->offset++; + + strncpy(dp->dent.d_name, dp->fileinfo.name, _MAX_FNAME+1); + dp->dent.d_ino = 1; + dp->dent.d_reclen = strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + return &(dp->dent); +} + +int readdir_r(DIR *dp, struct dirent *entry, struct dirent **result) +{ + if (!dp || dp->finished) { + *result = NULL; + return 0; + } + + if (dp->offset != 0) { + if (_findnext(dp->handle, &(dp->fileinfo)) < 0) { + dp->finished = 1; + *result = NULL; + return 0; + } + } + dp->offset++; + + strncpy(dp->dent.d_name, dp->fileinfo.name, _MAX_FNAME+1); + dp->dent.d_ino = 1; + dp->dent.d_reclen = strlen(dp->dent.d_name); + dp->dent.d_off = dp->offset; + + memcpy(entry, &dp->dent, sizeof(*entry)); + + *result = &dp->dent; + + return 0; +} + +int closedir(DIR *dp) +{ + if (!dp) + return 0; + _findclose(dp->handle); + if (dp->dir) + free(dp->dir); + if (dp) + free(dp); + + return 0; +} + +void rewinddir(DIR *dir_Info) +{ + /* Re-set to the beginning */ + char *filespec; + long handle; + int index; + + dir_Info->handle = 0; + dir_Info->offset = 0; + dir_Info->finished = 0; + + filespec = malloc(strlen(dir_Info->dir) + 2 + 1); + strcpy(filespec, dir_Info->dir); + index = strlen(filespec) - 1; + if (index >= 0 && (filespec[index] == '/' || filespec[index] == '\\')) + filespec[index] = '\0'; + strcat(filespec, "/*"); + + if ((handle = _findfirst(filespec, &(dir_Info->fileinfo))) < 0) { + if (errno == ENOENT) { + dir_Info->finished = 1; + } + } + dir_Info->handle = handle; + free(filespec); +} + + +#include +void sleep(int a) +{ + printf("Pretending to sleep for %d seconds\n",a); + getchar(); +} \ No newline at end of file diff --git a/src/exif.c b/src/exif.c new file mode 100644 index 0000000..c561def --- /dev/null +++ b/src/exif.c @@ -0,0 +1,1147 @@ +//-------------------------------------------------------------------------- +// Copied from jhead project to parse exif file header. +// +// Matthias Wandel +//-------------------------------------------------------------------------- +#include "jhead.h" + +#include + +static unsigned char * DirWithThumbnailPtrs; +static double FocalplaneXRes; +static double FocalplaneUnits; +static int ExifImageWidth; +int MotorolaOrder = 0; + +typedef struct { + unsigned short Tag; + char * Desc; +}TagTable_t; + + +//-------------------------------------------------------------------------- +// Table of Jpeg encoding process names +static const TagTable_t ProcessTable[] = { + { M_SOF0, "Baseline"}, + { M_SOF1, "Extended sequential"}, + { M_SOF2, "Progressive"}, + { M_SOF3, "Lossless"}, + { M_SOF5, "Differential sequential"}, + { M_SOF6, "Differential progressive"}, + { M_SOF7, "Differential lossless"}, + { M_SOF9, "Extended sequential, arithmetic coding"}, + { M_SOF10, "Progressive, arithmetic coding"}, + { M_SOF11, "Lossless, arithmetic coding"}, + { M_SOF13, "Differential sequential, arithmetic coding"}, + { M_SOF14, "Differential progressive, arithmetic coding"}, + { M_SOF15, "Differential lossless, arithmetic coding"}, +}; + +#define PROCESS_TABLE_SIZE (sizeof(ProcessTable) / sizeof(TagTable_t)) + +// 1 - "The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side." +// 2 - "The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side." +// 3 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side." +// 4 - "The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side." + +// 5 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual top." +// 6 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual top." +// 7 - "The 0th row is the visual right-hand side of of the image, and the 0th column is the visual bottom." +// 8 - "The 0th row is the visual left-hand side of of the image, and the 0th column is the visual bottom." + +// Note: The descriptions here are the same as the name of the command line +// option to pass to jpegtran to right the image + +static const char * OrientTab[9] = { + "Undefined", + "Normal", // 1 + "flip horizontal", // left right reversed mirror + "rotate 180", // 3 + "flip vertical", // upside down mirror + "transpose", // Flipped about top-left <--> bottom-right axis. + "rotate 90", // rotate 90 cw to right it. + "transverse", // flipped about top-right <--> bottom-left axis + "rotate 270", // rotate 270 to right it. +}; + +const int BytesPerFormat[] = {0,1,1,2,4,8,1,1,2,4,8,4,8}; + +//-------------------------------------------------------------------------- +// Describes tag values + +#define TAG_INTEROP_INDEX 0x0001 +#define TAG_INTEROP_VERSION 0x0002 +#define TAG_IMAGE_WIDTH 0x0100 +#define TAG_IMAGE_LENGTH 0x0101 +#define TAG_BITS_PER_SAMPLE 0x0102 +#define TAG_COMPRESSION 0x0103 +#define TAG_PHOTOMETRIC_INTERP 0x0106 +#define TAG_FILL_ORDER 0x010A +#define TAG_DOCUMENT_NAME 0x010D +#define TAG_IMAGE_DESCRIPTION 0x010E +#define TAG_MAKE 0x010F +#define TAG_MODEL 0x0110 +#define TAG_SRIP_OFFSET 0x0111 +#define TAG_ORIENTATION 0x0112 +#define TAG_SAMPLES_PER_PIXEL 0x0115 +#define TAG_ROWS_PER_STRIP 0x0116 +#define TAG_STRIP_BYTE_COUNTS 0x0117 +#define TAG_X_RESOLUTION 0x011A +#define TAG_Y_RESOLUTION 0x011B +#define TAG_PLANAR_CONFIGURATION 0x011C +#define TAG_RESOLUTION_UNIT 0x0128 +#define TAG_TRANSFER_FUNCTION 0x012D +#define TAG_SOFTWARE 0x0131 +#define TAG_DATETIME 0x0132 +#define TAG_ARTIST 0x013B +#define TAG_WHITE_POINT 0x013E +#define TAG_PRIMARY_CHROMATICITIES 0x013F +#define TAG_TRANSFER_RANGE 0x0156 +#define TAG_JPEG_PROC 0x0200 +#define TAG_THUMBNAIL_OFFSET 0x0201 +#define TAG_THUMBNAIL_LENGTH 0x0202 +#define TAG_Y_CB_CR_COEFFICIENTS 0x0211 +#define TAG_Y_CB_CR_SUB_SAMPLING 0x0212 +#define TAG_Y_CB_CR_POSITIONING 0x0213 +#define TAG_REFERENCE_BLACK_WHITE 0x0214 +#define TAG_RELATED_IMAGE_WIDTH 0x1001 +#define TAG_RELATED_IMAGE_LENGTH 0x1002 +#define TAG_CFA_REPEAT_PATTERN_DIM 0x828D +#define TAG_CFA_PATTERN1 0x828E +#define TAG_BATTERY_LEVEL 0x828F +#define TAG_COPYRIGHT 0x8298 +#define TAG_EXPOSURETIME 0x829A +#define TAG_FNUMBER 0x829D +#define TAG_IPTC_NAA 0x83BB +#define TAG_EXIF_OFFSET 0x8769 +#define TAG_INTER_COLOR_PROFILE 0x8773 +#define TAG_EXPOSURE_PROGRAM 0x8822 +#define TAG_SPECTRAL_SENSITIVITY 0x8824 +#define TAG_GPSINFO 0x8825 +#define TAG_ISO_EQUIVALENT 0x8827 +#define TAG_OECF 0x8828 +#define TAG_EXIF_VERSION 0x9000 +#define TAG_DATETIME_ORIGINAL 0x9003 +#define TAG_DATETIME_DIGITIZED 0x9004 +#define TAG_COMPONENTS_CONFIG 0x9101 +#define TAG_CPRS_BITS_PER_PIXEL 0x9102 +#define TAG_SHUTTERSPEED 0x9201 +#define TAG_APERTURE 0x9202 +#define TAG_BRIGHTNESS_VALUE 0x9203 +#define TAG_EXPOSURE_BIAS 0x9204 +#define TAG_MAXAPERTURE 0x9205 +#define TAG_SUBJECT_DISTANCE 0x9206 +#define TAG_METERING_MODE 0x9207 +#define TAG_LIGHT_SOURCE 0x9208 +#define TAG_FLASH 0x9209 +#define TAG_FOCALLENGTH 0x920A +#define TAG_SUBJECTAREA 0x9214 +#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_PIXEL_X_DIMENSION 0xA002 +#define TAG_PIXEL_Y_DIMENSION 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 +#define TAG_IMAGE_UNIQUE_ID 0xA420 + +static const TagTable_t TagTable[] = { + { TAG_INTEROP_INDEX, "InteropIndex"}, + { TAG_INTEROP_VERSION, "InteropVersion"}, + { TAG_IMAGE_WIDTH, "ImageWidth"}, + { TAG_IMAGE_LENGTH, "ImageLength"}, + { TAG_BITS_PER_SAMPLE, "BitsPerSample"}, + { TAG_COMPRESSION, "Compression"}, + { TAG_PHOTOMETRIC_INTERP, "PhotometricInterpretation"}, + { TAG_FILL_ORDER, "FillOrder"}, + { TAG_DOCUMENT_NAME, "DocumentName"}, + { TAG_IMAGE_DESCRIPTION, "ImageDescription"}, + { TAG_MAKE, "Make"}, + { TAG_MODEL, "Model"}, + { TAG_SRIP_OFFSET, "StripOffsets"}, + { TAG_ORIENTATION, "Orientation"}, + { TAG_SAMPLES_PER_PIXEL, "SamplesPerPixel"}, + { TAG_ROWS_PER_STRIP, "RowsPerStrip"}, + { TAG_STRIP_BYTE_COUNTS, "StripByteCounts"}, + { TAG_X_RESOLUTION, "XResolution"}, + { TAG_Y_RESOLUTION, "YResolution"}, + { TAG_PLANAR_CONFIGURATION, "PlanarConfiguration"}, + { TAG_RESOLUTION_UNIT, "ResolutionUnit"}, + { TAG_TRANSFER_FUNCTION, "TransferFunction"}, + { TAG_SOFTWARE, "Software"}, + { TAG_DATETIME, "DateTime"}, + { TAG_ARTIST, "Artist"}, + { TAG_WHITE_POINT, "WhitePoint"}, + { TAG_PRIMARY_CHROMATICITIES, "PrimaryChromaticities"}, + { TAG_TRANSFER_RANGE, "TransferRange"}, + { TAG_JPEG_PROC, "JPEGProc"}, + { TAG_THUMBNAIL_OFFSET, "ThumbnailOffset"}, + { TAG_THUMBNAIL_LENGTH, "ThumbnailLength"}, + { TAG_Y_CB_CR_COEFFICIENTS, "YCbCrCoefficients"}, + { TAG_Y_CB_CR_SUB_SAMPLING, "YCbCrSubSampling"}, + { TAG_Y_CB_CR_POSITIONING, "YCbCrPositioning"}, + { TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite"}, + { TAG_RELATED_IMAGE_WIDTH, "RelatedImageWidth"}, + { TAG_RELATED_IMAGE_LENGTH, "RelatedImageLength"}, + { TAG_CFA_REPEAT_PATTERN_DIM, "CFARepeatPatternDim"}, + { TAG_CFA_PATTERN1, "CFAPattern"}, + { TAG_BATTERY_LEVEL, "BatteryLevel"}, + { TAG_COPYRIGHT, "Copyright"}, + { TAG_EXPOSURETIME, "ExposureTime"}, + { TAG_FNUMBER, "FNumber"}, + { TAG_IPTC_NAA, "IPTC/NAA"}, + { TAG_EXIF_OFFSET, "ExifOffset"}, + { TAG_INTER_COLOR_PROFILE, "InterColorProfile"}, + { TAG_EXPOSURE_PROGRAM, "ExposureProgram"}, + { TAG_SPECTRAL_SENSITIVITY, "SpectralSensitivity"}, + { TAG_GPSINFO, "GPS Dir offset"}, + { TAG_ISO_EQUIVALENT, "ISOSpeedRatings"}, + { TAG_OECF, "OECF"}, + { TAG_EXIF_VERSION, "ExifVersion"}, + { TAG_DATETIME_ORIGINAL, "DateTimeOriginal"}, + { TAG_DATETIME_DIGITIZED, "DateTimeDigitized"}, + { TAG_COMPONENTS_CONFIG, "ComponentsConfiguration"}, + { TAG_CPRS_BITS_PER_PIXEL, "CompressedBitsPerPixel"}, + { TAG_SHUTTERSPEED, "ShutterSpeedValue"}, + { TAG_APERTURE, "ApertureValue"}, + { TAG_BRIGHTNESS_VALUE, "BrightnessValue"}, + { TAG_EXPOSURE_BIAS, "ExposureBiasValue"}, + { TAG_MAXAPERTURE, "MaxApertureValue"}, + { TAG_SUBJECT_DISTANCE, "SubjectDistance"}, + { TAG_METERING_MODE, "MeteringMode"}, + { TAG_LIGHT_SOURCE, "LightSource"}, + { TAG_FLASH, "Flash"}, + { TAG_FOCALLENGTH, "FocalLength"}, + { TAG_MAKER_NOTE, "MakerNote"}, + { TAG_USERCOMMENT, "UserComment"}, + { TAG_SUBSEC_TIME, "SubSecTime"}, + { TAG_SUBSEC_TIME_ORIG, "SubSecTimeOriginal"}, + { TAG_SUBSEC_TIME_DIG, "SubSecTimeDigitized"}, + { TAG_WINXP_TITLE, "Windows-XP Title"}, + { TAG_WINXP_COMMENT, "Windows-XP comment"}, + { TAG_WINXP_AUTHOR, "Windows-XP author"}, + { TAG_WINXP_KEYWORDS, "Windows-XP keywords"}, + { TAG_WINXP_SUBJECT, "Windows-XP subject"}, + { TAG_FLASH_PIX_VERSION, "FlashPixVersion"}, + { TAG_COLOR_SPACE, "ColorSpace"}, + { TAG_PIXEL_X_DIMENSION, "ExifImageWidth"}, + { TAG_PIXEL_Y_DIMENSION, "ExifImageLength"}, + { TAG_RELATED_AUDIO_FILE, "RelatedAudioFile"}, + { TAG_INTEROP_OFFSET, "InteroperabilityOffset"}, + { TAG_FLASH_ENERGY, "FlashEnergy"}, + { TAG_SPATIAL_FREQ_RESP, "SpatialFrequencyResponse"}, + { TAG_FOCAL_PLANE_XRES, "FocalPlaneXResolution"}, + { TAG_FOCAL_PLANE_YRES, "FocalPlaneYResolution"}, + { TAG_FOCAL_PLANE_UNITS, "FocalPlaneResolutionUnit"}, + { TAG_SUBJECT_LOCATION, "SubjectLocation"}, + { TAG_EXPOSURE_INDEX, "ExposureIndex"}, + { TAG_SENSING_METHOD, "SensingMethod"}, + { TAG_FILE_SOURCE, "FileSource"}, + { TAG_SCENE_TYPE, "SceneType"}, + { TAG_CFA_PATTERN, "CFA Pattern"}, + { TAG_CUSTOM_RENDERED, "CustomRendered"}, + { TAG_EXPOSURE_MODE, "ExposureMode"}, + { TAG_WHITEBALANCE, "WhiteBalance"}, + { TAG_DIGITALZOOMRATIO, "DigitalZoomRatio"}, + { TAG_FOCALLENGTH_35MM, "FocalLengthIn35mmFilm"}, + { TAG_SUBJECTAREA, "SubjectArea"}, + { TAG_SCENE_CAPTURE_TYPE, "SceneCaptureType"}, + { TAG_GAIN_CONTROL, "GainControl"}, + { TAG_CONTRAST, "Contrast"}, + { TAG_SATURATION, "Saturation"}, + { TAG_SHARPNESS, "Sharpness"}, + { TAG_DISTANCE_RANGE, "SubjectDistanceRange"}, + { TAG_IMAGE_UNIQUE_ID, "ImageUniqueId"}, +} ; + +#define TAG_TABLE_SIZE (sizeof(TagTable) / sizeof(TagTable_t)) + +//-------------------------------------------------------------------------- +// Convert a 16 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +int Get16u(void * Short) +{ + if (MotorolaOrder){ + return (((uchar *)Short)[0] << 8) | ((uchar *)Short)[1]; + }else{ + return (((uchar *)Short)[1] << 8) | ((uchar *)Short)[0]; + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit signed value from file's native byte order +//-------------------------------------------------------------------------- +int Get32s(void * Long) +{ + if (MotorolaOrder){ + return ((( char *)Long)[0] << 24) | (((uchar *)Long)[1] << 16) + | (((uchar *)Long)[2] << 8 ) | (((uchar *)Long)[3] << 0 ); + }else{ + return ((( char *)Long)[3] << 24) | (((uchar *)Long)[2] << 16) + | (((uchar *)Long)[1] << 8 ) | (((uchar *)Long)[0] << 0 ); + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit unsigned value to file's native byte order +//-------------------------------------------------------------------------- +void Put32u(void * Value, unsigned PutValue) +{ + if (MotorolaOrder){ + ((uchar *)Value)[0] = (uchar)(PutValue>>24); + ((uchar *)Value)[1] = (uchar)(PutValue>>16); + ((uchar *)Value)[2] = (uchar)(PutValue>>8); + ((uchar *)Value)[3] = (uchar)PutValue; + }else{ + ((uchar *)Value)[0] = (uchar)PutValue; + ((uchar *)Value)[1] = (uchar)(PutValue>>8); + ((uchar *)Value)[2] = (uchar)(PutValue>>16); + ((uchar *)Value)[3] = (uchar)(PutValue>>24); + } +} + +//-------------------------------------------------------------------------- +// Convert a 32 bit unsigned value from file's native byte order +//-------------------------------------------------------------------------- +unsigned Get32u(void * Long) +{ + return (unsigned)Get32s(Long) & 0xffffffff; +} + +//-------------------------------------------------------------------------- +// Display a number as one of its many formats +//-------------------------------------------------------------------------- +void PrintFormatNumber(void * ValuePtr, int Format, int ByteCount) +{ + int s,n; + + for(n=0;n<16;n++){ + switch(Format){ + case FMT_SBYTE: + case FMT_BYTE: printf("%02x",*(uchar *)ValuePtr); s=1; break; + case FMT_USHORT: printf("%d",Get16u(ValuePtr)); s=2; break; + case FMT_ULONG: + case FMT_SLONG: printf("%d",Get32s(ValuePtr)); s=4; break; + case FMT_SSHORT: printf("%hd",(signed short)Get16u(ValuePtr)); s=2; break; + case FMT_URATIONAL: + printf("%u/%u",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr)); + s = 8; + break; + + case FMT_SRATIONAL: + printf("%d/%d",Get32s(ValuePtr), Get32s(4+(char *)ValuePtr)); + s = 8; + break; + + case FMT_SINGLE: printf("%f",(double)*(float *)ValuePtr); s=8; break; + case FMT_DOUBLE: printf("%f",*(double *)ValuePtr); s=8; break; + default: + printf("Unknown format %d:", Format); + return; + } + ByteCount -= s; + if (ByteCount <= 0) break; + printf(", "); + ValuePtr = (void *)((char *)ValuePtr + s); + + } + if (n >= 16) printf("..."); +} + + +//-------------------------------------------------------------------------- +// Evaluate number, be it int, rational, or float from directory. +//-------------------------------------------------------------------------- +double ConvertAnyFormat(void * ValuePtr, int Format) +{ + double Value; + Value = 0; + + switch(Format){ + case FMT_SBYTE: Value = *(signed char *)ValuePtr; break; + case FMT_BYTE: Value = *(uchar *)ValuePtr; break; + + case FMT_USHORT: Value = Get16u(ValuePtr); break; + case FMT_ULONG: Value = Get32u(ValuePtr); break; + + case FMT_URATIONAL: + case FMT_SRATIONAL: + { + int Num,Den; + Num = Get32s(ValuePtr); + Den = Get32s(4+(char *)ValuePtr); + if (Den == 0){ + Value = 0; + }else{ + if (Format == FMT_SRATIONAL){ + Value = (double)Num/Den; + }else{ + Value = (double)(unsigned)Num/(double)(unsigned)Den; + } + } + break; + } + + case FMT_SSHORT: Value = (signed short)Get16u(ValuePtr); break; + case FMT_SLONG: Value = Get32s(ValuePtr); break; + + // Not sure if this is correct (never seen float used in Exif format) + case FMT_SINGLE: Value = (double)*(float *)ValuePtr; break; + case FMT_DOUBLE: Value = *(double *)ValuePtr; break; + + default: + ErrNonfatal("Illegal format code %d in Exif header",Format,0); + } + return Value; +} + +//-------------------------------------------------------------------------- +// Process one of the nested EXIF directories. +//-------------------------------------------------------------------------- +static void ProcessExifDir(unsigned char * DirStart, unsigned char * OffsetBase, + unsigned ExifLength, int NestingLevel) +{ + int de; + int a; + int NumDirEntries; + char IndentString[25]; + + if (NestingLevel > 4){ + ErrNonfatal("Maximum Exif directory nesting exceeded (corrupt Exif header)", 0,0); + return; + } + + memset(IndentString, ' ', 25); + IndentString[NestingLevel * 4] = '\0'; + + + NumDirEntries = Get16u(DirStart); + #define DIR_ENTRY_ADDR(Start, Entry) (Start+2+12*(Entry)) + + { + unsigned char * DirEnd; + DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries); + if (DirEnd+4 > (OffsetBase+ExifLength)){ + if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength){ + // Version 1.3 of jhead would truncate a bit too much. + // This also caught later on as well. + }else{ + ErrNonfatal("Illegally sized Exif subdirectory (%d entries)",NumDirEntries,0); + return; + } + } + } + + if (ShowTags){ + printf("(dir has %d entries)\n",NumDirEntries); + } + + for (de=0;de= NUM_FORMATS) { + // (-1) catches illegal zero case as unsigned underflows to positive large. + ErrNonfatal("Illegal number format %d for tag %04x in Exif", Format, Tag); + continue; + } + + if ((unsigned)Components > 0x10000){ + ErrNonfatal("Too many components %d for tag %04x in Exif", 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 in Exif", Tag,0); + continue; + } + ValuePtr = OffsetBase+OffsetVal; + }else{ + // 4 bytes or less and value is in the dir entry itself + ValuePtr = DirEntry+8; + } + + if (ShowTags){ + // Show tag name + for (a=0;;a++){ + if (a >= TAG_TABLE_SIZE){ + printf("%s Unknown Tag %04x Value = ", IndentString, Tag); + break; + } + if (TagTable[a].Tag == Tag){ + printf("%s %s = ",IndentString, TagTable[a].Desc); + break; + } + } + + // Show tag value. + switch(Format){ + case FMT_BYTE: + if(ByteCount>1){ + printf("%.*ls\n", ByteCount/2, (wchar_t *)ValuePtr); + }else{ + PrintFormatNumber(ValuePtr, Format, ByteCount); + printf("\n"); + } + break; + + case FMT_UNDEFINED: + // Undefined is typically an ascii string. + + case FMT_STRING: + // String arrays printed without function call (different from int arrays) + { + int NoPrint = 0; + printf("\""); + for (a=0;a= 32){ + putchar(ValuePtr[a]); + NoPrint = 0; + }else{ + // Avoiding indicating too many unprintable characters of proprietary + // bits of binary information this program may not know how to parse. + if (!NoPrint && a != ByteCount-1){ + putchar('?'); + NoPrint = 1; + } + } + } + printf("\"\n"); + } + break; + + default: + // Handle arrays of numbers later (will there ever be?) + PrintFormatNumber(ValuePtr, Format, ByteCount); + printf("\n"); + } + } + + // Extract useful components of tag + switch(Tag){ + + case TAG_MAKE: + strncpy(ImageInfo.CameraMake, (char *)ValuePtr, ByteCount < 31 ? ByteCount : 31); + break; + + case TAG_MODEL: + strncpy(ImageInfo.CameraModel, (char *)ValuePtr, ByteCount < 39 ? ByteCount : 39); + break; + + case TAG_DATETIME_ORIGINAL: + // If we get a DATETIME_ORIGINAL, we use that one. + strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); + // Fallthru... + + case TAG_DATETIME_DIGITIZED: + case TAG_DATETIME: + if (!isdigit(ImageInfo.DateTime[0])){ + // If we don't already have a DATETIME_ORIGINAL, use whatever + // time fields we may have. + strncpy(ImageInfo.DateTime, (char *)ValuePtr, 19); + } + break; + + case TAG_FNUMBER: + // Simplest way of expressing aperture, so I trust it the most. + // (overwrite previously computd value if there is one) + ImageInfo.ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format); + break; + +#if 0 // don't pull in extra math stuff. + case TAG_APERTURE: + case TAG_MAXAPERTURE: + // More relevant info always comes earlier, so only use this field if we don't + // have appropriate aperture information yet. + if (ImageInfo.ApertureFNumber == 0){ + ImageInfo.ApertureFNumber + = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2)*0.5); + } + break; + + case TAG_SHUTTERSPEED: + // More complicated way of expressing exposure time, so only use + // this value if we don't already have it from somewhere else. + if (ImageInfo.ExposureTime == 0){ + ImageInfo.ExposureTime + = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2))); + } + break; +#endif + case TAG_FOCALLENGTH: + // Nice digital cameras actually save the focal length as a function + // of how farthey are zoomed in. + ImageInfo.FocalLength = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_SUBJECT_DISTANCE: + // Inidcates the distacne the autofocus camera is focused to. + // Tends to be less accurate as distance increases. + ImageInfo.Distance = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURETIME: + // Simplest way of expressing exposure time, so I trust it most. + // (overwrite previously computd value if there is one) + ImageInfo.ExposureTime = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + + + case TAG_FLASH: + ImageInfo.FlashUsed=(int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ORIENTATION: + ImageInfo.Orientation = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_PIXEL_Y_DIMENSION: + case TAG_PIXEL_X_DIMENSION: + // Use largest of height and width to deal with images that have been + // rotated to portrait format. + a = (int)ConvertAnyFormat(ValuePtr, Format); + if (ExifImageWidth < a) ExifImageWidth = a; + break; + + case TAG_FOCAL_PLANE_XRES: + FocalplaneXRes = ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_FOCAL_PLANE_UNITS: + switch((int)ConvertAnyFormat(ValuePtr, Format)){ + case 1: FocalplaneUnits = 25.4; break; // inch + case 2: + // According to the information I was using, 2 means meters. + // But looking at the Cannon powershot's files, inches is the only + // sensible value. + FocalplaneUnits = 25.4; + break; + + case 3: FocalplaneUnits = 10; break; // centimeter + case 4: FocalplaneUnits = 1; break; // millimeter + case 5: FocalplaneUnits = .001; break; // micrometer + } + break; + + case TAG_EXPOSURE_BIAS: + ImageInfo.ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_WHITEBALANCE: + ImageInfo.Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_LIGHT_SOURCE: + ImageInfo.LightSource = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_METERING_MODE: + ImageInfo.MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_PROGRAM: + ImageInfo.ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXPOSURE_INDEX: + if (ImageInfo.ISOequivalent == 0){ + // Exposure index and ISO equivalent are often used interchangeably, + // so we will do the same in jhead. + // http://photography.about.com/library/glossary/bldef_ei.htm + ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); + } + break; + + case TAG_EXPOSURE_MODE: + ImageInfo.ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_ISO_EQUIVALENT: + ImageInfo.ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_DIGITALZOOMRATIO: + ImageInfo.DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format); + break; + + case TAG_EXIF_OFFSET: + if (ShowTags) printf("%s Exif Dir:",IndentString); + + case TAG_INTEROP_OFFSET: + if (Tag == TAG_INTEROP_OFFSET && ShowTags) printf("%s Interop Dir:",IndentString); + { + unsigned char * SubdirStart; + SubdirStart = OffsetBase + Get32u(ValuePtr); + if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength){ + ErrNonfatal("Illegal Exif or interop ofset directory link",0,0); + }else{ + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); + } + continue; + } + break; + + case TAG_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; + + + + case TAG_X_RESOLUTION: + if (NestingLevel==0) {// Only use the values from the top level directory + ImageInfo.xResolution = (float)ConvertAnyFormat(ValuePtr,Format); + // if yResolution has not been set, use the value of xResolution + if (ImageInfo.yResolution == 0.0) ImageInfo.yResolution = ImageInfo.xResolution; + } + break; + + case TAG_Y_RESOLUTION: + if (NestingLevel==0) {// Only use the values from the top level directory + ImageInfo.yResolution = (float)ConvertAnyFormat(ValuePtr,Format); + // if xResolution has not been set, use the value of yResolution + if (ImageInfo.xResolution == 0.0) ImageInfo.xResolution = ImageInfo.yResolution; + } + break; + + case TAG_RESOLUTION_UNIT: + if (NestingLevel==0) {// Only use the values from the top level directory + ImageInfo.ResolutionUnit = (int) ConvertAnyFormat(ValuePtr,Format); + } + break; + + } + } + + + { + // In addition to linking to subdirectories via exif tags, + // there's also a potential link to another directory at the end of each + // directory. this has got to be the result of a committee! + unsigned char * SubdirStart; + unsigned Offset; + + if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength){ + Offset = Get32u(DirStart+2+12*NumDirEntries); + if (Offset){ + SubdirStart = OffsetBase + Offset; + if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase){ + if (SubdirStart > OffsetBase && SubdirStart < OffsetBase+ExifLength+20){ + // Jhead 1.3 or earlier would crop the whole directory! + // As Jhead produces this form of format incorrectness, + // I'll just let it pass silently + if (ShowTags) printf("Thumbnail removed with Jhead 1.3 or earlier\n"); + }else{ + ErrNonfatal("Illegal subdirectory link in Exif header",0,0); + } + }else{ + if (SubdirStart <= OffsetBase+ExifLength){ + if (ShowTags) printf("%s Continued directory ",IndentString); + ProcessExifDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1); + } + } + } + }else{ + // The exif header ends before the last next directory pointer. + } + } +} + + +//-------------------------------------------------------------------------- +// Process a EXIF marker +// Describes all the drivel that most digital cameras include... +//-------------------------------------------------------------------------- +void process_EXIF (unsigned char * ExifSection, unsigned int length) +{ + unsigned int FirstOffset; + + FocalplaneXRes = 0; + FocalplaneUnits = 0; + ExifImageWidth = 0; + + if (ShowTags){ + printf("Exif header %u bytes long\n",length); + } + + { // Check the EXIF header component + static uchar ExifHeader[] = "Exif\0\0"; + if (memcmp(ExifSection+2, ExifHeader,6)){ + ErrNonfatal("Incorrect Exif header",0,0); + return; + } + } + + if (memcmp(ExifSection+8,"II",2) == 0){ + if (ShowTags) printf("Exif section in Intel order\n"); + MotorolaOrder = 0; + }else{ + if (memcmp(ExifSection+8,"MM",2) == 0){ + if (ShowTags) printf("Exif section in Motorola order\n"); + MotorolaOrder = 1; + }else{ + ErrNonfatal("Invalid Exif alignment marker.",0,0); + return; + } + } + + // Check the next value for correctness. + if (Get16u(ExifSection+10) != 0x2a){ + ErrNonfatal("Invalid Exif start (1)",0,0); + return; + } + + FirstOffset = Get32u(ExifSection+12); + if (FirstOffset < 8 || FirstOffset > 16){ + if (FirstOffset < 16 || FirstOffset > length-16){ + ErrNonfatal("invalid offset for first Exif IFD value",0,0); + return; + } + // Usually set to 8, but other values valid too. + ErrNonfatal("Suspicious offset of first Exif IFD value",0,0); + } + + DirWithThumbnailPtrs = NULL; + + // First directory starts 16 bytes in. All offset are relative to 8 bytes in. + ProcessExifDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0); +} + + + + +//-------------------------------------------------------------------------- +// Convert exif time to Unix time structure +//-------------------------------------------------------------------------- +int Exif2tm(struct tm * timeptr, char * ExifTime) +{ + int a; + + timeptr->tm_wday = -1; + + // Check for format: YYYY:MM:DD HH:MM:SS format. + // Date and time normally separated by a space, but also seen a ':' there, so + // skip the middle space with '%*c' so it can be any character. + timeptr->tm_sec = 0; + a = sscanf(ExifTime, "%d%*c%d%*c%d%*c%d:%d:%d", + &timeptr->tm_year, &timeptr->tm_mon, &timeptr->tm_mday, + &timeptr->tm_hour, &timeptr->tm_min, &timeptr->tm_sec); + + if (a >= 5){ + if (timeptr->tm_year <= 12 && timeptr->tm_mday > 2000 && ExifTime[2] == '.'){ + // LG Electronics VX-9700 seems to encode the date as 'MM.DD.YYYY HH:MM' + // can't these people read the standard? At least they left enough room + // in the header to put an Exif format date in there. + int tmp; + tmp = timeptr->tm_year; + timeptr->tm_year = timeptr->tm_mday; + timeptr->tm_mday = timeptr->tm_mon; + timeptr->tm_mon = tmp; + } + + // Accept five or six parameters. Some cameras do not store seconds. + timeptr->tm_isdst = -1; + timeptr->tm_mon -= 1; // Adjust for unix zero-based months + timeptr->tm_year -= 1900; // Adjust for year starting at 1900 + return TRUE; // worked. + } + + return FALSE; // Wasn't in Exif date format. +} + + + +//-------------------------------------------------------------------------- +// Show the collected image info, displaying camera F-stop and shutter speed +// in a consistent and legible fashion. +//-------------------------------------------------------------------------- +void ShowImageInfo(int ShowFileInfo) +{ + if (ShowFileInfo){ + printf("File name : %s\n",ImageInfo.FileName); + printf("File size : %d bytes\n",ImageInfo.FileSize); + } + + if (ImageInfo.CameraMake[0]){ + printf("Camera make : %s\n",ImageInfo.CameraMake); + printf("Camera model : %s\n",ImageInfo.CameraModel); + } + if (ImageInfo.DateTime[0]){ + printf("Date/Time : %s\n",ImageInfo.DateTime); + } + printf("Resolution : %d x %d\n",ImageInfo.Width, ImageInfo.Height); + + if (ImageInfo.Orientation > 1 && ImageInfo.Orientation <=8){ + // Only print orientation if one was supplied, and if its not 1 (normal orientation) + printf("Orientation : %s\n", OrientTab[ImageInfo.Orientation]); + } + + if (ImageInfo.FlashUsed >= 0){ + if (ImageInfo.FlashUsed & 1){ + printf("Flash used : Yes"); + switch (ImageInfo.FlashUsed){ + case 0x5: printf(" (Strobe light not detected)"); break; + case 0x7: printf(" (Strobe light detected) "); break; + case 0x9: printf(" (manual)"); break; + case 0xd: printf(" (manual, return light not detected)"); break; + case 0xf: printf(" (manual, return light detected)"); break; + case 0x19:printf(" (auto)"); break; + case 0x1d:printf(" (auto, return light not detected)"); break; + case 0x1f:printf(" (auto, return light detected)"); break; + case 0x41:printf(" (red eye reduction mode)"); break; + case 0x45:printf(" (red eye reduction mode return light not detected)"); break; + case 0x47:printf(" (red eye reduction mode return light detected)"); break; + case 0x49:printf(" (manual, red eye reduction mode)"); break; + case 0x4d:printf(" (manual, red eye reduction mode, return light not detected)"); break; + case 0x4f:printf(" (red eye reduction mode, return light detected)"); break; + case 0x59:printf(" (auto, red eye reduction mode)"); break; + case 0x5d:printf(" (auto, red eye reduction mode, return light not detected)"); break; + case 0x5f:printf(" (auto, red eye reduction mode, return light detected)"); break; + } + }else{ + printf("Flash used : No"); + switch (ImageInfo.FlashUsed){ + case 0x18:printf(" (auto)"); break; + } + } + printf("\n"); + } + + + if (ImageInfo.FocalLength){ + printf("Focal length : %4.1fmm",(double)ImageInfo.FocalLength); + if (ImageInfo.FocalLength35mmEquiv){ + printf(" (35mm equivalent: %dmm)", ImageInfo.FocalLength35mmEquiv); + } + printf("\n"); + } + + if (ImageInfo.DigitalZoomRatio > 1){ + // Digital zoom used. Shame on you! + printf("Digital Zoom : %1.3fx\n", (double)ImageInfo.DigitalZoomRatio); + } + + if (ImageInfo.ExposureTime){ + if (ImageInfo.ExposureTime < 0.010){ + printf("Exposure time: %6.4f s ",(double)ImageInfo.ExposureTime); + }else{ + printf("Exposure time: %5.3f s ",(double)ImageInfo.ExposureTime); + } + if (ImageInfo.ExposureTime <= 0.5){ + printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime)); + } + printf("\n"); + } + if (ImageInfo.ApertureFNumber){ + printf("Aperture : f/%3.1f\n",(double)ImageInfo.ApertureFNumber); + } + if (ImageInfo.Distance){ + if (ImageInfo.Distance < 0){ + printf("Focus dist. : Infinite\n"); + }else{ + printf("Focus dist. : %4.2fm\n",(double)ImageInfo.Distance); + } + } + + if (ImageInfo.ISOequivalent){ + printf("ISO equiv. : %2d\n",(int)ImageInfo.ISOequivalent); + } + + if (ImageInfo.ExposureBias){ + // If exposure bias was specified, but set to zero, presumably its no bias at all, + // so only show it if its nonzero. + printf("Exposure bias: %4.2f\n",(double)ImageInfo.ExposureBias); + } + + switch(ImageInfo.Whitebalance) { + case 1: + printf("Whitebalance : Manual\n"); + break; + case 0: + printf("Whitebalance : Auto\n"); + break; + } + + //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both + switch(ImageInfo.LightSource) { + case 1: + printf("Light Source : Daylight\n"); + break; + case 2: + printf("Light Source : Fluorescent\n"); + break; + case 3: + printf("Light Source : Incandescent\n"); + break; + case 4: + printf("Light Source : Flash\n"); + break; + case 9: + printf("Light Source : Fine weather\n"); + break; + case 11: + printf("Light Source : Shade\n"); + break; + default:; //Quercus: 17-1-2004 There are many more modes for this, check Exif2.2 specs + // If it just says 'unknown' or we don't know it, then + // don't bother showing it - it doesn't add any useful information. + } + + if (ImageInfo.MeteringMode > 0){ // 05-jan-2001 vcs + printf("Metering Mode: "); + switch(ImageInfo.MeteringMode) { + case 1: printf("average\n"); break; + case 2: printf("center weight\n"); break; + case 3: printf("spot\n"); break; + case 4: printf("multi spot\n"); break; + case 5: printf("pattern\n"); break; + case 6: printf("partial\n"); break; + case 255: printf("other\n"); break; + default: printf("unknown (%d)\n",ImageInfo.MeteringMode); break; + } + } + + if (ImageInfo.ExposureProgram){ // 05-jan-2001 vcs + switch(ImageInfo.ExposureProgram) { + case 1: + printf("Exposure : Manual\n"); + break; + case 2: + printf("Exposure : program (auto)\n"); + break; + case 3: + printf("Exposure : aperture priority (semi-auto)\n"); + break; + case 4: + printf("Exposure : shutter priority (semi-auto)\n"); + break; + case 5: + printf("Exposure : Creative Program (based towards depth of field)\n"); + break; + case 6: + printf("Exposure : Action program (based towards fast shutter speed)\n"); + break; + case 7: + printf("Exposure : Portrait Mode\n"); + break; + case 8: + printf("Exposure : LandscapeMode \n"); + break; + default: + break; + } + } + switch(ImageInfo.ExposureMode){ + case 0: // Automatic (not worth cluttering up output for) + break; + case 1: printf("Exposure Mode: Manual\n"); + break; + case 2: printf("Exposure Mode: Auto bracketing\n"); + break; + } + + if (ImageInfo.DistanceRange) { + printf("Focus range : "); + switch(ImageInfo.DistanceRange) { + case 1: + printf("macro"); + break; + case 2: + printf("close"); + break; + case 3: + printf("distant"); + break; + } + printf("\n"); + } +} + + +//-------------------------------------------------------------------------- +// Summarize highlights of image info on one line (suitable for grep-ing) +//-------------------------------------------------------------------------- +void ShowConciseImageInfo(void) +{ + printf("\"%s\"",ImageInfo.FileName); + + printf(" %dx%d",ImageInfo.Width, ImageInfo.Height); + + if (ImageInfo.ExposureTime){ + if (ImageInfo.ExposureTime <= 0.5){ + printf(" (1/%d)",(int)(0.5 + 1/ImageInfo.ExposureTime)); + }else{ + printf(" (%1.1f)",ImageInfo.ExposureTime); + } + } + + if (ImageInfo.ApertureFNumber){ + printf(" f/%3.1f",(double)ImageInfo.ApertureFNumber); + } + + if (ImageInfo.FocalLength35mmEquiv){ + printf(" f(35)=%dmm",ImageInfo.FocalLength35mmEquiv); + } + + if (ImageInfo.FlashUsed >= 0 && ImageInfo.FlashUsed & 1){ + printf(" (flash)"); + } + + printf("\n"); +} diff --git a/src/imgcomp.h b/src/imgcomp.h new file mode 100644 index 0000000..07d077a --- /dev/null +++ b/src/imgcomp.h @@ -0,0 +1,59 @@ +//---------------------------------------------------------------------------- +// image comparator tool prototypes +//---------------------------------------------------------------------------- + +typedef struct { + int width; + int height; + int components; + unsigned char pixels[1]; +}MemImage_t; + +typedef struct { + int x1, x2; + int y1, y2; +}Region_t; + +#define MAX_EXCLUDE_REGIONS 5 +typedef struct { + Region_t DetectReg; + Region_t ExcludeReg[MAX_EXCLUDE_REGIONS]; + int NumExcludeReg; +}Regions_t; + + +typedef struct { + int DiffLevel; + int x, y; +}TriggerInfo_t; + +MemImage_t MemImage; +extern int NewestAverageBright; +extern int Verbosity; + +extern char SaveDir[200]; +extern char SaveNames[200]; + + +extern Regions_t Regions; + +// compare.c functions +TriggerInfo_t ComparePix(MemImage_t * pic1, MemImage_t * pic2, char * DebugImgName); +void ProcessDiffMap(MemImage_t * MapPic); + +// jpeg2mem.c functions +MemImage_t * LoadJPEG(char* FileName, int scale_denom, int discard_colors, int ParseExif); +void WritePpmFile(char * FileName, MemImage_t *MemImage); + +// start_raspistill functions +int manage_raspistill(int HaveNewImages); +extern char raspistill_cmd[200]; +extern char blink_cmd[200]; +void run_blink_program(void); + +// util.c functions +char * CatPath(char *Dir, char * FileName); +char ** GetSortedDir(char * Directory, int * NumFiles); +void FreeDir(char ** FileNames, int NumEntries); +char * BackupPicture(char * Name, time_t mtime, int DiffMag); +int CopyFile(char * src, char * dest); \ No newline at end of file diff --git a/src/jhead.h b/src/jhead.h new file mode 100644 index 0000000..b7f95a0 --- /dev/null +++ b/src/jhead.h @@ -0,0 +1,182 @@ +//-------------------------------------------------------------------------- +// 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. +//-------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include + +//-------------------------------------------------------------------------- + +#ifdef _WIN32 + #include + + // Make the Microsoft Visual c 10 deprecate warnings go away. + // The _CRT_SECURE_NO_DEPRECATE doesn't do the trick like it should. + #define unlink _unlink + #define chmod _chmod + #define access _access + #define mktemp _mktemp +#else + #include + #include + #include + #include + #include +#endif + + +typedef unsigned char uchar; + +#ifndef TRUE + #define TRUE 1 + #define FALSE 0 +#endif + +#define MAX_COMMENT_SIZE 16000 + +#ifdef _WIN32 + #define PATH_MAX _MAX_PATH + #define SLASH '\\' +#else + #ifndef PATH_MAX + #define PATH_MAX 1024 + #endif + #define SLASH '/' +#endif + + +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]; + + unsigned FileSize; + char CameraMake [32]; + char CameraModel [40]; + char DateTime [20]; + unsigned Height, Width; + int Orientation; + int FlashUsed; + float FocalLength; + float ExposureTime; + float ApertureFNumber; + float Distance; + 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; + + float xResolution; + float yResolution; + int ResolutionUnit; + +// 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; +}ImageInfo_t; + + + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + + +// prototypes for jhead.c functions +int ReadExifPart(FILE * infile); +void ErrFatal(const char * msg); +void ErrNonfatal(const char * msg, int a1, int a2); + + +// Prototypes for exif.c functions. +int Exif2tm(struct tm * timeptr, char * ExifTime); +void process_EXIF (unsigned char * CharBuf, unsigned int length); +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 + + + + +// 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 Quantization Table +#define M_DHT 0xC4 // Define Huffmann Table +#define M_DRI 0xDD +#define M_IPTC 0xED // IPTC marker diff --git a/src/jpeg2mem.c b/src/jpeg2mem.c new file mode 100644 index 0000000..0037a40 --- /dev/null +++ b/src/jpeg2mem.c @@ -0,0 +1,141 @@ +//---------------------------------------------------------------------------------------- +// Code for using libjpeg to read an image into memory. +// from: stackoverflow.com/questions/5616216/need-help-in-reading-jpeg-file-using-libjpeg +//---------------------------------------------------------------------------------------- +#include +#include +#include +#include +#include +#include "../libjpeg/jpeglib.h" +#include "../libjpeg/jerror.h" +#include +#include "imgcomp.h" +#include "jhead.h" + +//---------------------------------------------------------------------------------------- +// for libjpeg - don't abort on corrupt jpeg data. +//---------------------------------------------------------------------------------------- +struct my_error_mgr { + struct jpeg_error_mgr pub; // "public" fields + + jmp_buf setjmp_buffer; // for return to caller +}; +typedef struct my_error_mgr * my_error_ptr; + +void my_error_exit (j_common_ptr cinfo) +{ + // cinfo->err really points to a my_error_mgr struct, so coerce pointer + my_error_ptr myerr = (my_error_ptr) cinfo->err; + + // Always display the message. + (*cinfo->err->output_message) (cinfo); + + // Return control to the setjmp point + longjmp(myerr->setjmp_buffer, 1); +} + + +//---------------------------------------------------------------------------------------- +// Use libjpeg to load an image into memory, optionally scale it. +//---------------------------------------------------------------------------------------- +MemImage_t * LoadJPEG(char* FileName, int scale_denom, int discard_colors, int ParseExif) +{ + unsigned long data_size; // length of the file + struct jpeg_decompress_struct info; //for our jpeg info + struct my_error_mgr jerr; + MemImage_t *MemImage; + int components; + + FILE* file = fopen(FileName, "rb"); // open the file + + //if the jpeg file doesn't load + if(!file) { + fprintf(stderr, "Error reading JPEG file %s!\n", FileName); + return NULL; + } + + if (ParseExif){ + // Get the exif header + ReadExifPart(file); + } + + info.err = jpeg_std_error(& jerr.pub); + jerr.pub.error_exit = my_error_exit; // Override library's default exit on error. + + + if (setjmp(jerr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_decompress(&info); + fclose(file); + return NULL; + } + + jpeg_create_decompress(& info); // fills info structure + + jpeg_stdio_src(&info, file); + jpeg_read_header(&info, TRUE); // read jpeg file header + + if (discard_colors) info.out_color_space = JCS_GRAYSCALE; + + info.scale_num = 1; + info.scale_denom = scale_denom; + + info.num_components = 3; + info.do_fancy_upsampling = FALSE; + + jpeg_start_decompress(&info); // decompress the file + + components = info.out_color_space == JCS_GRAYSCALE ? 1 : 3; + + data_size = info.output_width + * info.output_height * components; + + MemImage = malloc(data_size+offsetof(MemImage_t, pixels)); + if (!MemImage){ + fprintf(stderr, "Image malloc failed"); + return 0; + } + MemImage->width = info.output_width; + MemImage->height = info.output_height; + MemImage->components = components; + + //-------------------------------------------- + // read scanlines one at a time. Assumes an RGB image + //-------------------------------------------- + while (info.output_scanline < info.output_height){ // loop + unsigned char * rowptr[1]; // pointer to an array + // Enable jpeg_read_scanlines() to fill our jdata array + rowptr[0] = MemImage->pixels + + components * info.output_width * info.output_scanline; + jpeg_read_scanlines(&info, rowptr, 1); + } + //--------------------------------------------------- + + jpeg_finish_decompress(&info); //finish decompressing + fclose(file); //close the file + + jpeg_destroy_decompress(&info); + + return MemImage; +} + + + +//---------------------------------------------------------------------------------------- +// Write an image to disk - for testing. Not jpeg (ppm is a much simpler format) +//---------------------------------------------------------------------------------------- +void WritePpmFile(char * FileName, MemImage_t *MemImage) +{ + FILE * outfile; + outfile = fopen(FileName,"wb"); + if (outfile == NULL){ + printf("could not open outfile\n"); + return; + } + fprintf(outfile, "P%c\n%d %d\n%d\n", MemImage->components == 3 ? '6' : '5', + MemImage->width, MemImage->height, 255); + fwrite(MemImage->pixels, 1, MemImage->width * MemImage->height*MemImage->components, outfile); + fclose(outfile); +} diff --git a/src/jpgfile.c b/src/jpgfile.c new file mode 100644 index 0000000..672b2ea --- /dev/null +++ b/src/jpgfile.c @@ -0,0 +1,144 @@ +//-------------------------------------------------------------------------- +// Module to make exif.c from jhead project work. +// +// Matthias Wandel +//-------------------------------------------------------------------------- +#include "jhead.h" + +// Storage for simplified info extracted from file. +ImageInfo_t ImageInfo; + +int ShowTags = 0; +int SupressNonFatalErrors = 0; + +//-------------------------------------------------------------------------- +// Report non fatal errors. Now that microsoft.net modifies exif headers, +// there's corrupted ones, and there could be more in the future. +//-------------------------------------------------------------------------- +void ErrNonfatal(const char * msg, int a1, int a2) +{ + if (SupressNonFatalErrors) return; + + fprintf(stderr,"\nNonfatal Error : "); + fprintf(stderr, msg, a1, a2); + fprintf(stderr, "\n"); +} + + +//-------------------------------------------------------------------------- +// Parse the marker stream until SOS or EOI is seen; +//-------------------------------------------------------------------------- +static int FindExifInFile (FILE * infile) +{ + int a; + a = fgetc(infile); + + if (a != 0xff || fgetc(infile) != M_SOI){ + return FALSE; + } + + for(;;){ + int itemlen; + int marker = 0; + int prev; + int ll,lh, got; + uchar * Data; + + for (a=0;;a++){ + prev = marker; + marker = fgetc(infile); + if (marker != 0xff && prev == 0xff) break; + if (marker == EOF){ + fprintf(stderr, "Unexpected end of file"); + return 0; + } + } + + if (a > 10){ + fprintf(stderr, "Extraneous %d padding bytes before section %02X",a-1,marker); + return 0; + } + + if (marker != M_EXIF){ + printf("Image did not start with exif section\n"); + return 0; + } + + // Read the length of the section. + lh = fgetc(infile); + ll = fgetc(infile); + if (lh == EOF || ll == EOF){ + fprintf(stderr, "Unexpected end of file"); + return 0; + } + + itemlen = (lh << 8) | ll; + + if (itemlen < 2){ + fprintf(stderr, "invalid marker"); + return 0; + } + + Data = (uchar *)malloc(itemlen); + if (Data == NULL){ + fprintf(stderr, "Could not allocate memory"); + return 0; + } + + // 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){ + fprintf(stderr, "Premature end of file?"); + return 0; + } + + if (memcmp(Data+2, "Exif", 4) == 0){ + process_EXIF(Data, itemlen); + free(Data); + return 1; + } + } + return 0; +} + +//-------------------------------------------------------------------------- +// Parse the marker stream until SOS or EOI is seen; +//-------------------------------------------------------------------------- +int ReadExifPart(FILE * infile) +{ + int a; + a = FindExifInFile(infile); + rewind(infile); // go back to start for libexif to read the image. + return a; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..04db9ac --- /dev/null +++ b/src/main.c @@ -0,0 +1,611 @@ +//----------------------------------------------------------------------------------- +// Simple tool for monitor continuously captured images for changes +// and saving changed images, as well as an image every N seconds for timelapses. +//----------------------------------------------------------------------------------- +#include +#include // to declare isupper(), tolower() +#include +#include +#include +#include +#include +#ifdef _WIN32 + #include "readdir.h" + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #define strdup(a) _strdup(a) + extern void sleep(int); + #define unlink(n) _unlink(n) +#else + #include + #include +#endif + +#include "imgcomp.h" + +// Configuration variables. +static const char * progname; // program name for error messages +static char DoDirName[200]; +char SaveDir[200]; +char SaveNames[200]; +int FollowDir = 0; +static int ScaleDenom; + +static char DiffMapFileName[200]; +Regions_t Regions; + + +int Verbosity = 0; +static int Sensitivity; +static int Raspistill_restarted; +int TimelapseInterval; +char raspistill_cmd[200]; +char blink_cmd[200]; + + +//----------------------------------------------------------------------------------- +// Indicate command line usage. +//----------------------------------------------------------------------------------- +void usage (void)// complain about bad command line +{ + fprintf(stderr, "usage: %s [switches] ", progname); + fprintf(stderr, "inputfile outputfile\n"); + + fprintf(stderr, "Switches (names may be abbreviated):\n"); + fprintf(stderr, " -scale N Scale before detection by 1/N. Default 1/4\n"); + fprintf(stderr, " -region x1-x2,y1-y2 Specify region of interest\n"); + fprintf(stderr, " -exclude x1-x2,y1-y2 Exclude from area of interest\n"); + fprintf(stderr, " -diffmap A file to use as diff map\n"); + fprintf(stderr, " -dodir Compare images in dir, in order\n"); + fprintf(stderr, " -followdir Do dir and monitor for new images\n"); + fprintf(stderr, " -savedir Where to save images with changes\n"); + fprintf(stderr, " -savenames Output naming scheme. Uses strftime\n" + " to format the output name. May include\n" + " '/' characters for directories."); + fprintf(stderr, " -sensitivity N Set sensitivity. Lower=more sensitive\n"); + fprintf(stderr, " -blink_cmd Run this command when motion detected\n" + " (used to blink the camera LED)\n"); + fprintf(stderr, " -tl N Save image every N seconds regardless\n"); + fprintf(stderr, " -verbose or -debug Emit more verbose output\n"); + exit(-1); +} + +//----------------------------------------------------------------------------------- +// Case-insensitive matching of possibly-abbreviated keyword switches. +// keyword is the constant keyword (must be lower case already), +// minchars is length of minimum legal abbreviation. +//----------------------------------------------------------------------------------- +static int keymatch (const char * arg, const char * keyword, int minchars) +{ + int ca, ck, nmatched = 0; + + while ((ca = *arg++) != '\0') { + if ((ck = *keyword++) == '\0') return 0; // arg longer than keyword, no good */ + if (isupper(ca)) ca = tolower(ca); // force arg to lcase (assume ck is already) + if (ca != ck) return 0; // no good + nmatched++; // count matched characters + } + // reached end of argument; fail if it's too short for unique abbrev + if (nmatched < minchars) return 0; + return 1; +} + +//----------------------------------------------------------------------------------- +// Parse a region parameter. +//----------------------------------------------------------------------------------- +static int ParseRegion(Region_t * reg, const char * value) +{ + char * t; + t = strstr(value, ","); + if (t == NULL || t != value){ + // No comma, or comma not in first position. Parse X parameters. + if (sscanf(value, "%d-%d", ®->x1, ®->x2) != 2){ + if (sscanf(value, "%d+%d", ®->x1, ®->x2) != 2) return 0; + reg->x2 += reg->x1; + } + } + if (t != NULL){ + // Y parameters come after the comma. + if (sscanf(t+1, "%d-%d", ®->y1, ®->y2) != 2){ + if (sscanf(t+1, "%d+%d", ®->y1, ®->y2) != 2) return 0; + reg->y2 += reg->y1; + } + } + if (reg->y2-reg->y1 < 16 || reg->x2-reg->x1 < 16){ + fprintf(stderr,"Detect region is too small\n"); + exit(-1); + } + printf("Region is x:%d-%d, y:%d-%d\n",reg->x1, reg->x2, reg->y1, reg->y2); + return 1; +} + + +//----------------------------------------------------------------------------------- +// Parse command line switches +// Returns argv[] index of first file-name argument (== argc if none). +//----------------------------------------------------------------------------------- +static int parse_parameter (const char * tag, const char * value) +{ + if (keymatch(tag, "debug", 1) || keymatch(tag, "verbose", 1)) { + // Enable debug printouts. Specify more than once for more detail. + Verbosity += 1; + return 1; + + } else if (keymatch(tag, "scale", 2)) { + // Scale the output image by a fraction M/N. + if (!value) goto need_val; + if (sscanf(value, "%d", &ScaleDenom) != 1) + usage(); + } else if (keymatch(tag, "sensitivity", 2)) { + // Scale the output image by a fraction M/N. + if (!value) goto need_val; + if (sscanf(value, "%d", &Sensitivity) != 1) + usage(); + } else if (keymatch(tag, "timelapse", 1)) { + // Scale the output image by a fraction M/N. + if (!value) goto need_val; + if (sscanf(value, "%d", (int *)&TimelapseInterval) != 1) + usage(); + if (TimelapseInterval < 1){ + fprintf(stderr,"timelapse interval must be at least 1 second\n"); + exit(-1); + } + } else if (keymatch(tag, "aquire_cmd", 4)) { + // Set output file name. + if (!value) goto need_val; + strncpy(raspistill_cmd, value, sizeof(raspistill_cmd)-1); + } else if (keymatch(tag, "blink_cmd", 4)) { + // Set output file name. + if (!value) goto need_val; + strncpy(blink_cmd, value, sizeof(blink_cmd)-1); + } else if (keymatch(tag, "savedir", 5)) { + // Set output file name. + if (!value) goto need_val; + strncpy(SaveDir,value, sizeof(SaveDir)-1); + } else if (keymatch(tag, "savenames", 5)) { + // Set output file name. + if (!value) goto need_val; + strncpy(SaveNames,value, sizeof(SaveNames)-1); + { + int a,b; + for (a=0;SaveNames[a];a++){ + if (SaveNames[a] == '%'){ + for (b=0;b<10;b++) if (SaveNames[a+1] == "dHjmMSUwyY"[b]) break; + if (b >=10){ + fprintf(stderr, "savenames '%%' may only be followed by one of d,H,j,m,M,S,U,w,y, or Y\n"); + exit(-1); + } + } + } + } + + } else if (keymatch(tag, "region", 2)) { + if (!value) goto need_val; + if (!ParseRegion(&Regions.DetectReg, value)) goto bad_value; + printf("Region is x:%d-%d, y:%d-%d\n", + Regions.DetectReg.x1, Regions.DetectReg.x2, + Regions.DetectReg.y1, Regions.DetectReg.y2); + } else if (keymatch(tag, "exclude", 2)) { + if (!value) goto need_val; + + if (Regions.NumExcludeReg >= MAX_EXCLUDE_REGIONS){ + fprintf(stderr, "too many exclude regions"); + exit(-1); + }else{ + Region_t NewEx; + if (!ParseRegion(&NewEx, value)) goto bad_value; + printf("Exclude region x:%d-%d, y:%d-%d\n",NewEx.x1, NewEx.x2, NewEx.y1, NewEx.y2); + Regions.ExcludeReg[Regions.NumExcludeReg++] = NewEx; + } + } else if (keymatch(tag, "diffmap", 2)) { + if (!value) goto need_val; + strncpy(DiffMapFileName,value, sizeof(DiffMapFileName)-1); + + + } else if (keymatch(tag, "dodir", 2)) { + // Scale the output image by a fraction M/N. */ + if (!value) goto need_val; + strncpy(DoDirName,value, sizeof(DoDirName)-1); + FollowDir = 0; + + } else if (keymatch(tag, "followdir", 2)) { + if (!value){ + need_val: + fprintf(stderr, "Parameter '%s' needs to be followed by a vaue\n",tag); + usage(); + } + strncpy(DoDirName,value, sizeof(DoDirName)-1); + FollowDir = 1; + } else { + fprintf(stderr,"argument %s not understood\n\n",tag); + usage(); // bogus switch + bad_value: + fprintf(stderr, "Value of %s=%s\n not understood\n",tag,value); + usage(); + + } + return 2; +} + + +//----------------------------------------------------------------------------------- +// Parse command line switches +// Returns argv[] index of first file-name argument (== argc if none). +//----------------------------------------------------------------------------------- +static int parse_switches (int argc, char **argv, int last_file_arg_seen, int for_real) +{ + int argn; + char * arg; + char * value; + + // Scan command line options, adjust parameters + for (argn = 1; argn < argc;) { + //printf("argn = %d\n",argn); + arg = argv[argn]; + if (*arg != '-') { + return argn; + } + value = NULL; + if (argn+1 < argc) value = argv[argn+1]; + + argn += parse_parameter(arg+1, value); + } + return argc; +} + +//----------------------------------------------------------------------------------- +// Too many parameters for imgcomp running. Just read them from a configuration file. +//----------------------------------------------------------------------------------- +static void read_config_file() +{ + FILE * file; + char ConfLine[201]; + + file = fopen("imgcomp.conf", "r"); + if (file == NULL){ + fprintf(stderr, "No configuration file imgcomp.conf\n"); + return; + } + for(;;){ + char *s, *value, *t; + int len; + s = fgets(ConfLine, sizeof(ConfLine)-1, file); + ConfLine[sizeof(ConfLine)-1] = '\0'; + if (s == NULL) break; + + // Remove leading spaces + while (*s == ' ' || *s == '\t') s++; + + // Check line length not exceeded. + len = strlen(s); + if(len >= sizeof(ConfLine)-1){ + fprintf(stderr, "Configuration line too long:\n%s",s); + exit(-1); + } + + // Remove trailing spaces and linefeed. + t = s+len-1; + while (*t <= ' ' && t > s) *t-- = '\0'; + + if (*s == '#') continue; // comment. + if (*s == '\r' || *s == '\n') continue; // Blank line. + + value = strstr(s, "="); + if (value != NULL){ + t = value; + + // Remove value leading spaces + value += 1; + while (*value == ' ' || *value == '\t') value++; + + // remove tag trailing spaces. + *t = '\0'; + t--; + while (*t == ' ' || *t == '\t'){ + *t = '\0'; + t--; + } + } + // Now finally have the tag extracted. + printf("'%s' = '%s'\n",s, value); + + parse_parameter(s,value); + } +} + +typedef struct { + MemImage_t *Image; + char Name[500]; + int nind; // Name part index. + unsigned mtime; + int DiffMag; + //int Saved; + int IsTimelapse; + int IsMotion; +}LastPic_t; + +static LastPic_t LastPics[3]; +static time_t NextTimelapsePix; + +//----------------------------------------------------------------------------------- +// Figure out which images should be saved. +//----------------------------------------------------------------------------------- +static int ProcessImage(LastPic_t * New) +{ + static int PixSinceDiff; + + LastPics[2] = LastPics[1]; + LastPics[1] = LastPics[0]; + + LastPics[0] = *New; + LastPics[0].IsMotion = LastPics[0].IsTimelapse = 0; + + if (LastPics[1].Image != NULL){ + TriggerInfo_t Trig; + // Handle timelapsing. + if (TimelapseInterval >= 1){ + if (LastPics[0].mtime >= NextTimelapsePix){ + LastPics[0].IsTimelapse = 1; + } + + // Figure out when the next timelapse interval should be. + NextTimelapsePix = LastPics[0].mtime+TimelapseInterval; + NextTimelapsePix -= (NextTimelapsePix % TimelapseInterval); +//printf("Now %d\nNext %d\n", LastPics[0].mtime, NextTimelapsePix); + } + + // compare with previosu picture. + Trig.DiffLevel = Trig.x = Trig.y = 0; + + if (LastPics[2].Image){ + Trig = ComparePix(LastPics[0].Image, LastPics[1].Image, NULL); + } + + if (Trig.DiffLevel >= Sensitivity && PixSinceDiff > 5 && Raspistill_restarted){ + printf("Ignoring diff caused by raspistill restart\n"); + Trig.DiffLevel = 0; + } + LastPics[0].DiffMag = Trig.DiffLevel; + + printf("%s - %s:",LastPics[0].Name+LastPics[0].nind, LastPics[1].Name+LastPics[0].nind); + printf(" %3d at (%4d,%3d) ", Trig.DiffLevel, Trig.x*ScaleDenom, Trig.y*ScaleDenom); + + if (LastPics[0].DiffMag > Sensitivity){ + LastPics[0].IsMotion = 1; + } + + if (LastPics[2].Image && + LastPics[0].IsMotion && LastPics[1].IsMotion + && LastPics[2].DiffMag < (Sensitivity>>1)){ + // Compare to picture before last picture. + Trig = ComparePix(LastPics[0].Image, LastPics[2].Image, NULL); + + //printf("Diff with pix before last: %d\n",Trig.DiffLevel); + if (Trig.DiffLevel < Sensitivity){ + printf("(spurious %d) ", Trig.DiffLevel); + LastPics[0].IsMotion = 0; + LastPics[1].IsMotion = 0; + } + } + if (LastPics[0].IsMotion) printf("(motion) "); + if (LastPics[0].IsTimelapse) printf("(time) "); + if (!LastPics[0].IsMotion) printf(" "); // Overwrite rest of old line. + + if (LastPics[2].IsMotion || LastPics[2].IsTimelapse || LastPics[1].IsMotion){ + // If it's motion, pre-motion, or timelapse, save it. + if (SaveDir[0]){ + BackupPicture(LastPics[2].Name, LastPics[2].mtime, LastPics[2].DiffMag); + } + } + + printf("\n"); + Raspistill_restarted = 0; + } + + // Third picture now falls out of the window. Free it and delete it. + if (LastPics[2].Image != NULL) free(LastPics[2].Image); + if (FollowDir){ + //printf("Delete %s\n",LastPics[2].Name); + unlink(LastPics[2].Name); + } + + return LastPics[0].IsMotion; +} + +//----------------------------------------------------------------------------------- +// Process a whole directory of files. +//----------------------------------------------------------------------------------- +static int DoDirectoryFunc(char * Directory) +{ + char ** FileNames; + int NumEntries; + int a; + int ReadExif; + int NumProcessed; + int SawMotion; + + SawMotion = 0; + + FileNames = GetSortedDir(Directory, &NumEntries); + if (FileNames == NULL) return 0; + if (NumEntries == 0) return 0; + + ReadExif = 1; + NumProcessed = 0; + for (a=0;ax1 /= Denom; + Reg->x2 /= Denom; + Reg->y1 /= Denom; + Reg->y2 /= Denom; +} + +//----------------------------------------------------------------------------------- +// The main program. +//----------------------------------------------------------------------------------- +int main(int argc, char **argv) +{ + int file_index, a; + progname = argv[0]; + + // Reset to default parameters. + ScaleDenom = 4; + DoDirName[0] = '\0'; + Sensitivity = 10; + Regions.DetectReg.x1 = 0; + Regions.DetectReg.x2 = 1000000; + Regions.DetectReg.y1 = 0; + Regions.DetectReg.y2 = 1000000; + Regions.NumExcludeReg = 0; + TimelapseInterval = 0; + SaveDir[0] = 0; + strcpy(SaveNames, "%m%d/%H/%m%d-%H%M%S"); + + // First read the configuration file. + read_config_file(); + + // Get command line arguments (which may override configuration file) + file_index = parse_switches(argc, argv, 0, 0); + + // Adjust region of interest to scale. + ScaleRegion(&Regions.DetectReg, ScaleDenom); + for (a=0;a +#include +#include +#include + + +/* struct dirent - same as Unix */ + +struct dirent { + long d_ino; /* inode (always 1 in WIN32) */ + off_t d_off; /* offset to this dirent */ + unsigned short d_reclen; /* length of d_name */ + char d_name[_MAX_FNAME + 1]; /* filename (null terminated) */ +}; + + +/* typedef DIR - not the same as Unix */ +typedef struct { + long handle; /* _findfirst/_findnext handle */ + short offset; /* offset into directory */ + short finished; /* 1 if there are not more files */ + struct _finddata_t fileinfo; /* from _findfirst/_findnext */ + char *dir; /* the dir we are reading */ + struct dirent dent; /* the dirent to return */ +} DIR; + +/* Function prototypes */ +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +int readdir_r(DIR *, struct dirent *, struct dirent **); +int closedir(DIR *); +void rewinddir(DIR *); + + +#endif /* READDIR_H */ diff --git a/src/readdir_win.h b/src/readdir_win.h new file mode 100644 index 0000000..528d0a1 --- /dev/null +++ b/src/readdir_win.h @@ -0,0 +1,43 @@ +#ifndef READDIR_H +#define READDIR_H + +/* + * Structures and types used to implement opendir/readdir/closedir + * on Windows 95/NT. + */ + +#include +#include +#include +#include + + +/* struct dirent - same as Unix */ + +struct dirent { + long d_ino; /* inode (always 1 in WIN32) */ + off_t d_off; /* offset to this dirent */ + unsigned short d_reclen; /* length of d_name */ + char d_name[_MAX_FNAME + 1]; /* filename (null terminated) */ +}; + + +/* typedef DIR - not the same as Unix */ +typedef struct { + long handle; /* _findfirst/_findnext handle */ + short offset; /* offset into directory */ + short finished; /* 1 if there are not more files */ + struct _finddata_t fileinfo; /* from _findfirst/_findnext */ + char *dir; /* the dir we are reading */ + struct dirent dent; /* the dirent to return */ +} DIR; + +/* Function prototypes */ +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +int readdir_r(DIR *, struct dirent *, struct dirent **); +int closedir(DIR *); +void rewinddir(DIR *); + + +#endif /* READDIR_H */ diff --git a/src/start_raspistill.c b/src/start_raspistill.c new file mode 100644 index 0000000..83ef359 --- /dev/null +++ b/src/start_raspistill.c @@ -0,0 +1,240 @@ +//---------------------------------------------------------------------------------------- +// Code to launch raspistill as a separately running process +//---------------------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +#ifdef _WIN32 + typedef int pid_t; + #define execvp(a,b) + #define fork() 1 +#else + #include + #include +#endif + +#include "imgcomp.h" +#include "jhead.h" + +//"raspistill -q 10 -n -bm -th none -p 480,0,800,480 -w 1280 -h 720 -o /ramdisk/out%05d.jpg -t 4000000 -tl 300"; + +static int raspistill_pid = 0; +static int blink_led_pid = 0; +extern int NightMode; + +//----------------------------------------------------------------------------------- +// Parse command line and launch. +//----------------------------------------------------------------------------------- +static void do_launch_program(char * cmd_string) +{ + char * Arguments[51]; + int narg; + int a; + int wasblank = 1; + + // Note: NOT handling quoted strings or anything for arguments with spaces in them. + narg=0; + for (a=0;cmd_string[a];a++){ + if (cmd_string[a] != ' '){ + if (wasblank){ + if (narg >= 50){ + fprintf(stderr, "too many command line arguments\n"); + exit(0); + } + Arguments[narg++] = &cmd_string[a]; + } + wasblank = 0; + }else{ + cmd_string[a] = '\0'; + wasblank = 1; + } + } + Arguments[narg] = NULL; + + //printf("%d arguments\n",narg); + //for (a=0;a 0){ + SecondsSinceImage = 0; + printf("Exp:%5.1fms Iso:%d Nm=%d Bright:%d av=%5.2f\r", + ImageInfo.ExposureTime*1000, ImageInfo.ISOequivalent, + NightMode, NewestAverageBright, RunningAverageBright); + fflush(stdout); + }else{ + printf("No new images, %d\n",SecondsSinceImage); + } + + if (raspistill_pid == 0){ + // Raspistill has not been launched. + printf("Initial launch of raspistill\n"); + goto force_restart; + } + + if (SecondsSinceImage > 10){ + // Not getting any images for 10 seconds. Probably something went wrong with raspistill. + printf("No images timeout. Relaunch raspistill\n"); + goto force_restart; + } + + if (SecondsSinceLaunch > 36000){ + printf("1 hour raspistill relaunch\n"); + goto force_restart; + } + + if (SecondsSinceLaunch > 5 && InitialNumBr < 5 && NewImages){ + printf("Average in %d\n",NewestAverageBright); + InitialBrSum += NewestAverageBright; + InitialNumBr += 1; + // Save average brightness and reset averaging. + if (InitialNumBr == 5){ + InitialAverageBright = (InitialBrSum+2) / 5; + if (InitialAverageBright == 0) InitialAverageBright = 1; // Avoid division by zero. + RunningAverageBright = InitialAverageBright; + printf("Initial rightness average = %d\n",InitialAverageBright); + } + } + + // 20 second time constant brightness averaging. + RunningAverageBright = RunningAverageBright * 0.95 + NewestAverageBright * 0.05; + + // If brightness changes by more than 20%, relaunch. + if (SecondsSinceLaunch > 15){ + double Ratio; + Ratio = RunningAverageBright / InitialAverageBright; + if (Ratio < 1) Ratio = 1/Ratio; + if (Ratio > 1.2){ + printf("Brightness change by 20%%. Force restart\n"); + goto force_restart; + } + } + + // If image too bright and shutter speed is not fastest, launch raspistill + // if image is too dark and shutter speed is not 1/8, launch raspistill. + + + return 0; +force_restart: + launch_raspistill(); + SecondsSinceImage = 0; + SecondsSinceLaunch = 0; + InitialBrSum = InitialNumBr = 0; + return 1; +} + + +//----------------------------------------------------------------------------------- +// Run a program to blink the LED. +// Hitting the I/O lines requires root priviledges, so let's just spawn a program +// to do a single LED blink. +//----------------------------------------------------------------------------------- +void run_blink_program() +{ +#ifdef _WIN32 + return; } +#else + pid_t pid; + + if (blink_cmd[0] == 0){ + // No blink command configured. + return; + } + + if (blink_led_pid){ + // Avoid accumulating zombie child processes. + // Blink process should be done by now. + int exit_code = 0; + int a; + a = wait(&exit_code); + printf("Child exit code %d (wait returned %d)\n",exit_code,a); + blink_led_pid = 0; + } + + + printf("Run blink program\n"); + pid = fork(); + if (pid == -1){ + // Failed to fork. + fprintf(stderr,"Failed to fork off child process\n"); + perror("Reason"); + return; + } + + if(pid == 0){ + // Child takes this branch. + do_launch_program(blink_cmd); + }else{ + blink_led_pid = pid; + } + return; +} +#endif diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..1cfb20d --- /dev/null +++ b/src/util.c @@ -0,0 +1,334 @@ +//----------------------------------------------------------------------------------- +// Various utility functions. +//----------------------------------------------------------------------------------- +#include +#include // to declare isupper(), tolower() +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + #include "readdir.h" + #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) + #define strdup(a) _strdup(a) + #include + #define open(a,b,c) _open(a,b,c) + #define read(a,b,c) _read(a,b,c) + #define write(a,b,c) _write(a,b,c) + #define close(a) _close(a) +#else + #include + #include + #include + #define O_BINARY 0 +#endif + +#ifdef _WIN32 + #include // for mkdir under windows. + #define mkdir(dir,mode) _mkdir(dir) + #define S_ISDIR(a) (a & _S_IFDIR) + #define PATH_MAX _MAX_PATH +#else + #ifndef PATH_MAX + #define PATH_MAX 1024 + #endif +#endif + +#include "imgcomp.h" + +//----------------------------------------------------------------------------------- +// Concatenate dir name and file name. Not thread safe! +//----------------------------------------------------------------------------------- +char * CatPath(char *Dir, char * FileName) +{ + static char catpath[502]; + int pathlen; + + pathlen = strlen(Dir); + if (pathlen > 300){ + fprintf(stderr, "path too long!"); + exit(-1); + } + memcpy(catpath, Dir, pathlen+1); + if (catpath[pathlen-1] != '/' && catpath[pathlen-1] != '\\'){ + catpath[pathlen] = '/'; + pathlen += 1; + } + strncpy(catpath+pathlen, FileName,200); + return catpath; +} + + +//-------------------------------------------------------------------------------- +// Ensure that a path exists +//-------------------------------------------------------------------------------- +int EnsurePathExists(const char * FileName) +{ + char NewPath[PATH_MAX*2]; + int a; + int LastSlash = 0; + + // 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] == '/'){ + 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] == '/' || 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 + //printf("mkdir %s\n",NewPath); + if (mkdir(NewPath,0777)){ + fprintf(stderr,"Could not create directory '%s'\n",NewPath); + // Failed to create directory. + return 0; + } + } + } + return 1; +} + + + +//----------------------------------------------------------------------------------- +// Compare file names to sort directory. +//----------------------------------------------------------------------------------- +static int fncmpfunc (const void * a, const void * b) +{ + return strcmp(*(char **)a, *(char **)b); +} + +//----------------------------------------------------------------------------------- +// Read a directory and sort it. +//----------------------------------------------------------------------------------- +char ** GetSortedDir(char * Directory, int * NumFiles) +{ + char ** FileNames; + int NumFileNames; + int NumAllocated; + DIR * dirp; + + NumAllocated = 5; + FileNames = malloc(sizeof (char *) * NumAllocated); + + NumFileNames = 0; + + dirp = opendir(Directory); + if (dirp == NULL){ + fprintf(stderr, "could not open dir\n"); + return NULL; + } + + for (;;){ + struct dirent * dp; + struct stat buf; + int l; + dp = readdir(dirp); + if (dp == NULL) break; + //printf("name: %s %d %d\n",dp->d_name, (int)dp->d_off, (int)dp->d_reclen); + + + // Check that name ends in ".jpg", ".jpeg", or ".JPG", etc... + l = strlen(dp->d_name); + if (l < 5) continue; + if (dp->d_name[l-1] != 'g' && dp->d_name[l-1] != 'G') continue; + if (dp->d_name[l-2] == 'e' || dp->d_name[l-2] == 'E') l-= 1; + if (dp->d_name[l-2] != 'p' && dp->d_name[l-2] != 'P') continue; + if (dp->d_name[l-3] != 'j' && dp->d_name[l-3] != 'J') continue; + if (dp->d_name[l-4] != '.') continue; + //printf("use: %s\n",dp->d_name); + + // Check that it's a regular file. + stat(CatPath(Directory, dp->d_name), &buf); + if (!S_ISREG(buf.st_mode)) continue; // not a file. + + if (NumFileNames >= NumAllocated){ + //printf("realloc\n"); + NumAllocated *= 2; + FileNames = realloc(FileNames, sizeof (char *) * NumAllocated); + } + + FileNames[NumFileNames++] = strdup(dp->d_name); + } + closedir(dirp); + + // Now sort the names (could be in random order) + qsort(FileNames, NumFileNames, sizeof(char **), fncmpfunc); + + *NumFiles = NumFileNames; + return FileNames; +} + +//----------------------------------------------------------------------------------- +// Unallocate directory structure +//----------------------------------------------------------------------------------- +void FreeDir(char ** FileNames, int NumEntries) +{ + int a; + // Free it up again. + for (a=0;a= 'a' && SuffixChar <'z') ? SuffixChar+1 : 'a'; + }else{ + // New time. No need for a suffix. + SuffixChar = ' '; + LastSaveTime = mtime; + } + sprintf(NameSuffix, "%c%04d",SuffixChar, DiffMag); + PicDestFromTime(DstPath, SaveDir, mtime, NameSuffix); + EnsurePathExists(DstPath); + CopyFile(Name, DstPath); + } + return DstPath; +} + + +//----------------------------------------------------------------------------------- +// Copy a file. +//----------------------------------------------------------------------------------- +int CopyFile(char * src, char * dest) +{ + int inputFd, outputFd, openFlags; + int filePerms; + struct stat statbuf; + size_t numRead; + #define BUF_SIZE 8192 + char buf[BUF_SIZE]; + + //printf("Copy %s --> %s\n",src,dest); + + // Get file modification time from old file. + stat(src, &statbuf); + + // Open input and output files + inputFd = open(src, O_RDONLY | O_BINARY, 0); + if (inputFd == -1){ + fprintf(stderr,"CopyFile could not open src %s\n",src); + exit(-1); + } + + openFlags = O_CREAT | O_WRONLY | O_TRUNC | O_BINARY; + + filePerms = 0x1ff; + + outputFd = open(dest, openFlags, filePerms); + if (outputFd == -1){ + fprintf(stderr,"CopyFile coult not open dest %s\n",dest); + exit(-1); + } + + // Transfer data until we encounter end of input or an error + + for(;;){ + numRead = read(inputFd, buf, BUF_SIZE); + if (numRead <= 0) break; + if (write(outputFd, buf, numRead) != numRead){ + fprintf(stderr,"write error to %s",dest); + exit(-1); + } + } + + if (numRead == -1){ + fprintf(stderr,"CopyFile read error from %s\n",src); + exit(-1); + } + + close(inputFd); + close(outputFd); + + { + struct utimbuf mtime; + mtime.actime = statbuf.st_ctime; + mtime.modtime = statbuf.st_mtime; + utime(dest, &mtime); + } + + return 0; +} + + +