2011年4月23日土曜日

DIB(10) - 三角形描画


 三角形描画。
三角形ポリゴンのほうが通じそうだけど、個人的にはなんか違うかなと。
 

 まず設定された3点を、yでソートします。上からp1, p2, p3とします。
yが1進んだときのxの変化量を、p1からp2、p1からp3、p2からp3のそれぞれ求めます。
式は(p2.x-p1.x)/(p2.y-p1.y)などになります。
u,vも始点の座標と終点の座標を計算するための変化量を記録しておきます。
 後はレンダリングしていくだけですが、
始点をp1として、p2方向とp3方向のそれぞれにパラメータを変化させ、
描画開始点xと終了点xを決定。
その間のu,vも始点と終点を計算して1ドットずつ描画していきます。
終わったら次の行へ、を繰り返す。
 p1->p2間が完了したら、p2->p3用にパラメータを設定しなおして
また同じように一行ずつ処理していけばp3にたどり着くころには描画完了しているって流れになります。
 
 
 
■サンプル
 描画先の始点と終点決定をtriangle()
与えられた始点終点から実際に描画するのをrenderLine()で行っています。

#include <windows.h>
#include <cmath>
#include <algorithm>
const int WINDOW_WIDTH = 640;
const int WINDOW_HEIGHT = 480;
 
 
 
class DIB32
{
public:
  /**
   * Point
   */
  struct Point
  {
    LONG x, y;
    LONG u, v;
 
    void set( LONG x, LONG y, LONG u, LONG v )
    {
      this->x = x;
      this->y = y;
      this->u = u;
      this->v = v;
    }
  };
 
 
 
 
  DIB32()
    : m_pixel( NULL )
  {
    ZeroMemory( &m_bmi, sizeof(m_bmi) );
  }
 
  virtual ~DIB32()
  {
    release();
  }
 
  virtual bool create( LONG width, LONG height )
  {
    if ( width <= 0 || height <= 0 ) return false;
    release();
 
    m_pixel = new DWORD[ width * height ];
    if ( !m_pixel ) return false;
 
    m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    m_bmi.bmiHeader.biWidth = width;
    m_bmi.bmiHeader.biHeight = height;
    m_bmi.bmiHeader.biPlanes = 1;
    m_bmi.bmiHeader.biBitCount = 32;
    m_bmi.bmiHeader.biCompression = BI_RGB;
 
    return true;
  }
 
  /** create from HBITMAP */
  bool create( LPCTSTR fileName )
  {
    HBITMAP hBmp = static_cast<HBITMAP>( LoadImage( NULL, fileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE ) );
    if ( !hBmp ) return false;
 
    BITMAP bm = {0};
    GetObject( hBmp, sizeof(BITMAP), &bm );
 
    // create DC
    HDC hdc = CreateCompatibleDC( NULL );
    if ( !hdc ) return false;
 
    // create buf
    if ( !create( bm.bmWidth, bm.bmHeight ) )
    {
      DeleteDC( hdc );
      return false;
    }
 
    // copy
    GetDIBits( hdc, hBmp, 0, bm.bmHeight, m_pixel, &m_bmi, DIB_RGB_COLORS );
 
    DeleteDC( hdc );
    DeleteObject( hBmp );
 
    return true;
  }
 
  virtual void release()
  {
    if ( m_pixel )
    {
      delete [] m_pixel;
      m_pixel = NULL;
    }
    ZeroMemory( &m_bmi, sizeof(m_bmi) );
  }
 
  bool render( HDC hdc, HWND hWnd ) const
  {
    RECT rect;
    GetClientRect( hWnd, &rect );
    return GDI_ERROR != StretchDIBits(
      hdc, 0, 0, rect.right, rect.bottom,
      0, 0, getWidth(), getHeight(),
      m_pixel, &m_bmi, DIB_RGB_COLORS, SRCCOPY );
  }
 
 
 
  
  inline void renderLine(
    LPDWORD destLine,
    LONG destMaxWidth,
    LONG startX, LONG endX,
    float startU, float startV,
    float endU, float endV )
  {
    if ( startX > endX )
    {
      std::swap( startX, endX );
      std::swap( startU, endU );
      std::swap( startV, endV );
    }
 
    if ( startX > destMaxWidth || endX < 0 || (endX-startX)==0 ) return;
 
    const float addU = (endU-startU) / static_cast<float>(endX - startX);
    const float addV = (endV-startV) / static_cast<float>(endX - startX);
 
    // cliping-left
    if ( startX < 0 )
    {
      startU += (addU * static_cast<float>(-startX));
      startV += (addV * static_cast<float>(-startX));
      startX = 0;
    }
    // cliping-right
    if ( endX > destMaxWidth )
    {
      endX = destMaxWidth;
    }
 
    float u = startU;
    float v = startV;
    for (LONG x=startX; x<endX; x++)
    {      
      *(destLine + x) = getPixel(static_cast<LONG>(u), static_cast<LONG>(v));
 
      u += addU;
      v += addV;
    }
  }
 
