Advertisement
  1. Code
  2. OS X

Wie kann man Vektorgrafiken unter iOS erstellen?

by
Read Time:17 minsLanguages:

German (Deutsch) translation by Valentina (you can also view the original English article)

Einführung

Es gibt zwei grundlegende Arten von grafischen Ressourcen in der digitalen Welt: Raster und Vektor. Rasterbilder sind im Wesentlichen eine rechteckige Anordnung von Pixelintensitäten. Vektorgrafiken hingegen sind maThemetische Darstellungen von Formen.

Während es Situationen gibt, in denen Rasterbilder unersetzbar sind (z. B. Fotos), sind Vektorgrafiken in anderen Szenarien ein geeigneter Ersatz. Vektorgrafiken machen das Erstellen grafischer Ressourcen für mehrere Bildschirmauflösungen trivial. Zum Zeitpunkt des Schreibens gibt es auf der iOS-Plattform mindestens ein halbes Dutzend Bildschirmauflösungen.

Eines der besten Dinge an Vektorgrafiken ist, dass sie in jeder Auflösung gerendert werden können und dabei absolut klar und flüssig bleiben. Aus diesem Grund sehen PostScript- und TrueType-Schriftarten bei jeder Vergrößerung scharf aus. Da Smartphone- und Computerbildschirme Raster sind, muss das Vektorbild letztendlich als Rasterbild mit der entsprechenden Auflösung auf dem Display gerendert werden. Dies wird normalerweise von der Low-Level-Grafikbibliothek erledigt, und der Programmierer muss sich darüber keine Gedanken machen.

1. Wann werden Vektorgrafiken verwendet?

Schauen wir uns einige Szenarien an, in denen Sie die Verwendung von Vektorgrafiken in Betracht ziehen sollten.

App- und Menüsymbole, Elemente der Benutzeroberfläche

Vor einigen Jahren hat Apple Skeuomorphismus in der Benutzeroberfläche seiner Apps und von iOS selbst zugunsten mutiger und geometrisch präziser Designs vermieden. Schauen Sie sich zum Beispiel die Symbole der Kamera- oder Foto-App an.

Höchstwahrscheinlich wurden sie mit Vektorgrafiken erstellt. Entwickler mussten nachziehen und die meisten beliebten (Nicht-Spiel-) Apps wurden einer vollständigen Metamorphose unterzogen, um diesem Design-Paradigma zu entsprechen.

Spiele

Bei Spielen mit einfachen Grafiken (denken Sie an Asteroiden) oder geometrischen Themen (Super Hexagon und Geometry Jump) können die Sprites aus Vektoren gerendert werden. Gleiches gilt für Spiele mit prozedural generierten Levels.

Bilder

Bilder, in die Sie eine kleine Menge Zufälligkeit einfügen möchten, um mehrere Versionen derselben Grundform zu erhalten.

2. Bezierkurven

Was sind Bezier-Kurven? Lassen Sie uns, ohne auf die maThemetische Theorie einzugehen, nur über die Funktionen sprechen, die für Entwickler von praktischem Nutzen sind.

Freiheitsgrade

Bezier-Kurven zeichnen sich dadurch aus, wie viele Freiheitsgrade sie haben. Je höher dieser Grad ist, desto mehr Variationen kann die Kurve enthalten (aber auch maThemetisch komplexer).

Grad eins Beziers sind gerade Liniensegmente. Grad zwei Kurven werden Quad-Kurven genannt. Grad drei Kurven (Kubik) sind diejenigen, auf die wir uns konzentrieren werden, da sie einen guten Kompromiss zwischen Flexibilität und Komplexität bieten.

Kubische Beziers können nicht nur einfache glatte Kurven darstellen, sondern auch Schleifen und Höcker. Mehrere kubische Bezier-Segmente können durchgehend miteinander verbunden werden, um kompliziertere Formen zu bilden.

Kubische Beziers

Ein kubischer Bezier wird durch seine zwei Endpunkte und zwei zusätzliche Kontrollpunkte definiert, die seine Form bestimmen. Im Allgemeinen hat ein Grad n Bezier (n-1) Kontrollpunkte, ohne die Endpunkte zu zählen.

Ein attraktives Merkmal von kubischen Beziers ist, dass diese Punkte eine signifikante visuelle Interpretation aufweisen. Die Linie, die einen Endpunkt mit seinem benachbarten Kontrollpunkt verbindet, wirkt als Tangente an die Kurve am Endpunkt. Diese Tatsache ist nützlich für das Entwerfen von Formen. Wir werden diese Eigenschaft später im Tutorial ausnutzen.

Geometrische Transformationen

