4.PlayStation 1(one) 해부학 - Mult Palette, It's Tim!!(4Bit & 8Bit)

Mult Palette, It's Tim!!


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

 

※이 문서는 제작자의 허락 없이 무단 배포 할 수 없음을 알려 드립니다.

 

- 목  차 -
14.Palette의 개념
15.Mult Palette
16.8Bit Tim분석
17.Mult Palette for 4Bit Tim
18.동적인 4Bit Palette
19.DrawImage
20.필자의 글

 

14.Palette의 개념

사실 이런건 다른 문서들을 보거나, 인터넷을 통해 좀 더 자세하게 배울 수 있을 것이다. 하지만 요즘 Palette를 사용하는 곳은 찾아 보기 힘들 정도로 많이 줄었다. 윈도우로 발전하면서, 대용량화되고 고속화 되어진 컴퓨터에 발전 때문에 더 이상 Palette의 필요성이 사라진 것 같다. Dos 시절에 프로그래머라면 분명 Palette에 빠삭하겠지만, 나 또한 Windows 프로그래머 시대라서 처음 이 Palette를 컨트롤 하는데 약간 난감한 부분도 없지 않아 있었던 것 같다. 순서라 하면 Palette를 알고 나서 16Bit나 24Bit처럼 그런식에 이미지 컨트롤을 배우는게 순서일지 모르겠지만 요즘은 시대가 많이 바뀌어서 오히려 거꾸로 되어진 것 같다.
 Palette는 무엇일까? 필자가 이 단어를 처음 알게 된건 아마 초등학교 3∼4학년쯤이 아닐까 생각된다. 우리는 그림을 그릴 때 Palette라는 곳에다가 사용하고자 하는 물감을 넣어둔 후에 붓으로 Palette에 있는 물감을 사용해서 그림을 그린다. 아크릴판 같은걸로 많이 사용했었는데.. 어쨌든 여기서 말하는 Palette도 그것과 똑같다. 과거 Dos시절에는 그림을 그리는 작업 자체가 상당한 일이였던 것 같다. 물론 지금도 꽤 큰 일이긴 하지만.... 어떤 그림에서 사용할 색을 정해서 그 색의 묶음을 Palette로 만들어 두고, 이것만을 가지고 그림을 그리는 것이다.
 앞의 강좌(16Bit,24Bit)에서는 이미지의 한 픽셀이 갖는 정보에 따라서 16Bit와 24Bit로 나뉘어 젔었다. RGB를 어떻게 표현하고 있는지, 그러한 정보들이 이미지 데이터에 있었지만, Palette를 사용하는 8Bit나 4Bit에서는 이미지 데이터는 단순히 Index에 불과하다. 다시 말해서 24Bit에서는 이미지 데이터 자체가 색의 정보를 담고 있었다. 하지만 Palette를 사용하는 이미지에서는 이미지 데이터가 색의 정보가 아니라 Palette의 Index라는 것이다.
 8Bit를 예로 들면 총 256가지의 색을 사용하는데, 이는 한 픽셀의 데이터 값이 0∼255 라는 것이다. 이것이 실제의 RGB값이 아닌 Palette의 번호라는 것이다. Palette에 0번부터 255번까지 색을 저장해둔다. 빨주노초파남보....등 조금 더 이해를 돕기 위해서 행렬로 보자.(표로 만들면 표를 안먹는 부분들이 많아서..)

[0번:빨강] [1번:노랑] [2번:파랑] [3번:초록] [4번:검정] [5번:보라] 등..
위에 표현한 색들은 예제일 뿐이

 

다. 실제로 저런 배열로 들어 있는 것이 아니니, 착각하지 말길 바라며 이렇게 Palette를 구성 해두고 내가 빨간색을 쓰고 싶다면 이미지 데이터에 0번이 들어가는 것이다.

0 1 2 2 3 4 0 1 2...-> 이미지 데이터를 뽑아서 얻는 이런 값들은 Palette의 Index라는 점을 잊지 말자.

