Gedrehte Bildschirmausschnitte

GFA-Programmierer wissen die Effekte des allmächtigen PUT-Befehls zu schätzen. Wie wäre es aber, wenn man ihm noch einige weitere Parameter mit auf den weg geben könnte, etwa für einen genauen Drehwinkel oder die gewünschte Abbildungsgrösse ...?

Diese kleine Routine gestattet es. einen per GET eingelesenen Ausschnitt in einem beliebigen Winkel zu drehen und in ebenso beliebiger Größe darzustellen. Sie arbeitet nur in hoher Auflösung (640 x 400 Punkte). Die Breite des Ausschnitts muß ganzzahlig durch 16 teilbar sein, die Wahl der Höhe allerdings ist frei. Beim Drehen stehen die vier gebräuchlichsten Grafikmodi zur Verfügung.

Die Praxis

Die Syntax der Prozedur ist eng an den PUT-Befehl angelehnt und von daher denkbar einfach: put(x,y,b,h,a$,m,a). x und y beschreiben dabei - wie gehabt - die Koordinate der linken oberen Ecke des (eventuell) gedrehten Ausschnitts, b und h die gewünschte Breite und Höhe. Für b und h gilt: Ein negativer Wert zeigt an, wie die Originalgröße dargestellt werden soll, ein Mitführen von Variablen zur Speicherung dieser Werte ist also nicht erforderlich. a$ hat den Grafikausschnitt zum Inhalt und ist durch GET zu initialisieren, m zeigt den Modus der Verknüpfung von Vorder- und Hintergrund an; möglich sind die Werte von 1 bis 4 (s. u.). a ist schließlich derRotationswinkel in Altgrad (Null bis 360, gültig sind natürlich auch alle Kommazahlen), um den der Ausschnitt im Uhrzeigersinn gedreht werden soll (s. Abb. 1).

Die Prozedur arbeitet ohne jeden Seiteneffekt; sie verändert also weder die Übergabevariablen noch irgendwelche Grafikeinstellungen (auch nicht den Bildschirmmodus). Beim Einsatz dieser Routine reagiert die Grafikausgabe auch auf Clipping - im Gegensatz zum konventionellen PUT-Befehl. Leider ergeben sich in der Anwendung dieser Prozedur einige Einschränkungen:

  1. Die Projektion eines 96 x 96 Punkte großen Ausschnitts nimmt im Schnitt etwa 45 Sekunden in Anspruch, das Kompilat benötigt immerhin noch 35. Mit einem erweiterten PUT-Befehl haben wir es also nur bedingt zu tun. Diese Routine wird ihren Einsatz also »nur« zum Zwecke von Bildgestaltungen (z. B. in Zeichenprogrammen) finden, nicht aber zur Realisierung von Animationen.

  2. Beim Drehen kann der Bildschirm, der ja den Aufbau einer Matrix hat, nicht »flächendeckend« bemalt werden. So bleibt ein gedrehtes schwarzes Rechteck nicht überall schwarz, und ein weißes Rechteck, projiziert auf einen dunklen Hintergrund, ist an einigen Stellen zwangsläufig gesprenkelt. Diese unliebsamen Effekte sind nicht auf eine Unzulänglichkeit des Programms zurückzuführen, sondern rühren von der Natur der Rastergrafiken her. Hier hilft nur die Korrektur durch ein Zeichenprogramm. Soll ein Ausschnitt auf einem durchweg weißen Hintergrund abgebildet werden, ist Modus 2 (durchsichtig) dem Modus 1 (ersetzen) in jedem Fall vorzuziehen.

  3. Die Veränderung der Ausschnittgröße klappt zwar tadellos, jedoch entstehen bei der Vergrößerung Lücken in dunklen Flächen (wieder dasselbe Problem!). Hier ließe sich die Routine übrigens noch verbessern, indem sie bei der Vergrößerung weiß oder schwarz gefüllte Rechtecke statt kleiner Punkte zeichnete (was allerdings hübsche »Treppen« entstehen läßt). Ohnehin bietet es sich aber an, Ausschnitte, die größenverändert dargestellt werden sollen, anfangs möglichst groß zu entwerfen und hinterher zu verkleinern.

  4. Bei Rotationswinkeln, die nicht ein Vielfaches von 90 sind, kann ein Ausschnitt in seiner Größe nicht unproportional verändert werden; in solchen Fällen muß also beispielsweise ein Quadrat ein Quadrat bleiben - dies allerdings in beliebiger Größe. Im Bedarfsfall wird der Parameter h automatisch entsprechend korrigiert.

