고속 이미지 출력을 위한 DIB 기법

From YYpBD's MediaWiki

Jump to: navigation, search

제  목:[강좌] 고속 이미지 출력을 위한 DIB 기법         관련자료:없음  [1323]
보낸이:안영기  (SMgal   )  1999-10-31 02:39  조회:892  추천:0


안녕하세요...

이번의 강좌는 '델파이에서 DIB를 제어하는 방법'입니다.
( 이 강좌의 바이너리 자료와 풀소스는 VTOOL 자료실에 올리겠습니다. )

DIB에 대해서 간략하게 소개하자면 Device Independent Bitmap 입니다.

윈도우가 일반적으로 사용하는 DDB ( Device Dependent Bitmap ) 은 상당히
느립니다. 여러분들은 직접 폼에 그라데이션으로 점을 찍기 위해 다음과 같은
코드를 쓰신 적이 있을 겁니다.

--------------------------------------------------------------------------

   for j := 0 to Pred(Height) do
   for i := 0 to Pred(Width)  do begin
      k := (i * j) * (Width * Height) div (256*256);
      Canvas.Pixels[i,j] := RGB(k,k div 5,k div 10);
   end;
   // 결과는 직접 해보시면 압니다.. ^^;

--------------------------------------------------------------------------

이렇게 해보면 윈도우즈라는 운영체제가 얼마나 느린 것인지를 몸으로 깨닫게
될 겁니다. 이게 다 DDB 방식 때문이죠...

하지만 대부분의 윈도우 모드의 게임( 주로 일본쪽 게임들.. Direct X 를 쓰지
않음 )이 거의 순식간에 저런 그림들을 640 * 480 크기의 창에 찍어냅니다.

느린 윈도우즈의 그래픽 기능을 게임등을 위해 극대화 시킨 것이 WinG 라는
것이었고 Win95 가 나오면서 그 기능들은 고스란히 DIB 로 넘어 왔습니다.

그리고 그 덕분에 게임이나 그래픽이 많이 들어가는 애플리케이션에 고속으로
그림을 출력할 수 있는 방법이 생긴 것입니다.

보통 DDB 모드에서 흰색을 찍으려면 항상 RGB(255,255,255) 를 이용해서
32 bit 값인 TColorRef 로 변형이 됩니다.

만약 바탕화면이 하이컬러(16bit)로 설정되어있는 컴퓨터에서 (x,y)에 흰색을
찍는다면 윈도우즈의 메카니즘은 이렇습니다.

   Canvas.Pixels[x,y] := RGB(255,255,255); // clWhite 와 결과 같음

   1. RGB(255,255,255) 는 $00FFFFFF 라는 32bit 값으로 바뀐다.
   2. 현재의 바탕화면 모드를 알아 본다.. 결과 16 bit 하이컬러
   3. $00FFFFFF 라는 값을 실제 비디오 모드인 하이컬러에 맞게 수치를
      조정한다. 하이컬러의 점 구조는  R:G:B 가 각각 5:5:5 이기 때문에
      $07FF 라는 word 값으로 변화한다.
   4. 그 값을 (x,y)에 맞는 비디오 메모리에 쓴다.
      공식은 (((Form1.Top + y) * Screen.Width) + Form1.Left + x) * 2
      이며 이렇게 구해진 비디오 메모리 주소에 $07FF를 넣는다.
   5. 결과가 모니터 화면에 보인다.

대충 느끼셨겠지만 DDB 가 이런 구조로 출력을 하기 때문에 느릴 수 밖에
없는 것입니다.

그렇다면 똑같은 조건에서 256 컬러 DIB를 쓴다면 어떻게 될까요...

   1. 처음에 딱 한번 팔레트를 정의해준다.
      255번 인덱스를 가진 RGB(255,255,255) 라는 팔레트 값은 팔레트 세팅 때
      바탕 화면에 맞는 $7FFF 로 변경되어 진다.
   2. 255 라는 byte 값은 팔레트를 참조해 보니 $7FFF로 이미 바뀌어져 있다.
      즉 그 값을 변경없이 바로 사용할 수 있다.
   3. 비디오 메모리의 주소를 구해야 하지만 DIB 는 항상 순차적인 사각형
      영역으로 정의되므로 항상 이전의 점에서 +2 를 해준 값이 주소가 된다.
      즉 제일 처음에 (0,0) 때만 계산을 해주고 나머지는 더하기로 처리한다.
   4. 결과가 화면에 보인다.

( '2' 가 종종 들어가는 이유는, 예를든 하이컬러 모드가 16bit, 즉 2byte 가
  점 하나를 구성하는 구조이기 때문입니다... )

글로만 대충봐도 빠르다는 것을 알수 있을 겁니다.

