From YYpBD's MediaWiki
제 목:[강좌] 고속 이미지 출력을 위한 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