프로그래밍 관련/MFC

[MFC] UI, 이미지로드 변경수정시 Invalidate(); 하였을때 더블버퍼링으로 부드럽게,GDI+ 더블 버퍼링 관련

AlrepondTech 2016. 12. 1. 14:29
반응형

 

=================================

=================================

=================================

 

 

 

출처: http://fattarzan.tistory.com/entry/Invalidate-VS-UpdateWindow-%EC%B0%A8%EC%9D%B4%EC%A0%90

 

 

질문]

InvalidateRect(hWnd,NULL,TRUE);

UpdateWindow(hWnd);

두함수 모두 WM_PAINT를 강제로 호출 해서 원도우를 다시 
그리는걸로 아는대요..차이가 뭔가요?
InvalidateRect이함수는 다시 그릴 영역을 지정할수도 있는거 같은대
두함수 같이 쓰는 이유가 뭔가요?


답변]

InvalidateRect()는 윈도우의 클라이언트 영역 중에서 일부분을 무효화 시키는 것입니다.
이때, 첫번째 인자는 어떤 윈도우인지를 가리키는 윈도우 핸들이고,
두번째 인자는 무효과시킬 사각 영역 좌표입니다. 이게 NULL이면 클라이언트 영역 전체라는 것이구요.
그리고 세번째 인자는 그 무효화 영역이 지워지고 다시 그려져야 하는지를 설정합니다. 만약 TRUE이면, 그 윈도우에게는 WM_ERASEBKGND 메시지가 한번 날려지고, WM_PAINT 메시지가 날라갑니다. 만약 FALSE이면 WM_PAINT 메시지만 날라갑니다.

위의 답변에 보시면 InvalidateRect()가 WM_PAINT 메시지를 발생시키지 않는다고 했는데요, WM_PAINT 메시지를 발생 시킵니다.

그리고 UpdateWindow()의 역할은 WM_PAINT 메시지를 발생시키는것이며, 이때 윈도우가 무효화 영역이 있어야 합니다.
즉, 다시 그려져야할 필요가 있는 영역이 있다면 다시 그리도록 만드는 것입니다.

정리하면, InvalidateRect()는 특정 영역을 무조건 다시 그리도록 만드는 함수이며, UpdateWindow()는 현재 윈도우에서 다시 그릴 필요가 있는 영역이 있는지 확인하고 있다면 다시 그리도록 하는 함수입니다.


Tip. InvalidateRect를 하면 WM_PAINT를 호출하는 것이 아니라 단지 화면을 무효화하는 것뿐이고 화면이 무효화되면 WM_PAINT 메시지가 메시지 큐에 들어가게 된다. 따라거 큐에 있는 WM_PAINT를 처리하기 전에 다시 화면이 무효화된다면 윈도우는 그 이전 무효 영역과 다시 발생한 무효 영역을 합쳐서 한번에 그려주게 된다. 만약 무효화하자마자 WM_PAINT를 실행시키고 싶다면

InvalidateRect 호출 후에 UpdateWindow를 호출해준다.

 



출처: http://fattarzan.tistory.com/entry/Invalidate-VS-UpdateWindow-차이점 [뚱보타잔]

 

 

=================================

=================================

=================================

 

 

출처: http://programpark.com/3848

 

관련 글

출처 http://blog.naver.com/dolicom/10071034119

■ MFC 더블버퍼링으로 그림 그리기 - 깜박임 방지 & 윈도우 바탕화면 그리기 & 그림 버튼-double-buffer

■ MFC 그림으로 버튼, 스크롤, 리스트 만들기 - 더블버퍼링 & UML 문서화 기법 - 실제 프로그램 예

■ Thumbnail (썸네일) Windows MFC에서 그림 그리기 - GDI+ Graphics & Image 클래스 사용하기

■ 모달리스로 시작하는 인트로 만들기 - 초기에 modaless dialog을 표시하기 & 더블버퍼링 doube-buffer & PeekMessage

윈도우의 그림 처리 시, 여러가지 그림을 겹칠 때 깜박임 현상이 나타난다. 이것을 방지하기 위해서는 메모리를 사용하여 일단 한번 메모리에 그리고 최종적으로 디스플레이 버퍼 그림을 복사 한다.

개발 툴 : VC++ 6.0 & 포토샾

첨부파일 : 소스 및 bmp

bmp파일은 프로젝트\res\bmp에 복사하면 됨.

프로그램에서 사용된 그림은 다음 즐의 첨부파일 참조 - pic 디렉토리에 넣는다.

우주사진 1

▒ 우주사진 2

▒ 우주사진 3

▒ 우주사진 4

▒ 우주사진 5

그림을 일단 포토샾으로 그리고 BMP 파일로 저장 한다. 그리고 이를 RES에 통록하여 그림을 화면에 표시할 수 있는 준비를 한다.

이 프로그램 예제에서 깜박임은 바로 그림들을 차례대로 화면에 뿌리면서 나타난다. 그림을 차례대로 복사할 때 각각의 스텝의 시간 차와 특히 배경을 그리고 개발자가 원하는 화면 구성할 때 많이 나타난다.

깜박임이 발생하는 원인

더블버퍼링을 사용하지 않으면 다음과 같은 현상을 볼 수 있다. 실제하고 BitBlt하는 순서에 따라 약간은 다르지만 이해를 위해 0.1초 간격으로 이미지를 표시 했다면 다음과 같다.

 

이것을 천천히 돌려 본다.

 

- 위 GIF에서 처음에 흰색은 개발 소스의 코드없이도 윈도우에서 설정된 배경화면으로 색깔로 client 공간을 전부 채운다. 따라서 내가 그리려는 색깔은 검정색 계통이기 때문에 깜박임이 심하다. 만약 흰색을 내가 원하는 이미지나 색깔로 채우려면 BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC)에서 원하는 함수를 호출하면 된다. 다음에 언급 참고.

- 각각의 그림을 그리면서 시간차에 의해 깜박임이 발생한다.

기본적으로 전부 지우고 다시 그리기 때문에 여기서 깜박임이 발생한 것이다.

더블버퍼링 개념 이해

우선 개념적인 이중버퍼링 개념을 생각하기 전에 일반적인 더블버퍼링을 사용하지 않는 경우는

 

그리는 순서를 보면

0. 그리려는 그림을 로드 한다.

CDC memDC; ---> CBitmap 처리 CDC을 잡는다.
CBitmap *oldBitmap;

CBitmap m_background;

CBitmap m_bmp1;

CBitmap m_bmp2;

m_background.LoadBitmap(IDB_BACKGRD); // 배경 그림을 가져온다.

BITMAP bmp;

int res = m_background.GetObject(sizeof(BITMAP), (LPVOID) &bmp);
int cx = (int)bmp.bmWidth; // 그림의 폭 
int cy = (int)bmp.bmHeight; // 그림의 넓이

m_bmp1.LoadBitmap(IDB_BITMAP1); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.
m_bmp2.LoadBitmap(IDB_BITMAP2);

0.1 그림을 그리기 위한 pDC와 연결 한다.

memDC.CreateCompatibleDC(pDC);

1. 배경 그림을 선택하고 screen 버퍼에 그림을 복사 한다.


oldBitmap = memDC.SelectObject(&m_background);

pDC->BitBlt(0,0, cx, cy, &memDC, 0, 0, SRCCOPY);

이렇게 되면 이전에 그림 중에서 다른 bitmap1과 bitmap2가 화면에서 사라지고 배경만 나타난다.

