2.PlayStation 1(one) 2D Format 해부학 - Convert BMP(24Bit)

4월 18일날 쓴거네요 ^^;

 

Convert BMP(24Bit)


작성자 : kallru(신휘재)
E-mail : kallru@kornet.net


※ 이 문서를 다른 곳에 배포할 시에는 최소한 작성자에게 문의 해주시기 바랍니다.
앞 강좌에 배포는 자유이지만 지금 부터는 100% 제 원 소스이기 때문에 제게 허락을 맡고 배포해주시기 바랍니다. 이것은 제가 군대 갈때까지 효과를 지속합니다.
※ 오타, 혹은 잘못 되어진 곳, 잘못 표현되어진 곳등은 연락해주시기 바랍니다.

 

지식은 권한을 보호받고 공유되어야 한다.

 

- 목 차 -

6.비트맵의 종류

7.DDB의 구조&DIB의 구조

8.24Bit BMP는 어떻게 생겼나?

9.이제부터 시작이다!

 

 

6.비트맵의 종류
비트맵 파일은 2종류가 있다. 첫 번째는 DDB이고 두 번째는 DIB인데 DDB는 예전 윈도우 3.0 이전 버전에 사용된거라 한다. 이 DDB는 하드웨어적으로 많은 제약이 있었다고 한다. 잘은 모르겠지만 상당히 사용할 때 이런저런 제약들이 있었던 것 같다. 이런 단점을 보안한게 DIB인데 이 DIB는 아주 많은 정보들을 가지고 있다. DDB는 필요한 정보만 가지고 있는데 비해 이 DIB는 어떤 하드웨어에서도 사용할수 있도록 하기 위해서 많은 정보를 가지게 되었다고 한다. 이런이유로 요즘에는 DIB을 사용한다.
 하지만 DDB를 사용하지 않는건 아니다. 사용하지 않았다면 예초에 소개할 필요도 없었을 것이다. 윈도우 API에서 DC로 선택할수 있는건 DDB 뿐이라고 한다. 이유는 DIB에 비해서 속도가 빠르고 복잡하지 않아서 내부적으로 사용하기에는 DIB보다는 DDB가 좋다고 한다. DIB를 DC로 엑세스 할 방법은 아직까지 한번도 못봤다. 그렇다고해서 불가능 하다는 소리는 아니고...아직 나는 배운 것 보다 안배운게 몇십제곱은 더 많으니까..

 

7.DDB의 구조 & DIB의 구조
이 강좌가 점차 "윈도우즈 API정복" 책과 같은 목자로 가는 것 같아서 왠지 좀 그렇다. 하지만 이렇게 요점만을 딱 보여주게 제목을 정해야 하므로..^^ 여기서는 자세히 서술하지 않겠다 자세히 보고 싶다면 저 API정복 책을 참고하기 바라며, DDB의 구조를 잠깐 보자면
typedef struct tagBITMAP {
LONG  bmType;
LONG  bmWidth;
LONG  bmHeight;
LONG  bmWidthBytes;
WORD bmplanes;
WORD bmBitsPixel;
LPVOID bmBits;
}BITMAP;
직접 타이핑 했더니 힘들다. 내가 여기서 다루는건 DIB라서 DDB는 여기까지만 쓰고, 다음은 DIB이다. DIB에 구조체는 복잡하다. 구조체를 보자면

BITMAPFILEHEADER
BITMAPINFOHEADER
RGBQUAD
비트정보

이런 순으로 되어 있다. 파일헤더와 인포헤더로 헤더가 2개이고 그 다음 나오는 RGBQUAD는 팔레트이다. 이것까지 지나야 그제서야 이미지 데이터가 나오는 것이다.
각각의 구조체는 VC++에서 Go To Define...으로 찾아가 보던가 MSDN을 참고 하길 바란다.

 

