スクロールとズーム

ここではライブ画像のサイズの変更とスクロールの方法について説明します。

このサンプルはIC Imaging Controlインストールディレクトリの%TOPLEVEL%\samples\VCx\scrollandzoomにございます。たとえばVC++2010 では %TOPLEVEL%\Samples\VC10となり、VC8 では%TOPLEVEL%\Samples\VC8です。サンプルプログラムのclasslib Demoappより継承され、スクロールとズーム機能を実装します。

表示されているライブ画像のサイズを変えることでズームイン/アウトを行います。イメージを引き延ばす、もしくは縮めるということになります。

スクロールはライブ画像を親ウィンドウ内で動かします。

ズーム

ライブ画像のズーム画面(以下"ビデオウィンドウ"とします)はイメージのサイズを変更することで実現されます。
Grabberクラスライブラリリファレンス>クラス>Grabberがビデオウィンドウのサイズの取得、設定のためのメソッドを実装します。

getWindowPosition( long&x0, long&y0, long&width, long&height ) が現在のビデオウィンドウの位置とサイズを取得します。デフォルトの位置は親ウィンドウ内の(0/0)に、デフォルトのサイズは現在のビデオフォーマットのサイズが設定されます。

メソッドsetWindowSize(long width, long height)を呼び出すことでビデオウィンドウのサイズを変更し、画像を引き延ばしばす。これがデジタルズームとなります。

サンプルではツールバーにコンボボックスがあり、10% から 300%のズーム倍率が選択できます。この範囲が限界というわけではありませんが、今回の例ではそのようにしています。CMainFrameのOnSelchangeZoomはコンボボックスのハンドラです。このハンドラがズームの設定を行います。

void CMainFrame::OnSelchangeZoom()
{
   float fZoomFactor = 1.0f;
   long lWidth;        // ビデオウィンドウの新しい幅の値
   long lHeight;       // ビデオウィンドウの新しい高さの値

   if( m_Grabber.isDevValid() && m_Grabber.isLive()  )
   {         
    fZoomFactor = m_cZoomCombo.GetCurSel() + 1;  // 0ベースの値なので         
                              // パーセントを計算するために         
    fZoomFactor /= 10.0f;                              // 1を足す
        
    // 現在のビデオフォーマットを取得         
    lWidth = m_Grabber.getAcqSizeMaxX();         
    lHeight = m_Grabber.getAcqSizeMaxY();
        
    // ズームファクターを計算         
    lWidth  = (long)((float)lWidth  * fZoomFactor);         
    lHeight = (long)((float)lHeight * fZoomFactor);
       
    // グラバーがビデオウィンドウのサイズを変更できるよう、デフォルトのウィンドウ位置を消去         
    m_Grabber.setDefaultWindowPosition(false);
  
    //新しいウィンドウサイズを設定         
    m_Grabber.setWindowSize(lWidth, lHeight);
  }
}

(このサンプルコードは samples/vc71/scrollandzoom/mainframe.cpp にございます。)

注: ビデオウィンドウのサイズを設定する前に、パラメータをfalseにしてsetDefaultWindowPosition()メソッドをコールする必要があります。これをしなかった場合、ビデオウィンドウのサイズは常にデフォルトに設定されてしまします。その場合、ビデオウィンドウのサイズの変更が不可能になってしまいます。

スクロール機能を追加する

ビデオウィンドウを含むウィンドウにはスクロールバーを実装しWM_HSCROLL や WM_VSCROLL メッセージを扱う必要があります。

スクロールバーの実装

この例は"CChildView"クラスの子ウィンドウをもっています。"CChildView"は MFCの"CWnd"から派生したものです。