변환 해야할 모든 것을 제일 처음에 딱 한번만 하기 때문에 실제 데이터를
쓸 때는 팔레트 인덱스에 있는 값만 참조하며 쓰면 되기 때문입니다.
( 물론 이런 것들은 컴퓨터가 알아서 해줍니다. 우리는 그냥 쓰기만 하면
  되는 것입니다. )



아래는 제가 자료실에 올릴 자료의 소스들에 대한 설명입니다.


< MainUnit.Pas >

--------------------------------------------------------------------------

unit MainUnit;

interface

uses
   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
   SubUnit1, ExtCtrls;
// ~~~~~~~~ 이 Unit에 DIB 제어하는 방법이있습니다.

type
   TBasic = class(TForm)
      Timer1    : TTimer;

      procedure FormCreate(Sender: TObject);
      procedure FormDestroy(Sender: TObject);
      procedure FormKeyDown(Sender: TObject; var Key: Word;
                            Shift: TShiftState);
      procedure HandleMessage(var Msg : TMsg; var Handled : boolean);
      procedure IdleHandler(Sender : TObject; var Done : boolean);
      procedure Timer1Timer(Sender: TObject);

   private
      FCurrentColorSet : integer;

   public
      Bump             : TBump;
      // TBump 는 SubUnit1 에 정의된 class 입니다.

      procedure UpdateScreen(is_commandable : boolean);
   end;

var
   Basic : TBasic;

implementation

{$R *.DFM}

procedure TBasic.FormCreate(Sender: TObject);
begin

   Width  := SOURCE_X_WIDE;
   Height := SOURCE_Y_WIDE+24;

   // 아래의 주석을 지우면 전화면으로 DIB가 출력됩니다.
{
   Width  := Screen.Width;
   Height := Screen.Height;
}
   Color  := clBlack;
   Cursor := crNone;

   // SubUnit1 에 설정된 TBump 객체의 초기화입니다.
   Bump   := TBump.Create('test.raw');

   FCurrentColorSet      := 0;

   // 메세지가 발생하면 생길 핸들러 설정
   Application.OnMessage := HandleMessage;
   // 메세지가 큐가 비었을때 ( 메세지가 없을 때 ) 생길 핸들러 설정
   Application.OnIdle    := IdleHandler;
end;

procedure TBasic.FormDestroy(Sender: TObject);
begin
   // 유저가 만든 2개의 핸들러 제거
   Application.OnMessage := nil;
   Application.OnIdle    := nil;
   Bump.Free;
end;

procedure TBasic.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
   if Key = VK_ESCAPE then Close;
end;

procedure TBasic.HandleMessage(var Msg : TMsg; var Handled : boolean);
begin
   UpdateScreen(TRUE);
end;

procedure TBasic.IdleHandler(Sender : TObject; var Done : boolean);
begin
   UpdateScreen(TRUE);
   Done := FALSE;
end;

procedure TBasic.UpdateScreen(is_commandable : boolean);
begin
   Bump.DoAction;
end;

procedure TBasic.Timer1Timer(Sender: TObject);
begin
   if Assigned(Bump) then begin
      FCurrentColorSet := Succ(FCurrentColorSet mod MAX_SECTION);
      Bump.UpdatePalette(FCurrentColorSet);
   end;
end;

end.

--------------------------------------------------------------------------

위의 짧은 소스를 보시면 대충 전체적인 구조가 이해 되실 겁니다.
OnMessage 와 OnIdle 이벤트 시에는 모두 UpdateScreen() 으로 점프하게 되고
UpdateScreen() 은 Bump.DoAction 을 부르게 됩니다. 물론 타이머에 의해서
정기적으로 Bump.UpdatePalette() 가 호출됩니다.

쉽게 설명해서, 프로그램이 시작하면 계속 Bump.DoAction 을 호출하게 된다는
이야기입니다..


그럼 SubUni1 유닛에 있는 내용을 봐야겠죠..
문제의 TBump.DoAction 은 아래와 같이 정의되어 있습니다.

--------------------------------------------------------------------------

procedure   TBump.DoAction;
var
   i             : integer;
   BumpParameter : TBumpParameter;
begin
   FillChar(FScreenPage,sizeof(FScreenPage),0);

   for i := 1 to MAX_BUMP_OBJECT do begin
      if FBumpObject[i].doAction(BumpParameter) then begin
         ExcuteBumping(BumpParameter);
      end else begin
         FBumpObject[i].Free;
         FBumpObject[i] := nil;
         FBumpObject[i] := TBumpObject.Create(random(320),random(200),10,10,
                                              92,10,3,boExplose);
      end;
   end;

   FlipPage;
end;

--------------------------------------------------------------------------

뭐 대충 보니까.. FScreenPage 라는 것을 0 으로 Clear 하고 MAX_BUMP_OBJECT
만큼 FBumpObject[i].doAction()을 실행하고 마지막에 FlipPage를 합니다.

딴것은 다 그러려니 하고 자료실에 올라온 자료를 실행해 보시면 되겠지만
여기서 중요한 것이 바로 FlipPage 입니다.