Die Theorie

Zur mathematischen Betrachtung der Drehung: Gegeben sei eine Gerade AB mit A = {3:2} und B = {8;2} (s. Abb. 2). Soll diese Gerade am Punkt A um 10 Altgrad im Uhrzeigersinn gedreht werden, berechnet sich der Punkt B' wie folgt (alte Schneider-Freaks werden sich erinnern):

B'= {3+ (8-3) cos 10;2
    + (8-3) sin 10} = {7,92;2.87}.

Einen Beweis bleibe ich an dieser Stelle der Bequemlichkeit halber schuldig.

Mit diesem Wissen läßt sich nun der Inhalt eines Rechteckes der Breite b und der Höhe h drehen. Δy läuft dabei in Einerschritten die Werte von 0 bis h durch, Δx analog von 0 bis b. So wird jeder Punkt {Δx;Δy} des Rechteckes abgefragt, in meiner Routine zeilenweise von oben nach unten, in der Zeile von links nach rechts. Wie sich der durch GET erhaltene String zu diesem Zweck zerpflücken läßt, ist weiter unten beschrieben.

Nun findet der »zweifache Schneider« Einsatz (s. Abb. 3):

  1. Zunächst müssen von jeder gedrehten Zeile die Koordinaten des Rotationspunktes R = {x0;y0} der gleichzeitig den Anfangspunkt darstellt, ermittelt werden. Nichts leichter als das: R = {x + Δy cos (90 + α);y + Δy sin (90 + α)}. {x;y} ist dabei der linke obere Punkt des gedrehten Rechteckes, α der Rotationswinkel.

  2. Nun finden sich die Koordinaten eines jeden Punktes im gedrehten Rechteck: {Δx;Δy}' = (x0 + Δx cos α;y0 + Δx sin α). Das war's.

Das Programm

Im Programm werden die GFA-Funktionen SINQ und COSQ verwendet. Dies hat den praktischen Grund, daß diese - im Gegensatz zu SIN und COS - gleich in den gewünschten Altgrad rechnen. Zudem sind die verwendeten Funktionen etwas schneller; eine etwaige Rechenungenauigkeit hat für den Einsatz der Berechnung von Bildschirmkoordinaten keinerlei Bedeutung.

Der Befehl GET liefert einen String zurück, der folgenden Aufbau hat: a$ = MKI$(b-1) + MKI$(h-1) + MKI$(1) (konstant) + MKI$(1. 16 Punkte) + MKI$(2. 16 Punkte) + ... Jeder MKI$ ist zwei Zeichen lang und kann alle Werte von Null bis 65535 enthalten. Dieser Aufbau gestaltet sich etwas komplizierter, wenn b nicht ganzzahlig durch 16 teilbar ist oder im Farbbetrieb gearbeitet wird.

Per CVI erhält man den Wert eines einzelnen MKI$s, und ein einzelnes Bit a kann via BTST isoliert werden. Wer jetzt noch die genaue Formel wissen möchte, möge sich den Bandwurm im Listing zu Gemüte führen.

Nun zur Verknüpfung des Inhalts des gedrehten Rechteckes und des Hintergrundes, auf den dieses projiziert werden soll. Das Bit b, das die Information enthält, ob ein Punkt auf dem Bildschirm an der entsprechenden Koordinate bereits gesetzt ist oder nicht, kann durch POINT initialisiert werden. c sei schließlich das Bit, das aus der logischen Verknüpfung von a (Punkt im Rechteck) und b hervorgeht. Abhängig von c wird schließlich die Farbe des gesetzten Punktes gewählt (FALSE: 0, TRUE: 1).

Abb.1
Modus Verknüpfung Effekt
1 c = a Ersetzen
2 c = a OR b durchsichtig
3 c = a XOR b negativ
4 c = (NOT a) durchsichtig
OR b und negativ

Weitere Bildschirmmodi (es gibt überhaupt nur 16 mögliche) können schnell realisiert werden: die Routine ist in diesem Punkt sehr leicht zu verstehen.

Das Programm demonstriert die Arbeitsweise der Prozedur put (s. Abb. 4). Im linken oberen Viertel des Bildschirms zeigt es zwei Beispiele für eine Hintergrundverknüpfung. Rechts daneben wird der Ausschnitt größenverändert dargestellt. In der unteren Bildschirmhälfte sind schließlich einige Drehbeispiele zu bewundern.

