BMP를 DDB로 변환

Projects/CoVNC 2007.02.01 14:03 Posted by soulfree >동네청년<

출처 : 블로그 > Priss In The Attic
http://blog.naver.com/priling/80012774193
===========================================

[부대에서 구린 컴퓨터로나마 비주얼C를 돌려볼 수 있다는 것이 얼마나 고마운지 모릅니다.

2년이나 쉬어서 그런지 코드 한 줄 쓰기도 낯설더군요.

그래도 옛 생각 새록새록 나는게 기분이 삼삼~하네요

무려 3일 동안이나 삽질하던거를 잊어버리지 않도록 그냥 한번 써봤습니다.]



BMP 파일은 DIB이다. 윈도우즈 프로그램에서 이것을 이용하기 위해서는 내부적으로 DDB로 바꾸어 주는 편이 낫다. DIB의 형식을 유지한 채로 메모리 상에서 이용하는 방법이 없는 것은 아니지만 (CreateDIBSection함수) 이렇게 되면 당연히 이미지 처리 속도가 늦어지게 된다.
DIB라는 것이 화면의 DC와는 별개로 자체적인 컬러 포맷을 가진 이미지 형식이므로 이것을 DC에서 출력하기 위해선 DDB로 변환해 DC와 같은 포맷으로 만들어 주어야만 원만한 처리 속도를 기대할 수 있다.

1. BMP 파일을 어떻게 화면에 뿌릴까?
도스 시절 때처럼 생각하면 헤맨다. 도스 때에 그냥 메모리에 그림 파일을 읽어 올려서 화면 컬러 포맷에 맞추어서 이미지 데이터를 비디오 메모리에 전송하면 되었지만, 윈도우는 DC라는 시스템이 있어서 조금 거쳐가야 한다.

즉 읽어들인 그림 파일을 프로그래머 임의의 방식으로 가지고 있다가 화면에 뿌려주는 게 아니고 DC에 보관했다가 DC간의 메모리 전송으로 화면에 출력해야 한다는 것이다.

CreateCompatibleDC 함수를 사용해 메모리 상에 화면 DC와 같은 포맷의 DC를 만든다. 이것을 memDC라고 하면, memDC에서 SelectObject 함수를 사용해 HBITMAP 형식의 이미지를 붙일 수 있다. (이것은 DC에서 브러시나 펜 등을 설정하는 것과 같다. 한번에 하나의 비트맵이 선택될 수 있다.)

이제 우리는 두개의 DC를 가지고 있다.
주화면 DC (hDC라고 하자)와 memDC..
두 개의 DC끼리는 BitBlt 함수를 사용해 이미지를 복사할 수 있다.


2. 그럼 BMP 파일을 DDB로 바꾸는 방법은?
SelectObject 함수로 이미지를 DC에 선택하려면 DDB가 필요하다. 그러나 BMP 파일은 DIB이기 때문에 변환이 필요하다. 다음 함수가 바로 DIB를 DDB로 한방에 바꾸어 주는 놈이다.

HBITMAP CreateDIBitmap(
  HDC hdc,                        // handle to DC
  CONST BITMAPINFOHEADER *lpbmih, // bitmap data
  DWORD fdwInit,                  // initialization option
  CONST VOID *lpbInit,            // initialization data
  CONST BITMAPINFO *lpbmi,        // color-format data
  UINT fuUsage                    // color-data usage
);

인자 설명 ::
HDC hdc  => 생성될 DDB의 컬러 포맷을 참조할 DC (주화면 DC 핸들을 넣으면 된다)
CONST BITMAPINFOHEADER *lpbmih  => 말그대로 BMP파일의 BITMAPINFOHEADER
DWORD fdwInit  => CBM_INIT라는 상수를 넣으면 된다 (자세한 건 msdn참조)
CONST VOID *lpbInit  => 이미지 데이터 블록의 포인터를 넣어주자
CONST BITMAPINFO *lpbmi  => BMP 파일의 BITMAPINFO의 포인터를 넘기면 된다
UINT fuUsage  => 디스플레이의 현재 색비트 수가 8이하라면 DIB_PAL_COLORS, 16이상이면 DIB_RGB_COLORS 이다


3. 그외 겪었던 잡다한 문제들
(1) 색깔이 이상하게 나온다?
처음에 겪은 버그. 이미지의 모양은 제대로 출력 되지만 색깔이 이상했다.
RGB의 순서가 뒤바뀐 듯한 느낌. 실제로
빨강이 초록으로
초록이 파랑으로
파랑이 빨강으로 표시되는 현상이 생겼다.