  bool triangle( DIB32& dest, const Point& point1, const Point& point2, const Point& point3 )
  {
    Point p1( point1 ), p2( point2 ), p3( point3 );
 
    // 手動ソート
    if ( p1.y > p2.y ) std::swap( p1, p2 );
    if ( p1.y > p3.y ) std::swap( p1, p3 );
    if ( p2.y > p3.y ) std::swap( p2, p3 );
 
    const float height1_2 = static_cast<float>( p2.y-p1.y ? p2.y-p1.y : 1 );
    const float height1_3 = static_cast<float>( p3.y-p1.y ? p3.y-p1.y : 1 );
    const float height2_3 = static_cast<float>( p3.y-p2.y ? p3.y-p2.y : 1 );
    
    const float destAddX1_2 = static_cast<float>(p2.x-p1.x) / height1_2;
    const float destAddX1_3 = static_cast<float>(p3.x-p1.x) / height1_3;
    const float destAddX2_3 = static_cast<float>(p3.x-p2.x) / height2_3;
    
    const float addU1_2 = static_cast<float>(p2.u-p1.u) / height1_2;
    const float addU1_3 = static_cast<float>(p3.u-p1.u) / height1_3;
    const float addU2_3 = static_cast<float>(p3.u-p2.u) / height2_3;
    const float addV1_2 = static_cast<float>(p2.v-p1.v) / height1_2;
    const float addV1_3 = static_cast<float>(p3.v-p1.v) / height1_3;
    const float addV2_3 = static_cast<float>(p3.v-p2.v) / height2_3;
    
    // 1~2
    float startX = static_cast<float>( p1.x );
    float endX = static_cast<float>( p1.x );
    float startU = static_cast<float>( p1.u );
    float startV = static_cast<float>( p1.v );
    float endU = static_cast<float>( p1.u );
    float endV = static_cast<float>( p1.v );
    LPDWORD destLine = dest.getPixelAddr( 0, p1.y );
 
    for (LONG y=p1.y; y<p2.y&&y<dest.getHeight(); y++)
    {
      // render line
      if ( y >= 0 )
      {
        renderLine( destLine, dest.getWidth(), static_cast<LONG>(startX), static_cast<LONG>(endX), startU, startV, endU, endV );
      }
 
      startX += destAddX1_2;
      endX += destAddX1_3;
      startU += addU1_2;
      endU += addU1_3;
      startV += addV1_2;
      endV += addV1_3;
 
      destLine -= dest.getWidth();
    }
    // 2~3
    startX = static_cast<float>( p2.x );
    startU = static_cast<float>( p2.u );
    startV = static_cast<float>( p2.v );
    for (LONG y=p2.y; y<p3.y&&y<dest.getHeight(); ++y)
    {
      if ( y >= 0 )
      {
        renderLine( destLine, dest.getWidth(), static_cast<LONG>(startX), static_cast<LONG>(endX), startU, startV, endU, endV );
      }
 
      startX += destAddX2_3;
      endX += destAddX1_3;
      startU += addU2_3;
      endU += addU1_3;
      startV += addV2_3;
      endV += addV1_3;
 
      destLine -= dest.getWidth();
    }
 
    return true;
  }
 
  
  LONG getWidth() const { return m_bmi.bmiHeader.biWidth; }
  LONG getHeight() const { return m_bmi.bmiHeader.biHeight; }
  const LPDWORD getPixelAddr( LONG x, LONG y ) const { return m_pixel + ((getHeight()-1)-y)*getWidth() + x; }
  LPDWORD getPixelAddr( LONG x, LONG y ) { return const_cast<LPDWORD>(static_cast<const DIB32&>(*this).getPixelAddr(x, y)); }
 
  DWORD getPixel( LONG x, LONG y ) const
  {
    if ( x >= 0 && x < getWidth() && y >= 0 && y < getHeight() )
    {
      return *getPixelAddr( x, y );
    }
    return 0;
  }
  
 
protected:
 
  LPDWORD m_pixel;
  BITMAPINFO m_bmi;
};
 
 
 
 
 