CChildView::SetupScrollBarsメソッドはビデオウィンドウの位置とサイズを設定しスクロールバーを初期化します。ビデオウィンドウのサイズを変更(ズーム、またはビデオフォーマットの変更)する際にはこのメソッドをコールする必要があります。スクロールバーを隠すためには構造体SCROLLINFOの nPage メンバがnMax メンバより大きくなければいけません。それらが同じであった場合、スクロールバーは自動的に表示されます。ビデオウィンドウのサイズが親ウィンドウのサイズと同じ場合、ステートメントを使用しなければなりません。これによってスクロールバーが表示されなくなります。

スクロールバーのセットアップは多少ややこしい点がございます。どのように行われるかは以下をご覧ください。

void CChildView::SetupScrollBars()
{
  long posx;
  long posy;
  long width;
  long height;
  RECT clientRect; // 長方形のクライアントウィンドウ
  long clientEdge; // スロールバーのエッジの長さを格納
  SCROLLINFO si; // スクロールバーをセットアップするための構造体

  ZeroMemory(&si, sizeof(SCROLLINFO) );
  si.cbSize = sizeof(si); if( m_pGrabber->isDevValid())
  {
    //client rectのサイズを取得
    GetClientRect( &clientRect );

    // Grabberのビデオウィンドウのサイズと位置を取得
    if( m_pGrabber->getWindowPosition( posx, posy, width, height ) == true )
    {
      // 全クライアントエリアを使用する
      clientEdge = clientRect.right - clientRect.left;
      if( clientEdge > width + posx && posx < 0)
      {
        posx -= posx + width - clientEdge;
        if( posx > 0) posx = 0;
      }
      clientEdge = clientRect.bottom - clientRect.top;
      if( clientEdge > height + posy && posy < 0)
      {
        posy -= posy + height - clientEdge;
        if( posy > 0) posy = 0;
      }
      m_pGrabber->setWindowPosition(posx ,posy);

      si.fMask = SIF_PAGE|SIF_RANGE|SIF_POS;
      si.nMin = 0;
      if( height > clientRect.bottom)  // サイズが同じであればスクロールバーを隠す
      {
        // スクロールバーのページサイズはクライアントウィンドウの幅と高さ
        si.nPage = clientRect.bottom ;

        // 最大スクロール値はビデオウィンドウの幅と高さ
        // その最大値がページサイズよりも小さい場合、スクロールバーが隠れます
        si.nMax = height;

        // スクロールポジションはビデオウィンドウ位置の負数となります
        si.nPos = -posy;
      }
      else
      {
        si.nMax = 2;
        si.nPage = si.nMax +1;  // これがスクロールバーを隠すものです
      }

      // 縦スクロールバーをセットアップ
      SetScrollInfo( SB_VERT,&si);

      if( width > clientRect.right)  // サイズが同じであればスクロールバーを隠す。
      {
        // 横スクロールバーでも同様にする
        si.nPage = clientRect.right;
        si.nMax = width;
        si.nPos = -posx;
      }
      else
      {
        si.nMax = 2;
        si.nPage = si.nMax +1; // スクロールバーを隠す

      }
      SetScrollInfo( SB_HORZ,&si);
      PaintImage();
    }
  }
}

ビデオウィンドウのサイズが変更になる際にはSetupScrollBarsメソッドをコールする必要があります。
ズームファクターの変更時、WM SIZEイベントの発生時、 setDefaultWindowSize(true) またはliveStart()がコールされる場合です。

スクロールバーはWM_HSCROLL、またはWM_VSCROLLメッセージを作成します。
これらはメソッドOnHScrollOnVScrollによって扱われます。

///////////////////////////////////////////////////////////////////////////////
/*! ユーザーが縦のスクロールバーを動かすと、OnVScrollメッセージハンドラがコールされます。
*/
void CChildView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
  OnScroll(nSBCode, nPos, SB_VERT);
}

///////////////////////////////////////////////////////////////////////////////
/*! 横のスクロールバーの場合も同様です。
*/
void CChildView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
  OnScroll(nSBCode, nPos, SB_HORZ);
}

