황동준님의 윈도우 32비트 프로그래밍 21~25

  윈도우 32비트 프로그래밍 21

안녕하세요…..돌팔이 황동준입니다…. 이번에는 여러분들이 관심을 가질만한 비트맵
출력에 관해서 알아 보겠습니다. 먼저 강좌를 하기 전에 여러분들에게 한가지 묻고 싶은게 있습니다.

여러분들한테 묻고자 하는 것은 제가 하는 강좌가 실제로 도움이 되는지 제가 궁금하다는
것입니다. 도움이 되는 분들은 도움이 된다고 멜 좀 부탁합니다. 그래야 저도 힘이 나서 더 열심히 하죠…. 제가 회사일때문에 시간이 잘 나지
않아서 그냥 중단할까 라는 생각이 들거든요. 그런데 많은 사람들이 원한다면야 계속 해야 겠지만요.

여기까지 쓸데없는 얘기였습니다.

그러면 본격적으로 강좌를 시작해 보겠습니다.

윈도우즈에서 비트맵 그림을 출력하는 방법은 생각 외로 간단합니다. 그런데 이 루틴을 가지고
게임을 만들 수 있을까요? 너무 느리죠.

이런 단점을 보완해서 나온 것이 WinG라고 하는 그래픽 출력 라이브러리인데 그나마 속력이
많이 개선 되었다고 할 수 있습니다. 우리가 아래에서 다룰 예제로는 쉽게 빠르다는 것을 구분할 수 없지만요…

자 그러면 먼저 일반적인 출력 방법부터 알아 봅시다. 일반적으로 비트맵을 다루기 위해서는
리소스 파일에서 그 비트맵 파일을 정의 해 주었다는 것을 기억할 겁니다. 역시 출력할 비트맵 파일을 리소스 파일에서 정의하면 됩니다. 비트맵
파일을 정의하고 나서 그 파일을 이용하려면 비트맵 핸들을 얻었다는 것을 기억하십니까? 잘 기억이 안난다고요? LoadBitmap()이라는 함수를
이용해서 했잖아요.

여기까지 이해가 된다면 이제 추가된 새로운 방법을 알아 봅시다. 먼저 비트맵 그림을 출력하기
위해서는 메모리에 디바이스 컨텍스트 핸들을 생성해야 됩니다. 우리가 일반적으로 생성한 디바이스 컨텍스트 핸들을 이용해서 문자를 출력하면 바로
화면에 보여질 겁니다. 그렇죠? 여지껏 그렇게 했잖아요. 그런데 메모리에 디바이스 컨텍스트 핸들을 생성하고 그 핸들을 이용해서 출력하면 화면에
보일까요? 당연히 안보이죠. 그런데 왜 비트맵을 출력할때 이런과정이 필요할까요? 그것은 메모리에 그림 전체를 출력해 놓은 다음에 그것을 디바이스
컨텍스트 핸들을 이용해서 통채로 복사하기 위해서입니다. 게임 프로그래밍을 해 보신 분은 쉽게 이해할 수 있겠죠?

HDC CreateCompatibleDC(HDC hDC);

위 함수를 이용해서 메모리에 디바이스 컨텍스트 핸들을 생성할 수 있습니다. 파라미터로 현재
디바이스 컨텍스트 핸들을 지정하면 됩니다. 메모리 디바이스 컨텍스트 핸들을 생성해서 사용한 후에는 꼭 해제해주어야 합니다.

BOOL DeleteDC(HDC hDC);

위 함수를 이용해서 해제해주면 됩니다. 파라미터로 메모리 디바이스 컨텍스트 핸들을 지정해
주면 됩니다.

자 이번에는 비트맵 그림의 크기를 얻는 방법을 알아 봅시다. 우리가 출력할 비트맵 그림이야
그 크기가 얼마나 되는지 자신이 알고 있을 수도 있지만 모르고 있는 경우도 있습니다. 그런데 왜 그림의 크기를 알고 있어야 할까요? 메모리에
있는 그림을 현 디바이스 컨텍스트로 복사할 때 사용하는 함수가 크기를 지정하도록 하는 파라미터를 요구하기 때문입니다.

int GetObject(HGDIOBJ hgdiobj, int cbBuffer, LPVOID
lpvObject);

위 함수를 이용해서 비트맵 그림에 대한 정보를 얻을 수 있습니다. 첫번째 파라미터로 비트맵
핸들을 지정하면 되고 세번째 파라미터에 BITMAP 구조체로 선언한 변수의 주소를 지정하면 됩니다. 두번째 파라미터는 이 BITMAP 구조체의
크기를 지정하면 되구요. 두번째 파라미터에 sizeof(BITMAP)이라고 지정하면 되겠죠.

아래는 BITMAP 구조체의 원형입니다.

typedef struct tagBITMAP {
    LONG
bmType;
    LONG bmWidth;
    LONG bmHeight;
    LONG
bmWidthBytes;
    WORD bmPlanes;
    WORD bmBitsPixel;
    LPVOID
bmBits;
} BITMAP;

위 구조체의 맴버중 bmWidth, bmHeight에 그 크기의 정보가 저장됩니다.

BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest,
int nWidth, int nHeigth,
    HDC hdcSrc, int nXSrc, int nYSrc, DWORD
dwRop);

위 함수를 이용해서 메모리에 있는 그림을 현 디바이스 컨텍스트 핸들로 복사할 수 있습니다.
즉 화면에 출력할 수 있는것입니다.

첫번째 파라미터가 출력할 현 디바이스 컨텍스트 핸들을 의미하는 것입니다. 두번째 파라미터와
세번째 파라미터는 출력할 좌표를 의미하고 네번째, 다섯번째 파라미터는 그 넓이와 높이를 의미합니다. 이 파라미터에 비트맵 그림의 크기를 지정하면
됩니다. 여섯번째 파라미터는 메모리 디바이스 컨텍스트 핸들을 의미합니다.

그러면 일곱번째와 여덟번째 파라미터는 뭘 의미할까요? 바로 메모리 디바이스 컨텍스트 핸들의
첫번째 좌표를 의미하는 것입니다. 처음부터 다 복사하려면 0, 0을 지정하면 되죠. 마지막 파라미터는 복사를 어떻게 할 것인지를 의미하는
것입니다. 이 파라미터에 올 수 있는 예약어에는 다음과 같은 것들이 있습니다.

SRCCOPY      메모리의 내용을 그대로 복사한다.
SRCAND
      메모리의 내용과 현 화면과 AND 연산을 한다.
SRCINVERT    메모리의 내용과 현 화면을 XOR 연산을
한다.
SRCPAINT     메모리의 내용과 현 화면을 OR 연산을 한다.

자 그러면 대략적으로 어떤 식으로 되는지 알아 보았으니 실제로 이것을 이용한 예제를 봅시다.
아래는 비트맵 그림을 화면에 출력하는 예제입니다.

먼저 리소스 파일입니다.

#include <windows.h>

MyBitmap BITMAP “test.bmp”

프로그램 소스입니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
HBITMAP hBitmap;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        hBitmap = LoadBitmap(hInstance,
“MyBitmap”);

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC hDC,
hMemDC;
        BITMAP bitmap;
        PAINTSTRUCT ps;
        static