8. 24Bit BMP는 어떻게 생겼나?
이제 비트맵을 좀 알겠다면 24Bit가 어떻게 이루어지는가를 짚고 넘어가야한다.
24Bit는 한점(픽셀)을 3Byte로 나타내는걸 말한다. 24Bit를 트루컬러라고도 하는데,
윈도우 바탕화면에서 등록정보를 봐보자 그곳에 해상도 설정과 컬러 설정을 하는 곳을 보면 하이컬러(16) 트루컬러(32)라고 되어 있는걸 볼수가 있다.
 24Bit가 안나온다. 왜 인지는 잘 모르겠지만 주워들은 봐로는 24Bit가 32Bit보다 느리다??
그런 소리를 들었다. 윈도우가 32Bit 운영체제이기 때문에...잘은 모르겠고 어쨌든 윈도우에서는 24Bit를 외면한건가∼
 본론으로 돌아와서 좀 더 자세하게 24Bit를 알 필요성이 있다. 혹시 이 강좌를 읽는 독자가 RGB가 무엇인지 모른다면 알고 오길 바라며, 24Bit에서는 각각 1Byte씩 사용한다.

R=1Byte
G=1Byte
B=1Byte
그렇다면 한 점(픽셀)은 3Byte로 표현되는게 맞는 셈이다. 1Byte는 곧 8Bit 이다. 8Bit를 가지고 표현 할수 있는 수는 256, 즉 RGB 각각 0∼255개에 색상을 같는 것이다.
좀 더 쉽게 말하면 같은 빨강이라 해도 붉고 어두운 빨간색이 있는가 하면 눈이 아플정도로 밝고 화사한 빨간색이 있다. 이렇게 0부터 255단계로 사용할수 있다는 것이다. 이 정도로 단계가 디테일해지다 보니까 사람의 눈으로는 구별해내기가 거의 불가능할 정도로 선명하고 디테일해젔다. 굳이 32Bit 까지 사용할 필요도 없을 것 이다. 3D 분야에서나 사용할 것 같다.
 24Bit에 설명은 여기까지 하고 BMP 이 녀석에 대해서 가장 명심해야 될 것 2가지만 정리하고 다음 강좌로 가자. 혹시 모를 것 같아서 꼭 짚고 넘어가는 것이다.

 

①BMP에 이미지 데이터 정보는 거꾸로 되어 있다.
②BMP는 4Byte 정렬을 한다.

 

이 2가지를 꼭 기억하자. 혹시 잘 모르겠다 싶으면 자료를 찾아보길 바란다. 여기서 이걸 다루면 이 목차 만큼 분량이 더 불어나기 때문에...

 

9.이제부터 시작이다! Tim->Bmp로
여기서부터는 전 강좌 "TimFormat을 읽자"처럼 전체 소스를 올리지 않고, 부분적으로 중요 포인트를 집고, 거기에 대한 설명을 덧 붙이는 식으로 진행하겠다.
지금부터 잘 기억하길 바란다. 앞으로 험난하기 때문에 기초를 잘 다저야 한다. 모든 것은 전 단계에서부터 응용되는 수학에 법칙에 따라...-_-; 수학은 기초를 모르면 어렵다. 최근 나는 구구단을 잊어가고 있다.


 소스 강좌에 들어가기 앞서 주석처리를 정하겠다.
