본문 바로가기
프로그래밍/PSP

비트맵 8,16,24 비트 상호변환

by 베리베리 2008. 7. 28.

참조 사이트

http://myhome.naver.com/eryners/programming_1.htm


 - BMP(비트맵) 파일

  BMP파일, 혹은 비트맵(Bitmap)파일 이라 부르며, 요즘 대부분의 2D 기반의 게임에서는
  비트맵 파일을 사용해서 게임을 제작하고 있습니다.
  설명에 앞서 BMP 파일도 장치 의존적인 비트맵(DDB)파일과 장치 독립적인 비트맵(DIB)
  파일 두 종류로 나누어 지는데, DDB 방식은 게임에서 거의 쓰이지 않으므로 DIB 방식에
  대해서만 알아 보겠습니다. DDB 파일에 대해 궁금하신 분들은 API관련 서적을 참고하시
  기 바랍니다.
 



 
1. BMP 구성

 
  (1) 파일 구성도
    
                                                  
             그림1) 8비트                                             그림2) 8비트 이상


   (2) 파일헤더(FileHeader)
    - 파일크기(s), 파일 형태(t), 등등의 정보를 가지고 있는 구조체입니다.


   (2) 정보헤더(InfoHeader)
    - 가로크기(w), 세로크기(h), 비트 수(b) 등등의 정보를 가지고 있는 구조체입니다.


   (3) 팔레트(Palette)
    - 컴퓨터에서 사용되는 모든 색은 R,G,B 조합으로 만들어 진다는 것을 알고 계실 것
     입니다. 8비트(256 색) 컬러에서는 256가지 색 밖에 사용할 수 없는데 그 많고 많은
     색 중에서 256가지 색을 어떻게 알아낼 수 있을까요??  답은 팔레트(Palette)입니다.
     8비트 컬러는 픽셀 자체가 색상 정보를 가지고 있지 않기 때문에... 팔레트를 필요로
     합니다.

   (4) 이미지 데이터
    - 실제 이미지 데이터는 그림1,그림2와 같이 거꾸로 저장되어 있는 것이 특징입니다.


 2. 컬러별 특징
   
   (1) 8비트 컬러

   

    - 8비트 컬러는 각 픽셀이 8비트 즉 1바이트의 R,G,B 값 중에 하나를 가지며, 픽셀 자
    체가 색상 정보를 가지고 있지 않기 때문에 팔레트를 필요로 합니다. 256색을 가지고
    하나의 그림을 표현해야 하기때문에 색에 대한 많은 제약을 받지만 출력할 때 빠른 속
    도가 장점입니다.

 
   (2) 24비트 컬러

   

    - 24비트 컬러는  각 픽셀이 24비트 즉 3바이트의 R,G,B 조합의 값으로 이루어져 있
    는데 각 R,G,B 값들이 1바이트씩 가지고 있습니다. 픽셀 자체가 색상 정보를 가지고
    있기 때문에 8비트처럼 팔레트를 필요로 하지 않습니다.


    16만 가지 이상의 색(255 * 255 * 255)을 사용해서 하나의 그림을 표현 하므로, 색에
    대한 제약은 거의 받지 않지만 8비트보다 데이터의 용량이 커지고 출력할 때 느리다
    는 단점이 있습니다.



 3. 주위 사항들

   (1) 회전(Rotate)
    - BMP 파일의 이미지 데이터는 메모리 상에서 위의 그림들처럼, 거꾸로 저장되어
     있는데 출력될 때에는 거꾸로 뒤집혀서 출력되기 때문에 우리가 실제 느낄 수는 없
     는 것 입니다. 이 이야기가 무슨 이야기냐 하면.... 윈도우에서는 좌표 (0,0)은 좌측
     상단의 처음을 의미 하지만,   BITMAP에서의 좌표 (0,0)은 좌측 상단이 아닌 좌측
     하단의 처음이 되기 때문에 이미지 데이터가 위에서 아래가 아닌 아래에서 위로 즉
     뒤집혀서 저장되어 있는 것 입니다.

       


   (2) 4의 배수
     - BMP파일의 가로(width) 크기는 되도록 4의 배수의 법칙을 적용하여 4의 배수로
     저장 하셔야 합니다. 꼭 필수는 아니지만 그러기를 권장합니다.  그 이유는 비디오
     메모리 4의 배수의 체계를 가지고 있기 때문입니다.

     그림 이란 것이 우리가 눈으로 보는 것처럼 가로(width), 세로(height)의 큰 사각형
     처럼 저장되어 있는 것이 아니라... 선형 즉 연속적인 데이터로 저장되어 있어서 이
     것을 화면에 출력해 주기 위해서는 가로(width) 크기에 맞게 끊어서 출력해 주어야
     합니다.
     
         
------   


     앞서도 이야기 했듯이 비디오 메모리가 4의 배수 체계를 가지고 있으니
4, 8, 12, 16,
    
20, 24, 28, 32...160... 320... 640... 1280바이트 등등이 될 수 있습니다. 가로(width)
     의 크기가