LRESULT CALLBACK wndProc(
  HWND hWnd,
  UINT msg,
  WPARAM wParam,
  LPARAM lParam )
{
  static DIB32 back, image;
  switch (msg)
  {
  case WM_DESTROY:
    ShowWindow( hWnd, SW_HIDE );
    PostQuitMessage(0);
    break;
  case WM_CREATE:
    back.create( WINDOW_WIDTH, WINDOW_HEIGHT );
    image.create( TEXT("image.bmp") );
 
    {
      DIB32::Point p1, p2, p3, p4;
      p1.x = 100;
      p1.y = 100;
      p1.u = 0;
      p1.v = 0;
      p2.x = 100;
      p2.y = 200;
      p2.u = 0;
      p2.v = 32;
      p3.x = 200;
      p3.y = 100;
      p3.u = 32;
      p3.v = 0;
      image.triangle( back, p1, p2, p3 );
 
      p4.x = 300;
      p4.y = 200;
      p4.u = 32;
      p4.v = 32;
      image.triangle( back, p2, p3, p4 );
    }
    break;
  case WM_PAINT:
    {
      PAINTSTRUCT ps = {0};
      HDC hdc = BeginPaint( hWnd, &ps );
      back.render( hdc, hWnd );
      EndPaint( hWnd, &ps );
    }
    break;
  default:
    return DefWindowProc( hWnd, msg, wParam, lParam );
  }
 
  return 0;
}
 
 
 
 
int WINAPI WinMain(
  HINSTANCE hInstance,
  HINSTANCE, PSTR, int )
{
  LPCTSTR WINDOW_NAME = TEXT("sample");
 
  WNDCLASSEX wc;
  wc.style    = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc  = reinterpret_cast<WNDPROC>( wndProc );
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.cbSize    = sizeof( WNDCLASSEX );
  wc.hInstance  = hInstance;
  wc.hIcon    = NULL;
  wc.hIconSm    = NULL;
  wc.hCursor    = LoadCursor( NULL, IDC_ARROW );
  wc.hbrBackground= reinterpret_cast<HBRUSH>( GetStockObject(WHITE_BRUSH) );
  wc.lpszMenuName  = NULL;
  wc.lpszClassName= WINDOW_NAME;
  if ( !RegisterClassEx(&wc) ) return 0;
 
  LONG winWidth = WINDOW_WIDTH
    + GetSystemMetrics(SM_CXEDGE)
    + GetSystemMetrics(SM_CXBORDER)
    + GetSystemMetrics(SM_CXDLGFRAME);
  LONG winHeight = WINDOW_HEIGHT
    + GetSystemMetrics(SM_CYEDGE)
    + GetSystemMetrics(SM_CYBORDER)
    + GetSystemMetrics(SM_CYDLGFRAME)
    + GetSystemMetrics(SM_CYCAPTION);
  HWND hWnd = CreateWindowEx(
    0, WINDOW_NAME, NULL, WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME,
    CW_USEDEFAULT, CW_USEDEFAULT, winWidth, winHeight,
    NULL, NULL, hInstance, NULL);
  if ( !hWnd ) return -1;
 
  ShowWindow( hWnd, SW_SHOWNORMAL );
  UpdateWindow( hWnd );
  
  MSG msg;
  for (;;)
  {
    if ( !GetMessage(&msg, NULL, 0, 0) ) break;
    TranslateMessage( &msg );
    DispatchMessage( &msg );
  }
 
  
  UnregisterClass( WINDOW_NAME, hInstance );
  return msg.wParam;
}

 
 
 
■そのほか
 renderLineで左から右へ描画するために左右ソートしてる。
別に左右判別しなくてもいけるらしいと調べてて見かけたが、
ぱっと処理が思いつかったのでソートする方法をとった。
 u,vからのピクセル取得時に範囲外参照を防ぐためif文チェックしているが、u&(getWidth()-1)とかやったほうが早いかもしれない。
 floatは遅いので固定小数点数かブレゼンハムにするといくらか速くなる。

■関連記事:
生産がす: 32bitDIB(1) 作成と破棄
生産がす: 32bitDIB(2) 画像読み込み
生産がす: 32bitDIB(3) 塗りつぶし
生産がす: 32bitDIB(4) DIBに描画
生産がす: 32bitDIB(5) ファイルに出力
生産がす: 32bitDIB(6) 拡大縮小描画
生産がす: 32bitDIB(7) DIBSection
生産がす: DIB(8) - 直線描画
生産がす: DIB(9) - 回転描画
生産がす: DIB(10) - 三角形描画
生産がす: 日記ちゃん半透明合成
生産がす: 32bitDIBから無圧縮AVI2.0

0 件のコメント: