iOS-Grafiken für Fortgeschrittene – Teil I: iOSGraphics (aka CGContext)

Naja, ok, der Titel klingt ein bisschen hochgestochen, soll aber Tenor einer kleinen Reihe von wannauchimmer erscheinenden Artikeln sein, die vielleicht ein wenig Licht in die iOS-Grafikprogrammierung bringen und umständliche sowie zeitfressende manuelle Lösungen unnötig machen.

Xojo, darüber habe ich ja schon mehrfach referiert, hat einen plattformübergreifenden Ansatz. Das heißt, von vielen Funktionen, die die Betriebssysteme zur Verfügung stellen, finden sich oftmals nur die kleinsten gemeinsamen Nenner, eben das, was alle Systeme gleichermaßen bieten. Entsprechend bleibt einem bei einer plattformübergreifenden Anwendung dann in der Regel die Wahl, grafische Finessen in Xojo-Code von Hand zu ergänzen oder auf optimierte Bibliotheken zurückzugreifen, die zumindest für einen Teil der verfügbaren Systeme Zugriff auf OS-Funktionen bieten.

Bei iOS ist die Sache ja ein wenig anders gelagert, weil Xojo – zumindest aktuell – keine anderen Mobilplattformen unterstützt. Da iOS ebenso wie sein großer Bruder OS X eine Vielzahl wirkich kräftig optimierter Grafikroutinen bietet, wäre es doch eigentlich schade, auf diesen Komfort zu verzichten. Das Xojo-iOS-Forum liefert eine Menge kleiner Codeschnipsel, die per Declare so manchen business-standard Grafikcode auf Actiongame-Niveau hieven. Aber es sind eben oftmals kleine Schnipsel, die kein so rechtes Gesamtbild ergeben wollen und das Programm schnell unübersichtlich machen. Zeit für ein wenig Abhilfe, und bei der Gelegenheit gleich wieder ein bisschen Eigenwerbung für die neue Version von iOSLib, oder vielmehr auf die dort neue AppleCGContext-Klasse.

CGContext? Falls Ihnen zum Verständnis der Kontext fehlt, habe ich dafür volles Verständnis. Klingt erst einmal wenig aussagekräftig, ist Ihnen vermutlich aber schon gut vertraut, nämlich von den Algorithmen, die Sie in den Paint-Event eines Canvas programmiert haben. Der Paint-Event bietet Ihnen ein geheimnisvolles Objekt, über das man außerhalb so gut wie nie stolpert: g As Graphics bzw. hier bei iOS natürlich g As iOSGraphics.

Sollten Sie damit noch keine Berührungen gehabt haben: Öffnen Sie an dieser Stelle einmal Xojo und schauen Sie sich in den beiliegenden Beispielprojekten einige der Graphics & Multimedia-Projekte an, insbesondere die mit „Canvas“ im Namen. In der Regel passiert dort etwas im Paint-Event des Canvas: Es wird das Grafikobjekt modifiziert – durch direktes Hineinmalen mit Vektorpfaden, durch Einblenden von Bildern, Anzeigen von Textzeilen oder ganzen Textblöcken etc. Im Code sieht das dann immer irgendwie so aus:

Sub Paint(g As iOSGraphics)
g.DrawLine 0, 0 ,me.width, me.height
End Sub

zeichnet eine vertikale Linie quer über einen Canvas.

Nur: Was ist dieses iOSGraphicsObjekt eigentlich? Wo ist es, wenn gerade kein Paint-Event gefeiert wird?

Wenn Sie in der Xojo-Sprachreferenz nachschlagen, finden Sie die erste Antwort schnell heraus: iOSGraphics entspricht der Apple-Klasse CGContextRef. Und diese ist, wenn Sie so wollen, eine virtuelle Zeichenoberfläche. Egal was und worauf Sie zeichnen mögen: Pixelbild, Text, Vektorgrafik auf Fenster bzw. View, Drucker oder PDF: Sie zeichnen einfach mit den verfügbaren Methoden dort hinein. CoreGraphics als grundlegende Grafik-Engine der Apple-Betriebssysteme kümmert sich dann um die Umrechnung in die ausgabegerätabhängigen Daten. Und da CoreGraphics so grundlegend ist, ist es auch kräftig optimiert und nutzt wo immer möglich die Grafikkarte oder weitere Parallelisierungstechniken zur schnellen Berechnung.