디버깅 모드에서 break-point을 잡고 여기 까지 실행하면 쉽게 볼수 있다. VC++와 실행 프로그램을 윈도우 상에 겹치게 하면 WM_PAINT가 계속 호출되기 때문에 디버깅 할 때는 받듯이 VC++툴과 프로그램을 별도로 분리 해야 한다. 이래서 이중모니터를 사용하면 쉽게 디버깅이 가능한데, 개인이 모니터를 두개를 사용하는 것은 드문일... 그러므로 화면을 분할하여 VC++와 실행 프로그램을 실행하자마자 분리 한다. 처음에 break-point을 제거해야 프로그램을 VC++과 분리할 수 있다. 분리를 하고 다시 OnDraw에서 break-point을 다시 잡는 센스...

2. 이번에는 bitmap1을 선택하고 복사

memDC.SelectObject(&m_bmp1);

pDC->BitBlt(100, 300, 80, 50, &memDC, 0, 0, SRCCOPY);

3. 이번에는 bitmap2을 선택하고 복사

memDC.SelectObject(&m_bmp2);

pDC->BitBlt(220,300, 80, 50, &memDC, 0, 0, SRCCOPY);

4. 그림 뿐만 아니라 기타 함수를 사용하여 원하는 표시를 한다.

CString msg;

msg.Format("Hello");

pDC->TextOut(10,10, msg);

pDC->MoveTo(50.50);

pDC->LineTo(100.50);

pDC->LineTo(100.100);

pDC->LineTo(50, 100);

pDC->LineTo(50, 50);

5. 이제 마무리 하면

memDC.SelectObject(oldBitmap);
memDC.DeleteDC();

이렇게 하면 그림을 그릴 수 있다. 그러나 이렇게 되면 배경을 그리면 다른 그림이 사라지면서 다시 나타나는 깜박임이 나타난다.

만약 배경 그림이 없다면 원하는 배경색으로

FillRect() 함수 등으로 화면을 원하는 색으로 칠 할 수 있다. 이것 역시 마찬가지로 지워지는 마찬가지 이다.

 

CBrush brush,*pOldBrush;
brush.CreateSolidBrush(RGB(255, 255, 255));
pOldBrush = pDC->SelectObject(&brush);
CRect rect(10,10,200,100);
pDC->FillRect(&rect, &brush);
pDC->SelectObject(pOldBrush);
brush.DeleteObject();

 

또는

 

CBrush *pbrush = CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH));
CBrush *pOldBrush = pDC->SelectObject(pbrush);
CRect rect(10,10,200,100);
pDC->FillRect(&rect, pbrush);
pDC->SelectObject(pOldBrush);
pbrush->DeleteTempMap();

 

이렇게 되면 흰색으로 배경을 칠할 수 있다.

더블버퍼링 개념

그리서 다음과 같은 더블버퍼링을 사용한다. 개념부터 보자.

 

0. 우선 메모리 버퍼에 배경그림을 로드 한다.

CDC memDC; ...

LoadBitmap()

1. 다른 그림을 bitmap1등의 그림을 메모리 버퍼 memDC에 복사 한다.

2. 계속 원하는 그림 memDC에 그린다.

3. 그림과 글씨등 여러가지 함수를 사용하여 그림 뿐만 아니라 도형등을 memDC에 그린다.

TextOut, FillRect, LineTo, ....

4. 이제 memDC에 그림이 완성되면, 최종적으로 표시 버퍼인 pDC에 그림을 복사한다. BitBlt()

5. 마무리로 생성한 그림 핸들러 등을 닫는다.

DeleteObject, SelectObject, DeleteDC( DC을 Create 했을 때)

실제로 프로그램에 적용된 그림은 다음과 같다.

 

- IDB_BACKGRD : 바탕그림 - 우선적으로 화면의 밑바탕이다.

- IDB_LBTN, IDB_LBTN_SEL, IDB_LBTN_ACT : 왼쪽 방향 ( List에서 UP으로 사용)

- IDB_RBTN, IDB_RBTN_SEL, IDB_RBTN_ACT : 오른쪽 방향 ( List에서 DOWN으로 사용)

- IDB_LISTBAR : 리스트의 선택을 표시하는 그림이다. 이 위에 글쓰를 쓴다.

- IDB_SCRL_BAR : 스크롤의 바로 사용

위의 그림을 적절히 배치, 표현 함으로써 버튼, 리스트, 스크롤의 동작을 그림으로만으로도 구현 할 수 있다.

이제 깜박임을 생각해 본다.

그림을 그릴 때 BitBlt 함수를 사용하는데, 그림이 한장이면 깜박임 현상이 없다.

CBitmap m_background;

CDC dmemDC;

CBitmap *oldBitmap;

m_background.LoadBitmap(IDB_BACKGRD);

memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_background);

pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);

memDC.SelectObject(oldBitmap);
memDC.DeleteDC();

가장 일반적인 방법이다. 그러나 그림이 여러장이라고 생각하자.

CBitmap m_background;

CBitmap m_btnLeft;

m_background.LoadBitmap(IDB_BACKGRD);

m_btnLeft.LoadBitmap(IDB_LBTN);

. . .

// Step1 : 배경그림 그리기

oldBitmap = memDC.SelectObject(&m_background);

pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);

// Step 2: 다음 그림 그리기
oldBitmap = memDC.SelectObject(&m_btnLeft);

pDC->BitBlt(stPos_x, stPos_x, width, height, &dmemDC, 0, 0, SRCCOPY);

. . .

이렇게 2장을 겹치면 첫번째를 그리면 순간적은 step2가 실행되기전에 두번째 그림이 삭제되어 화면에 표시되면서 눈이 이를 감지 한다.

그림이 2장이고 두번째가 작다면 깜박임 효과가 덜 하겠지만 그림이 많다거나 그림이 크다면 확실하게 눈이 감지 한다.

이를 방지 하기 위해 그림을 버퍼를 하나 더 두고, 메모리에 일단 모든 그림을 그리고 마지막에 이를 표시 메모리 버퍼로 한번에 BitBlt 한다. 마치 DirectX의 그림 그리는 방식과 같다.

함수로 작성하면

 

class CXxxDlg : public CDialog

{

// ...

public:

CBitmap m_background;

BITMAP m_Bitmap;

int m_stateLoadBitmap;

 

CBitmap m_btnLeft;

CBitmap m_btnRight;

CBitmap m_scrlBar;

 

void DrawPaint(CDC *pDC);

 

};

 

BOOL CXxxDlg ::OnInitDialog()
{

...

// TODO: Add extra initialization here

m_btnLeft.LoadBitmap(IDB_LBTN); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.

m_btnRight.LoadBitmap(IDB_RBTN);

m_scrlBar.LoadBitmap(IDB_SCRL_BAR);

 

m_background.LoadBitmap(IDB_BACKGRD);

m_background.GetObject(sizeof(BITMAP), (LPVOID) &m_Bitmap); // 배경그림의 사양을 얻는다. 크기...

//m_stateLoadBitmap = 1;

 

return TRUE; // return TRUE unless you set the focus to a control
}

 

만약 다이얼로그가 아니라 CView라면

class CDrawPicView : public CView

{

/// ...

};

 

void CDrawPicView::OnInitialUpdate() 
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

 

m_btnLeft.LoadBitmap(IDB_LBTN); // 필요한 그림을 가져온다. 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.

m_btnRight.LoadBitmap(IDB_RBTN);

m_scrlBar.LoadBitmap(IDB_SCRL_BAR);

 

m_background.LoadBitmap(IDB_BACKGRD);

