'API'에 해당되는 글 6건

Message Map 사용

API 2009. 1. 22. 10:13

메시지 맵이란 무엇인가?


메시지 맵을 이해 하기 전에 먼저 WIN32 API가 어떤 방식으로 구동 되는 지를 알아야 한다.  윈도우 GUI프로그래밍 언어인 WIN32 API는

메시지 처리방식으로 프로그램이 움직인다.

메시지 처리방식이란 런타임중 환경의 변화등이 있었을때 예를 들어 키보드를 눌렀다거나 화면에 변화가 왔다거나 하는 등의 변화가 있을때

운영체제인 윈도우로 부터 메시지가 송신되고 우리가 만든 프로그램에서 그메시지를 처리하는 방식으로 프로그램이 움직이게 된다.

API에는 이 메시지 처리를 담당하는 함수가 존재하는데 그것을 메시지 처리 프로시저라고도  한다.

메시지 처리 프로시저의 프로토타입(함수 원형)은  다음과 같다.

LRESULT CALLBACK WndProc(HWND ,UINT ,WPARAM ,LPARAM )

이런 프로시저를 보통 사용할 때는 다음과 같이 사용한다.

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
    switch(iMessage)

    {

       case WM_PAINT:

                /화면에 변화가 있었을 때 WM_PAINT 메시지가 발생하고 그때 처리해야할 일들을 이곳에서 처리/

          break;

       case WM_KEYDOWN:

                /키보드가 눌려지면 WM_KEYDOWN 메시지가 발생하고 그때 처리해야할 일들을 이곳에서 처리/

          break;

             .

             .

             .

    }

 return (DefWindowProc(hWnd,iMessage,wParam,lParam));
}

위에서 볼수 있는 것과 같이 프로그램 구동중에 생길 수 있는 모든 메시지들을 처리하게 되며 메시지가 왔을때 그것을 처리하기 위해

분기명령인 switch문을 사용 하게 된다.

프로그램의 크기가 적어 처리 해야할 메시지가 많지 않을 경우에는 별 문제가 되지 않지만 프로그램의 크기가 커져  처리해야할 메시지가 많아지고 각 메시지당 처리해야할 일들이 많아진다면 소스코드의 복잡성이 늘어나며 소스의 이해도가 떨어지게 된다.


어차피 switch문은 해당 하는 곳으로 바로 점프하는 분기방식아닌가 !!!!

해당 하는 곳말고는 없는 코드나 마찬가지가 아닌가 !!!!

메시지가 올때마다 해당하는 메시지에 맞는  함수를 호출 시켜 줄수 있다면 !!!!!!


이런 생각에서 나온게 바로 메시지 맵이란 기법이다.


사설이 길었다..... 본론으로..

메시지 맵이란 각각의 메시지 처리함수로 만들어 처리함수 마다 해당 메시지에 대한 처리만을 담당하게 하고 메시지가 호출되면 해당 함수를

호출 시켜주는 기능을 말한다.


기존의 메시지 처리함수(WndProc)에서는 들어오는 메시지를  메시지 맵에서 찾아 해당 하는 메시지 처리함수를 호출하는 일만 처리하게 된다.


이렇게 메시지 맵 기법이 사용 가능한것은 모든 메시지가 같은 메시지 처리함수에서 처리된다는 점 때문이다.

같은 함수내에서 처리하는 방식만 틀리기 때문에 함수포인트를 이용해서 함수를 분리해 내는 것이 가능한것이다.


여기서 또하나 !!

함수 포인터란 무었인가?

아는 사람은 살포시 패스~!! 모르는 사람은 필독!! 모르면  다음 부터 이해하기 힘이 든다.

프로그램을 실행 시킨다는 의미는 프로그램을 메모리에 올린다는 의미다. 변수가 메모리에 올라가듯 함수역시 메모리 어딘가에 올라가게 되는 것이다.

