Welcome to the new Friends-of-FPC!

Here you can find all kinds of information about the FreePascal Compiler. We have many tutorials and howtos as well as a selection of tools to help you with your programming. We also have some example codes for you. And if you want to contribute some information/ sources/ tools yourself you can do so.
Also we have finally relaunched the FoFPC forum. It's your chance for some Q&A about everything FreePascal.

Friends-of-FPC

Tutorials: Learn how to code with FreePascal.

Source Codes: A collection of examples, miscellaneous source codes and open source stuff.

Tools and Help Files: Intro- duction of some tools that might help you with FPC.

Community

Forum: Ask or answer questions about the FreePascal Compiler, programming or just babble about coding.

Contribute! Contribute your own Tutorial, Source Codes or Tools and send them to us!

Website

About: Information about Friends-of-FPC.org.

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

Useful Links









Link to us