5이고, 세로(height)의 크기가 6인 24비트의 BMP 파일을 화면에 출력 했
     다면 결과는 어떻게 나올까요?  가로(width) 총 바이트 수(
5 x 3= 15)  4의 배수가
     되지 않으니 화면에 제대로 된 그림이 출력되지 않겠네요.  여러분들의 이해를 돕기
     위해 그림을 준비 했습니다.


   *********************************************************************  
    * 원본 그림(가로 15바이트)

      

    * 그림출력(4의 배수 미적용 )

        

      - 비디오 메모리는 4의 배수 체계 때문에
15바이트가 아니라 16바이트가 됩니다.
        먼저
15바이트의 빨간색을 출력한 후에 다음 라인으로 이동해서 노란색을 출력
        해야 하지만, 가로(width) 총 바이트 수는
15바이트 뿐 이기때문에 엉뚱한 위치
         에 출력될 수 밖에 없는 것 입니다. ㅡㅡ;
               
    * 그림 출력(4의 배수 적용)

      

      - 4의 배수를 적용해 가로(width)의 총 바이트 크기를
15가 아닌 16바이트로 해 주
        었기 때문에 화면에 제대로 된 그림이 출력됩니다. ^^;

     * 4의 배수 법칙
      - 가로(width)크기를
51로 해서 저장했을 경우 4의 배수 법칙에 의해 실제로는 52
         크기만큼 해주어야 합니다.
      -
42 -> 4423 -> 2481 -> 84     
    *********************************************************************


    *********************************************************************
    우리가 윈도우즈의
"그림판"이나, 어도비사의 "포토샵"에서  가로(width) 크기를 4의
    배수로 하지 않아도 원하는 그림의 결과를 얻을 수 있는 이유는 그 프로그램들 자체
    적에서 4의 배수로 만들어서 저장/읽기 해주기 때문입니다. 그런데..... 만약 우리가
    BMP파일 저장/로드 기능이 있는 프로그램(Sprite Tool, Map Tool)을 만든다고 했
    을 때에는 4의 배수 적용을 반드시 해 주어야만 합니다. 그렇지 않으면 원하는 결과
    를 얻을 수 없겠죠. 밑에 저장/ 읽기 그림을 보시면서 이해해 보세요.
    *********************************************************************


    * 저장(save)/읽기(load)하는 그림이 4의 배수가 적용이 되지않았을 때,
      즉 가로(width)크기가 4의 배수가 아니 였을 경우...


      * 24비트 BMP 저장(Save)
 
                                          
                               4의 배수 적용                   4의 배수 미적용


      * 24비트 BMP 로드(Load)

                                          
                               4의 배수 적용                   4의 배수 미적용


      * 4의 배수 구하는 공식

             4의 배수 =  (가로크기 * (비트수/8) + 3) & ~3


      제가 예전에 회사를 다닐 때의 기억이 떠오르네요. 그 게임은 PC용 아케이드 게임
      이었고, 오락실용으로 컨버팅(converting)하는 작업을 하고 있었습니다. 오락실용
      게임기에서는 4바이트(byte)가 기준이라 하여... 하는 수 없이 2바이트(byte) 화면
      출력 알고리즘을 4바이트(byte)로 수정해서 화면에 출력해 보았습니다.

      그런데 어떤 그림은 제대로 출력이 되고 또 어떤 그림은 찌그러져 출력이 되어서...
      당황했던 적이 있었습니다. 나중에 알고 보니 그림의 가로(width) 크기가 4의 배수
      가 되지않고 홀수인 그림들이 찌그러져 나오는 것 이더군요. ㅡㅡ;      

      *  테스트 파일
           
파일명 :  hero.spr
            비   트 : 16비트
            가   로 : 51
            세   로 : 60

      * 2바이트 화면 출력 알고리즘(16비트 컬러 기준)

           
int nTargetWidth= ( SCREEN_WIDTH - m_ImageWidth);     

           
for(int y =0; y < m_ImageHeight; y++)
           {
               
for(int x =0; x < m_ImageWidth; x++)
               {
                    *pTarget++ = *pSource++;
                   
// pTarget, pSource는 2바이트
                    // pTarget는 실제 화면 포인터
                    // pSource는 그림데이터를 가리키는 포인터  

               }
               pTarget= pTarget +
nTargetWidth;
           }


     
* 4바이트 화면 출력 알고리즘(16비트 컬러 기준)      

           
int nTargetWidth= (SCREEN_WIDTH - m_ImageWidth) >>1;

           
for(int y =0; y < m_ImageHeight; y++)
           {
               
for(int x =0; x < m_ImageWidth/2; x++)
               {
                    *pTarget++ = *pSource++;
                   
// pTarget, pSource는 4바이트
                    // pTarget는 실제 화면 포인터
                    // pSource는 그림데이터를 가리키는 포인터

               }
               pTarget= pTarget +
nTargetWidth;
           }


      화면에 출력할 그림의 가로 크기가 51인 경우 51/2 = 25 이기에 이런 방식으로 출
     력 하게 되면 실제 가로크기는
51(102 바이트)인데 반해... 지금 화면에 실제 그려
     지는 가로 크기(
25 x 4)는 50(100 바이트)까지 밖에 되지 않으므로... 이 남게 되는
    