int nX, nY;

        switch(message)
        {
            case
WM_PAINT :

                hDC = BeginPaint(hWnd,
&ps);
                hMemDC =
CreateCompatibleDC(hDC);
                SelectObject(hMemDC,
hBitmap);
                GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
                nX = bitmap.bmWidth;
                nY =
bitmap.bmHeight;
                BitBlt(hDC, 0, 0, nX, nY, hMemDC, 0, 0,
SRCCOPY);
                DeleteDC(hMemDC);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

먼저 리소스 파일을 보면 알겠지만 비트맵 파일을 정의하죠?

hBitmap = LoadBitmap(hInstance, “MyBitmap”);

정의한 비트맵 그림을 로드하는 함수입니다. 이미 설명을 드렸을 겁니다. 자 그러면 실제로
화면에 출력하는 WM_PAINT 메시지 부분을 보도록 합시다.

case WM_PAINT :

    hDC = BeginPaint(hWnd, &ps);
    hMemDC
= CreateCompatibleDC(hDC);

메모리에 디바이스 컨텍스트 핸들을 얻는 과정입니다.

    SelectObject(hMemDC, hBitmap);

메모리 디바이스 컨텍스트 핸들로 앞에서 로드한 비트맵 핸들을 취하는 과정입니다.

    GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);

비트맵 핸들을 이용해서 비트맵에 관한 정보를 BITMAP 구조체에 넣는 과정입니다.

    nX = bitmap.bmWidth;
    nY =
bitmap.bmHeight;

비트맵의 크기를 선언한 변수에 저장하고 있습니다. 이게 왜 필요한지는 아래 함수

보면 알수 있을 겁니다.

    BitBlt(hDC, 0, 0, nX, nY, hMemDC, 0, 0,
SRCCOPY);

메모리 디바이스 컨텍스트 핸들을 이용해서 메모리에 출력했던 것을 현 디바이스
컨택스트
핸들을 취하는 현재 화면에 복사하는 과정입니다.

    DeleteDC(hMemDC);

메모리 디바이스 컨텍스트 핸들을 이용한후에는 반드시 해제 해 주어야 한다고 했

겁니다.

    EndPaint(hWnd, &ps);
    return 0;

자 이번에는 그래픽 출력을 빨리 해 준다는 WinG를 이용한 비트맵 그림의 출력에 대해 알아
보겠습니다. 원리는 같죠. 단지 함수만 다를 뿐입니다. 우선 메모리에 디바이스 컨텍스트 핸들을 생성하는 부분과 실제로 화면에 복사하는 함수만
WinG에서 제공하는 함수로 사용하면 됩니다.

HDC WINGAPI WinGCreateDC( void );

위 함수를 이용해서 디바이스 컨텍스트 핸들을 생성하면 됩니다. 파라미터를 요구하지
않는군요.

BOOL WINGAPI WinGBitBlt( HDC hdcDest, int
nXOriginDest,
        int nYOriginDest, int nWidthDest, int nHeightDest, HDC
hdcSrc,
        int nXOriginSrc, int nYOriginSrc );

위 함수를 이용해서 실제 화면에 복사하면 됩니다. 앞에서 알아본 BitBlt()함수와
파라미터의 의미는 같습니다. 단지 BitBlt()함수에 들어갔던 마지막 파라미터가 없다는 차이점은 있습니다.

간단하죠? 그러면 소스를 보도록 합시다.

아래는 리소스 파일입니다.

#include <windows.h>

MyBitmap BITMAP “test.bmp”

프로그램 소스입니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>
#include “wing.h”

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
HBITMAP hBitmap;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        hBitmap = LoadBitmap(hInstance,
“MyBitmap”);

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC hDC,
hMemDC;
        BITMAP bitmap;
        PAINTSTRUCT ps;
        static