메모리에 올라간다는 것은 당연 해당 번지가 할당되게 되며 그 해당 번지가 함수의 주소가 되는 것이다. 해당 주소를 찾아가면 그 함수를 실행

시킬수 있게 되는 것이다. 하지만 무턱대고 찾아간다고 함수가 실행 되는 것이 아니다. 변수 역시 자료형을 알아야  포인터를 이용해서 사용이 가능하듯 함수 역시 자료형이 존재 하며 함수의 자료형은 그함수의 프로토타입을 의미하기도 한다. 함수의 자료형이라고 어렵게 생각할거 없다.

함수의 프로토 타입에서 함수의 이름부분을 ( * ) 로 바꿔주면 그만이다.


이쯤에서 함수포인터에 대한 연습한번 !!

기존의 메시지 처리함수의 프로토타입(원형)을 가지고 함수 포인터를 만들어 보자


LRESULT  WndProc(HWND ,UINT ,WPARAM ,LPARAM )

프로토 타입이 위와 같으므로  메시지 처리 함수의 자료형은 다음과 같이 만들수 있다.


LRESULT (*) (HWND ,UINT ,WPARAM ,LPARAM );

 

붉은색 으로 표시한 곳을 보듯이 함수명 부분을 (*)로 변경 해주면 자료형이다. 어렵지 않다.

함수의 자료형이 있다면 함수형의 변수 역시 선언 가능하지 않겠는가!!

그렇다 있다.

(*) 부분에 변수 명을 써주면 된다. 어떻게? ( * Fxn) 이렇게 쓰면 된다.

LRESULT (*)(HWND ,UINT ,WPARAM ,LPARAM ) 형 함수를 가리키는 함수포인터 Fxn을 선언 하는 것이다.



- 메시지 맵의 구현

1.메시지 맵에 사용할 구조체를 생성 한다.

typedef struct _DecodeMsg
{
 UINT Code;
 LRESULT (*Fxn)(HWND ,UINT ,WPARAM ,LPARAM );
}DecodeMsg;


Code는 실제 메시지의 값을 저장할 곳이며 아래있는 함수 포인터는 메시지 처리함수에서 발생한 메시지와 Code를 비교하여 해당하는 함수포인트를 호출할때 사용하게 될것이다.


2.메시지 맵 구현( 메시지와 해당하는 함수포인트로 구성된 구조체의 배열 )

const DecodeMsg MsgProc[] = {      // : []는 배열을 나타내는 대괄호다. 폰트가 대괄호를 []로 표시해 버린다....  ;;
 WM_PAINT,Dopaintmain,
 WM_CREATE,Docreatemain,
 WM_TIMER,Dotimermain,
 WM_KEYDOWN,Dokeydownmain,
 WM_DESTROY,Dodestroymain

                     .

                     .

                     .
};
위에서 Dopaintmain,Docreatemain,Dotimermain등은 미리 정의 되어있는 각각의 메시지를 처리하게될 함수다.

함수의 이름은 그 함수의  해당 메모리 주소를 나타낸다. 배열의 이름이 배열의 시작 번지를 나타내는 것이랑 같다고 생각하자


각각의 함수 프로토 타입은

LRESULT Dopaintmain(HWND hWnd , UINT iMessage , WPARAM wParam , LPARAM lParam)

처럼 메시지처리 함수(WndProc)과 같은 타입으로 정의 되어야 한다. 그래야 함수 포인터를 사용해서 호출이 가능하다.


3.메시지 처리함수 내에서의 메시지 비교 와 함수 호출( 실재 메시지 맵을 사용해서 메시지를 처리하는 모습 )

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 int Cnt;
 for (Cnt = 0;Cnt < dim(MsgProc);Cnt++)
 {
    if (iMessage == MsgProc[Cnt].Code)
    {
       return (*MsgProc[Cnt].Fxn)(hWnd,iMessage,wParam,lParam);
    }
 }
 return (DefWindowProc(hWnd,iMessage,wParam,lParam));
}