m_background.GetObject(sizeof(BITMAP), (LPVOID) &m_Bitmap); // 배경그림의 사양을 얻는다. 크기...

//m_stateLoadBitmap = 1;

 

// ... 
}

CASE 1 :




OnInitDialog() 혹은 OnInitialUpdate()

- 배경그림 및 기타 그림을 로드 한다.

- 그림 로드는 OnPaint에서 할 수도 있으나 미리 해 놓으면 빠른 동작에 유리 하다.

OnPaint()

- 전체 그림을 위한 메모리 비트맵 메모리 버퍼를 만든다.

- 그림 그릴 준비를 한다.

- 배경을 우선 메모리 버퍼에 그린다.

- Z-order에 따라 그림과 기타 문자, 도형 등을 그린다. 일반적인 모든 화면 조작을 메모리에 그린다.

- 그림이 완성되면 최종으로 화면 버퍼에 전송에 그림이 나타나게 한다. 그림을 조립하는 과정에서의 깜박임을 방지하기 위한 방법이다.

void CXxxDlg::DrawPaint(CDC *pDC) 
{

CDC memDC; // 처리 CDC을 지정 한다.

// 빈공간을 새롭게 만든다.

CDC mdcOffScreen; // 더블버퍼링을 위한 메모리 그림버퍼
CBitmap bmpOffScreen; // 더블버퍼링을 위한 비트맵 객체를 만든다.


CBitmap *oldbitmap;

if (! m_background.m_hObject) // 로드되어 있는지 확인

m_background.LoadBitmap(IDB_BACKGRD); // 만약 그림 배경 그림이 로드되지 않았으면 그림을 가져온다.

// 이미 배경은 OnInitDialog() 혹은 OnInitialUpdate()에서 로드되어 있으므로 다시 할 필요는 없다.

// 초기에서 로드가 실패하지 않았다면 다시할 필요가 없다.

// m_btnLeft.LoadBitmap(IDB_LBTN); // 만약 로드가 되지 않았다면, 필요한 그림을 가져온다.

// m_btnRight.LoadBitmap(IDB_RBTN); // 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.

// m_scrlBar.LoadBitmap(IDB_SCRL_BAR);

 

memDC.CreateCompatibleDC(pDC);

mdcOffScreen.CreateCompatibleDC(pDC);

// 화면 크기로 빈공간의 버퍼를 생성 한다.

bmpOffScreen.CreateCompatibleBitmap(pDC, m_Bitmap.bmWidth, m_Bitmap.bmHeight);

// 아직 dmemDC의 메모리에는 아무런 그림이 없다.

// 만약 어떤 색깔로 채우고자 한다면 FillRect() 함수등으로 특정색으로 칠할 수 있다.

// 그러나 다음에 배경 그림을 로드하므로 필요없는 일이다.

oldbitmap = mdcOffScreen.SelectObject(&bmpOffScreen);

// 이제 메모리 준비는 끝났다. 지금 부터는 그림을 그린다.

//우선 배경 그림이 맨 밑이므로 배경을 메모리에 복사 한다.

memDC.SelectObject(&m_background); // 배경 그림을 선택하고

mdcOffScreen.BitBlt(0, 0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &memDC, 0, 0, SRCCOPY);

// ==> 배경을 메모리버퍼에 복사 한다. 아직 화면에는 나타나지 않는다.

// 따라서 그림은 화면에 나타나지 않고, 디버깅이 힘들다.

// 디버깅을 싶게 한다면

//pDC->BitBlt(0, 0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &dmemDC, 0, 0, SRCCOPY);

// 한줄 더 넣어 화면을 확인하고 디버깅이 끝나면 삭제 한다.

 

memDC.SelectObject(&m_btnLeft);
mdcOffScreen.BitBlt(m_btnLeftRect.left, m_btnLeftRect.top, m_btnLeftRect.right, m_btnLeftRect.bottom,
&memDC, 0, 0, SRCCOPY);

// ==> 표시 버퍼인 pDC가 아니라 메모리에 복사 한다. 이렇게 되면 배경 그림이 있는 메모리 버퍼 mdcOffScreen에 복사 한다.

// 아직 메모리에 그림이 존재한다.

// 따라서 VC++에서 각 스텝별로 디버깅 할 때 여기까지 진행 했다면 아직 그림이 화면에 나타나지 않는다.

// 디버깅은 좀 불편

memDC.SelectObject(&m_btnRight);
mdcOffScreen.BitBlt(m_btnRightRect.left, m_btnRightRect.top, m_btnRightRect.right, m_btnRightRect.bottom, 
&memDC, 0, 0, SRCCOPY);

// ==> 계속 메모리 버퍼 mdcOffScreen에 복사 한다. 배경화면에 계속 다른 그림을 겹친다.

// 따라서 mdcOffScreen의 배경 그림은 변경된다.

memDC.SelectObject(&m_scrlBar);
mdcOffScreen.BitBlt(m_scrlBarRect.left, m_scrlBarRect.top, m_scrlBarRect.right, m_scrlBarRect.bottom, 
&memDC, 0, 0, SRCCOPY);

// ==> 계속 메모리 버퍼 mdcOffScreen에 복사 한다.

// 이번에는 BitBlt 말고 다른 윈도우 함수를 역시 메모리에 복사 한다.

mdcOffScreen.SetTextColor( (COLORREF) 0x00FFFFFF );

//==> 이것은 pDC->SetTextColor( (COLORREF) 0x00FFFFFF );와는 별개로 상관없음
mdcOffScreen.SetBkMode( TRANSPARENT ); // 글자의 배경색을 없앤다.
mdcOffScreen.TextOut(150,10, m_Msg ); // 글자를 쓴다. 물론 메모리에

// 여기까지 모든 그림이 완성되어 지만, 아직 표시 버퍼에 출력된 상태가 아니다. 디버깅을 해보면 아직 그림이 표시되지 않는다.

// 최종적으로 표시 화면 메모리에 복사 한다. 
pDC->BitBlt(0,0, m_Bitmap.bmWidth, m_Bitmap.bmHeight, &mdcOffScreen, 0, 0, SRCCOPY);

// 이때서야 화면에 그림이 나타난다.

 

memDC.DeleteDC();

mdcOffScreen.SelectObject(oldbitmap);
mdcOffScreen.DeleteDC();
bmpOffScreen.DeleteObject();

}

CASE 2 :



이번에는 비트맵 빈공간 생성을 하지 않고 배경이미지를 매번 로드해서 처리하는 방식이다.

 

void CXxxDlg::DrawPaint(CDC *pDC) 
{

CDC memDC;
CDC dmemDC;
CBitmap *oldBitmap;

if (m_background.m_hObject)

m_background.DeleteObject();

 

m_background.LoadBitmap(IDB_BACKGRD); // 배경 그림을 가져온다.

// 이 그림은 memDC와 복사되어 다른 그림을 그리면 배경 그림이 변경되므로 다음에 다시 로드할 필요가 있다.

// 즉, memDC에 전체 화면에 완성 되면, m_background 변수에 있는 그림은 완성된 전체그림이다. 배경만 있는 그림이 아니다.

// 만약, m_background에 이미 그림이 로드(LoadBitmap()) 되어 있는 상태에서는 다시 로드가 안된다. 이때는

// m_background.DeleteObject()로 제거하고 다시 로드하는 센스...

 

// m_btnLeft.LoadBitmap(IDB_LBTN); // 만약 로드가 되지 않았다면, 필요한 그림을 가져온다.

// m_btnRight.LoadBitmap(IDB_RBTN); // 이 그림들은 처음에 한번 로드를 하고 계속 사용할 수도 있다.

// m_scrlBar.LoadBitmap(IDB_SCRL_BAR);

 

memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_background);