int nX, nY;

        switch(message)
        {
            case
WM_PAINT :

                hDC = BeginPaint(hWnd,
&ps);
                hMemDC =
WinGCreateDC();
                SelectObject(hMemDC,
hBitmap);
                GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
                nX = bitmap.bmWidth;
                nY =
bitmap.bmHeight;
                WinGBitBlt(hDC, 0, 0, nX, nY, hMemDC, 0,
0);
                DeleteDC(hMemDC);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

뭐 특별한 부분은 없죠?

#include “wing.h”

헤더 파일에 WinG 라이브러리를 사용하기 위한 헤더 파일을 선언했습니다.

case WM_PAINT :

    hDC = BeginPaint(hWnd, &ps);
    hMemDC
= WinGCreateDC();
    SelectObject(hMemDC,
hBitmap);
    GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
    nX = bitmap.bmWidth;
    nY =
bitmap.bmHeight;
    WinGBitBlt(hDC, 0, 0, nX, nY, hMemDC, 0,
0);
    DeleteDC(hMemDC);
    EndPaint(hWnd, &ps);
    return
0;

실제로 출력하는 부분인데 정말 간단하죠? 이 프로그램을 컴파일 할려면 wing32.lib
파일을 같이 묶어서 컴파일 해 주어야 합니다.

오늘은 여기까지
끝~~~~~~~~~~~~~~~~~~~~

 

  윈도우 32비트 프로그래밍 22

안녕하세요……..돌팔이 황동준입니다…..

이번시간에도 역시 비트맵 그림을 출력하는 예제를 볼건데 이것은 그림의 크기에 관계없이
작업영역에 꽉차게 출력되게 하는 것입니다. 즉 그림크기가 작업영역 크기보다 작으면 확대하고 크면 축소해서 출력하는 것입니다.

역시 같은 방법으로 API 함수를 이용해서 하는 방법과 WinG 그래픽 라이브러리를 이용해서
하는 방법 두 가지로 알아 보겠습니다.

보면 알겠지만 앞에서 그림출력하는 과정을 잘 이해하셨다면 이번 부분은 특별하게 새로 배울
부분은 없을 겁니다. 왜냐하면 함수 하나만 다르거든요. 그림을 축소, 확대해서 복사하는 함수가 있습니다.

그러면 먼저 API 함수를 이용해서 해보도록 합시다. 작업영역의 크기만큼의 그림으로
출력할려면 작업영역의 크기를 얻는 과정이 있어야 합니다. 물론 여러분들은 어떻게 크기를 얻는지 알 수 있을 겁니다. 앞에서 여러번 했으니까요.
그리고 한가지만 더 알면 됩니다. 바로 BitBlt() 함수대신에 StretchBlt()함수를 사용한다는 것입니다.

BOOL StretchBlt(HDC hdcDest, int nXDest, int
nYDest, int nWidthDest,
    int nHeigthDest, HDC hdcSrc, int nXSrc, int
nYSrc, int nWidth,
        int nHeight, DWORD dwRop);

위 함수를 이용해서 축소하거나 확대할 수 있는데 네번째, 다섯번째 파라미터에 유저가 값을
지정해주면 그 크기로 복사가 됩니다. 그리고 BitBlt() 함수에 없던 새로운 파라미터가 있는데 바로 아홉번째와 열번째 파라미터입니다. 이
부분에는 실제그림의 크기를 지정해 주면 됩니다.

자 그러면 예제를 보도록 합시다.

아래는 리소스 파일의 소스입니다.

#include <windows.h>

MyBitmap BITMAP “test.bmp”

프로그램 소스입니다.

#include <windows.h>
#include
<stdlib.h>
#include <string.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
HBITMAP hBitmap;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        hBitmap = LoadBitmap(hInstance,
“MyBitmap”);

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC hDC,
hMemDC;
        BITMAP bitmap;
        PAINTSTRUCT ps;
        RECT
rect;
        static int nX, nY;

        switch(message)
        {
            case
WM_PAINT :

                hDC = BeginPaint(hWnd,
&ps);
                hMemDC =
CreateCompatibleDC(hDC);
                SelectObject(hMemDC,
hBitmap);
                GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
                nX = bitmap.bmWidth;
                nY =
bitmap.bmHeight;
                GetClientRect(hWnd,
&rect);
                StretchBlt(hDC, 0, 0, rect.right, rect.bottom,
hMemDC, 0, 0,
                    nX, nY,
SRCCOPY);
                DeleteDC(hMemDC);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

머리가 좋으신분은 이미 다 이해했을 겁니다. 너무 쉽죠.

case WM_PAINT :

    hDC = BeginPaint(hWnd, &ps);
    hMemDC
= CreateCompatibleDC(hDC);
    SelectObject(hMemDC,
hBitmap);
    GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
    nX = bitmap.bmWidth;
    nY =
bitmap.bmHeight;
    GetClientRect(hWnd, &rect);
    StretchBlt(hDC,
0, 0, rect.right, rect.bottom, hMemDC, 0, 0, nX, nY,
        SRCCOPY);

작업 영역의 크기를 얻은뒤 그 크기만큼 그림을 화면에 출력하는 과정입니다.

    DeleteDC(hMemDC);
    EndPaint(hWnd,
&ps);
    return 0;

별로 설명 드릴부분은 없군요. 이번에는 WinG 그래픽 라이브러리를 이용해서 똑같은 기능을
하는 프로그램을 만들어 봅시다. 이번에도 역시 함수 하나만 알면 됩니다.

BOOL WINGAPI WinGStretchBlt( HDC hdcDest, int
nXOriginDest,
        int nYOriginDest, int nWidthDest, int nHeightDest, HDC
hdcSrc,
        int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int
nHeightSrc );

파라미터의 의미는 StretchBlt() 함수와 같습니다. 단지 마지막 파라미터에 있던 것이
없어졌을 뿐입니다.

#include <windows.h>

MyBitmap BITMAP “test.bmp”

#include <windows.h>
#include
<stdlib.h>
#include <string.h>
#include “wing.h”

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
HBITMAP hBitmap;

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        hBitmap = LoadBitmap(hInstance,
“MyBitmap”);

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        HDC hDC,
hMemDC;
        BITMAP bitmap;
        PAINTSTRUCT ps;
        RECT
rect;
        static int nX, nY;

        switch(message)
        {
            case
WM_PAINT :

                hDC = BeginPaint(hWnd,
&ps);
                hMemDC =
WinGCreateDC();
                SelectObject(hMemDC,
hBitmap);
                GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
                nX = bitmap.bmWidth;
                nY =
bitmap.bmHeight;
                GetClientRect(hWnd,
&rect);
                WinGStretchBlt(hDC, 0, 0, rect.right,
rect.bottom, hMemDC,
                    0, 0, nX,
nY);
                DeleteDC(hMemDC);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

간단하죠?

case WM_PAINT :

    hDC = BeginPaint(hWnd, &ps);
    hMemDC
= WinGCreateDC();
    SelectObject(hMemDC,
hBitmap);
    GetObject(hBitmap, sizeof(BITMAP), (BITMAP
*)&bitmap);
    nX = bitmap.bmWidth;
    nY =
bitmap.bmHeight;
    GetClientRect(hWnd,
&rect);
    WinGStretchBlt(hDC, 0, 0, rect.right, rect.bottom, hMemDC, 0,
0, nX, nY);

작업영역의 크기를 알아 낸뒤 그 크기만큼 그림을 출력하는 과정입니다.

    DeleteDC(hMemDC);
    EndPaint(hWnd,
&ps);
    return 0;

오늘는 여기까지
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ps> 다음 시간에는 DIB에 대해서 알아 보겠습니다. 아마 이제껏 배운 것중 가장
어려운 부분이 될 것 같군요. 물론 앞으로 이것보다 더 어려운 것이 많이 나오지만요. 우리 쉽게
합시다.

 

  윈도우 32비트 프로그래밍 23

안녕하세요…….돌팔이 황동준입니다……………

이번 시간에는 DIB에 대해서 알아 보겠습니다.

이번 강좌는 조금 어려울 겁니다. 이번 강좌를 통해서 여러분들은 부수적으로 파일 입출력과
메모리 할당하는 방법도 같이 배우게 될 겁니다.

자 그러면 먼저 DIB가 무엇인지 알아 봅시다.

윈도우즈에서 사용하는 비트맵 그림은 크게 두가지로 나눌 수 있습니다. 바로 장치 독립적인
비트맵 그림과 장치 의존적인 비트맵 그림입니다.

예를 들어 그림을 그린 후에 256컬러로 저장했다면 그 그림은 장치 독립적인 그림이 됩니다.
그런데 이 그림을 우리가 앞에서 배운 프로그램을 이용해서 화면에 출력하게 되면 어떻게 될까요? 바로 장치 의존적인 비트맵 그림으로 됩니다.

우리는 비트맵 그림을 출력하기 위해서 리소스 파일을 이용했을 겁니다. 리소스 라는 것은
자원입니다. 즉 윈도우즈에서 관리하는 거죠.

이 리소스 파일을 이용해서 그림 파일을 출력하게 되면 장치 독립적인 비트맵 그림이 장치
의존적인 비트맵 그림으로 바뀌는 거죠.

자 그러면 앞에서 배운 비트맵 그림 출력 결과를 생각해 봅시다. 그림은 256컬러인데 출력된
결과가 16컬러 아닙니까? 바로 장치 의존적으로 변해서 그런겁니다.

장치 의존적인 비트맵 그림은 윈도우즈의 컬러수에 의존하게 됩니다. 즉 윈도우즈의 색깔을
가져다가 출력하는 프로그램이 컬러를 이용하게 되면 이 이용한 컬러의 영향때문에 제대로 출력이 되지 않는 것입니다. 윈도우즈 자체의 인터페이스가
그림이기 때문에 더욱 그렇죠.

자 그러면 이제부터 장치 독립적인 비트맵 컬러를 그대로 출력해 봅시다. 그러면 아마 앞에서
만든 프로그램에서 출력했을 때 깨졌던 그림이 제대로 나올겁니다.

먼저 전체적으로 어떻게 돌아가는지 알아 봅시다. 우선 비트맵 파일의 구조를 알아 봅시다.
비트맵 파일은 다음과 같은 구조로 되어 있습니다.

1)파일헤더
2)그림정보헤더
3)팔레트 데이터
4)실제 데이터

비트맵 파일 하나가 위와 같이 구성되어 있다고 이해하면 됩니다. 그리고 2번부터 4번을
실제적인 비트맵 정보라고 합니다. 실제적인 비트맵 정보가 시작되는 곳에 그림정보 헤더가 위치해 옴을 알 수 있습니다.

한번 하나씩 알아봅시다. 아마 다른 윈도우즈 프로그래밍 책에서 이 DIB 부분을 보고 쉽게
이해할 수 있는 분은 없었을 겁니다. 그러나 이번에는 아마 이해할수 있을 겁니다.

먼저 비트맵 그림파일을 열어야 겠죠? 드디어 파일 입출력 부분이 나오는군요. 파일을 열기
위해서는 _lopen()이라는 함수를 사용합니다.

HFILE _lopen(
    LPCSTR  lpszFileName,

    int  fnOpenMode
   );

첫번째 파라미터로 열 파일이름을 지정하고 두번째 파라미터에는 지정된 예약어를 지정하면
됩니다. 아래는 이 부분에 지정될 수 있는 예약어입니다.

OF_READ         읽기 전용을 연다
OF_READWRITE    읽기,
쓰기용으로 연다
OF_WRITE        쓰기 전용으로 연다

파일 여는데 실패하면 -1을 리턴합니다. 파일을 성공적으로 열었으면 읽어야 할 겁니다. 먼저
파일헤더 부분만큼 자료를 읽어야 하는데 파일헤더 크기를 어떻게 알 수 있을까요? 비트맵 파일의 파일헤더의 크기는 어느 그림이나 같습니다. 그리고
더욱 중요한 것은 이 비트맵 파일의 파일 헤더의 크기와 구조가 같은 구조체가 이미 정의되어 있다는 것입니다. 이 구조체 변수를 이용해서 그
구조체의 크기만큼 파일을 읽으면 파일헤더 부분을 뽑아 낼 수 있죠. 파일에서 자료를 읽어 올 때에는 _lread()라는 함수를
사용합니다.

UINT _lread(
    HFILE  hFile,
    LPVOID
 lpBuffer,
    UINT  cbRead
   );   

_lopen()함수를 이용해서 얻은 파일 핸들을 첫번째 파라미터로 지정하면 됩니다. 두번째
파라미터는 읽은 데이터를 저장할 버퍼를 세번째 파라미터는 버퍼의 크기를 지정하면 됩니다.

파일 헤더 부분을 뽑아 내기 위한 구조체의 원형은 다음과 같습니다.

typedef struct tagBITMAPFILEHEADER
{
        WORD    bfType;
        DWORD   bfSize;
        WORD
   bfReserved1;
        WORD    bfReserved2;
        DWORD
  bfOffBits;
} BITMAPFILEHEADER;

여기서 다른 멤버는 알 필요없고 단지 첫번째 멤버인  bfType과 두번째 멤버인
bfSize만 알면 됩니다. bfType은 파일에는 파일의 타입이 저장되고 bfSize에는 파일의 전체 크기가 저장됩니다. 물론 이 구조체
변수를 이용해서 파일을 읽게 되면요.

이 구조체를 이용해서 이 크기만큼의 자료를 뽑아내면 이 구조체에 비트맵 파일에 대한 기본적인
정보가 기억되는데 이중에 파일 타입을 알아보는 bfType값이 0x4d42 이면 이 파일은 비트맵 파일임을 확인할 수 있습니다. 이 멤버값을
가지고 비트맵 파일인지 확인한 후에는 어떤 작업을 해야 할까요? 비트맵 정보를 읽어와야겠죠?
_lread()함수로 자료를 읽어오면 파일
포인터가 그 만큼 이동되어 있기 때문에 바로 읽어오면 됩니다. 여러분들 중에 도스에서 파일 입출력을 해보신 분은 이미 알고 있을 겁니다. 이렇게
합시다. 파일 헤더 부분을 제외한 나머지 부분 전체를 읽어오면 결국은 비트맵 정보부분을 다 읽어 오는 셈이 되겠네요. 나머지 부분을 어떻게 읽어
올까요? 간단합니다.

파일헤더를 읽어 왔을 때 우리는 bfType이라는 멤버를 이용해서 이 파일이 비트맵 파일인지
알아 보았는데 다른 멤버인 bfSize변수에 이 파일의 전체 크기가 저장되어 있습니다. 이 변수값에 비트맵 파일 헤더 부분의 크기를 빼면 바로
파일 헤더 다음부터의 전체 크기가 되겠죠? 그 크기만큼 _lread() 함수를 이용해서 읽어오면 되는 것입니다. 이 읽어온 자료의 주소시작이
바로 그림정보부분이 시작되는 곳이 됩니다. 이 주소를 두가지 변수에 기억시켜 놓아야합니다.

먼저 BITMAPINFO라는 구조체로 선언한 변수에 기억시켜 놓아야 하고
BITMAPINFOHEADER라는 구조체로 선언한 변수에 기억시켜 놓아야 합니다. 전자는 나중에 실제로 그림파일을 출력할 때 필요한 함수의
파라미터로 쓰기 위함이고 후자는 비트맵 그림의 색깔이 몇 컬러인지 알아내고 또 그림정보 헤더 부분 다음에 있는 팔레트 데이터 부분등을 읽기
위해서 사용합니다.

아래는 이 두 구조체의 원형입니다.

typedef struct tagBITMAPINFO
{
   BITMAPINFOHEADER bmiHeader;
   RGBQUAD          bmiColors[1];
}
BITMAPINFO;

두번째 맴버인 RGBQUAD란 자료형은 팔레트 데이처를 의미하는 것입니다. 아래는 이
구조체의 원형입니다.

typedef struct _RGBQUAD {
    BYTE
   rgbBlue;
    BYTE    rgbGreen;
    BYTE    rgbRed;
    BYTE
   rgbReserved;
} RGBQUAD;

멤버 이름만 봐도 어떤 의미를 가지는지 쉽게 짐작할 수 있죠?

typedef struct tagBITMAPINFOHEADER{
   DWORD
 biSize;
   LONG   biWidth;
   LONG   biHeight;
   WORD
  biPlanes;
   WORD   biBitCount
   DWORD  biCompression;
   DWORD
 biSizeImage;
   LONG   biXPelsPerMeter;
   LONG
  biYPelsPerMeter;
   DWORD  biClrUsed;
   DWORD  biClrImportant;
}
BITMAPINFOHEADER;

무지하게 복잡하죠? 몇개의 의미만 알면 됩니다. 첫번째 멤버인 biSize는 그림정보 헤더의
크기를 의미합니다. 즉 바로 이 구조체의 전체크기를 의미하는 셈이죠. 두번째와 세 번째 멤버는 이름에서 알 수 있듯이 그림의 크기를 의미합니다.
 다섯 번째 멤버인 biBitCount는 비트수를 의미하는데 여러분들이 흔히 말하는 256컬러 16컬러 뭐 이런 것들이 들어 갑니다. 단
비트수로 들어가기 때문에 256컬러인 경우에는 8이란 값이 저장되죠. 2의 8승이 256이죠. 일곱번째 멤버인 biSizeImage는 그림전체
크기가 저장됩니다. 마지막 멤버쪽에 있는 biClrUsed는 사용된 컬러수가 저장됩니다. 이 값은 비트로 표시되지 않고 그냥 사용된 색의 수로
저장됩니다.

자 다시 생각해 봅시다. 비트맵 정보부분 전체를 읽은 후에 그 시작주소를
BITMAPINFOHEADER 구조체 변수에 저장한다고 위에서 설명드렸을 겁니다. 비트맵 정보부분은 다시 그림정보 헤더, 팔레트 데이터, 실제
데이터 부분으로 나뉘는데 팔레트 데이터가 시작되는 주소를 RGBQUAD 구조체 변수에 기억시키려면 어떻게 해야 할까요? 현재 비트맵 정보부분
시작 주소를 BITMAPINFOHEADER  크기만큼 이동시키면 바로 그 주소가 팔레트 데이터의 첫 주소가 될겁니다. 한번 잘 생각해
보세요.
그렇죠? 그런 후에 이 팔레트 데이터 부분의 색깔 정보를 가지고 가상 팔레트에 복사한 후에 조작하고 그 결과를 이용해서 팔레트를
생성하고 사용한 후에 그림을 출력하면 됩니다. 출력하고 나서는 그 팔레트를 원래 대로 돌려 주면 됩니다.

팔레트를 생성할 때에는 아래 함수를 이용해서 할 수 있습니다.