이름 그대로 Page 를 Fliping 하는 것인데 여기서 DIB 를 이용해 고속으로
화면에 뿌려주는 기능이 들어 있습니다.



먼저 FlipPage 가 실행될때는 FScreenPage 라는 메모리 영역에 그 점의 컬러
인덱스가 들어가 있습니다. ( 인덱스에 대한 개념은 위에 말씀 드렸습니다. )

--------------------------------------------------------------------------

procedure   TBump.FlipPage;
type
   TBitMapInfo = record
      bmiHeader : TBitmapInfoHeader;
      bmiColors : array[0..pred(MAX_PALETTE)] of TRGBQuad;
   end;
var
   i            : integer;
   temp_palette : hPalette;
   BitmapInfo   : TBitMapInfo;
   DC           : hDC;
begin

   FillChar(BitmapInfo,sizeof(BitmapInfo),0);

   // DIB 정보를 기록합니다.
   with BitmapInfo.bmiHeader do begin
      // 헤더의 크기
      biSize        := sizeof(BitmapInfo.bmiHeader);
      // 출력할 DIB 의 가로 크기
      biWidth       := SOURCE_X_WIDE;
      // 출력할 DIB 의 세로 크기
      biHeight      := SOURCE_Y_WIDE;
      // 출력할 DIB 의 플레인수, 256 컬러는 1개다.
      biPlanes      := 1;
      // 출력할 DIB 의 픽셀bit수, 8 이면 8bit 컬러, 즉 256 컬러
      biBitCount    := 8;
      // DIB는 RGB 식으로 되어 있다.
      biCompression := BI_RGB;
      // DIB 메모리의 크기
      biSizeImage   := SOURCE_X_WIDE*SOURCE_Y_WIDE;
   end;

   with BitmapInfo, FLogPalette do
   for i := 0 to 255 do begin
      // 256 개의 팔레트를 입력한다.
      bmiColors[i].rgbRed   := palPalEntry[i].peRed;
      bmiColors[i].rgbGreen := palPalEntry[i].peGreen;
      bmiColors[i].rgbBlue  := palPalEntry[i].peBlue;
   end;

   // 찍을 곳의 DC ( Device Context ) 를 구한다.
   DC := Basic.Canvas.Handle;

   // 현재 팔레트를 저장한다.
   temp_palette := SelectPalette(DC,FPalette,FALSE);
   RealizePalette(DC);

   // 문제의 DIB 함수, 아래에 다시 설명한다.
   SetDIBitsToDevice(DC,0,0,SOURCE_X_WIDE,SOURCE_Y_WIDE,0,0,0,SOURCE_Y_WIDE,
                     @FScreenPage,Windows.TBitmapInfo((@BitmapInfo)^),
                     DIB_RGB_COLORS);

   // 이전의 팔레트로 복귀
   SelectPalette(DC,temp_palette,FALSE);

end;

--------------------------------------------------------------------------

의외로 이렇게 사용법이 간단합니다..
SetDIBitsToDevice() 함수가 제일 문제가 되는 것인데... 이것이 DIB 를 실제
화면에 뿌려주는 역할을 합니다.

보시면 파라메터가 굉장히 많습니다. 그만큼 한꺼번에 많은 일을 한다는 것이죠.

SetDIBitsToDevice(
                  // 출력될 곳의 Device Context
                  DC,
                  // 출력될 곳의 DC 상의 상대적인 (x,y) 좌표
                  0,0,
                  // 출력될 Width 와 Height
                  SOURCE_X_WIDE,SOURCE_Y_WIDE,
                  // DIB 에서 출력을 시작할 (x,y) 좌표
                  0,0,
                  // DIB 의 몇번째 줄부터 몇줄을 출력할 것인가
                  // 지금은 0 번째(첫번째) 줄부터 Height 만큼
                  0,SOURCE_Y_WIDE,
                  // DIB 가 저장된 메모리 시작 번지
                  @FScreenPage,
                  // DIB 정보
                  Windows.TBitmapInfo((@BitmapInfo)^),
                  // 이 DIB 는 RGB 기반이다.
                  DIB_RGB_COLORS
);

이렇게 파라메터를 넘겨주면 알아서 윈도우즈가 그 배경화면 모드에 맞게
변환해서 뿌려줍니다.

사실 말로해서는 제대로 설명하기 어려운 부분이 많은데.. 일단 자료실에
올린 실행 파일의 결과를 보시고 풀 소스를 보시면 금방 이해가 되리라고
생각합니다... 그리고 풀 소스에는 확대 축소까지 하는 기능을 가진
StretchDIBits() 라는 함수의 용법도 있습니다.

( 제 말이 더 어려운 것 같아서 죄송할 따름입니다... T_T )

그럼...  SMgal.

1999/10/31


맞춤검색