Search

'분류 전체보기'에 해당되는 글 113건

  1. 2011.08.25 Event, Thread
  2. 2011.08.25 MFC 더블버퍼링 메모리 버퍼 이용
  3. 2011.08.11 RAW Data Draw
  4. 2011.08.10 YUV to RGB ...

Event, Thread

Program Visual C++ 2011. 8. 25. 10:33 Posted by HisPark


이벤트는 특정 사건의 발생을 다른 쓰레드에 알리는 경우에 주로 사용한다.

이벤트를 이용한 동기화는 다음과 같은 과정으로 진행된다.

이벤트 A를 비신호 상태로생성

한 쓰레드가 작업을 진행하고, 나머지 쓰레드는 이벤트 A가 신호 상태가 될때까지 대기

쓰레드가 작업을 완료하면 이벤트 A를 신호상태로 변경

나머지 쓰레드 작업 시작

기본적으로 이벤트는 신호상태일때 접근 가능하고, 비신호상태일때 접근 불가능하다.

이벤트 생성함수

CreateEvent (SECURITY_ATTRIBUTES (대부분 NULL로 사용),

bManualReset (TRUE 이면 수동리셋 이벤트,FALSE 이면 자동리셋 이벤트생성),

bInitialState (TRUE면 신호상태로,FALSE면 비신호 상태로 시작),

lpName (이벤트를 서로다른 프로세스에 속한 쓰레드가 사용할수 있도록

이름을 줄수 있다. NULL을 사용하면 이름없는 이벤트가 생성)

)

자동리셋이벤트: 이벤트를 신호상태로 바꾸면 기다리는 쓰레드 중 하나만 꺠운후

자동으로 비신호 상태가 된다. 따라서 자동리셋 이벤트에 대해서는

ResetEvent 함수를 사용할 필요가 없다.

수동리셋이벤트: 이벤트를 신호상태로 바꾸면 계속 신호상태를 유지하므로 결과적으로

대기중인 쓰레드를 모두 깨우게 된다. 이 경우 비신호 상태로 바꾸려면

ResetEvent 함수를 사용해야 한다.

상태변경함수

BOOL SetEvent(이벤트 핸들) // 비신호->신호

BOOL ResetEvent(이벤트 핸들) // 신호->비신호

이벤트 예제

하나의 쓰레드가 버퍼에 데이터를 쓰고 나머지 쓰레드에서 출력하는 예제

#define BUFSIZE 16

HANDLE hReadEvent;

HANDLE hWriteEvent;

int buf[BUFSIZE];

DWORD WINAPI WriteThread(LPVOID arg)

{

DWORD retval;

for (int k=0; k<10; k++)

{

retval = WaitForSingleObject(hReadEvent,INFINITE); //Read이벤트 끝날때 까지 대기

if (retval == WAIT_FAILED) break;

for(int i=0; i<BUFSIZE; i++)

buf[i] = i;

SetEvent(hWriteEvent); //Write이벤트 신호 상태로 변경

}

CloseHandle(hWriteEvent); //이벤트 제거

return 0;

}

DWORD WINAPI ReadThread(LPVOID arg)

{

DWORD retval;

while(1)

{

retval=WaitForSingleObject(hWriteEvent,INFINITE); //Write 이벤트 끝날때 까지 대기

if (retval == WAIT_FAILED) break;

cout << "Thread ID " << GetCurrentThreadId() << ":\t";

for(int i=0; i<BUFSIZE; i++)

cout << buf[i] << " ";

cout << "\n";

ZeroMemory(buf,sizeof(buf)); //버퍼 초기화

SetEvent(hReadEvent); //Read이벤트 신호 상태로 변경

}

return 0;

}

void main()