메시지가 발생 하게 되면 기존에 만들어 두었던 메시지 맵의 Code와 비교를 하게된다. 비교 후 같은 메시지를 발견 하게되면

해당하는 메시지의 함수를 호출하게 된다.


메시지 맵에서 메시지를 찾을수 없는(메시지맵에서 처리하지않은)메시지는 반복문을 빠져나와

DefWindowProc 함수가 이를 처리하게 된다.


실재 MFC가 이런 메시지 맵 방식으로 메시지를 처리하며 메시지 맵이 어떤 방식으로 움직이는 지를 알아 놓으면  MFC를 이해 하는데 많은 도움이

될것이며 이런 기법을 응용해서 좀더 구조적인 프로그래밍을 할 수 있을 것이다.

자 메시지 맵은 여기까지 !!

 
Posted by 응이

#include<windows.h>
#define ID_WRITE 100
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
DWORD WINAPI MechaRead(LPVOID NotUse);

HINSTANCE g_hInst;
HWND HWndMain;
LPCTSTR lpszClass=TEXT("메카트로닉스");
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
           ,LPSTR lpszCmdParam,int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra=0;
  WndClass.cbWndExtra=0;
  WndClass.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);
  WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
  WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
  WndClass.hInstance=hInstance;
  WndClass.lpfnWndProc=WndProc;
  WndClass.lpszClassName=lpszClass;
  WndClass.lpszMenuName=NULL;
  WndClass.style=CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);

  hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
    NULL,(HMENU)NULL,hInstance,NULL);
  ShowWindow(hWnd,nCmdShow);

  while(GetMessage(&Message,NULL,0,0)){
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return (int)Message.wParam;
}
//위 까지는 기본적인 API 메인 부분이다
HWND  PrintWnd,WriteWnd,WriteWnd2;//각종 컨트롤들의 제어를 위한 핸들러
HANDLE  hComm;//통신을 위해 사용할 포트 핸들러를 저장할 변수
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
  HDC        hdc;
  PAINTSTRUCT    ps;
  DWORD      dwWritten;//포트를 열고 읽을 때 사용할 변수로 얼마나 읽고 썼는지를 담을 변수
  DCB        sPState;//시리얼 통신포트 설정을 위한 구조체
  const char    *ucpData = "TEST ";//테스트용으로 사용할 전송 메시지
  DWORD      ThreadID;//스레드 생성시 얻어지는 아이디
  static HANDLE  hThread;//스레드 핸들러
 
COMMTIMEOUTS  cTime; //타임아웃 구조체(통신의 지연시간 대기시간 등등)
  TCHAR Buff[256];//버퍼로 사용할 공간
  int len;//문자열의 길이를 담을 변수
  switch(iMessage)
  {
  case WM_CREATE:
    HWndMain = hWnd;

//전송이나 수신에 사용할 에디터창을 생성하고 핸들러를 리턴 받고있다.
    CreateWindow(TEXT("static"),TEXT("전송받은 문자"),WS_CHILD|WS_VISIBLE,30,10,100,25,hWnd,(HMENU)-1,g_hInst,NULL);
    CreateWindow(TEXT("static"),TEXT("수신한 문자"),WS_CHILD|WS_VISIBLE,260,10,100,25,hWnd,(HMENU)-1,g_hInst,NULL);
    CreateWindow(TEXT("static"),TEXT("수신할 문자"),WS_CHILD|WS_VISIBLE,330,260,100,25,hWnd,(HMENU)-1,g_hInst,NULL);
    PrintWnd  = CreateWindow(TEXT("edit"), NULL, WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOVSCROLL|WS_BORDER,
      20, 40, 200, 200, hWnd, (HMENU)-1, g_hInst, NULL);
    WriteWnd2  = CreateWindow(TEXT("edit"), NULL, WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOVSCROLL|WS_BORDER,
      260, 40, 200, 200, hWnd, (HMENU)-1, g_hInst, NULL);
    WriteWnd  = CreateWindow(TEXT("edit"), NULL, WS_CHILD|WS_VISIBLE|ES_MULTILINE|ES_AUTOVSCROLL|WS_BORDER,
      20,260, 300, 25, hWnd, (HMENU)ID_WRITE, g_hInst, NULL);
//포트를 열고 핸들러를 리턴 받는다.
    hComm = CreateFile(TEXT("COM7"),GENERIC_READ | GENERIC_WRITE, 0 , NULL, OPEN_EXISTING, 0,0);
//오픈시 에러를 검출하고 있다. 에러라면 INVALID_HANDLE_VALUE(-1)이 리턴 된다.
    if(INVALID_HANDLE_VALUE == hComm)
    {
      MessageBox(hWnd, TEXT("포트 열 수 없음"), TEXT("ERROR"), MB_OK);
      DestroyWindow(hWnd);
      return 0;
    }
//4096 입력 버퍼 크기, 3096 츨력 버퍼 크기
    if(0 == SetupComm(hComm, 4096, 3096))         {
      MessageBox(hWnd, TEXT("버퍼 설정 에러"), TEXT("ERROR"), MB_OK);
      DestroyWindow(hWnd);
      return 0;
    }

//모든 출력을 중단하고 출력 버퍼 클리어 
    if( 0 == PurgeComm(hComm, PURGE_TXABORT | PURGE_TXCLEAR))      {
      MessageBox(hWnd, TEXT("버퍼 초기화 에러"), TEXT("ERROR"), MB_OK);
      DestroyWindow(hWnd);
      return 0;
    }
//포트설정 구조체의 크기
    sPState.DCBlength = sizeof(sPState);
 //시리얼 포트 상태를 조사한다.
    if(0 == GetCommState(hComm, &sPState))       {
      MessageBox(hWnd, TEXT("시리얼 상태 읽기 에러"), TEXT("ERROR"), MB_OK);
      DestroyWindow(hWnd);
      return 0;
    }

//타임아웃 구조체를 설정 하고 있다.

//한문자를 수신한후 다음 문자가 수신될때까지 기다리는 시간 이시간이 지나면 버퍼를 리턴한다.
    cTime.ReadIntervalTimeout      = MAXDWORD;

    cTime.ReadTotalTimeoutMultiplier  = 0;//한문자를 수신하는데 걸리는 시간

//읽을 바이트수에 ReadTotalTimeoutMultiplier 값을 곱한 값에 ReadTotalTimeoutConstant값을 더해

//총 타임아웃시간을 구할수 있다.
    cTime.ReadTotalTimeoutConstant    = 0; 
    cTime.WriteTotalTimeoutMultiplier  = 0;
    cTime.WriteTotalTimeoutConstant    = 0;
    SetCommTimeouts(hComm, &cTime);
//보우율,전송비트,페리티모드,스탑비트등을 설정 한다.
    sPState.BaudRate  = CBR_115200;
    sPState.ByteSize  = 8;
    sPState.Parity    = NOPARITY;
    sPState.StopBits  = ONESTOPBIT;
//설정한 포트 값을 적용 시킨다.
    if( 0 == SetCommState(hComm, &sPState))   //시리얼 포트 상태를 조사
    {
      MessageBox(hWnd, TEXT("시리얼 상태 설정 에러"), TEXT("ERROR"), MB_OK);
      DestroyWindow(hWnd);
      return 0;
    }

//스레트를 생성한다. MechaRead 함수는 아래 정의 되어 있는 수신용 함수다
    hThread = CreateThread(NULL, 0, MechaRead, NULL, 0, &ThreadID);
    return 0;

  case WM_PAINT:
    hdc = BeginPaint(hWnd,&ps);
    EndPaint(hWnd,&ps);
    return 0;
  case WM_COMMAND:
    switch(LOWORD(wParam))//컨트롤에서 메시지가 오면
    {
    case ID_WRITE://전송 부분의 컨트롤의 메시지였다면
      switch(HIWORD(wParam))
      {
      case EN_CHANGE://에디트창의 문자열에 변화가 있었을 경우
        len = GetWindowText(WriteWnd,Buff,sizeof(Buff));//에디트창의 문자열을 읽어 들인다
        if (Buff[len-1]=='\n')//문자열의 마지막이 엔터일때까지 기다리다 엔터라면 전송을 시작한다.
        {
          Buff[len] = '\0';
          WriteFile(hComm, Buff,lstrlen(Buff),&dwWritten,0);//버퍼의 내용을 전송한다.
          if (!dwWritten)
          {
            MessageBox(hWnd,TEXT("전송 실패"),TEXT("실패"),MB_OK);
          }
          SetWindowText(WriteWnd,TEXT(""));//전송후 에디트창의 값을 리셋한다.

          //전송후 전송한 메시지를 다른 에디터창에 나타낸다.
          SendMessage(WriteWnd2, EM_REPLACESEL, (WPARAM)TRUE,(LPARAM)Buff);
        }
        break;
      }
      break;
    }
    return 0;
  case WM_LBUTTONDOWN:

//마우스 왼쪽 클릭시 테스트용 메시지를 전송한다.
    WriteFile(hComm, ucpData, (DWORD)strlen(ucpData),&dwWritten,0);
    return 0;

  case WM_DESTROY:
    CloseHandle(hThread);//생성한 스레드는 반드시 닫아준다
    CloseHandle(hComm);//오픈한 포트 역시 닫아 주어야 한다.
    PostQuitMessage(0);
    return 0;
  }
  return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
//스레드 함수의 원형은 반드시 아래와 같아야 한다.
DWORD WINAPI MechaRead(LPVOID NotUse)
{
  TCHAR  tt[3]={};
  DWORD  dwRead;

  while(1)
  {
    Sleep(50);//스레드 함수에서 과도한 CPU의 점유를 막기위해 잠시 잠들어 있게 한다.

//포트를 통해 전송된 문자를 읽어 들인다. 읽어들인 문자가 없어도 블러킹 되어 있지 않고 리턴한다.

//위에서 타임아웃 구조체를 설정해 주었기 때문에 블러킹 되지 않는것이다.
    ReadFile(hComm, tt,2,&dwRead,NULL);
    if(0 != dwRead)//전송된 문자가 있었다면
    {

//에디터창에 수신된 문자열을 출력한다.
      SendMessage(PrintWnd, EM_REPLACESEL, (WPARAM)TRUE,(LPARAM)tt);
    }
  }
}
 

Posted by 응이

1)윈도우 스레드

- 스레드 생성하기

윈도우에서 스레드를 구동시키기 위해서는 Win32 API인 CreateThread 함수를 사용하면 된다. 이 함수는 윈도우에서 사용하는 가장 저수준의 스레드 구동 함수다.

함수의 원형은 다음과 같다.

CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes,                      //Security Descriptor

SIZE_T dwStackSize,                                            //초기 스택 크기

LPTHREAD_START_ROUTINE lpStartAddress,                         //스레드 함수

LPVOID lpParameter,                                            //스레드 인자

DWORD dwCreationFlags,                                         //생성 옵션

LPDWORD lpThreadId                                             //스레드 아이디

);

첫 번째 인자생성될 스레드에 대한 사용권한을 지정하는데 사용하며 보통 NULL로 설정하게 되고 이는 THREAD_ALL_ACCESS를 의미하므로 어떤 함수든 이 스레드의 핸들을 이용해서 스레드를 제어할수 있다.

 

두 번째 인자스택 크기는 생성되는 스레드의 스택 크기를 정하는 인자며 만약 0으로 주게 되면 기본 값으로 1MByte의 스택이 생성된다.

 

세 번째 인자lpStartAddress는 생성된 스레드가 실행시킬 함수며 함수의 원형은 아래와 같다.