WM_HSCROLLとWM_VSCROLLのメッセージハンドラは両イベントの扱い方が同じであるため、いずれの場合もOnScrollメソッドをコールします。イベントを開始したスクロールバーはOnScrollパラメータとしてに渡されます。

まず最初に、現在のビデオウィンドウのサイズと位置がOnScroll内に取得されます。
これらの値は後でsetWindowPositionのために使用されることになります。そしてcaseステートメントがスクロールバーにおいてどのような処理をするべきかをチェックすると共に新しいスクロールバーの値を計算します。bSIsScrollフラグは新しいスクロール位置が計算されたかどうかを示します。このフラグがtrueに設定されると、取得されたウィンドウパラメータと新しいスクロール位置をsetWindowPositionに渡すことでスクロールが実行されます。このようにしてアプリケーションのクライアントウィンドウ内にあるビデオウィンドウが動きます。

///////////////////////////////////////////////////////////////////////////////
/*! OnScrollがHScrollとVScrollイベントを扱う。
*/
void CChildView::OnScroll(UINT nSBCode, UINT nPos, int iScrollBar)
{
  long posx;     
  long posy;     
  long width;     
  long height;     
  bool bIsScroll = false;
  SCROLLINFO si;    // スクロールバーをセットアップするための構造体
  ZeroMemory(&si, sizeof(SCROLLINFO) );         
    
  // 現在のビデオウィンドウのサイズと位置を取得
  m_pGrabber->getWindowPosition( posx, posy, width, height);
   
  si.cbSize = sizeof(si);
  si.fMask = SIF_POS;     
  GetScrollInfo( iScrollBar, &si);     

   switch( nSBCode )     
  {     
    case SB_LINERIGHT :    si.nPos++;
                bIsScroll = true;
                break;

    case SB_LINELEFT :  si.nPos--;
               bIsScroll = true;
               break;

    case SB_PAGERIGHT : si.nPos+=5;
               bIsScroll = true;
               break;

    case SB_PAGELEFT :  si.nPos-=5;
               bIsScroll = true;
               break;

    case SB_THUMBTRACK:     
    case SB_THUMBPOSITION:    si.nPos   = nPos;
                  bIsScroll = true;
                  break;
  }

  // スクロールイベントが送られたかどうかをチェック。
  if ( bIsScroll == true )
  {         
    // スクロールバー位置をパラメータとしてsetWindowPositionに渡すことで         
    // 縦または横にスクロールします。         
    // setWindowPositionに渡されるスクロール位置は負の値でなければなりません         
    if( iScrollBar == SB_HORZ )         
    {         
      m_pGrabber->setWindowPosition(-si.nPos, posy );         
    }         
    else
    {         
      m_pGrabber->setWindowPosition(posx ,-si.nPos);         
    }
        
    // 注: スクロールバーが新しい位置を表示するように、その位置を渡す必要があります。         
    si.fMask  = SIF_POS;         
    SetScrollInfo( iScrollBar, &si, TRUE);         
    OnPaint();       
  }
}

スクロールバーに関するヒント:
 1.新たに計算されたスクロール位置は関連するスクロールバーに渡さなければいけません。
   スクロールバーが値を受け取れなければスクロールができません。
 2.WM_HSCROLLもしくは WM_VSCROLL メッセージの送信後、システムが2つ目を開始します。
   2つ目のメッセージはメッセージハンドラに無効なnPos 値(0)を渡すため、スクロール用に処理されないようにする必要があります。

ズーム関数の拡張

スクール機能の実装が完了したので、今度はCMainFrame::OnSelchangeZoom()を拡張しなければなりません。なぜかというと現在の実装はズームファクターを変更するたびにビデオウィンドウの位置を(0/0) にリセットしてしまうからです。

getWindowPositionをコールしてウィンドウの現在位置の取得と新しいズームファクターに応じたウィンドウ位置の計算を行います。算出された位置はウィンドウ位置を(0/0)にリセットするsetDefaultWindowPosition(false)をコール後、setWindowPosition を使って設定することができます。