{

/*

둘다 자동리셋 이벤트로 시작

대기중인 쓰레드중 하나의 쓰레드만 이벤트의 제어권을 넘겨 받고 자동으로

비신호 상태가 된다

*/

hReadEvent = CreateEvent(NULL,FALSE,TRUE,NULL); //신호상태 시작

if (hReadEvent == NULL) return;
hWriteEvent=CreateEvent(NULL,FALSE,FALSE,NULL); //비신호상태 시작
if (hWriteEvent == NULL) return;

HANDLE hThread[3];
DWORD ThreadId[3];

hThread[0] = CreateThread(NULL,0,WriteThread,NULL,0,&ThreadId[0]);
hThread[1] = CreateThread(NULL,0,ReadThread,NULL,0,&ThreadId[1]);
hThread[2] = CreateThread(NULL,0,ReadThread,NULL,0,&ThreadId[2]);

WaitForMultipleObjects(3,hThread,TRUE,INFINITE);

CloseHandle(hReadEvent);
cout << "모든작업을 완료했습니다..!!\n";

}

Wirte이벤트 와 Read이벤트가 서로 동기화를 하고 있고

같은 Read이벤트를 제어하는 1번과 2번 쓰레드가 한번 더 동기화를 하고 있다.

Read이벤트는 신호상태로 Write 이벤트는 비신호 상태로 시작하였다.

Write 이벤트가 비신호 상태로 시작하였기 때문에 Read쓰레드 에서 Write 이벤트가

끝나기 전까지 무한정 대기하게 된다.

Write 쓰레드의 루프가 한번 돌면 Write이벤트가 신호상태가 되면서

대기중이던 Read 쓰레드의 코드가 실행된다. Read이벤트는 자동 리셋 이벤트로

생성되었으므로 제어권이 1쓰레드에 넘어갔을때는 자동적으로 비신호 상태가 된다.

1번쓰레드->2번쓰레드 순으로 코드 를 실행한후 Read이벤트가 신호상태가 되면서

다시 Write 쓰레드의 루프가 돌아가는 방식이다.

Write 쓰레드에서 동기화를 시키지 않았다면?

Write 이벤트는 비신호 상태로 시작하였다. 때문에 Read 쓰레드에서 무한정 대기하게 되며

상태가 바뀌지 않으므로 무한 대기상태에 빠지게 된다.

Read 쓰레드에서 동기화를 시키지 않았다면?

SetEvent 로 신호상태로 전환해 주지 않으면 1번쓰레드에 있는

제어권이 2번쓰레드로 넘어가지 않을것 이다. 또한 신호상태가 되지 않았기 때문에

Write 쓰레드에서도 무한정 대기하게 된다. 결과적으로 1번쓰레드에 있는 출력코드가

1번 실행대고 무한 대기상태에 빠지게 된다.

[출처] 쓰레드 이벤트|작성자 로지어

'Program Visual C++' 카테고리의 다른 글

AVI 파일 다루기  (0) 2011.08.30
정적 라이브러리에서 MFC 사용  (0) 2011.08.25
MFC 더블버퍼링 메모리 버퍼 이용  (0) 2011.08.25
RAW Data Draw  (0) 2011.08.11
YUV to RGB ...  (0) 2011.08.10

관련 글

출처 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에서는 설정한 크기만을 표시 하였다. 정해진 크기보다 크면 배경색 지우기가 없어졌다.

 

 

바깥부분의 배경색이 문제가 발생 했다. 이것은 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();
}

 

 

'Program Visual C++' 카테고리의 다른 글

정적 라이브러리에서 MFC 사용  (0) 2011.08.25
Event, Thread  (0) 2011.08.25
RAW Data Draw  (0) 2011.08.11
YUV to RGB ...  (0) 2011.08.10
쓰레드 사용 예제?? AfxBeginThread, CreateThread, _beginthreadex  (0) 2010.10.10

RAW Data Draw