dmemDC.CreateCompatibleDC(&memDC);

dmemDC.SelectObject(&m_btnLeft);
memDC.BitBlt(m_btnLeftRect.left, m_btnLeftRect.top, m_btnLeftRect.right, m_btnLeftRect.bottom,
&dmemDC, 0, 0, SRCCOPY);

// ==> 표시 버퍼인 pDC가 아니라 메모리에 복사 한다. 이렇게 되면 배경 그림이 있는 메모리 버퍼 memDC에 복사 한다.

// 아직 메모리에 그림이 존재한다.

// 따라서 VC++에서 각 스텝별로 디버깅 할 때 여기까지 진행 했다면 아직 그림이 화면에 나타나지 않는다. 디버깅은 좀 불편

dmemDC.SelectObject(&m_btnRight);
memDC.BitBlt(m_btnRightRect.left, m_btnRightRect.top, m_btnRightRect.right, m_btnRightRect.bottom, 
&dmemDC, 0, 0, SRCCOPY);

// ==> 계속 메모리 버퍼 memDC에 복사 한다. 배경화면에 계속 다른 그림을 겹친다. 따라서 memDC의 배경 그림은 변경된다.

dmemDC.SelectObject(&m_scrlBar);
memDC.BitBlt(m_scrlBarRect.left, m_scrlBarRect.top, m_scrlBarRect.right, m_scrlBarRect.bottom, 
&dmemDC, 0, 0, SRCCOPY);

// ==> 계속 메모리 버퍼 memDC에 복사 한다.

// 이번에는 BitBlt 말고 다른 윈도우 함수를 역시 메모리에 복사 한다.

memDC.SetTextColor( (COLORREF) 0x00FFFFFF );
memDC.SetBkMode( TRANSPARENT );
memDC.TextOut(150,10, m_Msg );

// 여기까지 모든 그림이 완성되어 지만, 아직 표시 버퍼에 출력된 상태가 아니다. 디버깅을 해보면 아직 그림이 표시되지 않는다.

// 최종적으로 표시 메모리에 복사 한다. 
pDC->BitBlt(0,0, m_bgRect.Width(), m_bgRect.Height(), &memDC, 0, 0, SRCCOPY);

// 이때서야 화면에 그림이 나타난다.

 

dmemDC.DeleteDC();

memDC.SelectObject(oldBitmap);
memDC.DeleteDC();

 

// m_stateLoadBitmap = 0;

// m_background.DeleteObject(); // 지금 완성 된 그림을 다른 함수에서 사용한다면 그림을 없애지 않을 수 있다.

// 이렇게 되면 화면에 나타난 완성된 그림은 메모리에 남게 된다.

// m_background 이것은 클래스의 멤버변수이므로 사라지지 않는다.

// 다시 사용할 수 있다. m_background에 다시 그림을 로드하기 위해 m_background.LoadBitmap(IDB_BACKGRD)가 호출되면

// 에러가 발생 한다.

 

}

 

이렇게 이중 버퍼링을 통해 깜박임을 방지 한다.

 

이렇게 예제 그림이 나온다.

CDC dmemDC;
CDC memDC;

memDC.CreateCompatibleDC(pDC);

dmemDC.CreateCompatibleDC(pDC);

이것도 될것 같고....

pDC는 어디서

1. CDialog에서 상속 받은 다이얼로그라면

void CXxxDlg::OnPaint() 
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CClientDC dc(this);
DrawDoalog(&dc);
CDialog::OnPaint();
}
}

2. CView로 부터 상속받은 일반적인 윈도우 View Class 라면

void CXxxView::OnDraw(CDC* pDC)
{
CXxxDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here

DrawDoalog(pDC);


}

3. WM_PAINT 이벤트가 아니라 다른데서 한다면, 이 때는 View클래스나 Dialog에서 하면 된다.

CDC* pDC = GetDC();

DrawDoalog(pDC);

ReleaseDC(pDC ); ---> GetDC()을 했다면 이 함수를 호출하는 센스...

MFC가 아니거나 CWnd로 상속 받지 않았다면

HDC GetDC(
HWND
 hWnd // handle to a window
);

int ReleaseDC(
HWND
 hWnd// handle to window
HDC hDC // handle to device context
);

이 함수를 사용해야 하는데.

CDC* pDC = ::GetDC(hWnd);

/// 그림을 그리고

::ReleaseDC(hWnd, pDC );

MFC을 사용한 경우인데 CWnd의 상속 클래스가 아닌곳에서 호출하려고 얻으려면

class CXxxDlg : public CDialog
{
public:
CXxxDlg ();

};

CXxxDlg dlg;

HWND hWnd = dlg.m_hWnd;

 

다이얼로그가 아니라면 View class에서 부터 얻어야 한다. 만약 View라면 GetDC가 CWnd에 있지만, 다른 클래스에서 호출하려면 일단 View 클래스를 얻어야 한다. 프로그램 구조적으로 View 클래스를 포인터 관리하면 쉽지만 이게 아니라면 일단 View 클래스의 포인터를 얻으면 된다.

 

class CXxxView : public CWnd
{
public:
CXxxView ();

};

 

 

CMainFrame* pFrame = (CMainFrame*)AfxGetMainWnd();

CXxxView * pView = (CXxxView *)pFrame->GetActiveView();

 

HWND hWnd = pView->m_hWnd;

 

그렇다면 Multi-View라면 View 클래스를 Document 클래스로 부터 얻을 수 있다.

 

void CXxxDoc::OnRepaintAllViews()

{

POSITION pos = GetFirstViewPosition(); // 첫번째 뷰가 있는 위치 변수 포인터

while (pos != NULL) {

CView* pView = GetNextView(pos); // 다음 뷰들의 포인터

 

// 만약 이것이 그리려면 윈도우라면 break;

// pView->UpdateWindow();

}

}

 

 

CWnd::m_hWnd 

Remarks

The handle of the Windows window attached to this CWnd. The m_hWnd data member is a public variable of type HWND.

CView에서 깜박임

메모리버퍼링에 의한 깜박임의 제거 했다고 모두 없어지는 것은 아니다. 다음 문제가 윈도우의 크기 변경할 때, 윈도우 크기를 변경하고 윈도우 배경색을 칠한다. 이러면 내가 그리려는 배경과 다른 배경이 처음에 한번 칠해지면서 깜박임이 나타난다. 보통 흰색계열의 배경이 칠해지므로 여기서 제시된 검정색 배경과 플립현상으로 깜박임이 나타난다.

따라서 WM_SIZE의 이벤트에서 크기 변경 후, 다시 배경색을 칠하는 부분을 제어 하면 된다.

이때 사용되는 함수가

BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC) 
{

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

return CView::OnEraseBkgnd(pDC);
}
이다.

 

이벤트 발생 순서를 알기 위한 디버깅을 하면

 

void CDrawPicView::OnSize(UINT nType, int cx, int cy) 
{
CView::OnSize(nType, cx, cy);


// TODO: Add your message handler code here
TRACE("OnSize(type=%X, %d,%d )\n", nType, cx, cy);
}

BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC) 
{

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

TRACE("OnEraseBkgnd()\n");

return CView::OnEraseBkgnd(pDC);
}

이렇게 메시지 출력을 하면

 

