Basic optimieren - Teil 5
mad/os

Im heutigen Teil des Kurses geht es wie versprochen um den Grafikmodus 13. Dieser Modus arbeitet nur mit einer Ausflösung von 320 x 200 Punkten, aber dafür bietet er erstaunliche 256 Farben. Zumindest war das damals erstaunlich, als VGA langsam zum Standard wurde. Die Arbeit mit diesem Modus ist denkbar einfach, weil der komplette Grafikspeicher in den normalen Arbeitsspeicher bei Adresse A000:0000 "gespiegelt" ist. Sämtliche änderungen in diesem Speicherbereich wirken sich sofort auf den Inhalt des Bildschirmes aus. Und weil ein Bildpunkt genau ein Byte beansprucht, sind keinerlei aufwendige Umrechnungen nötig.

Trotz dieser Einfachheit (oder gerade deswegen?) kennt PowerBASIC bis heute kein SCREEN 13. Also müssen wir die dafür nötigen Befehle selbst schreiben. Allerdings ist das sehr gefährlich! Keine Angst, es kann nichts kaputt gehen. Aber wenn ein Programm versehentlich falsche Speicherstellen überschreibt, dann endet das praktisch immer mit einem Absturz. Es sollte also sehr regelmäßig gespeichert werden.

Die wichtigsten Befehle, die wir jetzt brauchen, sind DEF SEG, POKE und POKE$. Mit DEF SEG wird zuerst - und das ist extrem wichtig - der Speicherbereich &HA000 aktiviert. Fehlt dieser Befehl oder wurde eine andere Segmentadresse eingestellt, dann zerstört das Programm "irgendwelche" Speicherbereiche. Diesen wichtigen Grundsatz sollte man immer bedenken. Beginnen wir nun mit einem Unterprogramm, das den Befehl SCREEN ersetzt oder besser gesagt ergänzt.

sub VScreen (Modus)
  if Modus=13 then
    reg 1,&H13: call interrupt &H10
    def seg=&HA000
  elseif Modus=0 then
    reg 1,&H03: call interrupt &H10
    def seg
  end if
end sub

Jetzt muß der Bildschirm gelöscht werden. Das realisieren wir mit wenigen schnellen POKE$, die in einem Zug fast beliebig viele Bildpunkte zeichnen können. Und wenn wir gerade dabei sind, können wir unser neues CLS auch gleich um eine wählbare Farbe erweitern.

sub VCls (Farbe)
  def seg=&HA000
  z$=string$(320, Farbe)
  for i=0 to 199: poke$ i*320, z$: next
end sub

Der Befehl DEF SEG ist hier nicht unbedingt erforderlich, ich habe ihn zur Sicherheit jedoch noch einmal eingefügt. Und nun zu den wichtigsten Grundfunktionen, dem Setzen und Lesen von Bildpunkten. Dazu werden die Koordinaten des Punktes in eine Speicheradresse umgerechnet, an der dann ganz einfach der Farbwert gesetzt oder gelesen wird. Eine kurze Abfrage sorgt dafür, daß keine Speicherstellen außerhalb des sichtbaren Bildes überschrieben werden.

sub VPset (x, y, Farbe)
  if y<0 or y>199 then exit sub
  poke y*320+x, Farbe
end sub

function VPoint (x, y)
  VPoint=peek(y*320+x)
end function

Der Befehl LINE zählt mit seinen Variationen zu den wohl meistgenutzten Grafikbefehlen. Einen vollwertigen Ersatz dafür zu programmieren, ist schwierig. Darum habe ich für die Optionen B und BF zwei getrennte Unterprogramme vorgesehen.

sub VLine (x1, y1, x2, y2, Farbe, p)
  if p<1 then p=1
  lx=x2-x1: ly=y2-y1
  Laenge=sqr(lx*lx+ly*ly)
  dx!=lx/Laenge: dy!=ly/Laenge
  for i=0 to Laenge step p
    call VPset(x1+dx!*i, y1+dy!*i,Farbe)
  next
end sub

sub VLineB (x1, y1, x2, y2, f)
  if x1>x2 then swap x1,x2
  if y1>y2 then swap y1,y2
  if y1<0 or y2>199 then exit sub
  for i=y1+1 to y2-1
    call vpset (x1, i, f)
    call vpset (x2, i, f)
  next
  poke$ 320*y1+x1, string$(x2-x1+1, f)
  poke$ 320*y2+x1, string$(x2-x1+1, f)
end sub

sub VLineBF (x1, y1, x2, y2, f)
  if x1>x2 then swap x1,x2
  if y1>y2 then swap y1,y2
  if y1<0 or y2>199 then exit sub
  for i=y1 to y2
    poke$ i*320+x1, string$(x2-x1+1, f)
  next
end sub

Auch Kreise werden immer wieder gezeichnet. In der folgenden Prozedur geben S! und E! den Start- und Endwinkel in Radiant an. Der letzte Parameter A! bezeichnet ein Streckungs- oder Stauchungsverhältnis. Alle drei Parameter können auf Null gesetzt werden, wenn sie unerwünscht sind.