Program Visual C++ 2011. 8. 11. 16:31 Posted by HisPark


  1. //이미지를 읽어들일 배열 선언
  2. BYTE *Img = new BYTE[Width*Height];
  3. //이미지를 읽음
  4. FileRead("C\\~~~", Width, Height, Img, sizeof(BYTE));
  5. //이미지를 읽는 것은 끝!!! 이제 뿌리는 일만 남음.
  6. //FileRead 함수는 읽기 편하게 만든 템플릿 함수로, 그냥 하나 만들어 놓으면 쓸만함.
  7. //FileRead 함수는 아래와 같음.
  8. template <typename T>
  9. FileRead(CString FilePath, int Width, int Height, T* Mem, int TypeSize)
  10. {
  11. //파일 오픈 및 에러 체크
  12. FILE *fp;
  13. int errorno = 0;
  14. int FileLen = widht * height;
  15. if( (errorno = _wfopen_s(&fp, FullPath, L"rb") ) )
  16. {
  17. AfxMessageBox(L"Read File Open Error 00!");
  18. return 99999;
  19. }
  20. fseek(fp, 0L, SEEK_END);
  21. long len = ftell(fp);///sizeof(short) - WIDTH*HEIGHT;
  22. fseek(fp, len-FileLen*TypeSize, SEEK_SET);
  23. //파일 메모리에 적재 및 읽기 에러 체크
  24. int ReadCount = fread(LoadMem, TypeSize, FileLen, fp);
  25. if(ReadCount < FileLen)
  26. {
  27. TRACE(L"Read Count : %d \n", ReadCount);
  28. AfxMessageBox(L"File Read Error 00!");
  29. return 99998;
  30. }
  31. //파일을 메모리에 적재 후 닫는다.
  32. fclose(fp);
  33. return 0;
  34. }


위 과정으로 Raw 데이터를 적재하는 과정이 끝남.

이제 화면에 보여주기만 하면 됨. 그런데,,,별거 없지만 여긴 좀 과정이 긴....

일단, MDI 또는 SDI 기반에서 화면에 이미지 출력하기!

화면에 출력하기 위해서 비트맵 헤더 정보를 입혀야 하므로, 비트맵 정보를 만들어줌.

  1. //bitmapInfo 형 포인터를 선언 후 할당
  2. BITMAPINFO *m_pBmpInfo8BitGray = NULL;
  3. if(m_pBmpInfo8BitGray != NULL)
  4. {
  5. delete [] m_pBmpInfo8BitGray;
  6. m_pBmpInfo8BitGray = (BITMAPINFO*) new BYTE[sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD))];
  7. }else
  8. m_pBmpInfo8BitGray = (BITMAPINFO*) new BYTE[sizeof(BITMAPINFOHEADER) + (256 * sizeof(RGBQUAD))];
  9. //할당 후 각 항목을 채우는데, 각 항목에 관한 자세한 설명은 나중에 첨부..해야할듯.
  10. m_pBmpInfo8BitGray->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  11. m_pBmpInfo8BitGray->bmiHeader.biPlanes = 1;
  12. m_pBmpInfo8BitGray->bmiHeader.biCompression = BI_RGB;// 0;
  13. m_pBmpInfo8BitGray->bmiHeader.biWidth = Width; //이미지 가로 크기를 적음.
  14. m_pBmpInfo8BitGray->bmiHeader.biHeight = Height;
  15. m_pBmpInfo8BitGray->bmiHeader.biBitCount = 8;
  16. m_pBmpInfo8BitGray->bmiHeader.biXPelsPerMeter = 0;
  17. m_pBmpInfo8BitGray->bmiHeader.biYPelsPerMeter = 0;
  18. m_pBmpInfo8BitGray->bmiHeader.biClrUsed = 256;
  19. m_pBmpInfo8BitGray->bmiHeader.biClrImportant = 0;//SHRT_MAX;
  20. m_pBmpInfo8BitGray->bmiHeader.biSizeImage =m_nHeight * WIDTHBYTES( m_nWidth * 8 );
  21. for(int i = 0; i < 256; i++)
  22. {
  23. m_pBmpInfo8BitGray->bmiColors[i].rgbBlue = i;
  24. m_pBmpInfo8BitGray->bmiColors[i].rgbGreen = i;
  25. m_pBmpInfo8BitGray->bmiColors[i].rgbRed = i;
  26. m_pBmpInfo8BitGray->bmiColors[i].rgbReserved= 0;
  27. }