Aufgrund der maThemetischen Natur dieser Kurven können Sie problemlos geometrische Transformationen wie Skalierung, Drehung und Translation auf sie anwenden, ohne die Wiedergabetreue zu beeinträchtigen.

Das folgende Bild zeigt eine Auswahl verschiedener Arten von Formen, die ein einzelner kubischer Bezier annehmen kann. Beachten Sie, wie die grünen Liniensegmente als Tangenten an die Kurve wirken.

Cubic Bezier ShapesCubic Bezier ShapesCubic Bezier Shapes

3. Core Graphics und die UIBezierPath-Klasse

Unter iOS und OS X werden Vektorgrafiken mithilfe der C-basierten Core Graphics-Bibliothek implementiert. Darauf aufgebaut ist UIKit/Cocoa, das ein Furnier mit Objektorientierung hinzufügt. Das Arbeitstier ist die UIBezierPath-Klasse (NSBezierPath unter OS X), eine Implementierung einer mathemetischen Bezier-Kurve.

Die UIBezierPath-Klasse unterstützt Bezier-Kurven vom Grad eins (gerade Liniensegmente), zwei (Quad-Kurven) und drei (kubische Kurven).

Programmatisch kann ein UIBezierPath-Objekt Stück für Stück erstellt werden, indem neue Komponenten (Unterpfade) daran angehängt werden. Um dies zu vereinfachen, verfolgt das UIBezierPath-Objekt die Eigenschaft currentPoint. Jedes Mal, wenn Sie ein neues Pfadsegment anhängen, wird der letzte Punkt des angehängten Segments zum aktuellen Punkt. Jede zusätzliche Zeichnung, die Sie machen, beginnt im Allgemeinen an diesem Punkt. Sie können diesen Punkt explizit an einen gewünschten Ort verschieben.

Die Klasse verfügt über praktische Methoden zum Erstellen häufig verwendeter Formen wie Bögen und Kreise, (abgerundete) Rechtecke usw. Intern wurden diese Formen durch Verbinden mehrerer Unterpfade erstellt.

Der Gesamtpfad kann entweder eine offene oder eine geschlossene Form haben. Es kann sich sogar selbst schneiden oder mehrere geschlossene Komponenten aufweisen.

4. Erste Schritte

Dieses Tutorial soll einen Überblick über die Generierung von Vektorgrafiken geben. Aber selbst wenn Sie ein erfahrener Entwickler sind, der Core Graphics oder UIBezierPath noch nicht verwendet hat, sollten Sie in der Lage sein, mitzumachen. Wenn Sie mit diesem Theme noch nicht vertraut sind, empfehle ich, die UIBezierPath-Klassenreferenz (und die zugrunde liegenden Core Graphics-Funktionen) zu durchsuchen, wenn Sie noch nicht damit vertraut sind. Wir können nur eine begrenzte Anzahl von Funktionen der API in einem einzigen Lernprogramm ausführen.

Genug Gerede. Beginnen wir mit dem Codieren. Im weiteren Verlauf dieses Tutorials werde ich zwei Szenarien vorstellen, in denen Vektorgrafiken das ideale Werkzeug sind.

Starten Sie Xcode, erstellen Sie einen neuen Spielplatz und stellen Sie die Plattform auf iOS ein. Xcode-Spielplätze sind übrigens ein weiterer Grund, warum die Arbeit mit Vektorgrafiken jetzt Spaß macht. Sie können Ihren Code optimieren und sofort visuelles Feedback erhalten. Beachten Sie, dass Sie die neueste stabile Version von Xcode verwenden sollten, die zum Zeitpunkt des Schreibens 7.2 ist.

Szenario 1: Erstellen von Wolkenformen

Wir möchten Bilder von Wolken erstellen, die an einer grundlegenden Wolkenform haften und dabei eine gewisse Zufälligkeit aufweisen, sodass jede Wolke anders aussieht. Das grundlegende Design, für das ich mich entschieden habe, ist eine zusammengesetzte Form, die durch mehrere Kreise zufälliger Radien definiert ist, die entlang eines elliptischen Pfades zufälliger Größe (innerhalb geeigneter Bereiche) zentriert sind.

Zur Verdeutlichung sehen Sie hier, wie das Gesamtobjekt aussieht, wenn wir den Vektorpfad streichen, anstatt ihn zu füllen.

Anatomy of a CloudAnatomy of a CloudAnatomy of a Cloud

Wenn Ihre Geometrie etwas rostig ist, zeigt dieses Wikipedia-Bild, wie eine Ellipse aussieht.

EllipseEllipseEllipse

Einige Dienstprogrammfunktionen