OnSize(type=0, 1268,801 )
OnEraseBkgnd()
OnPaint

 

출력 된다.

 

OnEraseBkgnd()에서 전체적으로 흰색계열로 칠하고, OnPaint에서 개발자가 BitBlt 한것이다. 이 때 바로 깜박임이 발생 한 것이다.

배경색 제거를 하려면

BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC) 
{
// TODO: Add your message handler code here and/or call default
TRACE("OnEraseBkgnd()\n");

//return CView::OnEraseBkgnd(pDC);
return FALSE;
}

변경하면 배경을 그리는 것은 없다. 그러면 다시 WM_PAINT가 발생하므로 여기서 화면 구성을 하면 된다.

 

이렇게 하면 일단 깜박임은 없어진다. 그런데 이 예에서는 WM_PAINT에서는 설정한 크기만을 표시 하였다. 정해진 크기보다 크면 배경색 지우기가 없어졌다.

 

 

 

 

반응형

 

728x90

 

 

 

바깥부분의 배경색이 문제가 발생 했다. 이것은 OnEraseBkgnd()에서 흰색 계통의 배경을 채웠던건데 여기서는 이 함수의 기능을 정지 했기 때문에 어디선가 배경을 칠해야 한다.

이것은 OnEraseBkgnd()에서 하든지, OnDraw()에서 하든지 둘중에 한곳에서 배경을 처리 해야 한다.

 

여기서는 OnEraseBkgnd()에서 처리하기로 결정 했다. 내가 설정한 영역을 벗어나는 부분은 배경 그림 데이터가 없기 때문에 검정색으로 그냥 채우기로 결정 하였다.

 


영역 A배경은 OnEraseBkgnd다음에 발생하는 OnPaint에서 칠하가로 하고, 우선 B영역을 검정색으로 칠하고, 다음으로 C영역을 칠 한다. B영역과 C영역을 OnPaint에서 칠해도 되지만 아무래도 OnEraseBkgnd()가 먼저 발생되기 때문에 A영역은 안없어지고 여기서는 B와 C영역을 칠하는 것이 좋은 상황이다.

BOOL CDrawPicView::OnEraseBkgnd(CDC* pDC) 
{
// TODO: Add your message handler code here and/or call default
TRACE("OnEraseBkgnd()\n");

if (m_pBackground->cx < m_bgRect.right) {
RECT rect;
rect.left = m_pBackground->cx;
rect.right = m_bgRect.right;
rect.top = 0;
rect.bottom = m_pBackground->cy;
pDC->FillRect(&rect, &m_bkgBrush); // B영역 칠하기
}
if (m_pBackground->cy < m_bgRect.bottom) {
RECT rect;
rect.left = 0;
rect.right = m_bgRect.right;
rect.top = m_pBackground->cy;
rect.bottom = m_bgRect.bottom;
pDC->FillRect(&rect, &m_bkgBrush); // C영역 칠하기
}

//return CView::OnEraseBkgnd(pDC);
return FALSE;
}

 

여기서 내가 설정 배경의 범위를 벗어나는 것은 알기 위해 사이즈를 일단 저장한다. 그리고 배경색을 지정하기 위한 CBrush을 하나 잡는다.

 

class CDrawPicView : public CView
{

CRect m_bgRect; // 화면 전체의 크기 - client 윈도의 전체크기
BitmapList *m_pBackground; // 전체 배경 그림의 포인트
CBrush m_bkgBrush; // 화면 크기를 벗어나면 칠해 준다.
// ...

};

 

화면 크기는

void CDrawPicView::OnSize(UINT nType, int cx, int cy) 
{
CView::OnSize(nType, cx, cy);
// TODO: Add your message handler code here

m_bgRect.left = 0;
m_bgRect.top = 0;
m_bgRect.right = cx;
m_bgRect.bottom = cy;

TRACE("OnSize(type=%X, %d,%d )\n", nType, cx, cy);
}

 

에서 저장하고

 

void CDrawPicView::OnInitialUpdate() 
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

CDrawPicDoc* pDoc = GetDocument();

m_bkgBrush.CreateSolidBrush( 0x00000000 ); // (COLORREF) crColor=0x00bbggrr );

}

 

이렇게 2가지를 저장과 설정을 하면 OnEraseBkgnd() 준비가 끝나고 이 함수가 호출될 때 바깥 부분을 검정색으로 처리 할 수 있다.

A영역 배경 그림 사이즈 알기

여기서 m_pBackground변수는 A영역의 배경 그림이다.

 

m_pBackground->cx;

m_pBackground->cy;

 

는 이미지 크기를 읽어 저장한 위한 변수이다.

 

typedef struct {
UINT idd;
int st_x;
int st_y;
int end_x;
int end_y;
int cx;
int cy;


CBitmap cbitmap;
char *name;

int state;

} BitmapList; // 각 그림들을 관리하기 위한 struct 이다.

 

void CDrawPicView::OnInitialUpdate() 
{
CView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class
CDrawPicDoc* pDoc = GetDocument();
int cnt;

 

// ...
BitmapLoad(lbmp, IDB_BACKGRD, POSLBTN_X, POSRBTN_Y); // 배경 그림을 로드하여 크기와 기타 정보를 저장한다.
m_pBackground = lbmp; lbmp++;

// ...

}

 

BitmapList *CDrawPicView::BitmapLoad(BitmapList *lbmp, UINT idd,
int sx, int sy)
{
lbmp->idd = idd;
lbmp->name = NULL;

lbmp->cbitmap.LoadBitmap(idd);

BITMAP bmp;
int res = lbmp->cbitmap.GetObject(sizeof(BITMAP), (LPVOID) &bmp);

lbmp->cx = (int)bmp.bmWidth; // 그림의 폭 
lbmp->cy = (int)bmp.bmHeight; // 그림의 넓이

lbmp->st_x = sx;
lbmp->st_y = sy;

lbmp->end_x = lbmp->st_x + lbmp->cx;
lbmp->end_y = lbmp->st_y + lbmp->cy;

return lbmp;
}

 

이것을 정리하면

 

CBitmap cbitmap;

BITMAP bmp;

 

cbitmap.LoadBitmap(IDB_BACKGRD);

 

int res = cbitmap.GetObject(sizeof(BITMAP), (LPVOID) &bmp);

lbmp->cx = (int)bmp.bmWidth; // 그림의 폭 
lbmp->cy = (int)bmp.bmHeight; // 그림의 넓이

 

 

여기서 사용된 CBrush는 프로그램 끝나면서 제거 했다.

CDrawPicView::~CDrawPicView()
{
m_bkgBrush.DeleteObject();
// ...
}

전체적인 프로그램을 적용하여 프로그램 하면 다음과 같은 결과가 나온다.

 

 

이렇게 바깥부분이 검정색으로 배경 그림과 표시가 나지 않도록 처리 하였다.

아니면 윈도우 사이즈 크기를 제한 하는 방법도 있을 것 같은데... 뀌찮음....

크기제한은 다음 메세지를 연구해야 할것 같다.

    POINT ptMaxTrackSize= { GetSystemMetrics(SM_CXSCREEN) / 3, GetSystemMetrics(SM_CYSCREEN)};

    POINT ptMinTrackSize = { 200,200};

    POINT ptMaxSize        = { GetSystemMetrics(SM_CXSCREEN) / 3 , GetSystemMetrics(SM_CYSCREEN)};

    POINT ptMaxPosition     = { GetSystemMetrics(SM_CXSCREEN) * 2 / 3 , 0};

 

    caseWM_GETMINMAXINFO:

        {

            LPMINMAXINFO pMinMax = (LPMINMAXINFO)lParam;

            pMinMax->ptMaxTrackSize = ptMaxTrackSize;

            pMinMax->ptMinTrackSize = ptMinTrackSize;

            pMinMax->ptMaxSize        = ptMaxSize;

            pMinMax->ptMaxPosition = ptMaxPosition;

        }

        return 0;

 

 

