日本ではNTSC方式と呼ばれるものが一般的。市販のキャプチャボードを用いれば、これをPCに動画や静止画として取り込むことができる。このためのソフトはボードに付属するケースが多いが、自作も可能。
前に(猿真似の)キャプチャソフトを作ったが、多少知識がついたので、DirectShowを使ってもうちょっと汎用的なキャプチャができればいいなあと思って勉強開始。
習作なのでバグや恥ずかしい勘違い等のオンパレードなのでしょう。どなたかこの先読み進まれる方、この点に注意して下さい。
キャプチャボードからビデオ信号を読み込み、静止画に保存する。Video For Windows という規格を用いているので、これに対応したデバイスならキャプチャボードやUSBカメラ等製品を問わず動作すると思われる(未確認)。Video For Windows は若干古めの規格で、時代的にはDirectShowの方が適切そうだったがややこしそうなのでやめた。
JPEG周りはIndependent JPEG Groupのコードを使用した。
SDKで作成。
起動し、デバイスと静止画保存フォーマットを選べばメインの画面となる。左が現在キャプチャしている画面で、右が静止画用。
違いが良くわからんが、
てなところ?全く自身がない。
capPreviewしておくと、常にcapSetCallbackOnFrameで指定されたコールバックが呼ばれる。 Previewの度に呼ばれるのだから当り前。 逆にcapOverlayだとそうはならない。ここでコールバックを呼ぶにはcapGrabFrameかcapGrabFrameNoStopを使う。 NoStopの方じゃないと、描画領域の更新が止まってしまう。
///////////////////////////////////////////////////////////////////////////////////////// // hiCapture - capture and save images (not video) via VFW (Video For Windows) device // ///////////////////////////////////////////////////////////////////////////////////////// // // Version 1.0 ('04 Jun, 30) // Hirotake Itoh <hiroitoh@issp.u-tokyo.ac.jp> // // NOTES: // * I'm no more than beginner and there may be lots bugs... use carefully. // * Of course, no warranty // * This program uses IJG works to deal with JPEG files. See "http://www.ijg.org/" // // REFERENCES: // http://www.katto.comm.waseda.ac.jp/~katto/Class/GazoTokuron/code/videocapture.html // http://black.sakura.ne.jp/~third/system/winapi/win.html // http://www7.plala.or.jp/keny01/win32/pre/index.html // http://www.kk.iij4u.or.jp/~kondo/bmp/index.html // http://www2m.biglobe.ne.jp/~yasutaka/ // #include <windows.h> #include <stdio.h> #include <vfw.h> #include <commctrl.h> #include "jpeg/jpeglib.h" #include "resource.h" //////////////// // prototypes // //////////////// LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // window procedure for the main window LRESULT PASCAL FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr); // callback for the video frame LRESULT PASCAL ErrorCallbackProc(HWND hWnd, int nErrID, LPSTR lpErrorText); BOOL CALLBACK TimerDlgProc(HWND hTimerDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); // dialog procedure (device selection) BOOL CALLBACK DeviceDlgProc(HWND hTimerDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); // dialog procedure (timer) void ShowBitmap(HWND hWnd); // show a captured frame in bitmap format int VideoInit(HWND hWnd); // video initialization int VideoClose(); // video housekeepings int write_JPEG_file (char * filename, int quality); int write_BMP_file (char * filename); //////////// // macros // //////////// #define MYWINDOWCLASS "hiCapture" #define MYWINDOWTITLE "hiCapture - report bugs to: hiroitoh@issp.u-tokyo.ac.jp" #define MAX_CAPDEVICE 10 // limited to 10 #define TIMERID 0 // can be any number ////////////////////// // global variables // ////////////////////// HINSTANCE hInst; // this application instance HWND hWndVideo; // window for the video frame HBITMAP hBitmapFrame; // bitmap handle for the static image BITMAPINFO bmpInfo; // bitmap information of the video frame BITMAPFILEHEADER bmfh; // used when the program saves the bitmap int vWidth, vHeight; // width and height (in pixel) of the video frame void *pPixelArray; // pixel data of the grabbed frame char DeviceName[MAX_CAPDEVICE][100]; // device name BOOL IsTimerRunning = FALSE; // used for timer UINT TimerInterval = 60; // used for timer (in second) BOOL PreferJpeg = FALSE; // format of the saved file ////////////////////////////////////// // WinMain and main window settings // ////////////////////////////////////// int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInst, LPSTR lpszCmdLine, int nCmdShow) { HWND hWnd; MSG msg; WNDCLASS myProg; hInst = hInstance; if (!hPreInst) { myProg.style = CS_HREDRAW | CS_VREDRAW; myProg.lpfnWndProc = WndProc; myProg.cbClsExtra = 0; myProg.cbWndExtra = 0; myProg.hInstance = hInstance; myProg.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1)); myProg.hCursor = LoadCursor(NULL, IDC_ARROW); myProg.hbrBackground = GetStockObject(WHITE_BRUSH); myProg.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); myProg.lpszClassName = MYWINDOWCLASS; if (!RegisterClass(&myProg)) return FALSE; } hWnd = CreateWindow( MYWINDOWCLASS, MYWINDOWTITLE, // class, title WS_OVERLAPPEDWINDOW, // style CW_USEDEFAULT, CW_USEDEFAULT, // horizontal and vertical positions CW_USEDEFAULT, CW_USEDEFAULT, // width and height NULL, NULL, // parent, menu or child window identifiers hInstance, // application instance NULL // window creation data ); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (msg.wParam); } /////////////////////////// // main window procedure // /////////////////////////// LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { // error handling char errstr[100]; int i; // error code of VideoInit // window size RECT rWindow, rClient; int WidthOffset; // (Window_region - Client_region).width int HeightOffset; // (Window_region - Client_region).height // file handling char filename[20]; SYSTEMTIME stTime; switch (msg) { case WM_DESTROY: PostQuitMessage(0); break; case WM_CREATE: i = VideoInit(hWnd); if(i != 0) { wsprintf(errstr, "Error in \"VideoInit\" (%d)", i); MessageBox(hWnd, errstr, "error", MB_OK); PostQuitMessage(0); } if(IDYES == MessageBox(hWnd, "Choose the format for the image saving.\nYes -> JPEG\nNo -> BMP", "image format", MB_YESNO)) PreferJpeg = TRUE; SendMessage(hWnd, WM_SIZE, 0, 0); break; case WM_SIZE: GetWindowRect(hWnd, &rWindow); GetClientRect(hWnd, &rClient); WidthOffset = (rWindow.right - rWindow.left) - rClient.right; HeightOffset = (rWindow.bottom - rWindow.top) - rClient.bottom + 1; SetWindowPos(hWnd, NULL, 0, 0, vWidth*2+WidthOffset, vHeight+HeightOffset, // width and height SWP_NOMOVE | SWP_NOZORDER); SetWindowPos(hWndVideo, NULL, 0, 0, vWidth, vHeight, // width and height SWP_NOMOVE | SWP_NOZORDER); break; case WM_CLOSE: VideoClose(); DestroyWindow(hWnd); break; case WM_PAINT: ShowBitmap(hWnd); break; case WM_TIMER: SendMessage(hWnd, WM_COMMAND, (WPARAM)ID_SNAPANDSAVEIMAGE, 0); break; case WM_COMMAND: switch(LOWORD(wParam)){ case ID_SNAP: capGrabFrameNoStop(hWndVideo); InvalidateRect(hWnd, NULL, FALSE); break; case ID_SAVEIMAGE: // time GetLocalTime(&stTime); wsprintf(filename, "%02d%02d%02d_%02d%02d%02d", stTime.wYear-2000, stTime.wMonth, stTime.wDay, stTime.wHour, stTime.wMinute, stTime.wSecond); if(PreferJpeg) write_JPEG_file(filename, 95); // decrease '95' to save space else write_BMP_file(filename); break; case ID_SNAPANDSAVEIMAGE: SendMessage(hWnd, WM_COMMAND, (WPARAM)ID_SNAP, 0); SendMessage(hWnd, WM_COMMAND, (WPARAM)ID_SAVEIMAGE, 0); break; case ID_TIMER: // 'TimerInterval' is the interval in seconds. // 0 means that the user doesn't want to use the timer. TimerInterval = DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOGTIMER), NULL, TimerDlgProc); if(IsTimerRunning) { // if the timer is running, KillTimer(hWnd, TIMERID); // stop it, IsTimerRunning = FALSE; // and update flag } if(TimerInterval != 0) { // if the user wants to use the timer, SetTimer(hWnd, TIMERID, TimerInterval * 1000, NULL); // start it, IsTimerRunning = TRUE; // and update flag } break; } return 0; } return(DefWindowProc(hWnd, msg, wParam, lParam)); } ////////////////////////////// // dialog procedure (timer) // ////////////////////////////// BOOL CALLBACK TimerDlgProc(HWND hTimerDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { // retrieve window handles for each controls const HWND hWndTimerCheck = GetDlgItem(hTimerDlg, IDC_CHECKTIMER); const HWND hWndTimerEdit = GetDlgItem(hTimerDlg, IDC_EDITINTERVAL); const HWND hWndTimerSpin = GetDlgItem(hTimerDlg, IDC_SPININTERVAL); int nResult; // timer interval in second switch(uMsg){ case WM_INITDIALOG: SendMessage(hWndTimerSpin, UDM_SETRANGE, 0, MAKELONG(10800,1)); // interval range: 1s - 10800s (3h) SendMessage(hWndTimerSpin, UDM_SETPOS, 0, MAKELONG(TimerInterval,0)); // set previous interval if(IsTimerRunning) SendMessage(hWndTimerCheck, BM_SETCHECK, BST_CHECKED, 0); // set previous flag else SendMessage(hWndTimerCheck, BM_SETCHECK, BST_UNCHECKED, 0); // return TRUE; case WM_COMMAND: switch(LOWORD(wParam)){ case IDOK: if(BST_UNCHECKED == SendMessage(hWndTimerCheck, BM_GETCHECK, 0, 0)) nResult = 0; else nResult = SendMessage(hWndTimerSpin, UDM_GETPOS, 0, 0); // now nResult is same as new interval, or 0, in case the user doesn't want to use the timer. EndDialog(hTimerDlg, nResult); break; default: return FALSE; } default: return FALSE; } return TRUE; } /////////////////////////////// // dialog procedure (device) // /////////////////////////////// BOOL CALLBACK DeviceDlgProc(HWND hDeviceDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { const HWND hWndCombo = GetDlgItem(hDeviceDlg, IDC_COMBO1); int i; char tmpstr[100]; switch(uMsg){ case WM_INITDIALOG: // only valid devices are to be added to combobox for(i=0; i<MAX_CAPDEVICE; i++){ if(0 != strcmp("none", DeviceName[i])){ wsprintf(tmpstr, "id: %d, %s", i, DeviceName[i]); SendMessage(hWndCombo, CB_ADDSTRING, 0, (LPARAM)tmpstr); } } // anyway, select one SendMessage(hWndCombo, CB_SETCURSEL, 0, 0); return TRUE; case WM_COMMAND: switch(LOWORD(wParam)){ case IDOK: // return device id# SendMessage(hWndCombo, WM_GETTEXT, sizeof(tmpstr), (LPARAM)tmpstr); sscanf(tmpstr, "id: %d,", &i); EndDialog(hDeviceDlg, i); break; default: return FALSE; } default: return FALSE; } return TRUE; } ////////////////////////// // Video initialization // ////////////////////////// int VideoInit(HWND hWnd){ int i; // used to retrieve indices of available drivers int j=0; // check the number of valid drivers char DeviceVersion[100]; // almost for tempral use BOOL bDriverDescription; // whether the specified driver is available or not DWORD wSize; // the size of the video format char VideoFormat[200]; // used only for user notification // capture window hWndVideo = capCreateCaptureWindow( "CapWindow", WS_CHILD | WS_VISIBLE, 0, 0, 320, 240, hWnd, 0 ); if(hWndVideo == NULL) return -1; // callbacks capSetCallbackOnFrame(hWndVideo, FrameCallbackProc); capSetCallbackOnError(hWndVideo, ErrorCallbackProc); // driver for(i=0; i<MAX_CAPDEVICE; i++){ bDriverDescription = capGetDriverDescription( i, (LPSTR)DeviceName[i], sizeof(DeviceName[i]), (LPSTR)DeviceVersion, sizeof(DeviceVersion) ); if(!bDriverDescription){ wsprintf(DeviceName[i],"none"); //wsprintf(DeviceMessage, "The following device was found:\n%s", DeviceName[i]); //MessageBox(hWnd, DeviceMessage, "Device Notification", MB_OK); } else j++; } if(j == 0) return -6; // no valid drivers i = DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOGDEVICE), NULL, DeviceDlgProc); capDriverConnect(hWndVideo, i); // picture format wSize = capGetVideoFormatSize(hWndVideo); if( !capGetVideoFormat(hWndVideo, &bmpInfo, wSize) ){ DestroyWindow(hWndVideo); return -2; } if( bmpInfo.bmiHeader.biCompression != BI_RGB || bmpInfo.bmiHeader.biBitCount != 24){ MessageBox(hWnd, "Sorry, only 24bit RGB. exitting...", "error", MB_OK); return -3; } vWidth = bmpInfo.bmiHeader.biWidth; vHeight = bmpInfo.bmiHeader.biHeight; wsprintf(VideoFormat, "Width: %d\nHeight: %d", vWidth, vHeight); MessageBox(hWnd, VideoFormat, "Size:", MB_OK); // prepare bitmap file header bmfh.bfType = 'B' + ('M'<<8); // 'BM' bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); // offset to pixel array bmfh.bfSize = bmfh.bfOffBits + bmpInfo.bmiHeader.biSizeImage; // entire filesize // create bitmap handle // NOTE: Memory allocation and deallocation of the array for pixels (pPixelArray) // are automatically done by CreateDIBSection and DeleteObject. In other words, // 'malloc' or 'free' for pPixelArray must NOT be done manually. hBitmapFrame = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pPixelArray, NULL, 0); if(hBitmapFrame == NULL){ MessageBox(hWnd, "\"CreateDIBSection\" failed", "error", MB_OK); return -4; } // show currently captured image capOverlay(hWndVideo, TRUE); return 0; } //////////////////////// // Video housekeeping // //////////////////////// int VideoClose(){ // release callbacks capSetCallbackOnError(hWndVideo, NULL); capSetCallbackOnFrame(hWndVideo, NULL); // destroy the window DestroyWindow(hWndVideo); // release memory (i.e. bitmap handle) DeleteObject(hBitmapFrame); return TRUE; } //////////////////////////// // showing captured image // //////////////////////////// void ShowBitmap(HWND hWnd){ HDC hDC, hBuffer; // hBuffer is used for memory device context, in order to show bitmap image hDC = GetDC(hWnd); hBuffer = CreateCompatibleDC(hDC); SelectObject(hBuffer, hBitmapFrame); BitBlt( hDC, // target DC vWidth+1, 0, // x, y coordinates of the target vWidth, // width of the transfering rectangle vHeight, // height of the transfering rectangle hBuffer, // source DC 0, 0, // x, y coordinates of the source SRCCOPY // raster operation mode ); DeleteDC(hBuffer); ReleaseDC(hWnd, hDC); return; } //////////////////////////////////////////////////////////////// // frame callback function, which retrieves raw captured data // //////////////////////////////////////////////////////////////// LRESULT PASCAL FrameCallbackProc(HWND hWnd, LPVIDEOHDR lpVHdr) { memcpy(pPixelArray, lpVHdr->lpData, lpVHdr->dwBufferLength); return (LRESULT) TRUE ; } ///////////////////////////// // error callback function // ///////////////////////////// LRESULT PASCAL ErrorCallbackProc(HWND hWnd, int nErrID, LPSTR lpErrorText) { return (LRESULT) TRUE; } /////////////////////// // file saving (BMP) // /////////////////////// int write_BMP_file (char * filename){ HANDLE hfile; char FilenameWithExt[256]; DWORD tmp; wsprintf(FilenameWithExt, "%s.bmp", filename); hfile = CreateFile(FilenameWithExt, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); WriteFile(hfile, &bmfh, sizeof(bmfh), &tmp, NULL); WriteFile(hfile, &bmpInfo.bmiHeader, sizeof(bmpInfo.bmiHeader), &tmp, NULL); SetFilePointer(hfile, bmfh.bfOffBits, NULL, FILE_BEGIN); GdiFlush(); WriteFile(hfile, pPixelArray, bmpInfo.bmiHeader.biSizeImage, &tmp, NULL); CloseHandle(hfile); return 0; } //////////////////////// // file saving (JPEG) // //////////////////////// int write_JPEG_file (char * filename, int quality){ // quality: 0(low)...100(high) 5-95 is useful range (from 'cjpeg.c') struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; FILE * outfile; /* target file */ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ int row_stride; /* physical row width in image buffer */ JSAMPLE * image_buffer; int i,j,k1,k2; unsigned char *temp; char FilenameWithExt[256]; // allocate memory for image_buffer image_buffer = (JSAMPLE *)malloc(bmpInfo.bmiHeader.biSizeImage); if(image_buffer == NULL) return -1; // // | Windows BMP (see RGBQUAD) | what JPEG program wants | // -------------------+---------------------------+-------------------------+- // color order | B,G,R,B,G,R,....... | R,G,B,R,G,B,........ | // -------------------+---------------------------+-------------------------+- // vertical drawing | from bottom to top | from top to bottom | // -------------------+---------------------------+-------------------------+- // // So we have to convert the pixel order of the source array to obtain nice JPEG image. // Otherwise, such as // memcpy(image_buffer, pPixelArray, bmpInfo.bmiHeader.biSizeImage); // a strange image will come. temp = (unsigned char *)pPixelArray; for(i=0; i< vHeight; i++) { k1 = i * 3 * vWidth; k2 = (vHeight - i - 1) * 3 * vWidth; for(j=0; j< 3*vWidth; j+=3) { *(image_buffer + k1 + j + 0) = *(temp + k2 + j + 2); *(image_buffer + k1 + j + 1) = *(temp + k2 + j + 1); *(image_buffer + k1 + j + 2) = *(temp + k2 + j + 0); } } // Step 1 cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); // Step 2 wsprintf(FilenameWithExt, "%s.jpg", filename); if ((outfile = fopen(FilenameWithExt, "wb")) == NULL) { fprintf(stderr, "can't open %s\n", FilenameWithExt); return -2; } jpeg_stdio_dest(&cinfo, outfile); // Step 3 cinfo.image_width = vWidth; /* image width and height, in pixels */ cinfo.image_height = vHeight; cinfo.input_components = 3; /* # of color components per pixel */ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); // Step 4 jpeg_start_compress(&cinfo, TRUE); // Step 5 row_stride = vWidth * 3; /* JSAMPLEs per row in image_buffer */ while (cinfo.next_scanline < cinfo.image_height) { row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride]; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } // Step 6 jpeg_finish_compress(&cinfo); fclose(outfile); // Step 7 jpeg_destroy_compress(&cinfo); // release image_buffer free(image_buffer); return 0; }
//Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // ニ?ワク?resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN) #ifdef _WIN32 LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT #pragma code_page(932) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Menu // IDR_MENU1 MENU DISCARDABLE BEGIN MENUITEM "snap", ID_SNAP MENUITEM "save", ID_SAVEIMAGE MENUITEM "snap&&save", ID_SNAPANDSAVEIMAGE MENUITEM "timer", ID_TIMER END #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ICON1 ICON DISCARDABLE "icon1.ico" ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_DIALOGDEVICE, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 186 TOPMARGIN, 7 BOTTOMMARGIN, 39 END IDD_DIALOGTIMER, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 129 TOPMARGIN, 7 BOTTOMMARGIN, 49 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_DIALOGDEVICE DIALOG DISCARDABLE 0, 0, 193, 46 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Choose device " FONT 11, "Arial" BEGIN DEFPUSHBUTTON "OK",IDOK,65,25,50,14 COMBOBOX IDC_COMBO1,7,7,179,79,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP END IDD_DIALOGTIMER DIALOG DISCARDABLE 0, 0, 136, 56 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Timer setting" FONT 11, "Arial" BEGIN DEFPUSHBUTTON "OK",IDOK,92,10,27,24 CONTROL "Timer",IDC_CHECKTIMER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,24,7,35,10 LTEXT "Interval [sec]",IDC_STATICTIMER,7,20,43,11 EDITTEXT IDC_EDITINTERVAL,7,32,71,12,ES_AUTOHSCROLL CONTROL "Spin2",IDC_SPININTERVAL,"msctls_updown32", UDS_SETBUDDYINT | UDS_ALIGNRIGHT | UDS_AUTOBUDDY | UDS_ARROWKEYS,63,31,11,14 END #endif // ニ?ワク?resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED