/* * * A batch controller for Autostitch * * This is a command-line utility for stitching batches of images using * Autostitch. * * To compile use MINGW32: * gcc stitch.c -o stitch.exe * * See below for usage and options. */ /* Copyright (c) 2006 Stewart Greenhill Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ char * usage = "stitch - A batch controller for AutoStitch\n" "\n" "This program attempts to stitch batches of images using Autostitch.\n" "1) Autostitch must be running.\n" "2) Autostitch must be \"pointing\" to the directory containing the source\n" " images. You can set this using File->Open.\n" "3) This program simulates the normal sequence of interactions with the GUI.\n" " File names on the command line are passed to the file selection dialog.\n" "\n" "Usage: stitch.exe [options] file1.jpg ... fileN.jpg\n" "\n" "Options:\n" "-help Show this message and exit\n" "-version Show version and exit\n" "-wait Wait for Autostitch to complete (single stitch only)\n" "-close Close preview opened by Autostitch (single stitch only)\n" "-template Specify template for batch operation\n" " A range of images is processed for start <= id <= end. Use sprintf\n" " formatting (eg. id=23, temp='file%%04d.jpg' -> 'file0023.jpg')\n" "-dryrun Just output batch image lists without calling Autostitch\n" "-path Path to image directory (for batch stitch). INCLUDE TRAILING '/'\n" "\n" "Stuff you probably don't need to change:\n" "-window Name of Autostitch window (default: autostitch)\n" "-status Name of Autostitch status window (default: Status)\n" "-output Name of preview window (default: 'pano.jpg - Windows Picture and Fax Viewer')\n" "-fileOpen Menu name for File->Open dialog (default: 'File:Open')\n" "-openTime Delay (in ms) for open dialog to populate controls (default: 500)\n" "-closeTime Delay (in ms) to wait before closing preview window (default: 2000)\n" "-outjpg Template (dir,id) for output files (default: '%%sout%%04d.jpg')\n" "-outtxt Template (dir,id) for output files (default: '%%sout%%04d.txt')\n\n" "Examples:\n\n" "Single stitch:\n" " stitch.exe file1.jpg file2.jpg file3.jpg\n\n" "Single stitch (waits for completion and closes preview window):\n" " stitch.exe -wait -close file1.jpg file2.jpg file3.jpg\n\n" "Batch stitch:\n" " stitch.exe -template frame%%03d.jpg 20 50 file1.jpg file2.jpg file3.jpg\n\n" "Examples with Autostitch's demo files:\n" " stitch.exe -wait -close 100-0023_img.jpg 100-0024_img.jpg 100-0025_img.jpg\n" "This example stitches the bottom three demo images\n\n" " stitch.exe -path s:/stewart/programs/autostitch/images/test/ \\\n" " -template 100-%%04d_img.jpg 38 40 \\\n" " 100-0023_img.jpg 100-0024_img.jpg 100-0025_img.jpg\n" "This last example separately stitches each of the three top images (38 to 40)\n" "against the three bottom images. This results in three output images \n" "(out0038.jpg ... out0040.jpg), each is stitched from 4 inputs.\n"; char * version = "Version 1.0 [19/05/2006]"; #include #include /* Name of main AutoStitch program window */ char * windowName = "autostitch"; /* Name of AutoStitch status window */ char * statusName = "Status"; /* Titles of output windows using default Windows jpeg viewers */ char * outputName = "pano.jpg - Windows Picture and Fax Viewer"; char * outputName2 = "Microsoft Photo Editor - [pano.jpg]"; char * outputName3 = 0; /* Time in ms to wait for Open window to build controls */ int openTime = 500; /* Time in ms to wait before trying to close preview window */ int closeDelay=2000; /* Menu for File Open dialog */ char * fileOpen = "File:Open"; /* path to image directory. Used to copy files in batch mode. */ char * path = ""; /* template (directory, id) for output jpg and text files */ char * outJpg = "%sout%04d.jpg"; char * outTxt = "%sout%04d.txt"; /* output debugging info */ int debug = 0; /* Error() * Print description of error condition. Prints error code, and if possible a * formatted message describing the error. */ void Error(void) { int error; char * result; error = GetLastError(); printf("Error code : %08x\n", error); if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error, LANG_NEUTRAL | (SUBLANG_DEFAULT * 1024), (LPTSTR) &result, 0, NULL)) { printf("Message: %s\n", result); LocalFree(result); } } /* ParseName(source, dest, destLength) * Parse a field from a colon-separated list of fields. * * source : source string * dest : destination string * destLength : length of destination string * * This function parses the first field, returning a null-terminated field of * up to bytes. It returns the remainder string after bypassing * any separators found. * * Example: * source = "File:Open" * dest = "File" * RETURNS "Open" */ char * ParseName(char * source, char * dest, int destLength) { int i; char ch; i = 0; ch = *source; while ( ch != ':' && ch != '\0') { if (i < destLength-1) { dest[i] = ch; ++i; } ++source; ch = *source; } if (ch == ':') ++source; dest[i] = '\0'; return source; } /* FindMenu(menu, menuName) * Find a menu ID given a textual description of the menu item. * * menu : root menu (eg. retreived by GetMenu) * menuName : menu description (eg. "File:Open") * * This function searches a menu tree for the named menu item. If found, it * returns the menu item identifier, which can then be used in a WM_COMMAND * message. If not found, the function returns 0. */ int FindMenu(HMENU menu, char * menuName) { char name[128]; /* requested item name (parsed from menuName) */ char buffer[128]; /* found item name (from GetMenuString) */ int i; char * nextMenuName = ParseName(menuName, name, sizeof(name)); /* for each item in menu, retrieve the item name and check for a match with * the first field of . When matching the last field in * , return the item ID. Otherwise, call this function * recursively to match the remainder. */ int count = GetMenuItemCount(menu); for (i=0; i0) { if (strcmp(name, buffer) == 0) { if (*nextMenuName == 0) { MENUITEMINFO mii; mii.cbSize = sizeof(MENUITEMINFO); mii.fMask = MIIM_ID; if (GetMenuItemInfo(menu, i, TRUE, &mii)) { return mii.wID; } else { Error(); return 0; } } else return FindMenu(GetSubMenu(menu, i), nextMenuName); } } } return 0; } /* SelectMenu(window, menu) * Activate a menu item for a window. * * window : Window handle. * menu : description of menu item (eg. "File:Open") * * This function simulates a menu selection by posting a WM_COMMAND message * with the correct menu item ID. */ void SelectMenu(HWND window, char * menu) { int id = FindMenu(GetMenu(window), menu); if (id) { PostMessage(window, WM_COMMAND, id, 0); } else { printf("Error: menu %s not found\n", menu); } } /* Functions for finding child window by name or class. We need to search the * whole window tree, so we use EnumChildWindows rather than FindWindowEx, * which only looks at the top level. */ struct Search { char * text; char * class; HWND result; }; /* Callback function for FindChild. */ BOOL CALLBACK SearchHandler(HWND hwnd, LPARAM lParam) { char buffer[128]; struct Search * search = (struct Search *) lParam; if (search -> text) { int result = GetWindowText(hwnd, buffer, sizeof(buffer)); if (result && !strcmp(buffer, search->text)) search->result = hwnd; } if (search -> class) { int result = GetClassName(hwnd, buffer, sizeof(buffer)); if (result && !strcmp(buffer, search->class)) search->result = hwnd; } return search -> result ? FALSE : TRUE; } /* Find a child of with matching window class or text. */ HWND FindChild(HWND window, char * class, char * text) { struct Search search; search.result = 0; search.text = text; search.class = class; EnumChildWindows(window, SearchHandler, (LPARAM) &search); return search.result; } /* Callback function for DisplayChildren */ BOOL CALLBACK DisplayHandler(HWND window, LPARAM lParam) { char buffer[128]; printf("Window %08lx", (unsigned long) window); printf(" id=%4ld", (long) GetWindowLong(window, GWL_ID)); if (GetClassName(window, buffer, sizeof(buffer))) { printf(" class=[%s]", buffer); } if (GetWindowText(window, buffer, sizeof(buffer))) { printf(" text=[%s]", buffer); } printf("\n"); return TRUE; } /* DisplayChildren(window) * Display some informative information about all children of the given window */ void DisplayChildren(HWND window) { EnumChildWindows(window, DisplayHandler, 0); } /* Control AutoStitch via its GUI * 1) Activate the File->Open menu * 2) Wait a while, then look for the "Open" file requestor dialog * 3) Find the child edit window and insert the given text * 4) Click on the "Open" button */ int Control(char * text) { /* Get main Autostitch window */ /* FIXME - there could be other windows with the same name, so we * should check using class, menus, etc that we have the right one */ HWND window = FindWindow(NULL, windowName); if (window) { if (debug) printf("Main window = %08lx\n", (unsigned long) window); /* Select File->Open menu item */ SelectMenu(window, fileOpen); /* Wait some time for the window to be built. After the window is * created, there is a small delay while controls are added to the * window. If we don't wait here, the controls we want won't be there. * */ Sleep(openTime); HWND open = FindWindow(NULL, "Open"); if (open) { if (debug) { printf("Open window = %08lx\n", (unsigned long) open); DisplayChildren(open); } HWND edit = FindChild(open, "Edit", NULL); HWND go = FindChild(open, NULL, "&Open"); if (edit && go) { SendMessage(edit, WM_SETTEXT, 0, (LPARAM) text); SendMessage(go, BM_CLICK, 0, 0); return 1; } } else printf("Control : Cannot find window '%s'\n", "Open"); } else printf("Control : Cannot find window '%s'\n", windowName); return 0; } /* * Wait for AutoStitch to process data * 1) While we can find find a "Status" window * a) Look for a Static text child, and output its value * b) Sleep for a while */ void Wait() { HWND status; char buffer [128]; Sleep(1000); while ((status=FindWindow(NULL, statusName)) != NULL) { HWND text = FindChild(status, "Static", NULL); if (text && GetWindowText(text, buffer, sizeof(buffer))) { printf("[%s]\n", buffer); } else { printf("waiting...\n"); } fflush(stdout); Sleep(1000); } } /* * After processing, AutoStitch opens the resulting image in the default * Windows JPEG viewer. If we can find one of these windows, we close it. * This should leave AutoStitch ready for another operation. */ int CloseOutputWindow(char * name) { printf("Window [%s]: ", name); HWND output = FindWindow(NULL, name); if (output != NULL) { PostMessage(output, WM_QUIT, 0, 0); printf("Closing Window\n"); return 1; } else { printf("Not found\n"); return 0; } } void Close() { if (debug) printf("Close:waiting ...\n"); Sleep(closeDelay); if (outputName3 && CloseOutputWindow(outputName3)) return; if (CloseOutputWindow(outputName)) return; if (CloseOutputWindow(outputName2)) return; printf("Can't close output window\n"); } /* MakeCommand - assemble list of files to be processed */ /* MINGW doesn't include strlcat ... :-( */ size_t strlcat(char *dst, const char *src, size_t len) { char *d = dst; const char *s = src; size_t n = len; size_t dlen; while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = len - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); } char * MakeCommand(char ** argv, int putFirst, char * name) { #define CMDSIZE 16384 char * fileName; char * buffer = (char *) malloc(CMDSIZE); buffer[0] = 0; if (putFirst && name) { strlcat(buffer, "\"", CMDSIZE); strlcat(buffer, name, CMDSIZE); strlcat(buffer, "\" ", CMDSIZE); } while ((fileName = *argv++)!=NULL) { strlcat(buffer, "\"", CMDSIZE); strlcat(buffer, fileName, CMDSIZE); strlcat(buffer, "\" ", CMDSIZE); } if (!putFirst && name) { strlcat(buffer, "\"", CMDSIZE); strlcat(buffer, name, CMDSIZE); strlcat(buffer, "\" ", CMDSIZE); } return buffer; } /* command line options */ int optRun = 1; int optWait = 0; int optClose = 0; int optTemplateFirst = 0; int optTemplate = 0; char * template=0; int templateStart=0, templateEnd=0; void DoSingle(char ** argv) { char * command = MakeCommand(argv, 0, NULL); printf("Files : %s\n" , command); if (optRun && Control(command)) { if (optWait) Wait(); if (optClose) Close(); } } #define MAXSTR 4096 void Copy(char * path, char * src, char * dst, int n) { char srcBuffer[MAXSTR]; char dstBuffer[MAXSTR]; snprintf(srcBuffer, sizeof(srcBuffer), src, path); snprintf(dstBuffer, sizeof(dstBuffer), dst, path, n); CopyFile(srcBuffer, dstBuffer, FALSE); } void DoBatch(int pos, int end, char * template, char ** argv) { char buffer[MAXSTR]; for (; pos<=end; pos++) { snprintf(buffer, sizeof(buffer), template, pos); char * command = MakeCommand(argv, optTemplateFirst, buffer); printf("Files : %s\n" , command); if (optRun && Control(command)) { Wait(); Close(); Copy(path, "%spano.jpg", outJpg, pos); Copy(path, "%spano.txt", outTxt, pos); } free(command); } } void Version() { printf("%s\n", version); } void Usage() { Version(); printf("\n"); printf(usage); exit(1); } void checkArg(char ** arg) { if (*arg == 0) { printf("Insufficient arguments"); exit(0); } } char ** Parse(char ** argv) { char * arg; while ((arg=*argv) && (*arg == '-')) { if (!strcmp(arg, "-version")) { Version(); exit(1); } else if (!strcmp(arg, "-help")) { Usage(); } else if (!strcmp(arg, "-wait")) { optWait = 1; } else if (!strcmp(arg, "-close")) { optClose = 1; } else if (!strcmp(arg, "-template")) { optTemplate = 1; checkArg(++argv); template = *argv; checkArg(++argv); templateStart = atoi(*argv); checkArg(++argv); templateEnd = atoi(*argv); } else if (!strcmp(arg, "-dryrun")) { optRun = 0; } else if (!strcmp(arg, "-first")) { optTemplateFirst = 1; } else if (!strcmp(arg, "-window")) { checkArg(++argv); windowName = *argv; } else if (!strcmp(arg, "-status")) { checkArg(++argv); statusName = *argv; } else if (!strcmp(arg, "-output")) { checkArg(++argv); outputName3 = *argv; } else if (!strcmp(arg, "-openTime")) { checkArg(++argv); openTime = atoi(*argv); } else if (!strcmp(arg, "-fileOpen")) { checkArg(++argv); fileOpen = *argv; } else if (!strcmp(arg, "-path")) { checkArg(++argv); path = *argv; } else if (!strcmp(arg, "-outjpg")) { checkArg(++argv); outJpg = *argv; } else if (!strcmp(arg, "-outtxt")) { checkArg(++argv); outTxt = *argv; } else if (!strcmp(arg, "-debug")) { debug = 1; } else { printf("Unknown argument: %s\n", arg); exit(1); } ++argv; } return argv; } int main(int argc, char ** argv) { ++ argv; if (argc == 1) Usage(); argv = Parse(argv); if (optTemplate) { DoBatch(templateStart, templateEnd, template, argv); } else { DoSingle(argv); } return 0; }