DWORD WINAPI ThreadFunc(LPVOID lpParam)

 

네 번째 인자스레드로 실행 시킬 함수의 인자값이며 인자로 포이트 포인트가 필요한 이유는 인자의 수와 자료형을 알수 없기 때문이다.

 

다섯 번째 인자스레드 생성 옵션에 해당하며 이 인자에 0을 주면스레드가 생성된 후 스레드가 바로 동작하는 것을 으미한다. 반면 CREATE_SUSPENDED를 인자로 주면 스레드가 멈춰있는 상태로 생성되는 것을 의미한다. 만약 스레드가 Suspend, 즉 몀춰있는 상태라면 ResumeThread 함수를 통해서 실행 가능한 상태를 의미하는 동작대기(Ready)상태로 바꿀수 있다.

 

마지막 인자인 여섯 번째 인자생성된 스레드의 스레드 아이디 값을 받는 인자다.

 

 

- 스레드 생성예(스레드 생성 버튼 클릭시 스레드를 생성하는 예이다)

 

DWORD WINAPI ThreadFunc(LPVOID lpParam)   //스레드로 생성될 함수 원형은 정해진 틀에 맞게 만들어 줘야한다.

{

   AfxMessageBox(L"나는 스레드~");

   return 0;

}

void CUdpMfcDlg::OnBnClickedThread()                                  //버튼 클릭시 호출되는 함수로 스레드를 생성하게 된다.

{

    DWORD dwThreadID;                                                      //생성될 스레드의 아이디를 받을 변수다

    HANDLE hThread;                                                        //생성될 스레드의 핸들러를 받을 변수다.

    int i; //카운터로 사용할 변수

    for (i=0;5>i;++i)                                                      // 5개의 스레드를 생성하기 위해 반복문을 돌린다.

    {

       // CreateThread 함수의 인자는 (스레드사용권한,스택크기,호출함수,함수인자,생성옵션,생성된아이디) 며

       // 생성된 스레드의 핸들러를 반환한다.

         hThread = CreateThread( NULL , 0 , ThreadFunc , 0 , 0 , &dwThreadID ); //스레드 생성 함수를 호출

    }

    if (hThread == NULL)                                             //핸들러가 비어 있다면 스레드 생성에 실패 한 것이다.

    {

         AfxMessageBox(L"스레드 생성 실패!!");

    }

    else

    {

        AfxMessageBox(L"스레드 생성 성공");

        CloseHandle(hThread);                                   //스레드 핸들은 CloseHandle을 사용해서 해제 해주어야 한다.

    }

}

 

- 스레드 제어/종료

생성된 스레드 내에서 자신의 핸들을 얻고 싶을 때는 아래 함수를 사용해서 얻어낼수 있다.

HANDLE GetCurrentThread(VOID)

 

생성된 스레드 내에서 스레드를 끝내려 한다면 스레드 내에서 return해서 종료하거나 ExitThread()를 사용해서 종료 시킬수 있다.

VOID ExitThread(DWORD dwExitCode)

스레드를 생성 하게 되면 윈도우는 각 스레드를 관리하는 스레드 객체를 커널에서 마련해서 관리하게 된다. 그리고 스레드가 시작되면 스레드 객체의 스레드의 종료 살태 코드는 STILL_ACTIVE를 갖게 된다. 그렇지만 스레드가 종료되면 스레드 객체는 시그널(종료)된 상태가 되고 스레드의 종료 상태 코드는 ExitThread 함수 호출시 지정한 dwExitCode로 바뀌게 된다.

 
Posted by 응이

Bitmap파일뷰어

API 2008. 10. 24. 10:16

가로 세로 픽셀값 잘못 찍혔다..

확인 꼭 해바라!!!
Posted by 응이

Bitmap 파일 구조.

API 2008. 10. 1. 09:47

Bitmap 파일 구조

디바이스에 독립적인 영상파일을 저장하는 표준규격