CDialog에서 다른 요소(에디터박스,...)와의 깜박임

 


CDialog에서 그림을 처리하는 과정은 배경에 그림을 그리는 형태이다. 이것은 따라서 다른 요소와의 깜박임이 있다. 리스트박스를 넣었다면 크기가 크므로 여기서 사용한 배경색 검정과 윈도우의 다이얼로고의 흰색 계열과 깜박임이 발생 한다. 크기가 클 수록 확실히 보인다.

 

 

 

여기서는 CEdit 클래스의 에디터박스이다. 오른쪽아래의 에디터박스를 흰색으로 변경하면 바탕의 검정과 흰색의 대비가 일어난다. 이것은 그림을 그릴 때나타나는 배경 칠하기와 각 요소를 그리는 것과의 문제이다.

 

void CDoubleBuffDlg::OnPaint() 
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
//...
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CClientDC dc(this);
DrawDoalog(&dc); // 개발자가 그리는 배경화면
CDialog::OnPaint(); // 다이얼로그의 다른 요소를 그리는 과정 (에디터박스, 버튼, ...)
}
}

DrawDoalog()이얼로 에서 배경을 그리면 모든 부분에 그림을 그린다. 여기서는 검정색 계통의 배경 그림이므로 이것이 일단 화면에 나타나고 다시 OnPaint에서 내부 요소들을 그린다. 따라서 크기가 큰 에디터 박스는 눈에 확실히 표시가 난다. 이것을 방지하기 위해 다음과 같은 모드로 변경 한다.

 

 

 

CEdit 를 점유하고 있는데 부모 클래스인 전체 다이얼로그의 속성을 Clip children으로 바꾸었다.

 

 

// 개발자가 그리는 배경화면

void CDoubleBuffDlg::DrawDoalog(CDC *pDC) 
{
CDC memDC;

CBitmap *oldBitmap;
CDC dmemDC;

m_pBackground->cbitmap.DeleteObject();
m_pBackground->cbitmap.LoadBitmap(IDB_BACKGRD); // 전체배경 그림을 그리고


memDC.CreateCompatibleDC(pDC);
oldBitmap = memDC.SelectObject(&m_pBackground->cbitmap);

dmemDC.CreateCompatibleDC(pDC);

 

/// 메모리에 그림을 그리고

dmemDC.SelectObject(&pbmplist->cbitmap);
memDC.BitBlt(pbmplist->st_x,pbmplist->st_y, pbmplist->cx,pbmplist->cy, &dmemDC, 0, 0, SRCCOPY);
/// ...

 

// 화면버퍼에 그림을 복사하여 화면에 나타나게

pDC->BitBlt(0,0, m_pBackground->cx, m_pBackground->cy, &memDC, 0, 0, SRCCOPY);

 


TRACE("OnPaint\n");
dmemDC.DeleteDC();

memDC.SelectObject(oldBitmap);
memDC.DeleteDC();
}

 

 

 

 

=================================

=================================

=================================

 

 

출처: http://myblue0324.tistory.com/27

 

 

 

한명의 화가가 캔버스에 풍경화 그림을 그리려고 합니다.
그림에는 산과 바다, 꽃과 나무, 기타동물, 그리고 자연속을 유유히 산책하며 걸어다니는 인물들 모두를 포함하여 그리려고 합니다.

이 화가가 그린 풍경화를 감상하고자 할때 그 그림을 감상하는 사람들은 그 그림의 결과만을 보고 감상하지만 화가가 만들어낸 풍경화의 결과 이전에는 화가의 굉장히 불연속적인 그리기 동작이 반복됩니다.

우리는 그 그림의 결과..즉 다 그려진 한폭의 풍경화를 감상하기를 원하지 화가의 지속적인 그리기 과정을 보고 싶어하지 않습니다.
뭐 그런걸 보고싶어하는 사람이 있을지 모르겠으나.. -_-; 그런분은 논외로 치고..

이 개념을 그대로 프로그래밍에 접목 시키면 더블버퍼링의 원리를 쉽게 이해할 수 있습니다.
비유가 적절한지는 모르겠지만.. -_-;

화면에 각종 이미지, 도형, UI 컨트롤등을 표현하고자 할때 Windows Application은 화면 DC(Device Context)에 그리기 동작을 수행합니다.
DC란 Device Context를 의미하며 그림을 그리고자 하는 캔버스의 의미로 생각하면 쉽습니다.
이러한 각각의 불연속적인 그리기 동작을 수행하고 Application이 이를 표출하는 과정에서 화면을 보는 사용자의 입장에서는 그리기 동작이 모두 끝나기 전까지 지속적인 화면 깜빡임 현상을 보게 됩니다.

이런 현상을 방지하기 위한 작업이 더블버퍼링입니다.
더블버퍼링의 기본원리는 직접적으로 표출되는 화면DC에 직접 그리는것이 아닌 가상의 메모리DC를 만들어 메모리DC에 모든 그리기 동작 수행후에 이를 화면 DC에 복사하는 원리입니다. 화면에 나타나지 않는 메모리 DC에 그림을 다 그리고 다 그려진 그림의 결과물(메모리DC)만을 화면에 복사하는 방식이니까 그리는 과정에서 나타나는 화면 깜빡임등의 현상은 나타나지 않겠네요.

  1. void CXXXDlg::OnPaint()  
  2. {  
  3.     CPaintDC dc(this); // device context for painting  
  4.     // TODO: 여기에 메시지 처리기 코드를 추가합니다.  
  5.     // 그리기 메시지에 대해서는 CWnd::OnPaint()을(를) 호출하지 마십시오.  
  6.   
  7.     // ***************************************************** //  
  8.     // 더블 버퍼링 처리.  
  9.     // ***************************************************** //  
  10.     CDC* pDC = GetDC();  
  11.   
  12.     CRect rect;  
  13.     GetClientRect(rect);  
  14.   
  15.     // 메모리 DC와 BITMAP 생성.  
  16.     CDC MemDC;  
  17.     CBitmap* pOldBitmap;  
  18.     CBitmap bmp;  
  19.   
  20.     // 메모리 DC 및 BITMAP과 현재 DC의 설정 일치.  
  21.     MemDC.CreateCompatibleDC(pDC);  
  22.     bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());  
  23.     pOldBitmap = (CBitmap*)MemDC.SelectObject(&bmp);  
  24.     MemDC.PatBlt(0, 0, rect.Width(), rect.Height(), WHITENESS);  
  25.   
  26.     // 메모리 DC에 그리기.  
  27.     DrawImage(&MemDC);  
  28.   
  29.     // 메모리 DC를 현재 DC에 복사.  
  30.     pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &MemDC, 0, 0, SRCCOPY);  
  31.   
  32.     // 사용된 메모리 DC 및 BITMAP의 삭제.  
  33.     MemDC.SelectObject(pOldBitmap);  
  34.     MemDC.DeleteDC();  
  35. }  
  36.   
  37. void CXXXDlg::DrawImage(CDC* pDC)  
  38. {  
  39.     // 파라미터로 넘겨진 메모리 DC에 그리기 동작 구현.  
  40. }  
<MFC 더블버퍼링 예시>