좋아! 일단 여기까지 하면 비트맵 헤더도 작성된거임. ㅇㅇ...

이제 진짜 OnDraw에서 화면에 출력만 하면 됨!!

  1. void CMyViewerView::OnDraw(CDC* pDC)
  2. {
  3. CMyViewerDoc* pDoc = GetDocument();
  4. ASSERT_VALID(pDoc);
  5. if (!pDoc)
  6. return;
  7. //주 화면을 그린다.
  8. //여기서 m_pViewImg 가 읽어들인 RAW DATA 임.
  9. if(pDoc->m_pViewImg != NULL)
  10. {
  11. int Width = pDoc->m_nWidth;
  12. int Height = pDoc->m_nHeight;
  13. //화면에 출력하기 전에 메모리에 먼저 적재한다. 또한, 비트맵 구조체를 만든다.
  14. CDC memDC;
  15. CBitmap bitMap, *pOldBitmap;
  16. //현재 뿌려야할 pDC와 호환되는 memDC를 메모리에 생성한다.
  17. memDC.CreateCompatibleDC(pDC);
  18. bitMap.CreateCompatibleBitmap(pDC, Width, Height);
  19. pOldBitmap = (CBitmap*)memDC.SelectObject(&bitMap);
  20. memDC.SelectObject(&bitMap);
  21. memDC.FillSolidRect(&rect, RGB(255, 255, 255));
  22. //이 함수는 설명이 길어서 안적음. 한번 찾아서 읽어보시길.
  23. SetDIBitsToDevice(
  24. memDC.GetSafeHdc(), // Handle to the device context.
  25. 0, 0, // Specifies the x and y-coordinate, in logical units, of the upper-left corner of the destination rectangle.
  26. Width, Height, //가로, 세로를 적는다.
  27. // Specifies the width and height, in logical units, of the DIB.
  28. 0, 0, // Specifies the x and y-coordinate, in logical units, of the lower-left corner of the DIB.
  29. 0, // Specifies the starting scan line in the DIB.
  30. Height, //세로를 적음. // Specifies the number of DIB scan lines contained in the array pointed to by the lpvBits parameter.
  31. pDoc->m_pViewImg, pDoc->m_pBmpInfo8BitGray, DIB_RGB_COLORS);
  32. //StretchBltMode 설정.
  33. pDC->SetStretchBltMode(COLORONCOLOR);
  34. //dc.BitBlt(0, 0, rectLP.right, rectLP.bottom, &memDC,0, 0, SRCCOPY);
  35. //pDC->StretchBlt(0,0,Width, Height, &memDC, 0, 0, Width, Height, SRCCOPY);
  36. //비트맵을 화면에 드디어 출력!
  37. pDC->BitBlt(0,0,Width, Height, &memDC, 0,0,SRCCOPY);
  38. }
  39. }



다소 어렵게 느껴질 수 있겠지만 요약하면 다음과 같음.

1. 먼저 Raw 파일을 읽어옴. (반듯이 가로, 세로 크기를 알아야함)
2. Raw 파일에 대한 BitmapInfo를 만들어줌. (화면 출력을 위한 사전 준비)
3. 화면에 RAW파일 출력
3.1 메모리DC 생성 (CDC memDC)
3.2 현재 View의 DC와 호환되도록 메모리DC 설정 (memDC.CreateCompatibleDC(pDC))
3.3 현재 View의 DC와 호환되는 Bitmap 생성 (bitmap.CreateCompatibleBitmap(pDC, Widht, Height))
3.4 SetDIBitsToDevice를 통해 장치 독립적인 bitmap을 화면에 출력하기 위해 설정.
3.5 BitBlt를 통해 화면에 영상 출력

이것만 알면 어떤 Raw파일이든 읽어서 뿌릴수 있음!!