JPEG, GIF, BMP, TIFF, PCX, PGM 등이 존재

보통 영상데이터는 정보량이 크기 때문에 압축을 통해 작은 크기로 변환하여 저장한다.

BMP파일 포맷은 압축을 수행하지 않으며 헤드가 있는 여러 형식의 파일 중에서

구조가 가장 간단하다.

파일 헤더

BITMAPFILEHEAER라는 구조체에 정의

사용자는 단지 구조체 변수를 선언해서 사용만 하면 된다.

현재의 파일 포맷이 정말 BMP인지에 대한 정보

BMP파일의 확장자 : *.BMP로 정의

다른 파일이 XX.BMP라고 썼을 수 있으므로, BMP디코더가 이를 분석

세가지 분석방법이 존재.

Bitmap 의 종류

DDB(Device-Dependent Bitmap) -> 장치종속 Bitmap

DIB(Device-Independent Bitmap) -> 장치독립 Bitmap

DIB 구조

BITMAPFILEHEADER -> File이 Bitmap File인지 구별하는 구조체.

BITMAPINFO -> Bitmap의 정보와 팔레트에 대한 정보를 제공하는 구조체.

BITMAPINFOHEADER -> Bitmap의 정보에 관한 구조체.

RGBQUAD -> 이미지의 색상 정보 구조체.

Image Data -> 실제자료가 pixel 로 구성되어 있음.

DIB 구조체

type struct tagBITMAPFILEHEADER

{

WORD bfType; // Bitmap파일을 나타내는 "BM" 문자의 ASCII값을 취함.

DWORD bfSize; // Bitmap파일의 크기

WORD bfReserved1; // 사용하지 않은 확장 예약변수

WORD bfReserved2; // 사용하지 않은 확장 예약변수

DWORD bfOffbits; // 실질 데이터(pixel)의 시작좌표를 나타냅니다.

}BITMAPFILEHEADER;

1. 파일의 첫 2바이트가 BM으로 시작하는가.

2. 바이트 단위의 정확한 파일크기와 헤더내의 bfSize가 있어야할 값과 일치하는가.

3. bfReserved1과 bfReserved2가 있어야 할 위치에 0값이 있는가.

세가지를 모두 체크, bmp파일 여부를 판명

디코더의 크기를 줄이기 위해, 첫 번째것만 체크하는 경우

파일의 헤더의 역할

포맷의 명확성을 표시

파일 내에 있어서 실질 데이터(pixel data)의 주소값을 나타내줍니다.

다시말해, 실질데이터의 파일 내 위치를 정의해 주는 역할

type struct tabBITMAPINFO

{

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmicolors[1];

} BITMAPINFO;

type struct tagBITMAPINFOHEADER

{

DWORD biSize;// 비트맵 BITMAPINFOHEADER의 크기

LONG biWidth; // 이미지의 가로 길이.

LONG biHeight; // 이미지의 세로 길이.

WORD biPlanes;// 플레인수

WORD biBitCount; // 이미지의 컬러 비트수.

예) 8 이면 2의 8승이 되어 256컬러임을 알수 있음.

DWORD biCompression;// 압축 유무

DWORD biSizeImage;// 이미지 크기

LONG biXPelsPerMeter;// 미터당 가로 픽셀

LONG biYPelsPerMeter;// 미터당 세로 픽셀

DWORD biClrUsed;// 컬러 사용 유무

DWORD biClrImportant;// 중요하게 사용하는 색

} BITMAPINFOHEADER;

typedef struct tagRGBQUAD

{

BYTE rgbblue; // 파란색값

BYTE rgbGreen;// 녹색값

BYTE rgbRed;// 빨간색값

BYTE rgbReserved;// 항상 0

} RGBQUAD;

1. 팔레트는 인덱스에 의한 컬러값을 저장하기 위한 구조체.

2. 이 구조체를 사용하여 팔레트의 수 만큼 배열을 할당하여 저장.

