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