MFC 더블버퍼링 샘플입니다.
원리를 알면 매우 간단한 코드이고 또한 MFC 프로그래밍에서 지속적으로 사용되는 개념이라 두고두고 활용될거 같아 블로그에 포스팅합니다.

 

 

=================================

=================================

=================================

 

 

출처: http://blog.naver.com/PostView.nhn?blogId=pkban&logNo=130169127570

 

Double buffering in GDI plus(GDI+ 더블 버퍼링)

 

GDI plus(GDI+)에서의 더블 버퍼링 방법을 소개한다. 

 

1. 먼저 기본적으로 화면을 하얗게 만들어 깜빡이는 현상을 유발하는  afx_msg BOOL OnEraseBkgnd(CDC* pDC);을 수정하여 준다.

BOOL CScrollViewEx::OnEraseBkgnd(CDC* pDC)
{
    return TRUE; // 강제 리턴 처리하여 하얗게 그려주는 루틴을 들어가지 않게 한다.

    //return CScrollView::OnEraseBkgnd(pDC);  // 이부분에서 배경을 하얗게 그려주므로 주석 처리함.
}

 

2. afx_msg void OnPaint();를 GDI+ 더블 버퍼링 환경에 맞게 코딩해 준다.

void CScrollViewEx::OnPaint()
{
     CPaintDC dc(this); // device context for painting
 
     // Double buffering start
     CRect rectDraw;
     GetClientRect(rectDraw);

    

     // 한번에 그리기 위한 buffer역할을 하는 bitmap을 생성한다.(깜빡임은 한번에 그리지 않고 여러번 그리기 때문에 발생함)
     Gdiplus::Bitmap bitmap(rectDraw.Width(), rectDraw.Height()); 
     Gdiplus::Graphics graphics(dc);
     Gdiplus::Graphics memDC(&bitmap);

    

     // 그리기 전 bitmap 바탕을 하얗게 그려준다. (이 부분이 없으면 잔상이 남게 된다)

     Gdiplus::SolidBrush whiteBrush( Gdiplus::Color(255, 255, 255, 255));
     memDC.FillRectangle(&whiteBrush, 0, 0, rectDraw.Width(), rectDraw.Height());
    

     // 원하는 이미지를 bitmap에 그린다.     
     Gdiplus::Rect DestRect(0, 0, rect.right, rect.bottom);

     Gdiplus::Image* imageDisplay = NULL;

     imageDisplay = m_pImage;
     memDC.DrawImage(imageDisplay, DestRect, 0, 0, rectDraw.right, rectDraw.bottom, Gdiplus::UnitPixel,  NULL, NULL, NULL);
 

     // 원하는 선을 bitmap에 그린다.
     Gdiplus::Pen GreenPen(Gdiplus::Color(255, 0, 255, 0), 1.0f);
     memDC.DrawLine(&GreenPen, Gdiplus::Point(0,  0), Gdiplus::Point(100,  100));  

    

     // 완성된 bitmap은 한번에 화면에 그린다.

     graphics.DrawImage(&bitmap, 0, 0);  
}

 

이상입니다.

 

 

 

=================================

=================================

=================================

 

 

출처: http://eachan.tistory.com/6

 

GDI+를 이용한 더블 버퍼링

 

지금까지 찾아본 GDI+ 더블 버퍼링 코드를 보면 거의 비슷한 내용이지만 구현하는 방법이 조금씩 달라서 도대체 어떤 것을 써야할지 헷갈리는 경우가 있다. 이럴 경우를 위해서 본인이 현재 쓰는 방법을 소개한다. 실제로, 더블 버퍼링 구현에는 여러가지 방법이 있으므로 참고만 하기 바란다.  앞으로 쓰여질 GDI+ 관련 내용도 아래에 있는 더블 버퍼링을 기준으로 설명할 것이다.



다음과 같은 순서로 구현한다

1. GDI+ 를 설정한다.

   stdafx.h 에 아래 내용 추가


   #include <GdiPlus.h>
   #pragma comment(lib,"gdiplus")
   using namespace Gdiplus;

   DoubleBufferingTest.h 에 변수 추가
   
ULONG_PTR gdiplusToken;

   DoubleBufferingTest.cpp 의 InitInstance()에 아래 내용 추가
   
GdiplusStartupInput gdiplusStartupInput;
   if(::GdiplusStartup(&gdiplusToken,&gdiplusStartupInput,NULL) != Ok)
   {
       AfxMessageBox(_T("ERROR: Failed to initialize GDI+ library!"));
       return FALSE;
   }

   DoubleBufferingTest.cpp의 ExitInstance()에 아래 내용 추가
   
::GdiplusShutdown(gdiplusToken);

2. OnEraseBkgnd()함수를 추가한다.

   BOOL CDoubleBufferingView::OnEraseBkgnd(CDC* pDC)
   {
       return TRUE;
   }

3. OnPaint()함수에 아래 내용을 추가한다

  
 void CDoubleBufferingView::OnPaint()
   {
       CPaintDC dc(this);
       CRect rlClientRect;
       GetClientRect(&rlClientRect);

       Rect rclClient(rlClientRect.left,rlClientRect.top,rlClientRect.Width(),rlClientRect.Height());

       CDC MemDC;
       MemDC.CreateCompatibleDC(&dc);
       CBitmap memBitmap;
       memBitmap.CreateCompatibleBitmap(&dc, rlClientRect.Width(),rlClientRect.Height());
       CBitmap *pOldBitmap = MemDC.SelectObject(&memBitmap);
       Graphics mem(MemDC.m_hDC);
       mem.SetSmoothingMode(SmoothingModeHighQuality);
       mem.FillRectangle(&bgBrush,rclClient); // 배경을 흰색으로 지운다
       ...
       ...
// 여기서부터 Graphics객체인 mem에 원하는 Drawing 작업을 한다.
       ...
       ...
       dc.BitBlt(0,0, rlClientRect.right, rlClientRect.bottom,&MemDC, 0, 0, SRCCOPY);

       MemDC.SelectObject(pOldBitmap);
       mem.ReleaseHDC(dc.m_hDC);
     }

 

=================================

=================================

=================================

 

 

출처: http://psychoria.tistory.com/entry/%EB%A9%94%EB%AA%A8%EB%A6%ACDC%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EB%8D%94%EB%B8%94%EB%B2%84%ED%8D%BC%EB%A7%81%EC%9C%BC%EB%A1%9C-%EA%B7%B8%EB%A6%AC%EA%B8%B0

 

 

일반적으로 비트맵을 화면에 출력할 때는 더블 버퍼링이라는 방법을 사용합니다.

출력을 할 때 바로 화면에 출력하는 것이 아닌 메모리에 먼저 그리고 그려진 내용을 화면에 출력하는 것입니다.

이 방법을 사용하면 그림을 그릴 때 깜빡거리는 현상을 줄일 수 있습니다.

계속 화면에 출력하는 것보다 메모리에 다 그리고 그려진 내용을 화면에 한 번에 출력하기 때문입니다.

보통 비트맵을 이렇게 그리지만, 그래프 등 출력이 많이 발생하는 경우에 활용이 가능합니다.

비트맵이 아닌 일반 선, 도형 등을 더블 버퍼링을 이용해서 출력하는 방법을 설명하도록 하겠습니다.

기준은 MFC 기준이고, Win32 API 기반에서도 충분히 활용이 가능합니다.

OnPaint()에서 적용하는 방법입니다.

WM_PAINT 처리 함수인 OnPaint() 함수를 정의합니다.