Beginnen wir mit dem Schreiben einiger Hilfsfunktionen.

Die random(lower:upper:)-Funktion verwendet die integrierte arc4random_uniform()-Funktion, um Zufallszahlen im Bereich lower und (upper-1) zu generieren. Die Funktion circle(at:center:) generiert einen Bezier-Pfad, der einen Kreis mit einem bestimmten center und radius darstellt.

Punkte und Pfade generieren

Konzentrieren wir uns nun darauf, die Punkte entlang des elliptischen Pfades zu generieren. Eine Ellipse, die am Ursprung des Koordinatensystems zentriert ist und deren Achsen entlang der Koordinatenachsen ausgerichtet sind, hat eine besonders einfache maThemetische Form, die so aussieht.

Wir weisen Zufallswerte für die Längen der Haupt- und Nebenachse zu, sodass die Form wie eine Wolke aussieht, die horizontal mehr gestreckt als vertikal ist.

Wir verwenden die stride()-Funktion, um Winkel mit regelmäßigem Abstand um den Kreis herum zu erzeugen, und verwenden dann map(), um Punkte mit regelmäßigem Abstand auf der Ellipse unter Verwendung des obigen maThemetischen Ausdrucks zu erzeugen.

Wir erzeugen die zentrale „Masse“ der Wolke, indem wir die Punkte entlang des elliptischen Pfades verbinden. Wenn wir das nicht tun, bekommen wir eine große Lücke in der Mitte.

Beachten Sie, dass der genaue Pfad keine Rolle spielt, da wir den Pfad füllen und nicht streicheln. Dies bedeutet, dass es nicht von den Kreisen zu unterscheiden ist.

Um die Kreise zu erzeugen, wählen wir zunächst heuristisch einen Bereich für die zufälligen Kreisradien. Die Tatsache, dass wir dies auf einem Spielplatz entwickeln, hat mir geholfen, mit den Werten zu spielen, bis ich ein Ergebnis erzielt habe, mit dem ich zufrieden war.

Vorschau des Ergebnisses

Sie können das Ergebnis anzeigen, indem Sie im Ergebnisfenster rechts in derselben Zeile wie die Anweisung "Pfad" auf das Symbol "Auge" klicken.

Quick lookQuick lookQuick look

Letzter Schliff

Wie rastern wir dies, um das Endergebnis zu erhalten? Wir brauchen einen sogenannten "grafischen Kontext", in dem wir die Pfade zeichnen können. In unserem Fall zeichnen wir in ein Bild (eine UIImage-Instanz). An diesem Punkt müssen Sie mehrere Parameter festlegen, die angeben, wie der endgültige Pfad gerendert werden soll, z. B. Farben und Strichbreiten. Schließlich streicheln oder füllen Sie Ihren Pfad (oder beides). In unserem Fall möchten wir, dass unsere Wolken weiß sind und wir möchten sie nur füllen.

Packen wir diesen Code in eine Funktion, damit wir so viele Clouds generieren können, wie wir möchten. Und während wir gerade dabei sind, schreiben wir einen Code, um ein paar zufällige Wolken auf einem blauen Hintergrund (der den Himmel darstellt) zu zeichnen und all dies in die Live-Ansicht des Spielplatzes zu zeichnen.

Hier ist der endgültige Code:

Und so sieht das Endergebnis aus:

Final clouds imageFinal clouds imageFinal clouds image

Die Silhouetten der Wolken erscheinen im obigen Bild etwas verschwommen, aber dies ist einfach ein Artefakt zur Größenänderung. Das wahre Ausgabebild ist scharf.

Stellen Sie sicher, dass der Assistenten-Editor geöffnet ist, um ihn auf Ihrem eigenen Spielplatz anzuzeigen. Wählen Sie im Menü Ansicht die Option Assitant-Editor anzeigen.

Szenario 2: Generieren von Puzzleteilen

Puzzleteile haben normalerweise einen quadratischen „Rahmen“, wobei jede Kante entweder flach ist, eine abgerundete Lasche hat, die nach außen ragt, oder einen Schlitz derselben Form, um mit einer Lasche eines benachbarten Teils zu tesellieren. Hier ist ein Abschnitt eines typischen Puzzles.

Jigsaw piece puzzle prototypeJigsaw piece puzzle prototypeJigsaw piece puzzle prototype

Variationen mit Vektorgrafiken berücksichtigen

Wenn Sie eine Puzzle-App entwickelt haben, möchten Sie eine puzzleteilförmige Maske verwenden, um das Bild, das das Puzzle darstellt, zu segmentieren. Sie können sich für vorgenerierte Rastermasken entscheiden, die Sie mit der App geliefert haben. Sie müssen jedoch mehrere Variationen hinzufügen, um alle möglichen Formvariationen der vier Kanten zu berücksichtigen.