3. 256컬러모드의 영상은 팔레트의배열 크기가 256개

4. 16비트 컬러영상은 팔레트 크기가 2의 16승개이다. biClrUsed변수를 참조하면 된다.

이미지는 모든색을 사용하는 것이 아니라 특정 색만을 사용한다.

예를 들어 8비트 Bitmap 이미지는 256색만을 사용한다.

Bitmap 이미지의 color정보를 이미지를 로드할 때마다 시스템에서 얻어오는 것보다 Bitmap 파일 안에 color 정보를 가지고, 그곳에서 얻어오는 것이 속도면에서 많은 이득이 있기 때문에 RGBQUAD 구조체를 사용하여 Bitmap 이미지의 color 정보를 저장한다.

이러한 color정보들의 모임을 다른 말로 '팔레트(Palette)라고 한다.

8비트 Bitmap이미지에서는 256개 color에 해당하는 256개의 RGBQUAD 배열, 즉 이미지 파일내에 팔레트가 존재하게 된다.

여기서는 문제점이 있는데 만약 Bitmap 이미지가 16비트 이상의 color 이미지이면 문제가 발생한다. 16비트 Bitmap 이미지는 컬러 수가 65536색 이며, 이것을 Bitmap 파일 내에 팔레트를 둔다면 65536개의 RGBQUAD배열을 만들어야 한다.

이는 배보다 배꼽이 큰 경우가 되기 때문에 조금 느리더라도 16비트 이상의 Bitmap 이미지에서는 내부 팔레트를 사용하지 않고, 시스템에서 제공하는 시스템 팔레트를 사용한다.

이미지의 저장 상태

이미지를 보는 그림과는 다르게 Image Data에는 상하가 뒤집어진 상태로 저장되어 있다.

이 때문에 Surface에 이미지를 로드 할 때 주의를 해야 한다.

이미지를 저장하거나 로드할 때는 항상 width, 즉 가로길이를 4의 배수로 맞추어야 한다.

예를 들어 이미지가 30 * 30 크기의 이미지이면 한 줄 읽을 때는 32byte를 읽어야 한다.

Bitmap 이미지는 항상 가로길이가 4의 배수이기 때문에 30byte는 이미지 data이고 나머지 2byte는 길이를 맞추기 위해 채워져야 한다.

이것을 모르고 그냥 이미지를 물러오면 이미지가 화면에 제대로 출력되지 않는다.

Posted by 응이

API

API 2008. 9. 1. 11:11

#include<windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass=TEXT("Class");

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
      ,LPSTR lpszCmdParam,int nCmdShow)
{
 HWND hWnd;
 MSG Message;
 WNDCLASS WndClass;
 g_hInst = hInstance;

 WndClass.cbClsExtra=0;
 WndClass.cbWndExtra=0;
 WndClass.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);
 WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
 WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
 WndClass.hInstance=hInstance;
 WndClass.lpfnWndProc=WndProc;
 WndClass.lpszClassName=lpszClass;
 WndClass.lpszMenuName=NULL;
 WndClass.style=CS_HREDRAW | CS_VREDRAW;
 RegisterClass(&WndClass);

 hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
  NULL,(HMENU)NULL,hInstance,NULL);
 ShowWindow(hWnd,nCmdShow);

 while(GetMessage(&Message,NULL,0,0)){
  TranslateMessage(&Message);
  DispatchMessage(&Message);
 }
 return (int)Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 HDC hdc;
 PAINTSTRUCT ps;

 switch(iMessage)
 {
  case WM_CREATE:
   hWndMain = hWnd;
   return 0;

  case WM_PAINT:
   hdc = BeginPaint(hWnd, &ps);
   EndPaint(hWnd, &ps);
   return 0;
 
  case WM_DESTROY:
   PostQuitMessage(0);
   return 0;
 }
 return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

Posted by 응이
1

Dream come true.
응이

달력

태그목록