void CMainFrame::OnSelchangeZoom()
{
  float fZoomFactor = 1.0f;
  float fOldZoomFactor = 1.0f;
  long lXpos;
  long lYpos;
  long lWidth; // 新しいビデオウィンドウの幅
  long lHeight; // 新しいビデオウィンドウの高さ

  if( m_Grabber.isDevValid()  )
  {
    fZoomFactor = m_cZoomCombo.GetCurSel() + 1.0f; // ゼロベースの値なので1を足す
    fZoomFactor /= 10.0f;                              // パーセントに変換

    // 現在のウィンドウ位置を取得
    m_Grabber.getWindowPosition(lXpos,lYpos,lWidth, lHeight);

     // ウィンドウ位置の再計算のため、ズームファクター 1.0.で古いズームファクターを算出

    fOldZoomFactor = (float)lWidth / (float)m_Grabber.getAcqSizeMaxX();

    lXpos = (long)((float)lXpos / fOldZoomFactor);
    lYpos = (long)((float)lYpos / fOldZoomFactor);

    lXpos = (long)((float)lXpos * fZoomFactor);
    lYpos = (long)((float)lYpos * fZoomFactor);

    // 現在のビデオフォーマットサイズを取得
    lWidth = m_Grabber.getAcqSizeMaxX();
    lHeight = m_Grabber.getAcqSizeMaxY();

    // ズームファクターをもとに新しい幅と高さを計算
    lWidth  = (long)((float)lWidth  * fZoomFactor);
    lHeight = (long)((float)lHeight * fZoomFactor);

    // デフォルトのウィンドウ位置を無効にし、grabberがビデオウィンドウのサイズを変更できるようにする。
    m_Grabber.setDefaultWindowPosition(false);

    // 新たなウィンドウ位置とサイズを設定する
    m_Grabber.setWindowPosition(lXpos,lYpos);
    m_Grabber.setWindowSize( lWidth, lHeight);
    m_wndView.SetupScrollBars();
  }
}
void CMainFrame::OnSelchangeZoom()
{
  float fZoomFactor = 1.0f;
  float fOldZoomFactor = 1.0f;
  long lXpos;
  long lYpos;
  long lWidth; // 新しいビデオウィンドウの幅
  long lHeight; // 新しいビデオウィンドウの高さ

  if( m_Grabber.isDevValid()  )
  {
    fZoomFactor = m_cZoomCombo.GetCurSel() + 1; // ゼロベースの値なので1を足す
    fZoomFactor /= 10.0f;                              // パーセントに変換

    // 現在のウィンドウ位置を取得
    m_Grabber.getWindowPosition(lXpos,lYpos,lWidth, lHeight);

     // ウィンドウ位置の再計算のため、ズームファクター 1.0.で古いズームファクターを算出

    fOldZoomFactor = (float)lWidth / (float)m_Grabber.getAcqSizeMaxX();

    lXpos = (long)((float)lXpos / fOldZoomFactor);
    lYpos = (long)((float)lYpos / fOldZoomFactor);

    lXpos = (long)((float)lXpos * fZoomFactor);
    lYpos = (long)((float)lYpos * fZoomFactor);

    // 現在のビデオフォーマットサイズを取得
    lWidth = m_Grabber.getAcqSizeMaxX();
    lHeight = m_Grabber.getAcqSizeMaxY();

    // ズームファクターをもとに新しい幅と高さを計算
    lWidth  = (long)((float)lWidth  * fZoomFactor);
    lHeight = (long)((float)lHeight * fZoomFactor);

    // デフォルトのウィンドウ位置を無効にし、grabberがビデオウィンドウのサイズを変更できるようにする。
    m_Grabber.setDefaultWindowPosition(false);

    // 新たなウィンドウ位置とサイズを設定する
    m_Grabber.setWindowPosition(lXpos,lYpos);
    m_Grabber.setWindowSize( lWidth, lHeight);
    m_wndView.SetupScrollBars();
  }
}