Mit Vektorgrafiken können Sie die Maske für jede Art von Stück im laufenden Betrieb generieren. Außerdem ist es einfacher, andere Variationen aufzunehmen, z. B. wenn Sie rechteckige oder schräge Teile (anstelle quadratischer Teile) wünschen.

Entwerfen der Puzzleteilgrenze

Wie gestalten wir das Puzzleteil tatsächlich, dh wie finden wir heraus, wie wir unsere Kontrollpunkte platzieren, um einen Bezierpfad zu erzeugen, der wie die gekrümmte Lasche aussieht?

Erinnern Sie sich an die nützliche Tangentialitätseigenschaft von kubischen Beziers, die ich zuvor erwähnt habe. Sie können beginnen, indem Sie eine Annäherung an die gewünschte Form zeichnen, diese in Segmente aufteilen, indem Sie abschätzen, wie viele kubische Segmente Sie benötigen (Sie kennen die Arten von Formen, die ein einzelnes kubisches Segment aufnehmen kann) und dann Tangenten an diese Segmente zeichnen, um dies herauszufinden wo Sie Ihre Kontrollpunkte platzieren könnten. Hier ist ein Diagramm, das erklärt, wovon ich spreche.

Bezier path for outward tabBezier path for outward tabBezier path for outward tab

Verknüpfen der Form mit den Kontrollpunkten der Bezierkurve

Ich entschied, dass vier Bezier-Segmente zur Darstellung der Registerkartenform gut geeignet sind:

  • zwei repräsentieren die geraden Liniensegmente an jedem Ende der Form
  • zwei repräsentieren die S-förmigen Segmente, die die Lasche in der Mitte darstellen

Beachten Sie die grünen und gelben gestrichelten Liniensegmente, die als Tangenten an die S-förmigen Segmente fungieren. Dadurch konnte ich abschätzen, wo die Kontrollpunkte platziert werden sollen. Beachten Sie auch, dass ich mir das Stück mit einer Länge von einer Einheit vorgestellt habe, weshalb alle Koordinaten Bruchteile von eins sind. Ich hätte meine Kurve leicht auf 100 Punkte lang machen können (Skalierung der Kontrollpunkte um den Faktor 100). Die Auflösungsunabhängigkeit von Vektorgrafiken bedeutet, dass dies kein Problem darstellt.

Zuletzt habe ich aus Bequemlichkeitsgründen kubische Beziers auch für die geraden Liniensegmente verwendet, damit der Code präziser und einheitlicher geschrieben werden kann.

Ich habe das Zeichnen der Kontrollpunkte der geraden Segmente im Diagramm übersprungen, um Unordnung zu vermeiden. Natürlich hat ein kubischer Bezier, der eine Linie darstellt, einfach die Endpunkte und Kontrollpunkte, die alle entlang der Linie selbst liegen.

Die Tatsache, dass Sie dies auf einem Spielplatz entwickeln, bedeutet, dass Sie die Kontrollpunktwerte leicht neu zusammenstellen können, um eine Form zu finden, die Ihnen gefällt, und umgehend Feedback zu erhalten.

Loslegen

Lass uns anfangen. Sie können denselben Spielplatz wie zuvor verwenden, indem Sie eine neue Seite hinzufügen. Wählen Sie im Menü Datei die Option Neu > Spielplatzseite oder erstellen Sie einen neuen Spielplatz, wenn Sie dies bevorzugen.

Ersetzen Sie den Code auf der neuen Seite durch Folgendes:

Generieren aller vier Seiten mithilfe geometrischer Transformationen

Beachten Sie, dass wir beschlossen haben, unseren Pfad 100 Punkte lang zu machen, indem wir eine Skalierungstransformation auf die Punkte anwenden.

Wir sehen das folgende Ergebnis mit der Funktion "Quick Look":

Quick LookQuick LookQuick Look

So weit, ist es gut. Wie erzeugen wir vier Seiten des Puzzleteils? Die Antwort ist (wie Sie sich vorstellen können), geometrische Transformationen zu verwenden. Durch Anwenden einer 90-Grad-Drehung, gefolgt von einer entsprechenden Verschiebung auf den path oben, können wir den Rest der Seiten leicht erzeugen.

Vorsichtsmaßnahme: Ein Problem bei der Innenfüllung