' Gedrehte Bildschirmausschnitte in GFA-BASIC 3.0 
' (c) 1990 by Philip Köster 
' Am Waldbad 13 - 2122 Bleckede 6
'
DEFFILL 1,3,12 !Ausschnitt zeichnen
PBOX 0,0,95,95
'
TEXT 1,14," Dies ist"
TEXT 1,28," ein GET-"
TEXT 1,42,"Ausschnitt"
'
GET 0,0,95,95,ausschnitt$ !... und speichern
'
DEFFILL 1,2,4 !Demo: Hintergrundverknüpfung 
PBOX 0,0,319,199
'
FOR i|=1 TO 2
    TEXT (i|-1)*160,15,"Modus "+STR$(i|)+", Alpha = 0"
    TEXT (i|-1)*160,31,"b = 96, h = 96    "
    '
    put(40+(i|-1)*160,40,-1,-1,ausschnitt$,i|,0)   !Prozedur-Aufruf
NEXT i|
'
TEXT 320,15,"Modus 2, Alpha = 0" !Demo: Größenveränderung
TEXT 320,31,"b =48, h = 96"
'
put(360,40,48,96,ausschnitt$,2,0)
'
TEXT 480,15,"Modus 2, Alpha = 0"
TEXT 480,31,"b =48, h = 48"
'
put(520,40,48,48,ausschnitt$,2,0)
'
FOR i|=1 TO 4 !Demo: Drehen
    TEXT (i|-1)*160,215,"Modus 2, Alpha = " +STR$(i|*20)
    TEXT (i|-1)*160,231,"b =96, h = 96"
    '
    put(40+(i|-1)*160,240,-1,-1,ausschnitt$,2,i|*20)
NEXT i|
'
PRINT AT(1,25);"Taste drücken.";
~INP(2)
'
END !Jetzt geht's erst los ...
'
PROCEDURE put(x&,y&,pb&,ph&,ausschnitt$,modus|,alpha&)
    LOCAL dx&,dy&,x0&,y0%,b&,h&,fx,fy,quelle!,ziel!,ergebnis! !Nicht im Hauptprg stören
    '
    b&=CVI(MID$(ausschnitt$,1,2))+1 !Breite ...
    h&=CVI(MID$(ausschnitt$,3,2))+1 !... und Höhe des Ausschnitts feststellen
    '
    IF pb&<0 'Keine Größenveränderung in x-...
        pb&=b&
    ENDIF
    IF ph&<0 !... oder y-Richtung?
        ph&=h& 
    ENDIF
    '
    fx=pb&/b& !Projektionsfaktoren in x-...
    fy=ph&/h& !... und y-Richtung feststellen
    '
    IF alpha& MOD 90 !"Ungerade Drehung": Keine Veränderung der Proportionen erlaubt
        fy=fx 
    ENDIF
    '
    FOR dy&=0 TO h&-1 !Von oben nach unten 
        x0&=x&+dy&*fx*COSQ(90+alpha&) !Rotationspunkt der aktuellen ... 
        y0&=y&+dy&*fy*SINQ(90+alpha&) !... gedrehten Zeile feststellen
        '
        FOR dx&=0 TO b&-1 !Von links nach rechts 
            quelle!=BTST(CVI(MID$(ausschnitt$,7+(dy&*b&/16+dx& DIV 16)*2,2)),15-dx& MOD 16) !Aktuelles Bit aus String isolieren
            '
            ziel!=POINT(x0&+dx&*fx*COSQ(alpha&),y0&+dx&*fy*SINQ(alpha&)) !Punkt gesetzt?
            '
            SELECT modus| !Verknüpfungsart feststellen
            CASE 1 !GRAPHMODE 1
                ergebnis!=quelle!
            CASE 2 !GRAPHMODE 2
                ergebnis!=quelle! OR ziel!
            CASE 3 !GRAPHMODE 3
                ergebnis!=quelle! XOR ziel!
            CASE 4 !GRAPHMODE 4
                ergebnis!=(NOT quelle!) OR ziel!
            ENDSELECT
            '
            IF ergebnis! !Farbe bestimmen 
                COLOR 1 
            ELSE
                COLOR 0 
            ENDIF
            '
            PLOT x0&+dx&*fx*COSQ(alpha&),y0&+dx&*fy*SINQ(alpha&) !Punkt setzen
        NEXT dx& 
    NEXT dy&
RETURN !War doch gar nicht so viel.

Philip Köster
Links

Copyright-Bestimmungen: siehe Über diese Seite