그리고 Palette를 변경 했을 경우!! 아주 재미난 일이 일어난다. 위 예제와 같은 Palette를
[0번:보라] [1번:검정] [2번:초록] [3번:파랑] [4번:노랑] [5번:빨강]

이렇게 거꾸로 순서를 바꾸어 준다면? 이걸 그대로 0 1 2 2 3 4 0 1 2.. 이런 이미지 데이터에 적용해준다면 같은 그림이지만 색이 전혀 다른 그림이 나오게 되는 것이다.

 

15.Mult Palette
이제 Palette는 알겠다. 그렇다면

 PlayStation 1에서 사용하는 이 지긋지긋한 Tim에서 사용하는 Mult Palette라는 건 몰까? 말 그대로 여러개의 Palette를 가지고 있는 것이다. Palette를 사용하는 그림들은 아주 재미난 특성 하나가 존재한다. 위에서도 언급했다시피 바로 Palette를 바꾸면 그림은 같지만 색이 마구마구 변하는 것이다. 바로 이런 점을 이용해서 PlayStation에서는 효율적으로 그림들을 컨트롤 하고 있다. 이것이 바로 Tim에 진짜 힘이자 존재 이유일 것이다. 이 Mult Palette라는 것은 <Palette가 여러개 있다>는 이것 하나다. 어려운 건 하나도 없다. 아래에서 바로 이런 Palette의 특성을 이용한 Tim의 재미난 점들을 자세하게 서술하게 될 것이다.
 Tim에서 Palette는 최대 256개의 색상을 가질 수 있다. 1개의 색상은 RGB 그리고
Reserved, 이렇게 4개의 데이터로 구성되어 있다. 그렇다면 Tim에서 Palette가 차지하는 용량은 256*4=1,024 라는 계산이 나온다. 그리고 알아야 할 것이 8Bit에서는 256개의 색상 Palette를 가지지만 4Bit에서는 16개의 색상 Palette를 가지게 된다. 결국 Tim에서 할당해주는 Palette의 공간은 1,024인데 4Bit로 보자면 16*4=64 로 1,024에 16분의 1이 된다. 즉 64/1024 = 1/16 이라는 것이다. 바로 이곳에서 Mult Palette를 사용하는 것이다. 4Bit는 16개의 서로 다른 색상 테이블, 즉 각각 다 다른 16개의 Palette를 가질 수 있는 것이다. 16색상을 16개를 가지니 16*16=1,024로 계산이 성립된다.

 

16.8Bit Tim 분석
이제 Mult Palette까지 알았으니 8Bit를 분석해보자. 24Bit 만큼이나 쉽다. 먼저 우리가 해줘야 할 것은 8Bit Tim에서 Palette를 뽑아줘야 한다. 뽑기 싫으면 말고... Palette에 존재하는 색상 값은 15Bit BGR 이다. 이것은 전 강좌인 ConverBMP(16Bit)에서 비트마스킹을 이용해서 뽑아 냈었다. 이걸 응용하면 되는 것이다.


 Ptt = new RGBQUAD[256];

 Temp1 = (WORD *)PInfo.Data;
 for(i=0; i<256; i++)
 {
  b = (Temp1[i] >> 7) & 0xf8;
  g = (Temp1[i] >> 2) & 0xf8;
  r = (Temp1[i] << 3) & 0xf8;

 

  Ptt[i].rgbBlue = b;
  Ptt[i].rgbGreen = g;
  Ptt[i].rgbRed = r;
  Ptt[i].rgbReserved = 0;
 }