HPALETTE CreatePalette(
    CONST LOGPALETTE
 *lplgpl
   );

파라미터는 가상 팔레트 구조체를 의미합니다. 물론 조작이 다 끝난 가상 팔레트죠.

위 함수를 이용해서 팔레트 핸들을 얻으면 이것을 실제로 디바이스 컨텍스트 핸들로 선택해
주어야 하는데 아래 함수를 이용해서 하면 됩니다.

HPALETTE SelectPalette(
    HDC
 hdc,
    HPALETTE  hpal,
    BOOL  bForceBackground
   );

첫번째, 두번쩨 파라미터가 뭔지는 짐작이 가죠? 세번째 파라미터에는 보통 FALSE 를
지정합니다.

이제 진짜로 사용하겠다는 것을 알려주면 됩니다.

UINT RealizePalette(
    HDC  hdc
   );

위 함수를 사용하면 됩니다.

int StretchDIBits(
    HDC  hdc,
    int
 XDest,
    int  YDest,
    int  nDestWidth,
    int  nDestHeight,

    int  XSrc,
    int  YSrc,
    int  nSrcWidth,
    int
 nSrcHeight,
    CONST VOID  *lpBits,
    CONST BITMAPINFO *
 lpBitsInfo,
    UINT  iUsage,
    DWORD  dwRop
   );   

위 함수를 이용해서 화면에 복사할 수 있습니다. 두번째, 세번째 파라미터는 화면에 출력될
시작 좌표를 의미하고 네번째, 다섯번째 파라미터는 화면에 찍힐 크기를 의미합니다. 여섯번째, 일곱번째, 여덟번째, 아홉번째 파라미터의 의미는
그림의 시작점과 크기를 의미하고 열번째 파라미터인  lpBits는 비트맵 그림 파일의 실제 데이터 부분의 시작주소를 지정하면 되고 열한번째
파라미터인 lpBitsInfo는 비트맵 정보의 시작주소를 지정해 주면 됩니다. 열두번째 파라미터인 iUsage에는 DIB_RGB_COLORS라는
예약어를 지정하면 되고 마지막 파라미터에는 앞에서 배운 BitBlt()함수의 마지막 파라미터와 같은 예약어를 지정해 주면 됩니다. 자
어떻습니까? 조금 복잡하죠.

그러면 이것을 구현한 실제 소스를 보도록 합시다.

