API-DDB와 DIB(비트맵)
- 프로그래밍/WindowsAPI
- 2011. 5. 17. 13:51
비트맵(Bitmap)이란 이미지를 저장하고 있는 그래픽 오브젝트이다.
미리 그려진 그림의 각 픽셀 색상과 기타 이미지의 크기, 해상도 등의 정보를 가지고 있는 이미지 데이터의 덩어리라고 할 수 있다. LineTo, Ellipse 등의 작도 함수로는 도저히 그릴 수 없는 복잡한 그림도 비트맵을 이용하면 간단하게 표현할 수 있으며 복잡한 계산없이 미리 그려져 있는 그림을 단순히 복사하는 것이기 때문에 출력 속도도 무척이나 빠르다. 하지만 데이터 양이 많기 때문에 실행 파일이 커지면 메모리를 많이 소모한다는 단점이 있기도 하다.
윈도우즈에서 비트맵은 여러 가지 용도로 사용된다. 화려한 인터페이스를 디자인하고자 할 때 비트맵은 장식을 위한 휼륭한 도구가 된다. 뿐만 아니라 비트맵은 프로그램 내부적인 화면 처리에도 종종 사용되며 깔끔하고 빠른 출력을 위해서도 사용된다. 브러시나 커스텀 컨트롤을 만들 때도 비트맵이 필요하고 비트맵 메뉴나 각종 오너 드로우 컨트롤 제작에도 활용된다.
윈도우즈가 지원하는 비트맵 포맷은 두 가지 종류가 있다.
첫째는 3.0 이전 버전에 사용하던 DDB(Device Dependent Bitmap) 인데 이 비트맵은 출력 장치에 많이 의존되며 몇 가지 제한이 있다. DDB는 이미지의 크기와 색상에 관한 기본적인 정보와 그리고 이미지 데이터만으로 구성되어 있기 때문에 다양한 해상도의 장치에 광범위하게 사용되지 못하며 만들어진 장치 외의 다른 장치에서 제대로 출력되지 못하는 경우가 있다. 예를 들어 256색으로 만들어진 DDB는 다른 장치나 다른 화면 모드에서 색상을 제대로 표현하지 못한다. 쉽게 말해 흑백 비트맵은 흑백 장치로만 출력할 수 있고 컬러 비트맵은 컬러 장치에만 출력할 수 있다.
윈도우즈가 지원하는 또 다른 비트맵 포맷은 DIB(Device Independent Bitmap)이다. OS/2 2.0의 프리젠테이션 메니저에서 처음 소개된 비트맵 포맷이며 윈도우즈는 3.0버전부터 이 포맷을 지원하기 시작했다. 이름이 의미하는 바대로 이 포맷은 장치에 독립적이기 때문에 어디에서나 제 모양대로 출력될 수 있다. DIB는 DDB에 비해 색상 테이블과 해상도 정보 등의 추가 정보를 가지므로 장치에 종속되지 않으며 활용 용도가 훨씬 더 광범위하고 호환성이 뛰어나다. 컬러 비트맵을 흑백 프린터로 출력할 수도 있고 색상 수가 조금 틀린 장치로도 출력할 수 있다. 확장자 BMP를 가지는 비트맵 파일들은 모두 DIB 포맷으로 저장된 파일이며 리소스 에디터에서 만드는 비트맵들도 모두 DIB이다.
DIB가 DDB보다는 훨씬 더 최신의 포맷이며 다양한 기능을 가지고 있기 때문에 Win32 환경에서 비트맵을 사용하려면 당연히 DDB보다는 DIB를 사용하는 것이 훨씬 더 유리할 것이다. 그러나 다음 몇 가지 이유로 DDB는 여전히 그 존재 가치를 가지고 있으며 장래에도 DIB와 마찬가지로 많이 사용될 전망이다.
(1) 하위 호환성을 위해 Win32는 여전히 DDB를 지원한다. 최근까지도 DDB는 많이 사용되고 있기 때문에 마이크로소프트는 이 포맷을 계속 지원할 것이다.
(2) DC에 선택될 수 있는 비트맵은 DDB뿐이다. DC의 색상 포맷이 고정되어 있으므로 DIB는 직접 DC에 선택될 수 없으며 일단 DDB로 변환해야 출력할 수 있다.
(3) 프로그램 내부에서 만들어지고 파괴되는 비트맵은 DDB가 훨씬 더 효율적이다. DIB는 많은 기능을 가지고 있지만 그만큼 복잡하기 때문에 내부적이고 임시적인 사용에는 적합하지 않다.
그래픽 파일을 직접 다루는 프로그램이 아닌 경우 내부적인 비트맵 처리에는 오히려 DDB가 더 많이 사용되며 사용하기도 쉽다. 그래서 여러분들은 DIB는 물론 DDB도 같이 알아야 하며 두 포맷의 차이점과 상호 변환방법에 대해서도 잘 알고 있어야 한다.
등등의 함수를 사용함
2. DDB의 구조
Win32에서 HBITMAP으로 지칭되는 비트맵 오브젝트는 DDB를 말하며 DDB만이 DC에 선택될 수 있다.
비록 리소스 에디터에 의해 만들어지는 비트맵 리소스들은 모두 DIB이지만 이 리소스는 LoadBitmap 함수에 의해 읽혀지면서 현재 비디오 모드와 호환되는 DDB로 변경된다. DDB 포맷은 다음과 같은 구조체 하나로 표현된다. 이 구조체 자체가 DDB 포맷인데 DIB에 비한다면 정말 간단한 구조를 가지고 있다.
typedef struct tagBITMAP // bm
{
LONG bmType;
LONG bmWidth;
LONG bmHeight;
LONG bmWidthBytes;
WORD bmPlanes;
WORD bmBitsPixel;
LPVOID bmBits;} BITMAP;
bmType 멤버는 비트맵의 타입을 지정하되 0으로 고정되어 있다. bmWidth, bmHeight는 이름 그대로 비트맵의 폭과 높이를 픽셀 단위로 지정한다. bmWidthBytes는 한 줄(Scan line)의 바이트 수, 즉 한 행의 이미지 정보를 저장하기 위해 몇 바이트가 필요한지를 나타내는데 비트맵은 WORD단위로 정렬되기 때문에 이 값은 반드시 짝수여야만 한다. bmPlanes는 색상면의 수를, bmBitsPixel은 한 픽셀을 표현하기 위해 필요한 비트수이다. 이 두 멤버에 의해 비트맵의 색상 수가 결정된다. bmPlanes는 보통 1이며 bmBitsPixel이 1이면 흑백 비트맵, 4면 16색, 8이면 256색 비트맵이 되고 24면 트루컬러 비트맵이 된다.
마지막으로 bmBits멤버는 비트맵의 실제 데이터, 즉 비트맵의 이미지 모양을 가지는 래스터 데이터(Raster Data)에 대한 포인터이다. 이 메모리에 기억된 데이터가 어떠한가에 따라 비트맵의 실제 모양, 즉 그림이 달라진다. LoadBitmap으로 읽은 비트맵 핸들을 가지고 있으면 GetObject 함수로 이 비트맵의 구조를 조사할 수 있다.
비트맵의 구조를 연구하다 보면 색상면(Plane)이라는 용어를 종종 접할 수 있는데 색상면은 표준 VGA의 비디오 메모리 구조에서 유래되었다. VGA는 640*480해상도에서 16색상을 출력할 수 있는데 이때 필요한 메모리가 비디오 램으로 할당된 영역을 초과하기 때문에 메모리를 선형 배치하지 못하고 입체적으로 겹쳐서 배치하게 되었다. 이때 각각의 겹쳐진 메모리를 플레인이라고 하고 이 개수에 따라 한 픽셀의 색상수가 결정된다. 예를 들어 플레인이 4겹으로 겹쳐 있으면 16색상을 표현할 수 있고 8겹이면 256색상까지 표현할 수 있다. 그러나 요즘 발표되는 그래픽 카드는 이런 불합리한 플레인 구조를 전혀 사용하지 않는다. 즉, 플레인이란 어떤 장점이 있어서 도입되었다기보다는 하드웨어적인 한계를 극복하기 위한 일종의 임시방편이었으나 현재까지 여러 군데에 그 잔재가 남아 있는 것이다. |
3. DDB의 생성
그럼 간단하게 DDB 비트맵을 만들어 보면서 DDB의 구조에 대해 실습을 해 보자. DDB 비트맵을 만드는 기본적인 함수는 다음 두 가지가 있는데 인수를 취하는 방식만 다를 뿐 근본적으로 같은 함수라 할 수 있다.
HBITMAP CreateBitmap(int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel,
CONST VOID *lpvBits);
HBITMAP CreateBitmapIndirect(CONST BITMAP *lpbm);
nWidth, nHeight로 만들고자 하는 비트맵의 크기를 지정하며 cPlanes, cBitsPerPel 인수로 만들고자 하는 비트맵의 색상 정보를 준다. 그리고 lpvBits에 비트맵의 래스터 데이터 포인터를 넘겨주되 이 때 래스터 데이터는 반드시 워드 단위로 정렬되어 있어야 한다. 즉, 한 줄당 바이트 수가 짝수여야지 홀수여서는 안 된다. 만약 홀수 바이트 폭이면(8픽셀이나 24픽셀이면) 워드 단위로 늘린 후 나머지 비트를 미사용으로 남겨둔다. 이 함수로 만들어진 비트맵은 DDB이기 때문에 같은 포맷의 DC에 곧바로 선택될 수 있으며 화면으로 출력할 수 있다.
실습을 위해 다음과 같이 8*8 크기의 흑백 비트맵 모양을 디자인해 보자. 연습지에 대충 바둑판 모양을 그려놓고 디자인하거나 아니면 모눈종이를 사용하여 그림을 그린다. 실습을 간단하게 하기 위해 작은 그림을 그렸는데 똑같은 방법으로 얼마든지 더 크고 복잡한 그림을 그릴 수도 있다. 그려진 그림의 흰 부분은 1이 되며 검정색 부분은 0으로 하여 2진수로 바꾸고 다시 이 수를 16진수로 변환하여 래스터 데이터를 준비해 둔다.
0xc3, 0xbd 등의 16진수가 비트맵의 실제 모양을 표현하는 래스터 데이터이다. 이 래스터 데이터를 배열로 만든 후 CreateBitmap의 lpBits 인수로 넘기면 원하는 비트맵이 만들어진다. 단 비트맵 데이터는 한 줄(Scan line)에 대해 워드 단위로 정렬되어야 하므로 매 줄마다 0xff를 넣어 남는 바이트를 흰색으로 채웠다. 첨부한 MakeDDB.cpp 파일을 참고하도록 하자.
Bit배열에 래스터 데이터를 저장해 두고 CreateBitmap으로 8*8 크기의 흑백 비트맵을 만들어 hBitmap에 대입하였다. 이제 만들어진 비트맵을 메모리 DC에 선택한 후 화면 DC로 전송하기만 하면 화면으로 출력된다. 메모리 DC와 비트맵 출력 방법에 대해서는 6장에서 이미 실습해 보았다. 실행 결과는 이렇다. 워낙 작은 비트맵이라 눈에 보일동 말동 한다. CreateBitmap 함수 대신 CreateBitmapIndirect 함수로도 동일한 비트맵을 만들 수 있다.
BITMAP bit;
.....
bit.bmType = 0;
bit.bmWidth = 8;
bit.bmHeight = 8;
bit.bmWidthBytes = 2;
bit.bmBitsPixel = 1;
bit.bmPlanes = 1;
bit.bmBits = Bits;
hBitmap = CreateBitmapIndirect(&bit);
BITMAP 구조체 변수 bit를 선언한 후 이 구조체에 만들고자 하는 비트맵의 특성을 설정하고 구조체의 포인터를 CreateBitmapIndirect 함수로 전달하면 된다. 또는 일단 비트맵부터 생성한 후 래스터 데이터를 별도의 함수로 설정할 수도 있다.
hBitmap = CreateBitmap(8,8,1,1,NULL);
SetBitmapBits(hBitmap,16,Bits);
CreateBitmap 함수의 마지막 인수를 NULL로 주면 래스터 데이터가 초기화되지 않은 비트맵이 생성된다. 이 비트맵의 래스터 데이터는 SetBitmapBits 함수로 따로 설정할 수 있다. CreateBitmap 함수로 컬러 비트맵을 만드는 것도 가능하지만 컬러 비트맵은 흑백보다 비트맵 데이터가 복잡하여 수작업으로 만들기가 어렵다. 또 컬러 비트맵은 반드시 현재 화면의 색상 포맷과 호환되어야만 출력할 수 있으므로 임의의 색상수로 비트맵을 만들어서는 안 되며 반드시 GetDeviceCaps 함수로 색상면의 수와 픽셀당 비트수를 조사하여 호환 비트맵을 만들어야 한다. 이 작업을 대신하는 함수가 바로 다음 함수이다.
HBITMAP CreateCompatibleBitmap(HDC hdc, int nWidth, int nHeight);
이 함수는 hdc와 호환되는 포맷의 비트맵을 nWidth, nHeight 크기로 만든다. 여기서 "호환된다"는 말은 색상 포맷이 같다는 뜻이며 더 정확하게 표현하자면 색상면과 픽셀당 비트수가 같다는 뜻이다. 즉 화면이 256모드면 비트맵도 256비트맵이 되고 트루 컬러 화면이면 비트맵도 트루 컬러로 만들어진다는 뜻이다. 이 함수는 비트맵을 만들기만 하며 래스터 데이터는 초기화하지 않으므로 내부 표면에 그림은 아직 없다. 컬러 비트맵은 직접 손으로 만드는 것보다는 CreateCompatibleBitmap 함수로 만드는 것이 보통이며 CreateBitmap은 흑백 비트맵을 만들 때만 사용하는 편이다.
참고자료: www.winapi.com
이 글을 공유하기