Das Ref hinter dem Namen im übrigen, weil CoreGraphics wie viele Kern-Frameworks der Apple-Systeme eigentlich gar nicht als Klasse aufgebaut ist, sondern nur die Referenz, der Pointer, von diversen geshareten Framework-Methoden angesprochen wird. In iOSLib habe ich aber eine Klasse nachempfunden, damit die Bedienung auf den gleichen komfortablen Standard gelangt wie von Xojo gewohnt.

Jedenfalls ist dies der Grund, warum Sie im Paint-Event nicht einfach in die Pixeldaten eines Canvas malen: CoreGraphics erlaubt es Ihnen, im Hintergrund alles vorzubereiten und dann erst die Ausgabedaten rechnen zu lassen.

Und wo ist der Context nun außerhalb des Paint-Kontexts? Einfache Antwort: Gar nicht. Wenn Sie mit einem Canvas arbeiten, erzeugt dieser seinen Context im Moment des Paint-Events selbst (bei anderen Objekten, die Sie via Context beeinflussen wollen, müssen Sie daher zunächst selbst einen Context in der gewünschten Größe erzeugen. Das schauen wir uns ein andermal an).

Was können Sie jetzt mit der neuen iOSLib-Erweiterung anfangen? Das iOSGraphics-Objekt im Paint-Event ist wie gesagt nichts anderes als der CGContext des Canvas. Die Methoden, die Ihnen standardmäßg geboten werden, sind also nicht optimierbar, da sie schon die Quartz-Routinen benutzen. Die iOSGraphicsExtension in iOSLib erweitert die Möglichkeiten aber zahlreich, und ein paar Anwendungsfälle möchte ich Ihnen hier zeigen:

Zunächst einmal können Sie so gut wie alle Funktionen ganz bequem ohne weitere Deklaration im Paint-Event verwenden, sobald Sie die iOSLib in Ihr Projekt kopiert haben. Autocomplete zeigt Ihnen dann alle Möglichkeiten: iosGraphics Extension
Und anderseits können Sie, sofern Sie sich in die Apple-Klassen hineingefunden haben, im Paint-Event den erweiterten GCContext aufrufen und mit den Apple-eigenen Datentypen und Klassen modifizieren. Das spart, wenn Sie eine Vielzahl von Contextbefehlen verwenden, jedesmal ein paar Zyklen Konvertierungszeit:

Dim myCGContext as New AppleCGContext (g)
ist der Befehl dafür im Paint-Event, wo g das vom Event gelieferte iOSGraphics-Objekt ist.

In den meisten Fällen allerdings sollte die indirekte, komfortable Programmierung über das Extension-Modul genügend schnelle Ergebnisse erzielen.

Egal wofür Sie sich entscheiden: Schauen wir uns die Arbeit mit CGContext mal genauer an!

 

Salamitaktik

Die Arbeit mit CGContext ähnelt klassischer, linearer Programmierung. Wir sind also fast doch wieder bei BASIC! 😀

Soll heißen: Alles, was Sie im Paint-Event programmieren, wird linear so abgearbeitet. Setzen Sie eine Füllfarbe, gilt diese für alle danach gezeichneten Objekte, bzw. bis Sie die Füllfarbe erneut verändern. Da es ja häufiger passieren kann, dass man mehrere Parameter nur für ein Grafikobjekt verändert, käme eine ganze Menge Programmierarbeit auf Sie zu, wenn Sie diese immer wieder rückgängig machen müssten, um dann das nächste Objekt mit neuen – oder den vorherigen – Einstellungen zu malen.

Damit Sie eben nicht den Überblick verlieren, verwendet CGContext eine Stack-Technik – eine Stapelbauweise. Mehrere Versionen des jeweils aktuellen Kontext können auf den Stapel gelegt und mit einem Befehl wieder vom Deck genommen werden, um die vorigen Einstellungen widerherzustellen. Diese Befehle liefert Xojo gleich standardmäig mit: Es sind

SaveState und RestoreState.

Sie sollten immer in Paaren vorkommen: Für jeden Save auch ein Restore, damit es keine Speicherleichen im iOS-Keller zu begraben gibt.

 

Malereien …

Wenn Sie einen der in iOSGraphics vorhandenen Vektorgrafikbefehle verwenden – von Drawline bis FillRoundRect – löst das eigentlich eine Vielzahl von CGContext-Befehlen aus: Es wird ggf. zu einem bestimmten Startpunkt gesprungen, ein neuer Subpfad (darauf kommen wir gleich zu sprechen) erstellt, und dieser schlussendlich gefüllt oder mit einem Umriss versehen. Das ist sehr komfortabel, aber wenn Sie einmal mehr Zeichengeschwindigkeit benötigen oder eigentlich einen zusammenhängenden, komplexeren Pfad erstellen wollen, eben nicht so performant wie eigentlich angelegt. Doch zunächst ein Wort zu den Pfaden:

