*ビデオ信号について
 *ビデオ信号について [#rf38138e]
 日本ではNTSC方式と呼ばれるものが一般的。市販のキャプチャボードを用いれば、これをPCに動画や静止画として取り込むことができる。このためのソフトはボードに付属するケースが多いが、自作も可能。
 -[[テレビとビデオ信号(その1):http://www.orixrentec.co.jp/tmsite/know/know_tv1-38.html]]([[オリックス・レンテック:http://www.orixrentec.co.jp/]]の[[測定器玉手箱:http://www.orixrentec.co.jp/tmsite/]]より)
 -[[テレビとビデオ信号(その2):http://www.orixrentec.co.jp/tmsite/know/know_tv2-39.html]]
 -[[ビデオの基礎:http://www.dvdforum.gr.jp/Technology/03-1.html]]([[DVD Forum:http://www.dvdforum.gr.jp/]]の[[DVD ビデオ周辺技術講座:http://www.dvdforum.gr.jp/Technology/index.html]]より)
 
 *自作キャプチャソフト(Windows)
 
 *DirectShow [#g6fcfbdd]
 前に(猿真似の)キャプチャソフトを作ったが、多少知識がついたので、DirectShowを使ってもうちょっと汎用的なキャプチャができればいいなあと思って勉強開始。
 
 **link [#g06026ad]
 -[[DirectShowプログラミング [VC++]:http://www.geekpage.jp/programming/directshow/index.php]]
 -[[「DirectShowについて語ろう」より「キャプチャサイズを変更するには?」:http://www.freeml.com/message/directshow@freeml.com/0002478]]
 -[[DirectShowサンプル「StillCap」について:http://hpcgi1.nifty.com/MADIA/Vcbbs/wwwlng.cgi?print+200502/05020021.txt]]
 -[[[00027881] DirectShow取得サイズ変更:http://forums.belution.com/ja/vc/000/278/81.shtml]]
 
 *自作キャプチャソフト(Windows) [#zee28e9b]
 
 習作なのでバグや恥ずかしい勘違い等のオンパレードなのでしょう。どなたかこの先読み進まれる方、この点に注意して下さい。
 
 **何をするものか
 **何をするものか [#zcfbd7e6]
 キャプチャボードからビデオ信号を読み込み、静止画に保存する。Video For Windows という規格を用いているので、これに対応したデバイスならキャプチャボードやUSBカメラ等製品を問わず動作すると思われる(未確認)。Video For Windows は若干古めの規格で、時代的にはDirectShowの方が適切そうだったがややこしそうなのでやめた。
 
 JPEG周りは[[Independent JPEG Group:http://www.ijg.org/]]のコードを使用した。
 
 **本体
 **本体 [#m113e1ed]
 SDKで作成。
 -[[実行ファイル(123KB):http://www.issp.u-tokyo.ac.jp/labs/spectroscopy/akiyama/itoh/data/hiCapture.exe]]
 -[[プロジェクトのディレクトリのアーカイブ(286KB):http://www.issp.u-tokyo.ac.jp/labs/spectroscopy/akiyama/itoh/data/hiCapture.zip]]
 
 起動し、デバイスと静止画保存フォーマットを選べばメインの画面となる。左が現在キャプチャしている画面で、右が静止画用。
 -snap ... 現在のキャプチャ画面を静止画領域に移(写)す 
 -save ... 静止画領域をファイルに保存ファイル名は「ID#_年月日_時分秒」+拡張子(#はキャプチャデバイスの識別番号) 
 -snap&save ... snapしてsaveする 
 -timer ... 決められた時間毎にsnap&save
 
 **参考link
 **参考link [#s780c36d]
 -[[ビデオキャプチャ:http://www.katto.comm.waseda.ac.jp/~katto/Class/GazoTokuron/code/videocapture.html]] (自作といいつつも、内容はほとんどここに依存しています)
 -[[ビデオキャプチャー:http://laputa.cs.shinshu-u.ac.jp/~gtakano/prog1.html]]
 -[[BMPファイルフォーマット:http://www.kk.iij4u.or.jp/~kondo/bmp/index.html]]
 -[[VFWのキャプチャーで・・・:http://forums.belution.com/ja/vc/000/165/41s.shtml]]
 -[[ビデオデータをキャプチャするには?:http://homepage1.nifty.com/MADIA/vb/vb_bbs/200401_04010125.html]]
 -[[VB上でWebカメラを操作するには?:http://hpcgi1.nifty.com/MADIA/VBBBS/wwwlng.cgi?print+200401/04010041.txt]]
 
 **メモ
 **メモ [#j65eb7f4]
 
 ***プレビューとオーバーレイ
 ***プレビューとオーバーレイ [#t560371e]
 違いが良くわからんが、
 -プレビュー(capPreview) -> ハードウェアの中身を一旦システムが解釈してからクライアント領域に表示
 -オーバーレイ(capOverlay) -> ハードウェアから直接?クライアント領域に描画
 
 てなところ?全く自身がない。
 
 capPreviewしておくと、常にcapSetCallbackOnFrameで指定されたコールバックが呼ばれる。
 Previewの度に呼ばれるのだから当り前。
 逆にcapOverlayだとそうはならない。ここでコールバックを呼ぶにはcapGrabFrameかcapGrabFrameNoStopを使う。
 NoStopの方じゃないと、描画領域の更新が止まってしまう。
 
 
 **ソース
 **ソース [#e9134e07]
 
  /////////////////////////////////////////////////////////////////////////////////////////
  // 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;
  }
 
 **リソーススクリプト
 **リソーススクリプト [#j8bb02e2]
 
  //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