기본적으로 다이얼로그 기반은 OnPaint가 이미 정의되어 있습니다.

그리고 기본적으로 CPaintDC dc(this) 코드가 존재합니다.

이 dc를 활용해서 그려주면 됩니다.

일단은 이 CPaintDC를 사용하기 위해서 if 문 바깥으로 빼줍니다.

그리고나서 내가 그릴 부분을 그리면 됩니다.

1
2
3
4
5
6
7
8
9
10
//메모리 DC를 생성합니다.
CDC memDC;
//그래프나 도형을 그릴 Bitmap을 생성합니다. (도화지 정도로 보시면 될 거 같습니다.)
CBitmap bmp, *pOldbmp;
//메모리 DC를 위의 CPaintDC인 dc에 호환되게 만들어 줍니다.
memDC.CreateCompatibleDC(&dc);
//주어진 dc에 호환하는 비트맵을 생성합니다.
bmp.CreateCompatibleBitmap(&dc, 작성할 비트맵 가로 사이즈(픽셀), 작성할 비트맵 세로 사이즈(픽셀));
//이제 memDC에 생성된 비트맵을 연결 시켜줍니다.
pOldbmp = memDC.SelectObject(&bmp);

이렇게 하고 그리고 싶은 것을 memDC에 그려주면 됩니다.

memDC.Rectangle(영역); 이런식으로 필요한 도형, 선 등을 그리면 됩니다.

그리고나서 dc에 memDC를 출력하면 됩니다.

dc.StretchBlt() API를 사용하면 되며 BitBlt()를 사용해서도 동일하게 가능합니다.

사용법은 아래 MSDN의 내용을 참고하시면 됩니다.

https://msdn.microsoft.com/en-us/library/3k5s37a5.aspx

그리고 마지막은 사용한 object와 메모리 dc의 메모리 할당을 해제해주면 끝입니다.

1
2
3
memDC.SelectObject(pOldbmp);
bmp.DeleteObject();
memDC.DeleteDC();

그럼 화면에 깜빡임이 없이 출력되는 것을 볼 수 있을 것입니다.

 

 

=================================

=================================

=================================

 

 

 

출처: http://ssmhz.tistory.com/208

 


더블 버퍼링

무조건 해야된다,

후배들이 질문이 자꾸 들어와서 글로 정리해 놓는다.

쉽다 간단하다. 이때까지 몰랐으면 이것을 보고 하면된다.

자 순서대로 설명을 하면

1. 그릴 장소를 얻고
2. 도화지를 깔고
3. 도화지 색깔채우고,
4. 그리고, 볶고, 삶고, 찌지고, 날리고
5. 다그린 도화지를 화면에 붙인다
6. 주변이 깨끗하게 썼던 재료는 버린다

자 쉽죠!?

전체 코드를 보면서 설명을..

void CNetStatics::OnPaint()  { 	CPaintDC dc( this );   	CDC memDC; 	CBitmap myBitmap; 	CBitmap* pOldBitmap;  	memDC.CreateCompatibleDC( &dc ); 	myBitmap.CreateCompatibleBitmap( &dc, m_rctRect.Width(), m_rctRect.Height() ); 	pOldBitmap = memDC.SelectObject( &myBitmap ); 	memDC.PatBlt( 0, 0, m_rctRect.Width(), m_rctRect.Height(), WHITENESS ); 	 	m_ptPoint = m_rctRect.TopLeft();  	// 수직선 y 	memDC.MoveTo( m_ptPoint.x + 35, m_ptPoint.y + 10 );		 	memDC.LineTo( m_ptPoint.x + 35, m_ptPoint.y + 236 ); 	memDC.MoveTo( m_ptPoint.x + 30, m_ptPoint.y + 115 ); 	memDC.LineTo( m_ptPoint.x + 40, m_ptPoint.y + 115 );  	// 수직선 x 	memDC.MoveTo( m_ptPoint.x + 20, m_ptPoint.y + 220 );		 	memDC.LineTo( m_ptPoint.x + 340, m_ptPoint.y + 220 );  	// 계속 기본 그래프는 보여준다  	InitGraph( &memDC );						  	// 다시 그려준다  	DrawGraph( &memDC );	 	 	dc.BitBlt( 0, 0, m_rctRect.Width(), m_rctRect.Height(), &memDC, 0, 0, SRCCOPY );  	dc.SelectObject( pOldBitmap ); 	myBitmap.DeleteObject(); 	ReleaseDC( &memDC ); 	DeleteDC( memDC ); } 


자 보면 

memDC.CreateCompatibleDC( &dc );


페인트디씨로 얻은 화면 dc(device context)로 부터 호환되는

dc를 얻어온다. ( 그릴장소를 얻는다 )

myBitmap.CreateCompatibleBitmap( &dc, 가로, 세로 );



가로, 세로 크기 만한 도화지를 만든다.

memDC.SelectObject( &myBitmap );



도화지를 선택한다

memDC.PatBlt( shala 솰라 shala ... );



화면에 색상을 채운다( 바탕색 WHITENESS 는 흰색 )

이거 안하면 뻥 뚫린 투명한 화면을 볼 것이다.

난리 굿을 떨고 난뒤


그중에서 함수에 &memDC로 넘겨주는것은 거기다가 그리라는 뜻

함수안에서 

GetDC()로 DC를 얻어서 그리면 무용지물!!


dc.BitBlt( 0, 0, m_rctRect.Width(), m_rctRect.Height(), &memDC, 0, 0, SRCCOPY );

가장 중요, 다 그린 도화지를 화면에 갖다 붙임

그 다음 루틴들은 쓰레기 정리, 핸들릭이나 GDI릭이 나면 안됨 주의!!


MFC Library Reference
CDC::BitBlt

Copies a bitmap from the source device context to this current device context.

 
BOOL BitBlt(    int x,    int y,    int nWidth,    int nHeight,    CDC* pSrcDC,    int xSrc,    int ySrc,    DWORD dwRop  );

Collapse imageReturn Value

Nonzero if the function is successful; otherwise 0.

Collapse imageRemarks

The application can align the windows or client areas on byte boundaries to ensure that the BitBltoperations occur on byte-aligned rectangles. (Set the CS_BYTEALIGNWINDOW or CS_BYTEALIGNCLIENT flags when you register the window classes.)

BitBlt operations on byte-aligned rectangles are considerably faster than BitBlt operations on rectangles that are not byte aligned. If you want to specify class styles such as byte-alignment for your own device context, you will have to register a window class rather than relying on the Microsoft Foundation classes to do it for you. Use the global function AfxRegisterWndClass.

GDI transforms nWidth and nHeight, once by using the destination device context, and once by using the source device context. If the resulting extents do not match, GDI uses the Windows StretchBlt function to compress or stretch the source bitmap as necessary.

If destination, source, and pattern bitmaps do not have the same color format, the BitBlt function converts the source and pattern bitmaps to match the destination. The foreground and background colors of the destination bitmap are used in the conversion.

When the BitBlt function converts a monochrome bitmap to color, it sets white bits (1) to the background color and black bits (0) to the foreground color. The foreground and background colors of the destination device context are used. To convert color to monochrome, BitBlt sets pixels that match the background color to white and sets all other pixels to black. BitBlt uses the foreground and background colors of the color device context to convert from color to monochrome.

Note that not all device contexts support BitBlt. To check whether a given device context does support BitBlt, use the GetDeviceCaps member function and specify the RASTERCAPS index.

 

=================================

=================================

=================================

 

 

반응형