본문 바로가기

Visual C++/Double Buffering in MFC

Double Buffering in MFC

MFC, DCMTK, CxImage, Double Buffering example


개요

MFC를 이용하여 화면에 무언가를 그릴 때는 최소한 2가지의 작업이 필요하다 즉, 화면을 지우고 이어서 필요한 그림을 그리는 2가지 작업으로 구분된다.

화면에 이 두가지의 작업이 노출될 경우에는 지우고 그리는 짧은 순간이 깜박거림으로 나타나기 때문에 자연스럽지 못하다. 그러므로 화면을 지우고 그 위에 다시 그림을 그리는 과정의 대상이 화면이 아니라 메모리를 대상으로 하고 메모리에 완성된 그림을 그대로 복사하여 그래픽 디바이스에 복사하면 깜박거림을 줄일 수 있다. 메모리에 일단 그린 다음 그 메모리의 영역을 복사하여 그래픽 메모리에 다시 복사하여 화면에 출력하는 방법을 더블버퍼링이라고 한다


다음의 예는 화면에 출력된 비트맵 이미지를 마우스로 드래그하여 이동할 수 있도록 한 것이며 깜박거림이 심하여 더블버퍼링을 적용한 결과 깜박거림을 없앨 수가 있었다.


모니터에 그려진 그림을 지우는 OnEraseBkgnd()함수의 내용을 모두 지우고 아래처럼 return TRUE 를 입력한다

BOOL ImageWnd::OnEraseBkgnd(CDC* pDC)

{

// TODO: Add your message handler code here and/or call default

    return TRUE; 

}


마우스가 드래그될 때마다 이미지가 그 거리만큼 이동하여 그려져야 하므로 마우스 드래그시 마다 이동 거리를 멤버변수에 반영하여 OnPaint()에서도 참조하여 해당 위치에 그림을 그릴 수 있도록 한다. Invalidate(FALSE) 를 호출하면 위의 OnEraseBkgnd() 함수가 호출되지 않는다. OnEraseBkgnd()함수는 화면의 그림을 지우는 기능을 하기 때문에 이 함수가 실행되면 깜박거럼이 이용자에게 보이게 된다.

void ImageWnd::OnDrag(CPoint curPoint)

{

CPoint dist = curPoint-m_prevMousePoint; // 마우스를 누르고 이동한 거리

m_ImagePos += dist;

m_prevMousePoint = curPoint;


Invalidate(FALSE);

}


OnPaint() 에서는 메모리에 배경색을 칠하고 그 위에 그림을 그린 후에 메모리를 복사하여 그래픽 메모리에 복사하여 화면을 새 그림으로 덮어 씌우는 작업을 한다. 여기서는 DICOM 이미지를 대상으로 했기 때문에 더블버퍼링과 무관한 코드도 상당히 포함되어 있다. 아래의 하늘색으로 표시한 곳이 더블버퍼링과 관련된 부분이다. 아래의 변수들은 생성자에서 초기화된다


주요 멤버변수들

HDC hMemDC;

CDC offScreenDC;

CBitmap* pOldBmp1;

CBitmap* pOldBmp2;

CBitmap offScreenBmp;

CBrush blackBrush;


생성자에서 주요한 멤버변수를 초기화한다

CRect rect;

GetClientRect(&rect);


offScreenBmp.CreateCompatibleBitmap(GetDC(), rect.Width(), rect.Height());


blackBrush.CreateSolidBrush(RGB(0,0,0));

hMemDC = CreateCompatibleDC(GetDC()->m_hDC);


offScreenDC.CreateCompatibleDC(GetDC());

offScreenDC.SetStretchBltMode(HALFTONE);



void ImageWnd::OnPaint()