#include <windows.h>
#include
<windowsx.h>
#include <string.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,
LPARAM);
BOOL PrintDIBFormat(HDC hDC, char *szBitmapFile);

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Graphic Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

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

        switch(message)
        {
            case
WM_PAINT :

                hDC = BeginPaint(hWnd,
&ps);
                PrintDIBFormat(hDC,
“test.bmp”);
                EndPaint(hWnd,
&ps);
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

BOOL PrintDIBFormat(HDC hDC, char
*szBitmapFile)
{
    HFILE hFile;
    HPALETTE hPalette,
hOldPalette;
    BITMAPFILEHEADER BitmapFileHeader;
    BITMAPINFO
*BitmapInfo;
    BITMAPINFOHEADER *BitmapInfoHeader;
    RGBQUAD
*RgbQuad;
    char *szBits, *Dib;
    int nColorNumber;
    int
nRealSize;
    DWORD dwTableSize;
    struct
tagLOGICALPALETTE
    {
        WORD version;
        WORD
NumberOfEntries;
        PALETTEENTRY PaletteEntry[256];
    }
LOGICALPALETTE;

    static int n;

    if(-1 != (hFile = _lopen(szBitmapFile,
OF_READ)))
        _lread(hFile, &BitmapFileHeader,
sizeof(BITMAPFILEHEADER));
    else
        return
FALSE;
    if(BitmapFileHeader.bfType != 0x4d42)
        return
FALSE;
    else
    {
        nRealSize =
BitmapFileHeader.bfSize-sizeof(BITMAPFILEHEADER);
        Dib = (char
*)GlobalAllocPtr(GMEM_MOVEABLE, nRealSize);
        _lread(hFile, Dib,
nRealSize);
        _lclose(hFile);
        BitmapInfo = (BITMAPINFO
*)Dib;
        BitmapInfoHeader = (BITMAPINFOHEADER *)Dib;
        RgbQuad
= (RGBQUAD
*)(Dib+BitmapInfoHeader->biSize);
        if((BitmapInfoHeader->biClrUsed
== 0) &&
            (BitmapInfoHeader->biBitCount <=
8))
            nColorNumber = (1 <<
BitmapInfoHeader->biBitCount);
        else

            nColorNumber =
BitmapInfoHeader->biClrUsed;

        if(BitmapInfoHeader->biSizeImage ==
0)
        {
            BitmapInfoHeader->biSizeImage
=
                BitmapInfoHeader->biWidth*BitmapInfoHeader->biHeight;
        }
        dwTableSize
= nColorNumber*sizeof(RGBQUAD);
        szBits =
Dib+BitmapInfoHeader->biSize+dwTableSize;
        LOGICALPALETTE.version =
0x300;
        LOGICALPALETTE.NumberOfEntries = 256;
        for(n=0;
n<nColorNumber;
n++)
        {
            LOGICALPALETTE.PaletteEntry[n].peRed =
RgbQuad[n].rgbRed;
            LOGICALPALETTE.PaletteEntry[n].peGreen =
RgbQuad[n].rgbGreen;
            LOGICALPALETTE.PaletteEntry[n].peBlue =
RgbQuad[n].rgbBlue;
            LOGICALPALETTE.PaletteEntry[n].peFlags =
0;
        }
        hPalette =
CreatePalette((LPLOGPALETTE)&LOGICALPALETTE);
        hO

ldPalette = SelectPalette(hDC, hPalette,
FALSE);
        RealizePalette(hDC);
        StretchDIBits(hDC, 0, 0,
BitmapInfoHeader->biWidth,
            BitmapInfoHeader->biHeight, 0,
0,
BitmapInfoHeader->biWidth,
                BitmapInfoHeader->biHeight,
szBits, BitmapInfo,
                    DIB_RGB_COLORS,
SRCCOPY);
        SelectPalette(hDC, hOldPalette,
FALSE);
        DeleteObject(hPalette);
        GlobalFreePtr(Dib);
    }
    return
TRUE;
}

자 오늘은 여기까지 끝~~~~~~~~~~~~~~~~~~~~~~

다음시간에는 위 소스를 분석해 보도록 하겠습니다.

그전까지 각자 분석해 보시기 바랍니다.

 

  윈도우 32비트 프로그래밍 24

안녕하세요………..돌팔이 황동준입니다……..

이번시간에는 저번시간에 알아 본 프로그램을 분석해 보겠습니다.

이 프로그램은 리소스를 이용해서 비트맵 그림을 출력하는 것이 아니기  때문에 리소스 파일에서
비트맵 그림을 정의 해줄 필요는 없습니다. 아마 봐서 알겠지만 그러한 부분이 없었을 겁니다.

그러면 먼저 윈도우가 처음생성될 때 그리는 부분인 WM_PAINT 메시지 처리 부분을 보도록
합시다.

case WM_PAINT :

        hDC = BeginPaint(hWnd,
&ps);
        PrintDIBFormat(hDC, “test.bmp”);
        EndPaint(hWnd,
&ps);
        return 0;

못보던 함수가 하나 있군요. 이것은 장치 독립적인  비트맵 그림을 출력하기 위해서 필자가
만든 함수입니다. 이 함수 안의 루틴만 이해하면 되겠죠?

BOOL PrintDIBFormat(HDC hDC, char
*szBitmapFile)
{
    HFILE hFile;

파일을 다루기 위해서 파일 핸들을 선언해준 것입니다.

    HPALETTE hPalette, hOldPalette;

파레트를 조작하고 다시 원래 팔레트로 돌아가기 위해서 위 변수를 선언했습니다.

    BITMAPFILEHEADER
BitmapFileHeader;
    BITMAPINFO *BitmapInfo;
    BITMAPINFOHEADER
*BitmapInfoHeader;
    RGBQUAD *RgbQuad;

위 4개의 구조체에 대한 것은 이미 앞에서 설명을 드렸습니다.

    char *szBits, *Dib;
    int
nColorNumber;
    int nRealSize;
    DWORD dwTableSize;
    struct
tagLOGICALPALETTE
    {
        WORD version;
        WORD
NumberOfEntries;
        PALETTEENTRY PaletteEntry[256];
    }
LOGICALPALETTE;

우리가 앞에서 알아본 CreatePalette()함수의 파라미터에 LOGPALETTE라는
구조체의 주소가 지정되어야 하는데 이 구조체와 같은 구조의 구조체를 직접 만든 것입니다. 실제로 도움말을 이용해서 이 구조체의 원형과 위
구조체의 원형을 비교해 보시기 바랍니다.

    static int n;

    if(-1 != (hFile = _lopen(szBitmapFile,
OF_READ)))
        _lread(hFile, &BitmapFileHeader,
sizeof(BITMAPFILEHEADER));
    else
        return FALSE;

비트맵 파일을 열어서 파일 헤더 부분을 읽는 구문입니다.

    if(BitmapFileHeader.bfType !=
0x4d42)
        return FALSE;

이 파일이 비트맵 파일인지 조사하는 구문입니다.

    else
    {

이 안의 루틴은 이 파일이 비트맵 파일일 때 수행되는 부분이겠죠?

        nRealSize =
BitmapFileHeader.bfSize-sizeof(BITMAPFILEHEADER);

파일 전체 크기에서 파일헤더 부분을 빼서 파일 헤더 부분을 제외한 크기를  얻고 있는
구문입니다.

        Dib = (char *)GlobalAllocPtr(GMEM_MOVEABLE,
nRealSize);

위에서 얻은 크기만큼 메모리를 할당하는 구문입니다. 우리가 도스에서 메모리를 할당한
malloc()함수의 사용방법과 같죠? 단 첫 번째 파라미터에  들어간 GMEM_MOVEABLE은 동적으로 할당하겠다는 의미입니다.

        _lread(hFile, Dib,
nRealSize);
        _lclose(hFile);

파일헤더 부분을 제외한 나머지 부분을 읽어 그 시작주소를 Dib에 저장하고 있습니다. 그리고
연 파일을 닫는군요.

        BitmapInfo = (BITMAPINFO
*)Dib;
        BitmapInfoHeader = (BITMAPINFOHEADER *)Dib;

앞에서 이 시작주소를 두가지 변수에 기억 시켜 놓아야 한다고 했을 겁니다.

        RgbQuad = (RGBQUAD
*)(Dib+BitmapInfoHeader->biSize);

나머지 부분 즉 비트맵 정보의 시작 주소가 Dib인데 이 부분에서 포인터를 그림정보 헤더
부분만큼 이동하면 그 시작 주소가 뭐가 됩니까? 바로 팔레트 데이터의 시작 주소가 되겠네요? 그렇죠? 그 주소를 팔레트 데이터 변수에 저장하고
있는 구문입니다.

        if((BitmapInfoHeader->biClrUsed == 0)
&&
            (BitmapInfoHeader->biBitCount <=
8))
            nColorNumber = (1 <<
BitmapInfoHeader->biBitCount);
        else
            nColorNumber =
BitmapInfoHeader->biClrUsed;

우리가 출력할 그림의 컬러수를 구하는 구문인데 쉬프트 연산이 있군요. 이것의 의미는 만약에
BitmapInfoHeader->biBitCount값이 8이면 2의 8승을 구한다는 것입니다. 왜 그런지 한번 생각해 보세요.

        if(BitmapInfoHeader->biSizeImage ==
0)
        {
            BitmapInfoHeader->biSizeImage
=
                BitmapInfoHeader->biWidth*BitmapInfoHeader->biHeight;
        }

그림의 크기가 0으로 되어 있으면 그림의 가로 크기와 세로 크기를 곱해서 크기를 구하는
구문입니다.

        dwTableSize =
nColorNumber*sizeof(RGBQUAD);

하나의 컬러는 하나의 팔레트 정보를 갖고 있으므로 이 전체 크기를 알기 위해서는 컬러수와 이
팔레트 데이터 변수의 크기를 곱해 주어야 그 실제 크기를 알수 있습니다.

        szBits =
Dib+BitmapInfoHeader->biSize+dwTableSize;

자 위와 같은 포인터 연산의 결과로 szBis는 어떤 정보의 주소를 가지게 됩니까? 바로
팔레트 데이터의 다음에 위치한 실제 데이터의 시작주소를 갖게 됩니다.

        LOGICALPALETTE.version =
0x300;
        LOGICALPALETTE.NumberOfEntries = 256;
        for(n=0;
n<nColorNumber;
n++)
        {
            LOGICALPALETTE.PaletteEntry[n].peRed =
RgbQuad[n].rgbRed;
            LOGICALPALETTE.PaletteEntry[n].peGreen =
RgbQuad[n].rgbGreen;
            LOGICALPALETTE.PaletteEntry[n].peBlue =
RgbQuad[n].rgbBlue;
            LOGICALPALETTE.PaletteEntry[n].peFlags =
0;
        }

현재 팔레트 데이터의 정보를 가상 팔레트에 저장하는 구문입니다.

        hPalette =
CreatePalette((LPLOGPALETTE)&LOGICALPALETTE);

가상 팔레트를 이용해서 팔레트를 생성하고 있습니다.

        hOldPalette = SelectPalette(hDC, hPalette,
FALSE);
        RealizePalette(hDC);

팔레트를 선택하고 실제로 사용하겠다는 것을 알려  주고 있습니다.
SelectPalette()함수의 리턴값은 선택하기 이전의 팔레트입니다.

        StretchDIBits(hDC, 0, 0,
BitmapInfoHeader->biWidth,
            BitmapInfoHeader->biHeight, 0,
0,
BitmapInfoHeader->biWidth,
                BitmapInfoHeader->biHeight,
szBits, BitmapInfo,
                    DIB_RGB_COLORS, SRCCOPY);

화면에 출력하고 있습니다. 이 함수는 위에서 설명드렸을 겁니다.

        SelectPalette(hDC, hOldPalette, FALSE);

출력이 끝난후에 원래 팔레트로 다시 맞춰주고 있습니다.

        DeleteObject(hPalette);
        GlobalFreePtr(Dib);

사용한 팔레트 핸들을 반환하고 할당한 메모리를 해제하는 구문입니다.

    }
    return TRUE;
}

어때요? 이제껏 알아본 것 중에 그래도 가장 복잡한 구조를 띄죠? 잘 이해가 되지 않으면
다시 한번 천천히 생각하면서 읽어 보시기 바랍니다.

 

  윈도우 32비트 프로그래밍 25

안녕하세요….돌팔이 황동준입니다……….

이번 시간부터는 멀티미디어에 대해 알아 보도록 하겠습니다. 아마 여러분들도 상당히 흥미있는
부분이 될 것입니다. 먼저 확장자가 wav인 파일을 어떻게 프로그램 상에서 플레이할 수 있는지 부터 알아 봅시다. 확장자가 wav인 파일은
윈도우즈에서 쉽게 녹음기를 이용해서 만들 수 있고 매체재생기를 이용해서 쉽게 듣을 수도 있었을겁니다. 이것을 프로그램상으로 구현하려면 쉬울 것
같습니까?

윈도우즈 프로그래밍에서는 보기보다 간단하게 이것을 구현할 수 있습니다. 자 그러면 간단하게
한번 구현해 봅시다.

먼저 함수 하나를 알아 보죠. 이 함수 하나로 간단하게 wav파일을 들을수 있습니다.

BOOL sndPlaySound(
    LPCTSTR
 lpszSoundName,
    UINT  fuOptions
   );

첫번째 파라미터는 wav파일을 지정해 주면 되고 두번째 파라미터는 디음과 같은 예약어를
지정해 주면 됩니다.

SND_SYNC                비동기적으로 플레이 해줍니다.

SND_ASYNC               동기적으로 플레이 해줍니다.
SND_LOOP                반복적으로 플레이
해줍니자.

아주 간단하죠? 위 함수 하나만 이용해서 쉽게 구현이 되는 것입니다. 그러면 실제로 구현한
프로그램을 보도록 합시다. 아래 프로그램은 Enter키가 눌릴때마다 wav 파일을 반복적으로 플레이하고 플레이하지 않는 기능을 하고 있습니다.
 

#include <windows.h>
#include
<string.h>
#include <mmsystem.h>

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

int WINAPI WinMain
(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR lpszArg, int nCmdShow)
{
        static
char szAppName[] = “Multimedia Example”;
        HWND hWnd;
        MSG
msg;
        WNDCLASS WndClass;

        WndClass.style =
CS_HREDRAW|CS_VREDRAW;
        WndClass.lpfnWndProc =
WndProc;
        WndClass.cbClsExtra = 0;
        WndClass.cbWndExtra =
0;
        WndClass.hInstance = hInstance;
        WndClass.hIcon =
LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor = LoadCursor(NULL,
IDC_ARROW);
        WndClass.hbrBackground =
GetStockObject(WHITE_BRUSH);
        WndClass.lpszMenuName =
NULL;
        WndClass.lpszClassName =
szAppName;
        if(!RegisterClass(&WndClass))
                return
FALSE;

        hWnd =
CreateWindow(
                szAppName,
                szAppName,
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                CW_USEDEFAULT,
                NULL,
                NULL,
                hInstance,
                NULL
        );

        ShowWindow(hWnd,
nCmdShow);
        UpdateWindow(hWnd);

        while(GetMessage(&msg, NULL, 0,
0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        return msg.wParam;
}

LRESULT CALLBACK
WndProc(HWND hWnd, UINT
message, WPARAM wParam, LPARAM lParam)
{
        static BOOL bCheck;

        switch(message)
        {
            case
WM_KEYDOWN :

                if(LOWORD(wParam) ==
VK_RETURN)
                {
                    if(!bCheck)
                        sndPlaySound(“d:test.wav”,
SND_ASYNC|SND_LOOP);
                    else
                        sndPlaySound(NULL,
0);
                    bCheck =
!bCheck;
                }
                return 0;

            case WM_DESTROY :

                PostQuitMessage(0);
                return
0;
        }
        return DefWindowProc(hWnd, message, wParam,
lParam);
}

자 그러면 프로그램 소스를 자세히 보도록 합시다. 먼저 헤더 파일을 보게되면 못보던 것이
있을 겁니다.

#include <mmsystem.h>

바로 위와 같이 선언되어 있는데 이 헤더 파일은 멀티미디어를 구현하기 위한 함수들이 정의되어
있습니다. 그러기 때문에 위 헤더 파일을 꼭 선언 해 주어야 합니다.

Enter키가 눌릴 때마다 플레이되고 플레이되지 않는다고 했으니 그 부분을 보도록
합시다.

case WM_KEYDOWN :

    if(LOWORD(wParam) == VK_RETURN)

Enter키가 눌렸는지 확인하는 구문이죠? Enter키의 토글을 체크하기 위해서 아래
bCheck라는 변수를 사용한 것입니다.

    {
        if(!bCheck)
            sndPlaySound(“d:test.wav”,
SND_ASYNC|SND_LOOP);

계속해서 wav파일을 연주하는 구문이 되겠네요. 만약에 위 두번째 파라미터 대신에
SND_SYNC를 지정하고 사용했다면 어떤 일이 발생할까요? 이것은 비동기적이므로  플레이가 다 될 때까지 다음 루틴이 실행되지 않게 될
겁니다. 차이점을 잘 알두시기 바랍니다.

        else
            sndPlaySound(NULL,
0);

첫번째 파라미터를 NULL로 지정하고 두번째 파라미터를 0으로 지정하면 연주가
중단됩니다.

        bCheck = !bCheck;
    }
    return
0;

이 프로그램을 컴파일하려면 lib디랙토리내에 있는 winmm.lib 파일을 프로젝트 파일에
추가한 뒤에 해주어야 합니다.

자 이번에는 다른 방법을 사용해서 구현해 봅시다. 이 방법으로 구현하면 플레이도중에
Pause시킬 수도 있고 다시 Resume시킬 수도 있습니다. 물론 중단 시키는 기능도 있습니다.

이 방법을 알아 보기 위해서는 먼저 MCI 디바이스에 대해 알아야 합니다. MCI라는 것은
간단하게 말해서 어떤 인터페이스 카드를 컴퓨터가 인식하게끔 하는 디바이스입니다. 우리는 이 MCI 디바이스를 이용해서 각종 멀티미디어 파일을
플레이할텐데 이 MCI 디바이스에 대한 것은 자신의 윈도우즈 디렉토리내의 system.ini 파일에 정의 되어 있습니다. 한번
찾아보세요.

[mci]
waveaudio=mciwave.drv
sequencer=mciseq.drv
cdaudio=mcicda.drv
avivideo=mciavi.drv
videodisc=mcipionr.drv
vcr=mcivisca.drv

필자같은 경우에는 위와 같이 되어 있군요. 어떤 문자열이 특수한 디바이스 파일로정의 되어
있죠. 우리는 이제 이 앞에 정의된 문자열을 이용해서 각 드라이버를 제어할 것입니다. 확장자가 drv인 파일이 바로 디바이스 드라이브
파일입니다.

이제 부터 우리가 알아볼 함수로 장치 이름을 지정하여 그 핸들(아이디)을 얻어 각종 파일들을
플레이할텐데 이 장치이름에 위에 정의된 waveaudio,
sequencer, cdaudio등을 지정하면 됩니다. waveaudio라는
것은 wav 파일을 의미하고 sequencer는 midi 파일, cdaudio는 cd 오디오를 의미합니다.

자 그러면 구체적으로 어떤 함수를 이용해서 이러한 것들을 구현할수 있는지 알아봅시다. 우리가
알아볼 것은 웨이브 파일에 대한 것이니 이 파일에 대해 먼저 알아 봅시다. 먼저 MCI 드바이스를 사용하기 위해서는 어떤 파일을 어떤 디바이스
드라이버로 열 것인지 지정해 주어야 합니다. 그래서 성공적으로 열리면 이 파일에 대한 아이디를 얻을 수 있는데 앞으로 이 아이디를 이용해서
플레이하거나 중지하거나 할 수 있습니다.

MCIERROR mciSendCommand(
    MCIDEVICEID
 IDDevice,
    UINT  uMsg,
    DWORD  fdwCommand,
    DWORD
 dwParam
   );

앞으로 우리는 이 함수를 많이 사용할 것입니다. 위 함수의 두번째 파라미터에 어떤 예약어를
지정함에 따라 파일을 열 수도 있고 닫을 수도 있으며 플레이할 수도 있습니다.

먼저 첫번째 파라미터에는 MCI 장치의 아이디를 지정하면 되는데 만약에 처음에 여
는과정이라면 장치 아이디가 없기 때문에 NULL을 지정하면 됩니다. 그리고 여는 과정에서 얻어진 아이디를 이용해서 다른 작업을 할 때에는 이
얻어진 아이디를 첫번째 파라미터에 지정해 주면 됩니다.

두번째 파라미터에 지정해주는 예약어에 따라 어떤 작업을 할 것인지 구분된다고 했는데 이
두번째 파라미터로 올 수 있는 예약어는 다음과 같습니다.

MCI_OPEN        MCI 장치를 엽니다.
MCI_CLOSE       MCI
장치를 닫습니다.
MCI_PLAY        플레이합니다.
MCI_PAUSE       잠시 중단합니다.
MCI_RESUME
     다시 재생합니다.
MCI_STOP        중단합니다.

세번째 파라미터는 두번째 파라미터에 어떤 것이 지정되느냐에 따라 그 의미가 달라지므로 이
부분은 다시 아래에 가서 설명드리겠습니다. 그리고 마지막 파라미터는 어떠한 정보로 구성되어 있는 구조체의 주소가 오는데 이 구조체도 두번째
파라미터에 따라서 달라지니 이것도 아래 부분에서 다시 설명드리겠습니다.

다시 원래대로 돌아와서 MCI 장치를 어떻게 여는지 봅시다.

MCI 장치를 열 때 위 함수의 첫번째 파라미터는 NULL을 지정하면 됩니다. 그리고 두번째
파라미터는 MCI_OPEN을 지정하면 되구요. 그리고 세번째 파라미터에는 MCI_OPEN_TYPE|MCI_OPEN_ELEMENT을 지정하면
됩니다. 여기서 MCI_OPEN_TYPE이라는 것은 마지막 파라미터로 오는 구조체에 지정된 MCI 장치를 사용하겠다는 것을 의미합니다.
MCI_OPEN_ELEMENT는 마지막 파라미터로 오는 구조체에 지정된 파일을 사용하겠다는 것을 의미합니다.

자 위의 설명을 잘 생각해 봅시다. 마지막 파라미터에 오는 구조체에 지정된 정보를
사용하겠다는 의미죠? 그러면 이 부분에 오는 구조체는 어떤 구조체일까요?

typedef struct {
    DWORD
 dwCallback;
    MCIDEVICEID  wDeviceID;
    LPCTSTR
 lpstrDeviceType;
    LPCTSTR  lpstrElementName;
    LPCTSTR
 lpstrAlias;
} MCI_OPEN_PARMS;

바로 위 구조체가 오게됩니다. 위 구조체 변수를 선언한 다음 파라미터중
lpstrDeviceType에 우리가 앞에서 알아본 웨이브 파일인 waveaudio을 지정해
주고 lpstrElementName에 플레이할
파일이름을 지정해 준 후에 이 구조체를 mciSendCommand()함수의 마지막 파라미터에  지정해 주면 됩니다. 조금 복잡한거 같지만 사실은
간단합니다. 이 함수가 성공적으로 수행되면 위 구조체 멤버중 dwDeviceID에 이 장치의 아이디가 저장됩니다. 앞으로 이 아이디를 다른데서
이용할테니 어떤 변수에 저장해 주어야 겠네요.

MCI 장치를 여는 방법을 알았으니 닫는 방법도 알아 봅시다. MCI장치를 닫을 때에는
mciSendCommand()함수의 첫번째 파라미터에 장치의 아이디를 지정하고 두번째 파라미터에 MCI_CLOSE를 지정하면 됩니다. 세번째
파라미터에는 MCL_WAIT를 지정하면 되고 마지막 파라미터에는 아래 구조체의 주소를 지정하면 됩니다. 뭐 특별히 구조체 멤버에 어떤 값을
지정한 후에 지정할 필요는 없습니다.

typedef struct {
    DWORD  dwCallback;
}
MCI_GENERIC_PARMS;

자 이번에는 플레이하는 방법을 알아 봅시다. 역시 이것도 mciSendCommand()
함수를 이용하면 됩니다. 역시 첫번째 파라미터에 장치의 아이디를 지정하고 두번째 파라미터에 MCI_PLAY를 지정하면 됩니다. 세번째
파라미터에는 MCI_NOTIFY 를 지정하는데 이것을 지정하게 되면 연주가 끝났을 때 메인 윈도우에 MM_MCINOTIFY라는 메시지를 전달해
줍니다. 마지막 파라미터에는 아래의 구조체 변수를 지정해주어야 하는데 구조체의 멤버중 dwCallback에 윈도우의 핸들을 넣어준 뒤
mciSendCommand()  함수에 넣어주어야 합니다.

typedef struct {
    DWORD
 dwCallback;
    DWORD  dwFrom;
    DWORD  dwTo;
} MCI_PLAY_PARMS;

그러면 플레이되고 있는 것을 잠시 중단하려면 어떻게 할까요. 더 간단합니다.

mciSendCommand() 함수의 첫번째 파라미터에 장치 아이디를 지정하고 두번째
파라미터에 MCI_PAUSE를 지정하면 됩니다. 세번째 파라미터에 MCI_WAIT를 지정하고 마지막 파라미터에 앞에서 알아본
MCI_GENERIC_PARMS 구조체로 선언한 변수의 주소를 지정하면 됩니다.

다시 플레이하려면 두번째 파라미터에 MCI_RESUME를 지정해서 사용하면 됩니다. 아예
중단을 하려면 MCI_STOP을 지정해서 사용하면 되구요. 어때요? 좀 복잡해보입니까? 알고 보면 그렇게 복잡하지도 않습니다. 물론 처음에
알아본 함수만을 이용해서 하는 방법보다는 복잡하죠.

다 그러면 실제로 이러한 함수를 가지고 구현한 프로그램을 보도록 합시다. 아차! 한가지
설명을 빠뜨렸군요. 그것은 플레이되는 위치를 지정하는 것인데 연주가 끝나면 플레이되는 지정을 처음으로 다시 옮길 필요가 있을 겁니다. 이때
다음과 같이  mciSendCommand()함수를 이용하면 됩니다.

먼저 첫번째 파라미터에 장치의 아이디를 지정하고 두번째 파라미터에
MCI_SEEK_TO_START를 지정하면됩니다. 이 두번째 파라미터의 의미가 바로 처음으로 이동한다는 의미입니다.

그리고 마지막 파라미터에 아래와 같은 구조체 변수의 주소를 지정하면 되는데 특별한 곳으로
이동하는 것이 아니기 때문에 초기에 어떤 값을 넣어서 지정할 필요는 없습니다. 만약에 특정한 곳으로 이동하려면 이 구조체의 맴버중 dwTo에
이동할 곳을 지정해 주면 됩니다.

typedef struct {
    DWORD
 dwCallback;
    DWORD  dwTo;
} MCI_SEEK_PARMS;

오늘은 여기까지
끝~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

zemna

Programmer/Web/Mobile/Desktop

You may also like...

Leave a Reply