1(2 바이트)때문에 실제 출력 시 그림이 찌그러져 나오는 것 이었습니다.  해결 방
     법은... 그림의 가로크기(width)에 4의 배수 법칙을 적용해
52로 수정해주는 것 밖
     에는 없었습니다.

     프로그래밍을 하다 보면 BMP파일의 가로크기에 x2를 해주거나 /2를 해주어야 할
     상황이 부득이하게 생기는데 이럴 때를 대비해서  가로 크기는 4의 배수로 해주는
     것이 좋겠지요.
          
  
   (3)
컬러(color) 추출
    - BMP 파일의 컬러저장 순서는 R,G,B가 아니라 B,G,R 형태로 저장 되어 있는 것
    이 특징입니다. 이 순서가 왜 중요하냐 하면??  그래픽 부분 프로그래밍을 하다 보
    면 24비트를 BMP 파일을 16비트로 변환 해야 하거나, 32비트 DWORD형 컬러 값
    을 16비트의 WORD형으로 변환해서 사용할 때가 많이 생기는데... 이처럼 컬러 데
    이터를 추출해서 사용해야 할 때에 컬러(color)가 저장된 순서는 매우 중요합니다.
    B,G,R 순서를 무시한 채, R,G,B 순으로 데이터를 추출해서 그림을 출력해 보면 원
    본 그림과 다른 엉뚱한 색이 출력되는 것을 보실 수 있을 것 입니다.
   
    * targetcolor은 32비트(DWORD) 컬러 데이터  

   
BYTE  r= (BYTE) (targetcolor),
              g= (BYTE) (targetcolor >>8),
              b= (BYTE) (targetcolor >>16);
 
   밑에 여러분의 이해를 돕기 위해 24비트를 16비트로 만들어 주는 알고리즘과 16비
   트를 24비트로 만들어 주는 알고리즘입니다.
     
     알고리즘     


                                             by : full_tree@hanmail.net

1. 24비트 -> 16비트

 
void CDib:: Convert_24To16(BYTE *pDst)
 {
    
// 24비트 데이터를 16비트로 변환

     int nWidthByte24= GetWByteN24(), nWidthByte16= GetWByteN16()>>1;
     i
nt ny= 0, nx= 0, nv= 0;
     BYTE r=
0, g= 0, b= 0;
     WORD *pTarget= m_pImageWord;

    
for(ny= 0; ny < GetHeight(); ny++)
    {
         
for(nx= 0, nv= 0; nx < nWidthByte24; nx+= 3, nv++)
         {

               b= *(pDst + (ny * nWidthByte24) + nx +
0) >> 3, // B
               g= *(pDst + (ny * nWidthByte24) + nx + 1) >> 3, // G
               r= *(pDst + (ny * nWidthByte24) + nx + 2) >> 3// R

               *(pTarget + nv)= RGB16BIT(r, g, b);

         }

         pTarget += nWidthByte16;
    }
 }



 
2. 16비트 -> 24비트

  void CDib:: Convert_16To24(BYTE *pDst)
 {
     // 16비트 데이터를 24비트로 변환

     int nWidthByte24= GetWByteN24(), nWidthByte16= GetWByteN16()>>1;
     int nx= 0, ny= 0, nv= 0;
     BYTE *pTarget= pDst;
     WORD pixel;

     for(ny= 0; ny < GetHeight(); ny++)
    {
          for(nx= 0, nv= 0; nx < nWidthByte24; nx+= 3, nv++)
         {
               pixel= *(m_pImageWord + ((ny * nWidthByte16) + nv));

               *(pTarget + nx + 0)= ((pixel) << 3);         // B
               *(pTarget + nx + 1)= ((pixel >>5 )<< 3);   // G
               *(pTarget + nx + 2)= ((pixel >>10)<< 3);  // R

         }
         pTarget += nWidthByte24;
    }
 }



 3. 8비트 -> 16비트

 
void CDib:: Convert_8To16(BYTE *pDst)
 {
    
// 8비트 데이터를 16비트로 변환

     // 8비트 이미지 데이터는 픽셀 자체 적으로 색(Color) 정보를
     // 가지고 있지 않기 때문에 팔레트(Palette)라는 것을 필요로 합니다.


    
int nx= 0, ny= 0, nWidthByte= GetWByteN8();
     BYTE r=
0, g= 0, b= 0, c= 0, *pSource= pDst;
     WORD *pTarget= m_pImageWord;

    
for(nx= 0; ny < GetHeight(); ny++)
     {
         
for(nx= 0; nx < nWidthByte; nx++)
         {
               c= *(pSource + nx);
              
// c는 팔레트 인덱스 번호(0~255)

               b= m_Rgbquad[c].peRed;     
// B
               g= m_Rgbquad[c].peGreen; 
// G
               r= m_Rgbquad[c].peBlue;    
// R

               *(pTarget + nx)= ((r>>
3)<<10) | ((g>>3)<<5) | (b>>3);
              
// 16비트 데이터로 만들어 줌
          }
          pTarget+= nWidthByte;
          pSource+= nWidthByte;
     }
 }

댓글