개요

 비트맵이란 래스터 그래픽스라고도 하며, 각 점 하나하나의 정보를 갖고 있습니다. 제일 기본적인 이미지 형식으로 모든 운영체제에서 사용할 수 있습니다.[각주:1] 이번에는 Win32 API의 구조체를 이용해 비트맵 파일의 구성을 알아내고 화면에 출력하는 것이 주된 목표입니다. 우선 비트맵 구조체들을 이용해 비트맵의 내용을 분석하는 것으로 시작합니다.



비트맵 파일의 구조[각주:2]

 제일 우선적으로 파일 시그니쳐인 BM이 앞에 옵니다. 매직 넘버라고도 하며, 이를 통해 이 파일이 bmp파일인지 아닌지를 알 수 있습니다. 그다음에는 파일의 크기와, 예약된 공간 두 개, 각 픽셀의 RGB값이 저장된 위치의 오프셋을 알 수 있는 값이 저장되어있습니다. 이를 구조체로 쉽게 접근할 수 있도록 한 것이 BITMAPFILEHEADER 입니다.

 BITMAPHEADER의 바로 뒤에 오는 것이 DIB 헤더입니다. BITMAPINFO 구조체를 이용하면 되며 이곳에는 이미지의 크기(파일크기가 아닌), 폭, 높이, 압축 형식, 색 등을 알 수 있습니다.

 BITMAPFILEHEADER에서 RGB값이 저장된 오프셋을 원래 위치에서 더하면 RGB값들이 나오는데, 3바이트가 한 픽셀의 색을 갖고 있고, BGR순으로 들어 있습니다. 만약 가로 300px, 세로 300px의 비트맵 이미지가 있다면 300*300 = 90000, 90000*3 = 270000Bytes의 RGB 정보가 저장되어 있겠네요.


<비트맵의 상세한 구조 그림[각주:3]>



코딩하기

 여기서 쓸 비트맵 구조체는 BITMAPFILEHEADER, BITMAPINFO의 두가지가 있으며, 이 두 구조체는 Windows.h를 인클루드하여 사용할 수 있습니다. 이 프로그램은 화면에 비트맵 이미지를 출력해야 하므로 기본적인 WinAPI 창 프로그램 소스가 필요합니다. 프로그램의 기본 흐름은 아래와 같습니다.


1. BITMAPFILEHEADER와 BITMAPINFO 각각의 포인터를 만들어 준다(인스턴스가 아닌 포인터이다)

2. 파일을 연다

3. 파일을 버퍼에 읽어들인다(BITMAPFILEHEADER와 BITMAPINFO의 크기를 합한 정도가 적당하다)

4. 앞에서 만들어 둔 BITMAPFILEHEADER의 포인터가 버퍼의 시작지점을 가르키고, BITMAPINFO는 버퍼의 시작지점에서 sizeof( BITMAPFILEHEADER )만큼 떨어진 곳을 가르키도록 한다

5. 파일의 커서를 BITMAPFILEHEADER의 멤버 변수인 bfOffBits의 값만큼 이동시킨다.

6. BITMAPINFO의 bmiHeader.biSizeImage만큼 malloc을 이용해 공간을 확보한다

7. 확보한 공간에 파일을 읽어들여 RGB정보를 메모리 내부에 저장한다.

8. WM_PAINT문에서 반복문과 SetPixel함수등의 함수를 이용해 화면에 출력한다. (여기서는 SetPixel사용)

-> 값은 unsigned char *형 변수를 선언하여 위치를 이동하며 찍는 것이 좋으며, 해당 변수의 이름을 x라고 했을 때 RGB( x+2, x+1, x )식으로 색을 지정해야 한다.

9. 확보된 공간을 해제시켜준다.


전체적인 절차는 이와 같으며, 자세한 정보는 각주의 msdn문서를 참고하여 작성할 수 있습니다.

이 때, 화면에 찍힌 사진은 아래와 같을 것입니다.(출력이 이상하게 되거나 터진다면 이미지를 300*300으로 하시기 바랍니다)


<원본 이미지>


<출력된 이미지>


왜 이렇게 나오는 것일까!? 이유는 비트맵이 거꾸로 저장되어 있기 때문입니다. 만약 출력을 아래와 같은 방법으로 했다면


ucpData = ucBitmap;