전 강좌와 마찬가지로 BYTE형으로 바꾸어서 읽고 쉬프트로 0∼31범위로 잡아준 후에 0xf8로 비트마스킹 하고 있다. 여기서 얻어낸 BGR을 Palette 변수에 넣어주고 있다.
 RGBQUAD 구조체는 내가 만든 것이 아니라 지원해주는 구조체이다. 아마 bmp format용으로 만들어진 구조체 일 것이다.
 이미지 데이터를 가공하는 건 똑같다. 먼저 이미지 데이터를 bmp처럼 역순으로 정렬한 뒤에 bmp Header을 쓴 후에 저장 할때 Ptt를 적절한 위치에 껴서 저장하면 된다.

 

 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] = imgInfo.Data[BmpOffset+j];
  }            //bmp에 맞추어서 역순으로 정렬한다.
  BmpOffset2 = (imgInfo.Size)-BmpOffset;
 }

//==================== BMP Info 작성 ========================
 Image.Width = imgInfo.Width*2;
 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);

 

이 소스만 보면 아래는 뻔하다는 걸

 

 알것이고, 다만 bmp로 저장할 때 Palette 정보가 어떻게 위치 되는지만을 언급 하겠다.

 

//Header 작성
WriteFile( Wfpt,&BMPFileHeader,sizeof(BITMAPFILEHEADER), &RWSize, NULL);
WriteFile( Wfpt,&BMPInfoHeader,sizeof(BITMAPINFOHEADER), &RWSize, NULL);

 

//Pallette Write
WriteFile( Wfpt, Ptt, 256*4, &RWSize, NULL);

  
//이미지 데이터 작성
WriteFile( Wfpt,Image.Data,Image.Size,&RWSize,NULL);

 

빨간색 부분이 작성한 Palette를 저장하는 것이다. bmp format은 header이 가장 먼저 오고 그 다음 Palette 정보가 온다. 이것이 다 온 뒤에 마지막으로 이미지 데이터가 오는 것이다. 이런 순서는 거의 모든 이미지 format들이 사용하고 있다. 물론 Tim도 이런 전체적인 순서는 다르지 않다.

 

17.Mult Palette for 4Bit Tim
드디어 16Bit, 그 이상에 난이도를 가지고 있는 4Bit Tim이다. Palette로 그림을 짤 경우, 하나의 아주 특이한 특성이 생기게 된다. 그것은 Palette를 교체하면 그림에 색상이 모두 교체한 Palette에 따라 바뀐다는 것인데, 바로 이걸 이용해서 아주 교모한 트릭을 사용한 format이 이 Tim이다. 위에서 4Bit는 최대 16개의 색상 Palette를 16개 가질 수 있다고 했다. 즉 색상 수로만 따지자면 256개를 사용 할 수 있는 것인데..이걸 이용하면 16장의 색이 다른 그림을 가질 수 있다는 것이다.
 첫장에 그림은 전체적으로 빨간색만을 사용하는 Palette를 쓰고 두 번째 장은 파란색, 세 번째 장은 초록색...이런식으로 16장의 그림을 "색이 다르게" 표현이 가능 하다는 것이다. 용량과 크기, 즉 메모리 점유는 같지만 16장의 색이 다른 그림을 표현하는게 가능 하다.

[예 : 같은 그림이지만 색이 모두 틀리다. 이렇게 Tim은 총 16장의 그림을 가질 수 있다]

 

이런 식으로 16장을 가진 다는 소리는 곧 16개의 Palette가 있다는 것인데, Palette를 잘 이해 했다면 이런건 그리 대단한게 아니라는 걸 알 수가 있다. Palette는 색상 정보 이고, 이미지 데이터는 색상 정보에 Index 값을 가지고 있는 구조 이다. 당연히 Palette를 교체하면 다른 색의 그림들을 얻을 수가 있다. 하지만 진짜 Tim은 바로 이 다음 부터라 할 수 있다.

 이런 구조 속에서 첫 번째장과 두 번째장의 그림이 다르다면?? 1개의 파일에 2∼3개의 그림이 존재한다면? 당연히 무압축이다. Palette가 가지고 있는 저런 교체되는 특징을 이용해서 1개의 그림 파일에 여러장의 그림파일을 위치 시키는 것이 가능하다. 물론 복잡한 그림이거나 하는건 힘들지도 모르겠지만, 어쨌든 여러장의 그림이 존재할 수가 있는 것이다.

 