sub VCircle (x, y, r, Farbe, s!, e!, a!)
  if a!=0 then a!=0.8
  if s!=e! then s!=0: e!=6.283
  for i!=s! to e! step 0.01
    if a!>1 then
      nx=r*cos(i!)/a!: ny=r*sin(i!)
    else
      nx=r*cos(i!): ny=r*sin(i!)*a!
    end if
    call VPset (x+nx, y-ny, Farbe)
  next
end sub

Bisher fehlte jedem Basic ein Befehl, um gefüllte Kreise zu zeichnen. Kein Problem, schließlich schreiben wir jetzt alles selbst! Der Parameter A! für ein Seitenverhältnis kann auch hier wieder auf Null gesetzt werden, wenn er nicht gebraucht wird.

sub VCircleF (x, y, r, Farbe, a!)
  if a!=0 then a!=0.8
  if a!<1 then rz=fix(r*a!+0.5) else rz=r
  for i=0 to rz
    if a!>1 then
      j=sqr(r*r-(i-0.5)*(i-0.5)) / a!
    else
      j=sqr(r*r-(i-0.5)*(i-0.5)/(a!*a!))
    end if
    z$=string$(2*j+1, Farbe)
    if y-i>=0 then
      poke$ (y-i)*320+x-j, z$
    end if
    if y+i<200 then
      poke$ (y+i)*320+x-j, z$
    end if
  next
end sub

Das soll erst einmal genügen. Anhand dieser Beispiele kann jeder selbst weiter programmieren und neue Funktionen ergänzen, zum Beispiel für Vielecke. Auch ein Ersatz für GET und PUT ist relativ einfach realisierbar. Ich möchte an dieser Stelle nur noch zwei Befehle hinzufügen, die nicht das "Was" sondern das "Wie" steuern. Mit Hilfe der Prozedur RGBSetzen können die Farben beliebig verändert werden. Für die Rot-, Grün- und Blauanteile sind dabei Werte von 0 bis 63 erlaubt. Es ist also möglich, aus einer Palette von genau 262.144 Farben zu wählen und 256 davon gleichzeitig zu nutzen.

sub RGBSetzen (Farbe, r, g, b)
  out 968, Farbe
  out 969, r: out 969, g: out 969, b
end sub

sub RGBLesen (Farbe, r, g, b)
  out 967, Farbe
  r=inp(969): g=inp(969): b=inp(969)
end sub

Und nun als Höhepunkt ein Unterprogramm, das jedes beliebige 256-farbige Bitmap anzeigen kann. Es ist so weit optimiert, wie es mit reinem PowerBASIC überhaupt möglich ist. Auf meinem P100 dauert das Laden eines bildschirmfüllenden 320 x 200 großen Bitmaps ganze 0,04 Sekunden!

sub VBmp (x, y, Datei$)
  open Datei$ for binary as 1
  seek 1, 54: get$ 1, 1023, w$
  def seg = strseg(w$): p& = strptr(w$)
  for i = 0 to 255
    out &h3c8, i
    out &h3c9, peek(p& + i * 4 + 2) \ 4
    out &h3c9, peek(p& + i * 4 + 1) \ 4
    out &h3c9, peek(p& + i * 4) \ 4
  next
  seek 1, 18: get$ 1, 4, b$: b = cvi(b$)
  b2 = ((b+3) \ 4) * 4: def seg = &HA000
  get$ 1, 2, h$: h = cvi(h$)
  if y < 0 or y+h > 200 then exit sub
  for i = 0 to h - 1
    seek 1, 1078 + i * b2: get$ 1, b, l$
    poke$ (y + h - i - 1) * 320 + x, l$
  next
  close 1
end sub

Zum Schluß habe ich alle Funktionen in einem kurzen Beispielprogramm zusammengefasst. Der Befehl DEFINT ist dabei sehr wichtig, da er das ganze Programm noch einmal beschleunigt.

defint a-z
call VScreen (13): call VCls (0)
call VBmp (0, 0, "BMP256.BMP")
call VLine (10, 70, 60, 130, 1, 1)
call VLineB (70, 70, 120, 130, 2)
call VLineBF (130, 70, 180, 130, 3)
call VCircle (215, 100, 30, 4, 0, 0, 1.2)
call VCircleF (275, 100, 30, 5, 1.2)
for i=0 to 63
  call VPset (310, i+67, i+20)
  call RGBSetzen (i+20, i, i, 0)
next
Farbe=VPoint (310, 90)
call RGBLesen (Farbe, Rot, Gruen, Blau)
i$=input$(1): call VScreen (0)
print "Farbe:"; Farbe, Rot; Gruen; Blau

Ich mußte einige der besprochenen Funktionen für diesen Kurs stark kürzen. Meine Toolbox FREE-FONT bietet jedoch wesentlich mehr, bis hin zu völlig frei gestaltbaren Schriftarten. Diese Toolbox kann entweder von der real.fake Homepage geladen oder bei mir angefordert werden.

mad/os