'Program Visual C++' 카테고리의 다른 글

Event, Thread  (0) 2011.08.25
MFC 더블버퍼링 메모리 버퍼 이용  (0) 2011.08.25
YUV to RGB ...  (0) 2011.08.10
쓰레드 사용 예제?? AfxBeginThread, CreateThread, _beginthreadex  (0) 2010.10.10
AfxBeginThread 사용법?  (0) 2009.05.05

YUV to RGB ...

Program Visual C++ 2011. 8. 10. 16:34 Posted by HisPark
// YUV_B, YUV_R, YUV_G 배열은 사용하기 전에 YUV_lookup_table() 함수로 초기화해야 한다.

double YY[256], BU[256], GV[256], GU[256], RV[256];
unsigned char YUV_B[256][256];
unsigned char YUV_R[256][256];
unsigned char YUV_G[256][256][256];

void YUV_lookup_table()
{
int i, j, k;
double i_value;
for( i=255; i>=0; i-- )
{
YY[i] = (1.164*(i-16.0));
BU[i] = (2.018*(i-128.0));
GV[i] = (0.831*(i-128.0));
GU[i] = (0.391*(i-128.0));
RV[i] = (1.596*(i-128.0));
}

for( i=255; i>=0; i-- ){
for( j=255; j>=0; j-- )
{
i_value = YY[i] + BU[j];
if ( i_value > 255 ) i_value=255;
else if ( i_value < 0 ) i_value=0;
YUV_B[i][j]=(int)i_value;

i_value = YY[i] + RV[j];
if ( i_value > 255 ) i_value=255;
else if ( i_value < 0 ) i_value=0;
YUV_R[i][j]=(int)i_value;
for( k=0; k<256; k++ )
{
i_value = YY[i] - (GU[j] + GV[k]);
if ( i_value > 255 ) i_value=255;
else if ( i_value < 0 ) i_value=0;
YUV_G[i][j][k] =(int)i_value;
}
}
}
}

// YUV 영상을 RGB 영상으로 바꾸는 함수
void yuv420_to_rgb( unsigned char *in, unsigned char *out, int w, int h )
{
int x,y;
double imgsize = w*h;
int w3 = w*3;
double uvsize = imgsize/4.0;

unsigned char *pY = in;
unsigned char *pV = in + (int)imgsize;
unsigned char *pU = in + (int)imgsize + (int)uvsize;

int y00, y01, y10, y11;
int u,v;
unsigned char *p;

// 윈도우에서는 영상의 상하가 거꾸로 저장되지 때문에 아래와 같이 코드 작성.
for( y=0; y<=h-2; y+=2 )
{
for( x=0; x<=w-2; x+=2 )
{
p = out + w3*(h-y-1) + x*3;
u = *pU;
v = *pV;

y00 = *pY;
y01 = *(pY+1);
y10 = *(pY+w);
y11 = *(pY+w+1);

*(p) = YUV_B[y00][u];
*(p+1) = YUV_G[y00][u][v];
*(p+2) = YUV_R[y00][v];

*(p+3) = YUV_B[y01][u];
*(p+3+1) = YUV_G[y01][u][v];
*(p+3+2) = YUV_R[y01][v];

*(p-w3) = YUV_B[y10][u];
*(p-w3+1) = YUV_G[y10][u][v];
*(p-w3+2) = YUV_R[y10][v];

*(p-w3+3) = YUV_B[y11][u];
*(p-w3+3+1) = YUV_G[y11][u][v];
*(p-w3+3+2) = YUV_R[y11][v];
pU++;
pV++;
pY = pY + 2;
}
pY = pY + w;
}

// 일반적인 경우 아래의 코드 사용함.
/*for( y=0; y<=h-2; y+=2 )
{
for( x=0; x<=w-2; x+=2 )
{
p = out + w3*y + x*3;
u = *pU;
v = *pV;

y00 = *pY;
y01 = *(pY+1);
y10 = *(pY+w);
y11 = *(pY+w+1);

*(p) = YUV_B[y00][u];
*(p+1) = YUV_G[y00][u][v];
*(p+2) = YUV_R[y00][v];

*(p+3) = YUV_B[y01][u];
*(p+3+1) = YUV_G[y01][u][v];
*(p+3+2) = YUV_R[y01][v];

*(p+w3) = YUV_B[y10][u];
*(p+w3+1) = YUV_G[y10][u][v];
*(p+w3+2) = YUV_R[y10][v];

*(p+w3+3) = YUV_B[y11][u];
*(p+w3+3+1) = YUV_G[y11][u][v];
*(p+w3+3+2) = YUV_R[y11][v];
pU++;
pV++;
pY = pY + 2;
}
pY = pY + w;
}*/
}