<< 이것에 대한 강좌는 로츠님께서 쓰신 강좌를 그대로 인용하겠습니다. >>


<출처 : 한식구(http://hansicgu.hemosu.com)>
그림 크기 문제로, 링크를 걸어 두겠습니다.

링크

이런 Tim특징 중 중요하게 알아야 할 것은 서로 다른 그림을 하나에 데이터로 묶을 때 OR를 사용한다는 점이다. 이것은 나중에 다시 언급할 기회가 올 것 같다.
이제 소스를 보도록 하자.

 Ptt = new RGBQUAD[16];
 BYTE r,g,b;

 //RGB 값 뽑기 팔레트는 16BitBGR컬러로 저장되어 있다
 Temp1 = (WORD *)PInfo.Data;
 for(int i=0; i<16; i++)
 {
  b = (Temp1[i] >> 7) & 0xf8;
  g = (Temp1[i] >> 2) & 0xf8;
  r = (Temp1[i] << 3) & 0xf8;

  Ptt[i].rgbBlue = b;
  Ptt[i].rgbGreen = g;
  Ptt[i].rgbRed = r;
  Ptt[i].rgbReserved = 0;
 }
8Bit 분석할 때와 똑같다. 다만 for문을 16번 돈다는게 틀리다면 틀린 점이라 할 수 있다. 여기서 주목 해야될 점이 또 한가지 있다 그것은 Temp[i] 바로 이 부분인데, 이곳에 대가 16배수를 더해주면 다음 Palette를 가르킬 수 있다. Palette는 연속적으로 위치하고 있기 때문이다. 처음 0∼15 까지는 1번째 Palette가 되고 두 번째 16∼31 까지는 2번째 Palette가 되는 것이다. 이렇게 0∼15번까지 16개의 Palette가 위치되어 있으니 Temp[i+16의 배수]를 해준 다면 계속해서 다음 걸 지목 할 수 있을 것이다.

 

이제 해야 될 것은 이미지 데이터를 저장하는 일 인데 4Bit는 그리 쉽지만은 않다. 여기서 우리는 지긋지긋한 Big 방식과 Litte 인가 몬가 하는 그곳에 또 다시 부딧친것만 같다. 이건 내 추즉인데 Big 방식과 Litte인가 하는 그 것 때문에 4Bit 단위로 거꾸로 되어 있는 것 같다. 하지만 어째서 4Bit 단위로 그렇게 되어 있는지 또 모르겠다. 어쨌든 우리가 해야 되는 것은 거꾸로 되어 있는 것들을 제대로 읽어야 한다는 것이다. 아니 제대로 있는걸 거꾸로 해야 하는걸까?? 어쨌든 뒤집어 줘야지만 제대로된 값을 얻을 수가 있다.

 

 //4비트씩 자리가 바뀌여저 있기에 4비트씩 비트 마스킹을 한다.
 //0xf0 -> 1111 0000
 //0x0f -> 0000 1111
 BYTE Bit1,Bit2,Bit3;

 for(i=0; i<imgInfo.Size; i++)
 {
  Bit1 = imgInfo.Data[i] & 0xf0;
  Bit2 = imgInfo.Data[i] & 0x0f;
  Bit3 = (Bit2<<4) | (Bit1>>4);
  index4.push_back( Bit2 );  //나중에 사용
  index4.push_back( (Bit1>>4) ); //나중에 사용
  Temp2[i] = Bit3;
 }


우린 여기서 다시 비트마스킹을 사용해야 한다.
0000 0000 이런 1Byte에서 4Bit씩 끊어 오겠다면
1111 0000 A
0000 1111 B

이렇게 두가지로 비트마스킹 하면 각각에 상위 4Bit와 하위 4Bit를 끊어서 얻어 올 수 있다.
끊어서 얻어온 4Bit를 B를 상위로 끌어 올리고 A는 하위로 끌어 내리면 결국 4Bit씩 뒤집어서 저장 할 수 있다. 이 데이터를 가지고 이제 역순 정렬하고, bmp로 header짜고, 저장할 때에는 8Bit와 마찬가지로 저장 시켜주면 된다. 단! 이때 256 * 4가 아니라 16 * 4라는 점을 잊지 말자.

 

18.동적인 4Bit Palette
굳이 이런건 읽는 독자가 생각해서 하면 되지만, 4bit는 그 특성상 Palette를 바꾸어 주어야 할 필요가 있다. 그렇기 때문에 Palette를 읽어 오는 부분을 따로 만들어 두는 것이다. 그래서 때에 따라서 적절하게 바꾸어 주면 되겠다. TimCollection 에서와 같이 한 장의 4Bit 이미지에 있는 여러장으 그림을 볼 수 있도록 해주려면 Palette를 따로 읽는 루틴을 가저야 할 것이다.

 

void CTIM::PaletteBMP4()
{
//=========== 4 BPP Mode Palette 작성 =============

 WORD *Temp1;
 Ptt = new RGBQUAD[16];
 BYTE r,g,b;

 //RGB 값 뽑기 팔레트는 16BitBGR컬러로 저장되어 있다
 Temp1 = (WORD *)PInfo.Data;
 for(int i=0; i<16; i++)
 {
  b = (Temp1[i+NextNum] >> 7) & 0xf8;
  g = (Temp1[i+NextNum] >> 2) & 0xf8;
  r = (Temp1[i+NextNum] << 3) & 0xf8;

  Ptt[i].rgbBlue = b;
  Ptt[i].rgbGreen = g;
  Ptt[i].rgbRed = r;
  Ptt[i].rgbReserved = 0;
 }
}


그냥 함수화 했을 뿐이지 틀린게 없다. 다만 NextNum 이라는 변수가 하나 추가되었는데 위에서 말했다시피 16의 배수가 들어 가게 된다. 이 값에 따라서 여러개의 Palette를 읽어 들이게 되는 것이다.

 

19.DrawImaeg
여지껏 많은 짓거리들을 해 왔는데 이것들을 이제 화면에 출력해야 한다. bmp format 변환도 화면에 출력하기 위해서 처음 시도했었는데 이제는 화면 출력이 부수적인게 되버렸다.
bmp 출력은 어렵지 않다. 이미 역순정렬과 header도 모두 작성했기 때문에, 실제 메모리에는 tim과 bmp 모두가 올라와 있는 것이 된 거다. 때에따라 적절하게 사용하면 된다. dib bmp를 출력하는 것은 StretchDIBits 라는 함수가 제공된다. 다 알겠지만, hdc를 하나 만들어 두고 이곳에다가 모두 저장이 완료되면 이걸 고속으로 화면 hdc에 복사하는 방식을 사용한다. 게임쪽에선 플리핑이라 하는데, 이것 보다도 더 빠른 처리를 하는 것은 페이징 플리핑이라 하여 포인터만을 바꿔주는 거지만, 이런건 몰라도 되고 이 StretchDIBits를 통해서 16Bit와 24Bit 그림을 화면에 뿌릴 것이다.

 

BITMAPINFO Bitmapinfo;
ZeroMemory(&Bitmapinfo,sizeof(BITMAPINFO));
memcpy(&Bitmapinfo.bmiHeader,&BMPInfoHeader,40);
   StretchDIBits(hdc,POS_X,POS_Y,Image.Width,Image.Height,0,0,Image.Width,Image.Height ,(PBYTE)Image.Data,&Bitmapinfo,DIB_RGB_COLORS,SRCCOPY);

 

이런식이다 StretchDIBits이 함수에 대한 자세한건 msdn같은 곳에서 찾아 보길 바라며, 8bit와 4bit의 출력 방식을 보겠다. 이 두 녀석들은 직접 화면에 찍어주어야 한다. 처음 bmp 출력으로 Palette도 잡아주고, 출력을 시도했었는데 색도 제대로 표현 안될뿐더러 그때그때 상황에 따라 색이 변질 되거나 하는 일이 발생했었다. 그래서 느리지만 직접 하나씩 찍어주기로 하자. 물론 빠른 처리를 해야된다면 좀 더 좋은 방법을 생각해 보길 바란다.

 

int i,j;
WORD Offset=0;
list<WORD>::iterator iter=index4.begin();
for(i=0; i<Image.Height;i++)
{
 for(j=0; j<Image.Width;j++,iter++)
 {
  switch(TIMHeader.BPPMode)
  {
  case 0x08: //4bit
   ::SetPixel(hdc,j+POS_X,i+POS_Y,RGB(Ptt[*iter].rgbRed,
    Ptt[*iter].rgbGreen, Ptt[*iter].rgbBlue));
   break;
  case 0x09: //8bit
  ::SetPixel(hdc,j+POS_X,i+POS_Y,RGB(Ptt[imgInfo.Data[Offset+j]].rgbRed,
  Ptt[imgInfo.Data[Offset+j]].rgbGreen,Ptt[imgInfo.Data[Offset+j]].rgbBlue));
  break;
  }
 }
 Offset += Image.Width;
}


SetPixel 이라는 함수를 이용하는 것이다. 앞서 말했듯이 4,8Bit는 이미지 데이터 자체가 Index값이라 그 데이터 값을 읽어온 Palette에 대입하고 그걸로 얻어진 값을 RGB 함수로 색체와 시키면 된다. 8Bit는 그냥 가저와서 찍어주면 되므로 별다른게 없지만, 문제는 4Bit이다 각각에 Index 값이 4bit로 되어있기 때문이다. 여기서 필자는 알고리즘<List>를 사용해서 아까 주석문으로 "나중에 사용" 이라고 붙여둔 부분이 있을 것이다. STL을 사용한 것으로 쪼개둔 4Bit를 하나씩 모두 저장해둔 것이다. 이 부분에 대해서는 더 이상 언급하지 않겠다 필자는 List를 이용해서 4Bit 값을 하나씩 저장 시켜둔 것이다. 여기서 필요한건 4Bit 값을 하나씩, 얻어 와야 한다는 것이다. SetPixel 부분에서 다시 비트마스킹 해서 얻어와도 큰 상관은 없겠지만...그렇게 까지 한다

 

면 상당히 느려질 듯 싶어서 필자는 알고리즘<List>를 사용한 것이니, 이 부분은 자신에 능력껏 해결해야 할 듯 싶다. 이것까지 완성 되면 이제 한달여간 쓴 강좌가 끝이 난다.

 

20.필자의 글
이제 다 쓴 것 같습니다. 처음 시작할 때 "이것 까지 써야지" 라고 생각했던 부분입니다. 부족한게 많고, 불필요한 말이 많아서 강좌에 분량이 좀 크지만....강좌를 다 쓰고 이런 말까지 덧붙이기는 처음이랍니다. 그래도 꼭 한번 해보고 싶었습니다. 이걸로 강좌를 더 이상 쓰지 않겠다는게 아니라 이제 막 일단락 된거라 생각됩니다. 마지막 강좌여서 그런지 그림파일도 많이 첨부가 되었고, Palette 강좌 때문에 색상도 많이 쓰여지게 되었습니다. 어쨌든 끝을 낼 수 있어서 기쁘고, 제가 여지껏 쓴 강좌를 배포 할때에는 먼저 제게 E-Mail로 또는 다른 연락처로 제게 물어주시기 바랍니다. 거절 하는 일은 없겠지만 최소한의 예의라 생각됩니다. 힘들게 쓴 것이 아무런 동의 없이, 이곳저곳에 날라 다닌다는건 그리 기분 좋은 일이 아니기 때문 이죠. 또 항상 출처 표기는 정확하게 해야 된다는 점도 잊지 말아 주시길 바라며, Tim 강좌는 여기서 마치도록 하겠습니다.

 

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


 
칼루
나만의 강의 2004. 5. 9. 17:24
,
Powerd by Tistory, designed by criuce
rss