[ // ],[ /* */ ] 이건 기본적으로 C/C++에서 사용되는 주석이다. 이걸로 주석처리가 되어 있는 건 내가 만들면서 처둔 주석이기 때문에 다소 틀린게 존재할지도 모른다. [ || ]이건 강좌를 쓰다가, 이 부분에는 좀 더 주석을 첨부해야 될 것 같을 때 사용할 주석 처리이다.[ :: ]이건 설명부분이다.

 

그럼 이제부터 소스를 보자

 

int i,j;
int BigOffset,BmpOffset,BmpOffset2=0;
BYTE *Temp,*Temp2;
Temp = new BYTE[ imgInfo.Size ];
Temp2 = new BYTE[ imgInfo.Size ];
ZeroMemory(Temp,imgInfo.Size);
ZeroMemory(Temp2,imgInfo.Size);
::사용할 함수 변수들이다. 이 변수들은 함수가 끝남과 동시에 자동 삭제 된다.
물론 new는 직접 delete 해줘야 한다.
::또 전 강좌에서 설명한 헤더 부분에서 IMAGEDATA Image24 이것을 Image 로 사용하니까 이점을 생각해두길 바란다.
::그외 특별히 선언한 변수나 구조체는 없으니까 구조체를 알고 싶으면 "TimFormat을 읽자"를 참고 하길 바란다.

 

//현재 Offset이 2 인건 24비트 칼라 이기 때문
//0 1 2 -> 2 1 0 으로 바꾸어야 한다.
//그러기 위해선 i 값(0,1,2...)에 +2를 한후 Offset 값을 -2 씩 해주면 거꾸로 돌아오는 오프//셋을 만들수가 있다. 다만 이것은 3바이트를 사용하는 24bit에서 사용되는 것이다.
BigOffset = 2;
for(i=0; i<imgInfo.Size;i++)
{
 if(BigOffset==-4) BigOffset=2;
 Temp[i] = imgInfo.Data[i+BigOffset]; //Big 방식으로 바꾸어준다
 BigOffset-=2;
}

::이 로직은 지금도 짜증을 유발시키는, 그러나 아주 중요한 개념인 Little Endian과 Big Endian에 대한 부분이다. 위에 주석에는 Big 방식으로 바꾸어준다고 되어 있지만 아직도 나는 잘 모르겠다 Little를 Big으로 바꾸는 것인지 Big을 Little로 바꾸어주는 것인지..어쨌든 내가 알고 있던건 단 하나! 반대로 바꾸어주어야 한다는 것이다. 난 그거 하나만을 머릿속에 담고 만들었다. -_-
 굉장히 원시적인 방법을 사용했는데 먼저 BigOffset를 2로 초기화 시켜둔다.
Temp[0]번 일 때 imgInfo.Data[2] 이렇게 되어야 한다. imgInfo.Data[2]번을 Temp[0]번으로 옮겨줘야 한다. 그래야 위에 주석처럼 바꾸어 줄수 있다. 이걸 16진수로 예를 들면 56 45 F2 -> F2 45 56 이렇게 바꾸어 주어야 한다. 표를 통해 좀 더 쉽게 보자

여기서 보면 중간은 안바뀐다는 사실을 알수가 있다. 좀 더 빠르게 바꾸려 한다면 이 중간 부분을 스킵하도록 로직을 짜면 되겠다. 나는 그렇게 해주지 않았고, 해줄 필요성을 느끼지 못했다.

 

// *역순 정렬
//BmpOffset은 파일 데이터에 가장 마지막 부분에서 한칸 -1 한 값을 가지고 있다.
//가장 마지막 부분에서 한칸 전 부터 마지막까지 읽는다 (이것이 마지막 라인이다.)
//한바퀴 돌면 다시 -1 하게 된다. 결국 점점 앞으로 올라오며,
//읽을때에는 + 로 읽기 때문에 한칸 뒤에서부터 읽는 것이다.
//예) File에 마지막이 200 이고 Width데이터가 20 이라면,
//180(Offset) 부터 시작해서 200(j를 돌리면서 하나씩 얻는다)까지 읽는다.
//한 라인을 다 읽었으면 다시 Offset에 -1을 해서 다음 라인으로 간다.
//160(Offset)부터 180까지 읽는다. 이렇게 0 으로 올라오면서 읽는다.

for(i=0; i<imgInfo.Height; i++)
{
 BmpOffset = ((imgInfo.Height-1)-i) * (imgInfo.Width*2);
 for(j=0; j<(imgInfo.Width*2); j++)
 {
  Temp2[BmpOffset2+j] = Temp[BmpOffset+j];  //위에서 Big 방식으로 바       //꾼 값을 bmp에 맞추어서 역                                                          //순으로 정렬한다.
 }
 BmpOffset2 = (imgInfo.Size)-BmpOffset;
}
::Bmp는 이미지 데이터가 거꾸로 되어 있다. 이게 완전히 거꾸로가 아니라 Height만 거꾸로 되어 있다. 그래서 Height를 참고해서 역으로 바꾸는 것인데, BmpOffset에 가장 마지막 줄을 넣는다. 그리고는 I를 돌리면서 하나씩 빼게 된다.
그럼 결국 BmpOffset은 끝줄부터 첫줄까지 올라오게 된다. j가 하는 일은 BmpOffset이 가리키는 줄을 Temp2로 복사하는 일인데, 여기서 중요한 건 BmpOffset은 아래부터 가르키지만 BmpOffset2는 맨 위에서부터 가르킨다. 즉 BmpOffset은 마지막 줄에서 첫줄로 오지만 BmoOffset2는 가장 첫 줄부터 마지막 줄로 내려간다. 결국 Temp에 가장 마지막 줄을 Temp2로 복사한다. 이런식으로 계속 반복해서 Height를 거꾸로 저장시키게 된다. 여기서 (imgInfo.Width*2) 가 궁금할 법도 한데, 이건 "Timformat을 읽자"에서 Image Width 데이터 길이 계산에서 말했다. 이제 남은건 BMP 헤더를 작성하는 것이다.

//==================== BMP Info 작성 ========================

Image.Width = imgInfo.Width/1.5;
Image.Height = imgInfo.Height;
Image.Size = imgInfo.Size;

 

Image.Data = new BYTE[imgInfo.Size];
ZeroMemory(Image.Data,imgInfo.Size);

memcpy(Image.Data,Temp2,imgInfo.Size);

 

// 해더 파일
BMPFileHeader.bfType   = 19778;
BMPFileHeader.bfSize   = Image.Size + 54;  <-★
BMPFileHeader.bfReserved1  = 0;
BMPFileHeader.bfReserved2  = 0;
BMPFileHeader.bfOffBits   = 54;  <-★

 

BMPInfoHeader.biSize   = 40;
BMPInfoHeader.biWidth   = Image.Width;
BMPInfoHeader.biHeight   = Image.Height;
BMPInfoHeader.biPlanes   = 1;
BMPInfoHeader.biBitCount  = 24;
BMPInfoHeader.biCompression  = 0;
BMPInfoHeader.biSizeImage  = 0;
BMPInfoHeader.biXPelsPerMeter  = 0;
BMPInfoHeader.biYPelsPerMeter  = 0;
BMPInfoHeader.biClrUsed  = 0;
BMPInfoHeader.biClrImportant  = 0;

delete []Temp;
delete []Temp2;

::여기선 별로 어려운 부분이 없다 BmpFileHeader 하고 BmpInfoHeader을 써주는 것 뿐이다. 다만 여기서 ★인 부분을 봐보자.
54는 헤더에 크기 인데 왜 ★표까지 하면서 봐야 하는 이유는 나중에 팔레트를 쓰게 되면 이 부분에 팔레트 크기를 + 해주기 때문이다. 잊지 말도록하자. 아, 그리고 마지막 Temp,Temp2를 delete 해주는걸 잊지 말자
::여기까지 오면 Image구조체에 기본적으로 필요한 데이터들을 Bmp 방식대로 바꾸어 두었다. Bmp 헤더도 짜놨으니, 메모리 상에 Tim->Bmp로 변환되어 있는 거나 마찬가지이다. 물론 실제로 메모리에는 Tim과 Bmp 모두 올라와 있는 거지만....24Bit는 별로 처리해줄게 없어서 가장 쉽다. RGB가 각각 1Byte씩 차지하는 것도 그렇고.....그럼 이 강좌는 여기서 마치도록 하겠다.

 

-ConvertBMP(24Bit)를 끝마치며......
작성하는데 1시간 반 정도된 것 같군요. 24Bit는 상대적으로 가장 쉬운 것 같아요. 물론 제가 팔레트 같은 걸 다루어본 경험이 없어서 그럴지도 모르겠지만...제게는 가장 쉬웠고, 가장 편해서 24Bit 분석부터 시작했었습니다. ^^; 다음은 아마 가장 어려운 16Bit가 기다리고 있습니다. 지금 생각해보면 별로 어려운 것도 아닌데....그럼 이만

 

Tim을 분석하고 있는 사람, 하려는 사람, Tim에 대한 실질적인 프로그램 레퍼런스!
나, 다음에 사람을 위해서 강좌는 계속 됩니다.

칼루
나만의 강의 2004. 5. 2. 18:33
,
Powerd by Tistory, designed by criuce
rss