Leider gibt es hier eine Einschränkung. Die Transformation verbindet einzelne Segmente nicht automatisch miteinander. Obwohl die Silhouette unseres Puzzleteils gut aussieht, wird das Innere nicht gefüllt und wir werden Probleme haben, es als Maske zu verwenden. Wir können dies auf dem Spielplatz beobachten. Fügen Sie den folgenden Code hinzu:

Quick Look zeigt uns Folgendes:

Improperly-filled jigsaw pieceImproperly-filled jigsaw pieceImproperly-filled jigsaw piece

Beachten Sie, dass das Innere des Stücks nicht schattiert ist, was darauf hinweist, dass es nicht gefüllt wurde.

Sie können die Zeichenbefehle herausfinden, die zum Erstellen eines komplexen UIBezierPath verwendet werden, indem Sie dessen debugDescription-Eigenschaft auf dem Spielplatz untersuchen.

Behebung des Füllproblems

Geometrische Transformationen in UIBezierPath eignen sich sehr gut für den allgemeinen Anwendungsfall, dh wenn Sie bereits eine geschlossene Form haben oder die zu transformierende Form an sich offen ist und Sie geometrisch transformierte Versionen davon generieren möchten. Unser Anwendungsfall ist anders. Der Pfad fungiert als Unterpfad in einer größeren Form, die wir bauen und deren Innenraum wir füllen möchten. Das ist etwas kniffliger.

Ein Ansatz wäre, mit den Interna des Pfads (unter Verwendung der CGPathApply()-Funktion der Core Graphics-API) herumzuspielen und die Segmente manuell zusammenzufügen, um eine einzelne, geschlossene und ordnungsgemäß ausgefüllte Form zu erhalten.

Aber diese Option fühlt sich ein bisschen hackisch an und deshalb habe ich mich für einen anderen Ansatz entschieden. Wir wenden die geometrischen Transformationen zuerst über die CGPointApplyAffineTransform()-Funktion auf die Punkte selbst an und wenden genau dieselbe Transformation an, die wir vor kurzem versucht haben. Wir verwenden dann die transformierten Punkte, um den Unterpfad zu erstellen, der an die Gesamtform angehängt wird. Am Ende des Tutorials sehen wir ein Beispiel, in dem wir eine geometrische Transformation korrekt auf den Bezier-Pfad anwenden können.

Generieren der Stückkantenvariationen

Wie generieren wir den Tab "Innie"? Wir könnten wieder eine geometrische Transformation mit einem negativen Skalierungsfaktor in y-Richtung anwenden (ihre Form umkehren), aber ich habe mich dafür entschieden, dies manuell zu tun, indem ich einfach die y-Koordinaten der Punkte in outie_points invertiere.

Für die Registerkarte mit flachen Kanten hätte ich einfach ein gerades Liniensegment verwenden können, um zu vermeiden, dass ich den Code für bestimmte Fälle spezialisieren muss. Ich habe einfach die y-Koordinate jedes Punkts in outie_points auf Null gesetzt. Dies gibt uns:

Als Übung können Sie aus diesen Kanten Bezier-Kurven generieren und diese mithilfe der Schnellübersicht anzeigen.

Sie wissen jetzt genug, damit ich Sie mit dem gesamten Code blitzen kann, der alles in einer einzigen Funktion zusammenhält.

Ersetzen Sie den gesamten Inhalt der Spielplatzseite durch Folgendes:

Der Code enthält nur einige weitere interessante Dinge, die ich klarstellen möchte:

  • Wir verwenden enum, um die verschiedenen Kantenformen zu definieren. Wir speichern die Punkte in einem Wörterbuch, das die Aufzählungswerte als Schlüssel verwendet.
  • Wir setzen die Unterpfade (bestehend aus jeder Kante der vierseitigen Puzzleteilform) in der Funktion incrementalPathBuilder(_) zusammen, die intern für die Funktion jigsawPieceMaker(size edges:) definiert ist.
  • Nachdem das Puzzleteil ordnungsgemäß gefüllt ist, können wir mit der Methode applyTransform(_:) sicher eine geometrische Transformation auf die Form anwenden, wie wir in der Quick Look-Ausgabe sehen können. Als Beispiel habe ich das zweite Stück um 60 Grad gedreht.
Examples of jigsaw puzzle piecesExamples of jigsaw puzzle piecesExamples of jigsaw puzzle pieces

Schlussfolgerung

Ich hoffe, Sie davon überzeugt zu haben, dass die Fähigkeit, Vektorgrafiken programmgesteuert zu generieren, eine nützliche Fähigkeit in Ihrem Arsenal sein kann. Hoffentlich werden Sie inspiriert sein, an andere interessante Anwendungen für Vektorgrafiken zu denken (und diese zu codieren), die Sie in Ihre eigenen Apps integrieren können.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.