이것은 작은 실수에 의한 것이다. 그러나 찾기는 쉽지 않다. 게다가 이미지의 모양은 제대로 출력되면서 색깔만이 이상하게 나오기 때문에 속기 쉬워서 컬러 포맷의 비트 변환에서 계속 문제를 찾느라 시간을 허비했다. 이 버그는 이미지의 왼쪽 세로 라인 하나가 오른쪽에 잘려붙여져 나타나는 현상을 동반한다.
이건 다름아니라.. 이미지 데이터 블록의 포인터를 잘못 잡은 탓이다.
BMP파일에서 이미지 데이터 블록을 읽어들일 때 한 바이트라도 빗나가면 RGB의 순서가 뒤바뀌게 되는 것이다. BMP 파일에서 헤더부터 순차적으로 읽어들이다가 조금이라도 어긋나면 이렇게 되버린다. 차라리 나은 방법은 fseek를 사용해서 BMP파일의 뒤에서 위치를 재어 들어가는 것이다.

// bmi는 BITMAPINFO 구조체, pixels는 이미지 데이터를 보관할 포인터
fseek(fp, -(int)(bmi->bmiHeader.biSizeImage), SEEK_END);
pixels = (unsigned char*) malloc(bmi->bmiHeader.biSizeImage);
fread(pixels, bmi->bmiHeader.biSizeImage, 1, fp);


(2) 8비트 BMP를 읽어들일 때 팔레트 문제
8비트라고 해서 별다른 게 있는 것은 아니다. CreateDIBitmap 함수는 컬러 포맷에 관계없이 대상 DC와 BITMAPINFO 구조체 정보를 이용해 양쪽을 잘 매치해준다.
다만, 8비트는 팔레트를 파일에서 읽어줘야 한다는 문제가 있다.
어려움을 겪은 부분은 BIMAPINFO 구조체의 팔레트 부분이었다.

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

두번째 멤버변수가 바로 팔레트를 위한 부분인데, 문제는 배열의 크기가 1이라는 것이다.
8비트 팔레트는 256개의 RGBQUAD가 필요한데 배열을 확보하기 위해서 약간의 꽁수가 필요하다.

BITMAPINFO* bmi=NULL;
bmi = (BITMAPINFO*) malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 255);

fread(bmi, sizeof(BITMAPINFOHEADER), 1, fp);
if (bmi->bmiHeader.biBitCount == 8)
fread(bmi->bmiColors, sizeof(RGBQUAD), 256, fp);

그냥 변수로 선언하면 팔레트엔 1개의 엔트리 뿐이게 되므로 포인터 선언을 한 후 메모리 할당을 해주는데 이때 255개의 RGBQUAD 분량의 메모리를 더 잡아주면 이미 BITMAPINFO에 들어 있는 1개의 엔트리에 255개 만큼의 엔트리가 더해지게 된다.
그 후에 BITMAPINFOHEADER를 읽어들이면 다음은 팔레트 차례가 된다.




// BMP 읽어서 화면에 뿌려주는 코드의 핵심부
HBITMAP hbm;
:
:
BMPLoad("a.bmp");
hdc = GetDC(hWnd);
hbm = CreateDIBitmap(hdc, &(bmi->bmiHeader), CBM_INIT, pixels, bmi, DIB_RGB_COLORS);
memDC = CreateCompatibleDC(hdc);
SelectObject(memDC, hbm);
BitBlt(hdc, 0, 0, bmi->bmiHeader.biWidth, bmi->bmiHeader.biHeight, memDC, 0, 0, SRCCOPY);
ReleaseDC(hWnd,hdc);   




// BMP 로드 함수
BITMAPFILEHEADER bf;
BITMAPINFO* bmi=NULL;
unsigned char* pixels=0;
:
:
int BMPLoad(char* filename)
{

FILE* fp;
fp = fopen(filename, "rb");

bmi = (BITMAPINFO*) malloc(sizeof(BITMAPINFO) + sizeof(RGBQUAD) * 255);

fread(&bf, sizeof(BITMAPFILEHEADER), 1, fp);
fread(bmi, sizeof(BITMAPINFOHEADER), 1, fp);
if (bmi->bmiHeader.biBitCount == 8)
{
  fread(bmi->bmiColors, sizeof(RGBQUAD), 256, fp);
}

       // 픽셀 데이터 읽기
fseek(fp, -(int)(bmi->bmiHeader.biSizeImage), SEEK_END);
pixels = (unsigned char*) malloc(bmi->bmiHeader.biSizeImage);
fread(pixels, bmi->bmiHeader.biSizeImage, 1, fp);

fclose(fp);


return TRUE;
}


색깔 바뀌는 버그는 군대 오기 전에도 DDRAW 만지면서 한 번 당해서 고생했던 기억이 난다.
문제는 다 해결하고 나서야 어? 전에도 이랬던 적이 있었던거 같애! 거 희한하네~ 하는게 문제지.

'Projects > CoVNC' 카테고리의 다른 글

DIB구조  (0) 2007.02.05
DIB를 DDB로 변환  (0) 2007.02.05
BMP를 DDB로 변환  (0) 2007.02.01
비트맵 파일 저장하고 읽기  (0) 2007.02.01
CF_BITMAP 사용하기  (0) 2007.01.30
클립보드에서 이미지 읽기  (0) 2007.01.25
TAG , ,