Ein CGContext kann immer nur einen Vektorpfad gleichzeitig in Arbeit haben. Dieser Pfad muss aber nicht zwangsläufig aus den primitiven geometrischen Objekten bestehen, sondern kann aus einer Vielzahl von Geraden und Kurven bestehen – den Subpfaden. Man legt den Pfad zunächst an und ruft dann erst einen Stroke- (Umrisslinie) oder Fill-Befehl auf.

Pfadelemente hinzufügen können Sie mit den „Add…“-Befehlen. Ein AddArc mit den entsprechenden Parametern fügt also einen Bogen zum bestehenden Pfad hinzu, ein AddLineToPoint verlängert seinen Endpunkt mit einer Geraden usw. Wenn Sie noch kein Pfadelement begonnen haben, setzen Sie den Anfangspunkt mit MoveToPoint (x, y). Mit StrokePath oder FillPath schließlich umranden oder füllen Sie ihn – FillPath bietet dazu noch zwei Optionen zur Bestimmung der Innenfläche des Pfades. Sie finden hier sogar „Massenroutinen“, denen Sie ein Array von Linienpunkten oder Rechtecken übergeben können, und die dann z.B. das Zeichnen einer Liniengrafik locker doppelt so schnell bewerkstelligen wie die Einzelaufrufe – wenn Sie Ihr Datenformat sogar gleich entsprechend aufbereiten, also grundsätzlich in dieses Array oder besser gleich den Memoryblock schreiben, den die Befehle wie AddLines erwarten, können Sie sogar mit noch weitaus mehr Geschwindigkeit rechnen.

… und Blendereien

Üblicherweise können Sie den Farben der iOSGraphics-Befehle einen Alpha-Wert geben, sie also teiltransparent machen. Wenn Sie sie übereinanderlegen, ist das Ergebnis jedoch ein anderes als erwartet. Für echte Farbtransparenzen bei voller Deckkraft gibt es die BlendMode-Property, die Ihnen die verschiedenen, von den Grafikprogrammen her bekannten Verschmelzungsmethoden wie Multiplikation, Aufhellen, Abdunkeln und wasweißich alles bietet. Zusammen mit den LineCap- und LineJoin-Properties für wahlweise Rundung oder Kantung von Linienabschlüssen und –Eckpunkten ergibt das ein wirklich rundes Bild:

CGShapes

 

… und wenn Sie’s ganz auf die Spitze treiben wollen, dann nehmen Sie ein Bild, lassen es automatisch mit dem DrawTileImage-Befehl skalieren und kacheln, versehen mit einem farbigen Schatten, legen einen multiplizierten Gradienten darüber, der aus beliebig vielen Farben mit unterschiedlichen Abständen zueinander bestehen kann, lassen Text feinpositionieren (unterschneiden in Typo-Fachsprache) und geben ihm noch einen schwarzen Schatten.

Wie das geht, sehen Sie in den Beispielviews in iOSLib.

Ach, ganz wichtig dazu: Quartz benutzt ein auf dem Kopf stehendes Koordinatensystem,  hat den Nullpunkt der y-Achse also unten, nicht oben. Ich fürchte, das muss ich noch in die Modul-Befehle einpflegen. Für die direkte übereinstimmende Arbeit im Paint-Event ist das ein einfacher Code zur Umkehr des Koordinatensystems (achten Sie dann aber darauf, nur Befehle von AppleCGContext zu verwenden, oder kehren Sie vor Aufruf einer Xojo-iOSGraphcis-Routine wieder mit Restore zur Originalausrichtung zurück:

g.SaveState // den aktuellen Zustand speichern
g.Translate (0, g.Height) // Context um seine Höhe verschieben
g.Scale (1, -1) // und vertikal spiegeln.

iOSGraphics Menu

 

In absehbarer Zukunft werden noch einige Funktionen folgen – der Context bietet unter anderem PDF-Handling, Clipping-Masken und Farbkonvertierungen. Bis dahin: Lassen Sie mich gerne wissen, wie bunt Ihre Projekte werden – und wo noch ein wenig geschraubt werden sollte.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s