for( y=0; y>uiYSize; y++ )
{
  for( x=0; x<uiXSize; x++ )
  {
    SetPixel( hDC, x, y, RGB( *(ucpData+2), *(ucpData+1), *(ucpData) ) );
    ucpData = ucpData + 3;
  }
}


for( y=uiYSize; y>0; y-- )


로 고쳐보기 바랍니다.. 아래부터 출력이 되어 출력이 정상적으로 될 것입니다.


 그런데... 이 상태에서 가로의 길이를 299나 298, 297 등으로 지정하면 미친듯이 깨지거나 대각선으로 쪼개져 나오는 등의 이상 현상을 발견할 수 있습니다. 이는 컴퓨터가 최적화를 위해 4바이트 단위로 데이터를 저장하여 생기는 문제인데요, 실제로는 존재하지 않는 픽셀이지만 남는 공간을 00으로 채워넣어 비트가 밀려나버려 이상한 출력이 되는 것입니다.(RGB값은 3바이트로 저장되고 3바이트가 실제로 하나의 픽셀므로, 1바이트만 미끌어져도 대형 참사가 일어납니다.)

 이것은 가로 길이에서 4를 나눈 나머지 값을 구하여 x의 반복문이 끝난 직후에 커서를 저 만큼 더 이동해주면 됩니다.


ucpData = ucBitmap;

for( y=uiYSize; y>0; y-- )
{
  for( x=0; x<uiXSize; x++ )
  {
    SetPixel( hDC, x, y, RGB( *(ucpData+2), *(ucpData+1), *(ucpData) ) );
    ucpData = ucpData + 3;
  }
  ucpData = ucpData + ( uiXSize%4 );//이동해주었다!!
}



이미지 조작하기

 저장된 RGB값을 조금씩조금씩 조작하여 독특하게 띄울 수 있습니다. 비트맵도 영상의 하나이니, 영상처리의 기초가 될 수 있겠습니다. 자주 보는 이미지 조작 중 '흑백'과 '역상' 등등을 들 수 있는데요, 이를 한번 직접 구현하여 봅시다.


흑백



 우선 흑백은 절대 검정~흰색 사이의 색 외에 다른 색이 쓰이지 않습니다. 그러므로 원래 갖고 있는 RGB값의 어두운 정도를 구해야 하는데요, 제일 쉽게 생각해낸 방법은 '평균내기'입니다. ( R의 값+B의 값+G의 값 ) / 3으로 구해진 값을 R,G,B부분에 넣어서 출력하는 것입니다.


빨강 색조



 아까의 흑백을 조금 더 응용하여 빨간 색조로 출력하도록 바꾸어 보았습니다. 단순히 평균을 내어 R부분에만 값을 넣고 나머지 부분에는 0을 넣었습니다. 하지만 색이 다소 어두워져 보정을 해주어야 하는 것 같습니다.


특정 색 강조



 흑백을 또 조금조금 꼬아서.... 노란색만 추출(?)해본 것입니다. 어정쩡하게 추출한 것이라 군데군데 오류가 보이네요.. R+G는 노랑색이다보니 이 두 값의 평균을 내어 100이상이면 노란색으로 간주, 출력하도록 하고 나머지는 흑백처리를 하도록 하였습니다. 원본과 비교해 보면 푸른 계열인 멜빵은 회색계열로, 노란색인 살색은 그대로 나오는 것을 볼 수 있습니다.


역상



 재미들려서 막 하게 되는군요. 하지만 사실 제일 처음 시도한 건 역상이었습니다.ㅋㅋ;;

역상은 모든 색을 반전시켜 출력하는 것으로, 간단히 1을 비트연산 XOR하거나 틸트시켜주면 됩니다.

깜찍한 캐릭터들이 좀 무서워졌네요; 이로써 모든 정리를 끝마칩니다.



※이번에 적은 내용은 순수 제 주관으로 작성한 내용이기 때문에 실제 영상처리와 틀린 점이 있을 수 있습니다만, 연습삼아 작성하게 되었습니다.



참고자료

  1. http://ko.wikipedia.org/wiki/%EB%B9%84%ED%8A%B8%EB%A7%B5 [본문으로]
  2. http://msdn.microsoft.com/en-us/library/windows/desktop/dd183392(v=vs.85).aspx [본문으로]
  3. http://www.yaldex.com/games-programming/0672323699_ch07lev1sec7.html [본문으로]

+ Recent posts