|

Grundlagen der Grafikprogrammierung - Teil 7 - by Delax
Hi there!
Das letzte Mal haben wir fpcX kennen gelernt. fpcX stellt uns einige grundlegende Dinge zur Verfügung: den Grafikmodus und es sorgt für das Darstellen auf dem Bildschirm. WAS allerdings dargestellt werden soll müssen wir besorgen. Ich bin im letzten Teil auch besonders auf den Pointer eingegangen, den wir definierten. In diesem Grafikspeicher (manchmal auch Virtual Screen genannt) wird alles festgehalten, was ihr auf dem Bildschirm darstellen wollt. Ruft ihr dann die Blit Routine auf, wird es auch wirklich dargestellt.
Bei dem Beispiel haben wir einfach nur den ganzen Bildschirmspeicher auf eine Farbe gesetzt und gut wars. Jetzt wollen wir allerdings eine Routine, die gezielt einen Pixel mit dem angegebenen Farbwert an einen Punkt (X,Y) setzt. Wie wir das machen? Nun, wir wissen, das alles, was wir in den Virtual Screen schreiben auch dargestellt wird und wir wissen, das der Virtual Screen linear abläuft, aber im Grunde nur ein Abbild des Monitors ist.
Zu allererst müssen wir uns über eine Formel im klaren sein, welche einen beliebigen Punkt (X,Y) in diesem Virtual Screen beschreibt. Die Antwort ist simpler als gedacht, denn wir sind im letzten Teil bei dem kleinen 320x200 Exkurs schon darauf eingegangen. Sie lautet X*2 + (Y*MaxX)*2. X ist hierbei die gewünschte X Koordinate, Y die gewünschte Y Koordinate und MaxX ist die X Größe unserer Auflösung, also in diesem Falle 640. Wenn wir die 640 direkt angeben würden, könnten wir die Auflösung nicht nach belieben ändern, was ziemlich nervig wäre.
Was wollen wir alles beim Aufruf der Routine angeben? Sinnvoll ist: X und Y Koordinate und die Farbwerte der beiden Bytes. Etwa so: PutPixel(10,100,200,200); die würde einen Pixel an die Koordinate 10/100 setzen mit der Farbe 200/200. Was wir tun müssen ist den Pointer (10*2) + (100*640)*2 = 128030 Stellen entlangzuwandern und dort einmal 200 und in das darauffolgende Byte auch 200 einzutragen. Wie das? Assembler!
"Oh jeh! Assembler" höre ich Ruf, doch keine Sorge, ich werde jeden Schritt so gut wie möglich erklären. Zuvor jedoch noch einmal der Kopf unserer PutPixel Prozedur:
PROCEDURE PutPixel(destiny : pointer; x,y : longint; color1, color2 : byte);
Ich denke das sollte jeder verstehen. destiny ist der Ziel-Virtual Screen, x, y die Koordinaten und die beiden Bytes die Farbwerte. Nun zum eigentlichen Inhalt. Es handelt sich wie schon gesagt um Assembler, ich gehe aber Zeile um Zeile vor und halte den Code so einfach wie möglich.
mov edi,destiny Bindet den Virtual Screen an das Register edi.
mov eax,x Kopiert den Inhalt von x in das Register eax.
mov ebx,2 Kopiert den Wert 2 in das Register ebx.
imul ebx Multipliziert ebx mit eax (mit anderen Worten: die angegebene X Koordinate mal 2)
add edi,eax Bewegt sich im Virtual Screen um das Ergebnis nach vorne.
mov eax,MaxX Bewegt MaxX nach eax.
imul ebx Multipliziert ebx mit eax (mit anderen Worten: die angegebene MaxX mal 2)
imul y Multipliziert eax mit y (Also MaxX mal 2 mal der angegebenen Y Koordinate).
add edi,eax Bewegt sich im Virtual Screen um das Ergebnis weiter.
mov al,color1 Kopiert den Inhalt von color1 nach al
mov [edi],al Trägt an der Stelle im Pointer den Wert ein, der in al steht.
add edi,1 Bewegt sich im Virtual Screen um eins nach vorne.
mov al,color2 Kopiert den Inhalt von color2 nach al.
mov [edi],al Trägt an der Stelle im Pointer den Wert ein, der in al steht.
So schwer war das doch nicht, oder? Klar, Assembler ist ziemlich komisch zu Anfang, aber
es hat gerade bei so Sachen große Vorteile. Unser fertiger Ablauf sieht also so aus:
ASM
mov edi,destiny
mov eax,x
mov ebx,2
imul ebx
add edi,eax
mov eax,TestX
imul ebx
imul y
add edi,eax
mov al,color1
mov [edi],al
add edi,1
mov al,color2
mov [edi],al
END;
So. Un nun wäre es noch nett, wenn von vorne herein sämtliche unsinnigen Pixelangaben wie z.B. 701 als X Wert bei einer 640x480 Auflösung gleich ignoriert würden. Was haltet ihr von folgender Zeile:
if (x<0) or (x>MaxX) or (y<0) or (y>MaxY) then exit;
Jene Zeile kommt vor die eigentliche Assembler Routine und dann? Wenn einmal PutPixel mit den Werten 700,501,10,10 aufgerufen wird, ist dieser Aufruf unzulässig und die Routione steigt aus. Somit ist auch eine Art rudimentäres Clipping an den Rändern gewährleistet. unsere gesamte Prozedur sieht also so aus:
PROCEDURE PutPixel(destiny : pointer; x,y : longint; color1, color2 : byte);
BEGIN
if (x<0) or (x>MaxX) or (y<0) or (y>MaxY) then exit;
ASM
mov edi,destiny
mov eax,x
mov ebx,2
imul ebx
add edi,eax
mov eax,TestX
imul ebx
imul y
add edi,eax
mov al,color1
mov [edi],al
add edi,1
mov al,color2
mov [edi],al
END;
END;
Es sieht komplizierter aus, als es ist - schaut euch noch einmal die Zeilen-Erklärung an, wenn ihr dem ASM Zeug nicht traut. Sicher hätten wir das ganze auch in reinem Pascal machen können, aber erstens eigenet sich ASM sehr gut um an Pointern herumzuspielen und zweitens dachte ich dies sei mal ein sanfter Einstieg. Wie gesagt gibt es später noch mehr dazu.
Un nun zu einem kleinen Beispielprogramm, welches die PutPixel Routine auch nutzt:
{$APPTYPE GUI}
uses windows,sysutils;
PROCEDURE GetDefaultDirectDraw(ParentWindow : hWND; x,y : longint);stdcall;
external 'fpcx' name 'GetDefaultDirectDraw';
PROCEDURE CloseDirectDraw;stdcall;
external 'fpcx' name 'CloseDirectDraw';
FUNCTION DirectDrawBlit(buf : pointer) : boolean;stdcall;
external 'fpcx' name 'DirectDrawBlit';
VAR hWindow: HWnd;
hHDC: HDC;
thisistheend : boolean;
AMessage : Msg;
vidvscr : pointer;
CONST MaxX=640;
MaxY=480;
FUNCTION WindowProc(Window: HWnd; AMessage, WParam, LParam: Longint):
Longint; stdcall; export;
BEGIN
WindowProc:=0;
case AMessage of
wm_Destroy, //KeyDown or Closed ??!
wm_KeyDown : begin
thisistheend:=true;
ReleaseDC(hWindow,hHDC);
hHDC:=0;
PostQuitMessage(0);
Exit;
end;
end;
WindowProc:=DefWindowProc(Window, AMessage, WParam, LParam);
END;
FUNCTION WinRegister: Boolean; //Register Window Class
Var WindowClass: WndClass;
BEGIN
WindowClass.Style:=cs_ByteAlignClient;
WindowClass.lpfnWndProc:=WndProc(@WindowProc);
WindowClass.cbClsExtra:=0;
WindowClass.cbWndExtra:=0;
WindowClass.hInstance:=system.MainInstance;
WindowClass.hIcon:=LoadIcon(0, idi_Application);
WindowClass.hCursor:=LoadCursor(0, idc_Arrow);
WindowClass.hbrBackground:=GetStockObject(BLACK_BRUSH);
WindowClass.lpszMenuName:=nil;
WindowClass.lpszClassName:='Source3'; //Class Name
WinRegister:=RegisterClass(WindowClass)<>0;
END;
FUNCTION WinCreate: HWnd; //Create Window Class
Var hWindow: HWnd;
BEGIN
hwindow:=createwindowex(0,'Source3',pchar('DirectX'),
WS_VISIBLE or WS_POPUP,0,0,50,50,0,0,system.MainInstance,nil);
if hWindow<>0 then begin
ShowWindow(hWindow, CmdShow);
UpdateWindow(hWindow);
hHDC:=GetDC(hWindow);
end;
WinCreate:=hWindow;
END;
PROCEDURE PutPixel(destiny : pointer; x,y : longint; color1, color2 : byte);
BEGIN
if (x<0) or (x>MaxX) or (y<0) or (y>MaxY) then exit;
ASM
mov edi,destiny
mov eax,x
mov ebx,2
imul ebx
add edi,eax
mov eax,MaxX
imul ebx
imul y
add edi,eax
mov al,color1
mov [edi],al
add edi,1
mov al,color2
mov [edi],al
END;
END;
var counter1, counter2 : WORD;
BEGIN
WinRegister;
hWindow:=WinCreate;
GetDefaultDirectDraw(hWindow,MaxX,MaxY); //Init with Handle of our Window
getmem(vidvscr,MaxX*MaxY*2);
FOR counter1 := 1 TO MaxX DO
FOR counter2 := 1 TO MaxY DO
PutPixel(vidvscr,counter1,counter2,random(255),random(255));
while GetMessage(@AMessage,0,0,0) do begin
DirectDrawBlit(vidvscr); //Show it
TranslateMessage(AMessage);
DispatchMessage(AMessage);
end;
CloseDirectDraw; //Close
END.
Hier als Datei zum download
Neu ist einfach nur die eingesetzte PutPixel Routine und anstatt den Bildschirmspeicher mit einer Farbe zu füllen, setzen wir von links nach rechts zufällige Pixel. Ist zwar nicht der Hit, zeigt aber, das die Prozedur ihren Zweck erfüllt. Das nächste Mal kommen dann mehr kleine Spielereien.
Delax/ Sundancer Inc.
[delax@sundancerinc.de]
Back to previous page
|