//////////////////////////////////////////////////////////////////////////////////////
// BMP 파일로 저장하기

int rgbSize = _imageSize.cx*_imageSize.cy*3;
BYTE* pRgb = new BYTE[rgbSize];
memset(pRgb, 0, rgbSize);

yuv420_to_rgb(_pImage, pRgb, _imageSize.cx, _imageSize.cy);

/*TCHAR curDirPath[512];
::GetModuleFileName(NULL, curDirPath, 512);
TCHAR* pExt = _tcsrchr(curDirPath, _T('\\'));
if(pExt) *pExt = 0;*/

TCHAR defExt[] = _T("bmp");
TCHAR filters[] = _T("bmp Files(*.bmp)|*.bmp|All Files(*.*)|*.*||");
DWORD flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
TCHAR cd[_MAX_PATH];
GetCurrentDirectory(_MAX_PATH, cd);

CFileDialog dlg(FALSE, defExt, 0, flags, filters, this);
dlg.m_ofn.lpstrInitialDir = cd;
if(IDOK == dlg.DoModal()){
HANDLE hFile;
hFile = CreateFile(dlg.GetPathName(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE){
CString s; s.Format(_T("Could not create file : %s (error %d)\n"), dlg.GetPathName(), GetLastError());
AfxMessageBox(s, MB_ICONSTOP|MB_OK);
return;
}

int headerSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD);
BITMAPFILEHEADER fileHeader = { ((WORD)('M'<<8)|'B'), headerSize + rgbSize, 0, 0, headerSize};
BITMAPINFOHEADER infoHeader = { sizeof(BITMAPINFOHEADER), _imageSize.cx, _imageSize.cy, 1, 24, BI_RGB, rgbSize, 0, 0, 0, 0 };
RGBQUAD rgbQuad = { 0, };

DWORD writtenLen = 0;
if(FALSE == WriteFile(hFile, &fileHeader, sizeof(fileHeader), &writtenLen, 0)){
CString s; s.Format(_T("Could not write file : %s (error %d)\n"), _T("File Header"), GetLastError());
AfxMessageBox(s, MB_ICONSTOP|MB_OK);
}

if(FALSE == WriteFile(hFile, &infoHeader, sizeof(infoHeader), &writtenLen, 0)){
CString s; s.Format(_T("Could not write file : %s (error %d)\n"), _T("Info Header"), GetLastError());
AfxMessageBox(s, MB_ICONSTOP|MB_OK);
}

if(FALSE == WriteFile(hFile, &rgbQuad, sizeof(rgbQuad), &writtenLen, 0)){
CString s; s.Format(_T("Could not write file : %s (error %d)\n"), _T("RGB Quad"), GetLastError());
AfxMessageBox(s, MB_ICONSTOP|MB_OK);
}
if(FALSE == WriteFile(hFile, pRgb, rgbSize, &writtenLen, 0)){
CString s; s.Format(_T("Could not write file : %s (error %d)\n"), dlg.GetPathName(), GetLastError());
AfxMessageBox(s, MB_ICONSTOP|MB_OK);
}
CloseHandle(hFile);
}
if(pRgb) delete [] pRgb;