{

CPaintDC dc(this); // device context for painting


// TODO: Add your message handler code here


USES_CONVERSION;


char* filename = W2A(m_strFileName);


DcmFileFormat* ptrDcmFileFormat = new DcmFileFormat();

OFCondition cond = ptrDcmFileFormat->loadFile(filename);

if (cond.good())

{

//AfxMessageBox(_T("DCM파일 로드성공"));

}

else if(cond.bad())

{

AfxMessageBox(_T("DCM파일 로드실패"));

exit(0);

}


DJDecoderRegistration::registerCodecs(); // register JPEG codecs

DcmDataset *dataset = ptrDcmFileFormat->getDataset();


// decompress data set if compressed

dataset->chooseRepresentation(EXS_LittleEndianExplicit,NULL);


E_TransferSyntax xfer = ptrDcmFileFormat->getDataset()->getOriginalXfer();


    DicomImage *ptrDicomImage = new DicomImage(ptrDcmFileFormat, xfer, CIF_AcrNemaCompatibility, 0, 1);


if(ptrDicomImage != NULL)

{

//AfxMessageBox(_T("DicomImage 인스턴스 생성 성공"));

}

else

{

AfxMessageBox(_T("DicomImage 인스턴스 생성 실패"));

exit(0);

}


if(ptrDicomImage->getStatus()!= EIS_Normal) exit(0);


int width  = (int)ptrDicomImage->getWidth();

int height = (int)ptrDicomImage->getHeight();

void *data = NULL; 


int nResult = ptrDicomImage->setMinMaxWindow();


if(ptrDicomImage->createWindowsDIB(data, width*height) && data) // data 는 사용후 반드시 해제해야 함

{

BITMAPINFO bi; 

bi.bmiHeader.biSize        = sizeof(bi); 

bi.bmiHeader.biWidth       = width; 

bi.bmiHeader.biHeight      = -height; 

bi.bmiHeader.biPlanes      = 1; 

bi.bmiHeader.biBitCount    = 24; 

bi.bmiHeader.biCompression = BI_RGB;

bi.bmiHeader.biSizeImage   = 0;


// hbmp 는 사용후 반드시 해제해야 함

HBITMAP hbmp = CreateDIBitmap(dc.GetSafeHdc(), &bi.bmiHeader, CBM_INIT, data, &bi, DIB_RGB_COLORS);


if(hbmp)

   //CxImage를 이용한 영상처리 부분 (상하 뒤집기)

   HBITMAP hbmp2 = CxImageDCM::Flip(hbmp);


   //더블버퍼링을 위한 코드 시작

   pOldBmp1 = (CBitmap*)SelectObject(hMemDC, hbmp2);

   pOldBmp2 =  (CBitmap*)offScreenDC.SelectObject(offScreenBmp);


   CRect rect;

   GetClientRect(&rect);

   //화면 크기의 메모리 영역과 연결된 offScreenDC를 통해 해당 영역을 검은 배경색으로 채운다

   offScreenDC.FillRect( &rect, &blackBrush);


   //영상이 윈도우에 꼭 맞는 폭과 높이를 구한다(폭과 높이의 비율이 유지된 상태로 축소/확대)

   int imgW = 0, imgH = 0;

   double ratioW = 0.0, ratioH = 0.0;

   GetFitSize(width, height, &imgW, &imgH, &ratioW, &ratioH);

   //검은 배경색으로 채워진 offScreenDC의 메모리에 hMemDC 의 비트맵을 축소/확대하여 그린다

   StretchBlt(offScreenDC.GetSafeHdc(), m_ImagePos.x, m_ImagePos.y,imgW,imgH, 

hMemDC,0,0,width,height,SRCCOPY);

   //이제 offScreenDC에 연결된 메모리에는 모니터의 크기와 동일한 완성된 그림이 저장된 상태이다

   // 더블버퍼링을 위한 버퍼에 그리기 완료


 //버퍼(offScreenDC의 메모리 영역)에 그려진 그림을 그래픽 디바이스에 복사한다. 즉 모니터에 출력한다

 SetStretchBltMode(dc.m_hDC, HALFTONE);

 BitBlt(dc.GetSafeHdc(),0, 0,rect.Width(),rect.Height(), offScreenDC.GetSafeHdc(),0,0,SRCCOPY);

 //StretchDIBits(), StretchBlt()등의 함수도 화면에 이미지를 출력할 수 있지만

 //축소 혹은 확대하여 그리기가 아닌 경우에는 BitBlt()를 사용해야 오류도 없고

 //오작동을 막을 수 있다


 SelectObject(hMemDC, pOldBmp1);

          SelectObject(offScreenDC.GetSafeHdc(), pOldBmp2);

 //사용된 비트맵을 해제하지 않으면 결국 메모리 누수로 프로그램이 다운된다

 DeleteObject(hbmp);

 DeleteObject(hbmp2);

}

}

//delete data


// Do not call CFrameWnd::OnPaint() for painting messages

delete ptrDcmFileFormat;

delete ptrDicomImage;

delete data;

}