Cum se creează grafică vectorială pe iOS

Introducere

Resursele grafice din lumea digitală sunt de două tipuri de bază, raster și vector. Imaginile raster sunt în esență o gamă rectangulară de intensități ale pixelilor. Grafica grafică, pe de altă parte, reprezintă reprezentări matematice ale formelor.

În timp ce există situații în care imaginile raster sunt de neînlocuit (de exemplu, fotografiile), în alte scenarii, grafica vectorială face înlocuitori capabili. Grafica grafică face sarcina de a crea resurse grafice pentru rezoluții multiple ale ecranului trivial. La momentul scrisului, există cel puțin o jumătate de duzină de rezoluții de ecran pe care să le poți lupta pe platforma iOS.

Unul dintre cele mai bune lucruri despre grafica vectorială este că ele pot fi redate la orice rezoluție rămânând însă absolut clare și netede. De aceea, fonturile PostScript și TrueType arată ascuțite la orice mărire. Deoarece afișările smartphone-urilor și ale computerului sunt în natură, în cele din urmă, imaginea vectorului trebuie să fie redată pe ecran ca o imagine raster la rezoluția corespunzătoare. Acesta este, de obicei, îngrijit de biblioteca grafică la nivel scăzut, iar programatorul nu trebuie să-și facă griji în legătură cu acest lucru.

1. Când să utilizați Vector Graphics?

Să aruncăm o privire asupra unor scenarii în care ar trebui să luați în considerare utilizarea graficii vectoriale.

Pictograme de aplicații și meniuri, Elemente de interfață utilizator

Acum câțiva ani, Apple a evitat skeuomorfismul în interfața de utilizare a aplicațiilor sale și a iOS-ului în sine, în favoarea unor modele îndrăznețe și geometrice precise. Uitați-vă de pictogramele Aparat foto sau Foto, de exemplu.

Mai mult decât probabil, acestea au fost concepute folosind instrumente grafice vectoriale. Dezvoltatorii au trebuit să urmeze exemplul și majoritatea aplicațiilor populare (care nu au fost folosite în jocuri) au suferit o metamorfoză completă pentru a se conforma acestei paradigme de proiectare.

Jocuri

Jocurile cu grafică simplă (gândiți Asteroizii) sau teme geometrice (Super Hexagon și Geometry Jump veniți în minte) pot avea spritele lor redate de la vectori. Același lucru este valabil și pentru jocurile care au niveluri generate în mod procedural.

Imagini

Imaginile în care doriți să injectați o cantitate mică de aleatorie pentru a obține mai multe versiuni ale aceleiași forme de bază.

2. Bezier Curves

Care sunt curbele lui Bezier? Fără a se îndrepta spre teoria matematică, să vorbim doar despre caracteristicile care sunt de folos practic pentru dezvoltatori.

Grade de libertate

Curbele Bezier sunt caracterizate de câte grade de libertate ei au. Cu cât este mai mare acest grad, cu atât mai multe variații pe care le poate include curba (dar și cea mai complexă din punct de vedere matematic).

Gradul 1 Beziers sunt segmente de linie dreaptă. Curbele de gradul doi se numesc curbele quad. Se vor concentra trei curbe (cubi), deoarece acestea oferă un bun compromis între flexibilitate și complexitate.

Cubic Beziers pot reprezenta nu numai curbe simple, ci și bucle și cuspuri. Câteva segmente Bezier cubice pot fi cuplate până la capăt pentru a forma forme mai complexe.

Cubic Beziers

Un Bezier cubic este definit de cele două puncte de capăt și de două puncte suplimentare de control care determină forma sa. În general, o diplomă n Bezier are (N-1) punctele de control, fără a lua în calcul punctele finale.

O caracteristică atractivă a lui Beziers cubice este că aceste puncte au o interpretare vizuală semnificativă. Linia care leagă un punct de capăt de punctul său de control adiacent acționează ca o tangentă la curbă la punctul final. Acest fapt este util pentru proiectarea de forme. Vom exploata această proprietate mai târziu în tutorial.

Transformări geometrice

Din cauza naturii matematice a acestor curbe, puteți să le aplicați cu ușurință transformări geometrice, cum ar fi scalarea, rotirea și traducerea, fără a pierde fidelitatea.

Următoarea imagine prezintă un eșantion de diferite tipuri de forme pe care le poate lua un singur Bezier cubic. Observați modul în care segmentele liniei verzi acționează ca tangente la curbă.

3. Core Graphics și UIBezierPath Clasă

Pe iOS și OS X, grafica vectorială este implementată utilizând biblioteca Core Graphics bazată pe C. Construit pe partea de sus este UIKit / Cocoa, care adaugă un furnir de orientare obiect. Munca de lucru este UIBezierPath clasa (NSBezierPath pe OS X), o implementare a unei curbe matematice Bezier.

UIBezierPath clasa suportă curbele Bezier de gradul unu (segmente de linie dreaptă), două (curbele quad) și trei (curbe cubice).

Programat, a UIBezierPath obiect poate fi construit bucată în parte prin adăugarea de noi componente (subpaths) la ea. Pentru a facilita acest lucru, UIBezierPath obiect ține evidența currentPoint proprietate. De fiecare dată când adăugați un nou segment de cale, ultimul punct al segmentului atașat devine punctul curent. Orice desen suplimentar pe care îl faceți în general începe în acest moment. Puteți muta în mod explicit acest punct într-o locație dorită.

Clasa are metode convenabile pentru a face forme uzuale, cum ar fi arce și cercuri, dreptunghiuri (rotunjite), etc. Pe plan intern, aceste forme au fost construite prin conectarea mai multor subpaths.

Calea globală poate fi o formă deschisă sau închisă. Poate chiar să se intersecteze sau să aibă mai multe componente închise.

4. Noțiuni de bază

Acest tutorial este menit să servească drept un aspect dincolo de elementele de bază ale generării grafice vectoriale. Dar chiar dacă sunteți un dezvoltator experimentat care nu a folosit Core Graphics sau UIBezierPath înainte, ar trebui să puteți urmări. Dacă sunteți nou în acest sens, vă recomandăm să treceți prin UIBezierPath (și funcțiile Core Graphics de bază) dacă nu sunteți deja familiarizat cu acesta. Putem exersa doar un număr limitat de caracteristici ale API într-un singur tutorial.

Vorbiți destul. Să începem codarea. În restul acestui tutorial, voi prezenta două scenarii în care grafica vectorială este instrumentul ideal de utilizat.

Activați Xcode, creați un nou loc de joacă și setați platforma la iOS. Incidental, locurile de joaca Xcode sunt un alt motiv pentru care lucrul cu grafica vectoriala este acum distractiv. Puteți modifica codul și puteți obține feedback instantaneu vizual. Rețineți că ar trebui să utilizați cea mai recentă versiune stabilă a Xcode, care este de 7,2 în momentul acestei scrieri.

Scenariul 1: Efectuarea de forme cloud

Ne-ar plăcea să generăm imagini de nori care să adere la o formă de bază a norului, având în același timp o aberație, astfel încât fiecare nor pare diferit. Designul de bază pe care l-am stabilit este o formă compusă, definită de mai multe cercuri de raze aleatorii centrate de-a lungul unei căi eliptice de mărime aleatoare (în intervale adecvate).

Pentru a clarifica, iată ce arată obiectul general dacă am mângâiat calea vectorială în loc să o umplem.

Dacă geometria dvs. este puțin ruginită, atunci această imagine Wikipedia arată cum arată o elipsă.

Unele funcții utilitare

Să începem prin a scrie câteva funcții de ajutor.

"javascript import UIKit

func randomInt (inferior inferior: Int, superior: Int) -> Int assert (inferior < upper) return lower + Int(arc4random_uniform(UInt32(upper - lower)))

(în centru: CGPoint, rază: CGFloat) -> UIBezierPath retur UIBezierPath (arcCenter: centru, radius: radius, startAngle: 0, endAngle: CGFloat (2 * M_PI)

aleatoare (inferior: :) superior funcția utilizează built-in-ul arc4random_uniform () pentru a genera numere aleatorii în interval inferior și (Superior-1). cerc (la: centru :) funcția generează o cale Bezier, reprezentând un cerc cu o dată centru și rază.

Generarea de puncte și căi

Să ne concentrăm acum pe generarea punctelor de-a lungul căii eliptice. O elipsă centrată la originea sistemului de coordonate cu axele aliniate de-a lungul axelor de coordonate are o formă matematică foarte simplă, care arată astfel.

P (r, θ) = (cos cos (θ), b sin (θ))

Atribuiți valori aleatoare pentru lungimile axei sale majore și minore astfel încât forma să arate ca un nor, mai alungit orizontal decât vertical.

Noi folosim pas() funcția de a genera unghiuri distanțate în mod regulat în jurul cercului și apoi utilizați Hartă() pentru a genera puncte distanțate periodic pe elipsă utilizând expresia matematică de mai sus.

"javascript lasă a = Double (randomInt (inferior: 70, superior: 100)) b = dublu (randomInt (inferior: 10, superior: 35)

(0: 0, n: 0) .map CGPoint (x: a * cos (2 * M_PI * $ 0)) “

Noi generăm "masa" centrală a norului prin aderarea la punctele de-a lungul căii eliptice. Dacă nu, vom obține un gol mare în centru.

"javascript lasa cale = UIBezierPath () path.moveToPoint (puncte [0])

pentru punct în puncte [1 ...

path.closePath ()“

Rețineți că calea exactă nu contează, pentru că vom umple calea și nu ne vom călca. Aceasta înseamnă că nu se poate distinge de cercuri.

Pentru a genera cercurile, alegem mai întâi din punct de vedere euristic o gamă pentru razele de cerc aleatorii. Faptul că dezvoltăm acest lucru într-un loc de joacă ma ajutat să joc cu valorile până când am obținut un rezultat cu care mă mulțumesc.

"javascript lasa minRadius = (Int) (M_PI * a / ndiv) lasa maxRadius = minRadius + 25

pentru punct în puncte [0 ...

cale"

Previzualizarea rezultatului

Puteți vizualiza rezultatul făcând clic pe pictograma "ochi" din panoul de rezultate din partea dreaptă, pe aceeași linie cu instrucțiunea "cale".

Finalul atinge

Cum rasterizăm acest lucru pentru a obține rezultatul final? Avem nevoie de ceea ce este cunoscut ca un "context grafic" în care să tragem căile. În cazul nostru, vom face o imagine (a UIImage instanță). În acest moment este necesar să setați mai mulți parametri care să specifice ce cale finală va fi redată ca, cum ar fi culorile și lățimile cursei. În cele din urmă, vă accidentați sau vă umpleți calea (sau ambele). În cazul nostru, vrem ca norii noștri să fie albi și nu vrem să le umplem.

Să adăugăm acest cod într-o funcție, astfel încât să putem genera cât mai mulți nori pe care le dorim. Și în timp ce ne aflăm la el, vom scrie un cod pentru a desena câteva nori aleatorii pe un fundal albastru (reprezentând cerul) și pentru a trage toate acestea în vizualizarea live a locului de joacă.

Iată codul final:

"importul javascript UIKit import XCPlayground

func generateRandomCloud () -> UIImage

func randomInt (inferior inferior: Int, superior: Int) -> Int assert (inferior < upper) return lower + Int(arc4random_uniform(UInt32(upper - lower)))  func circle(at center: CGPoint, radius: CGFloat) -> UIBezierPath întoarcere UIBezierPath (arcCenter: centru, rază: rază, startAngle: 0, endAngle: CGFloat (2 * M_PI), în sensul acelor de ceasornic: true) a = Datorită faptului că nu este suficient să se folosească o mulțime de obiecte pentru a obține o valoare mai mare decât cea a lui. * M_PI * $ 0), y: b * sin (2 * M_PI * $ 0)) permite calea = UIBezierPath () path.moveToPoint (puncte [0]  

Clasa Vizualizare: UIView override func drawRect (rect: CGRect) permite ctx = UIGraphicsGetCurrentContext () UIColor.blueColor () setFill () CGContextFillRect (ctx, rect)

 (ctx, CGRect (x: 20, y: 20, lățimea: CGImageGetWidth (cloud1), înălțimea: CGImageGetHeight (cloud1) CGImage permite cloud2 = generateRandomCloud (). ) CGContextDrawImage (ctx, CGRect (x: 300, y: 100, lățime: CGImageGetWidth (cloud2), înălțime: CGImageGetHeight (cloud2) lățime: CGImageGetWidth (cloud3), înălțime: CGImageGetHeight (cloud3)), cloud3) 

XCPlaygroundPage.currentPage.liveView = Vizualizare (cadru: CGRectMake (0, 0, 600, 800))

"

Și acesta este rezultatul final:

Silhouts de nori apar un pic încețoșat în imaginea de mai sus, dar acesta este pur și simplu un artefact de redimensionare. Imaginea reală de ieșire este ascuțită.

Pentru ao vedea în propriul Teren de joacă, asigurați-vă că Editor asistent este deschis. Selectați Afișați Editorul Assitant de la Vedere meniul.

Scenariul 2: Generarea de piese de puzzle

Jigsaw puzzle-urile au, de obicei, un "cadru" pătrat, fiecare margine fiind fie plată, având o lamă rotunjită care iese în afară, fie o fâșie de aceeași formă pentru a se tese cu o lamă dintr-o piesă adiacentă. Iată o secțiune a unui puzzle tipic.

Acoperirea variațiilor cu grafică vectorială

Dacă ați fost în curs de dezvoltare o aplicație puzzle, ar trebui să utilizați o mască în formă de puzzle în formă de puzzle pentru a segmenta imaginea reprezentând puzzle. Ați putea merge pentru măștile raster pre-fabricate pe care le-ați livrat cu aplicația, dar va trebui să includeți mai multe variante pentru a acoperi toate variantele de formă posibile ale celor patru muchii.

Cu grafică vectorială, puteți genera masca pentru orice fel de piesă în zbor. În plus, ar fi mai ușor să găzduiți și alte variante, cum ar fi dacă doriți piesele dreptunghiulare sau oblice (în loc de bucăți pătrate).

Proiectarea limitei piesei de tip Jigsaw

Cum de fapt proiectăm piesa de puzzle, adică cum ne dăm seama cum să punem punctele noastre de control pentru a genera o cale bezier care arată ca și tabăra curbă?

Amintiți-vă de proprietatea utilă de tangență a Beziers cubice pe care am menționat-o mai devreme. Puteți începe prin a arăta o aproximare a formei dorite, rupându-l în segmente estimând câte segmente cubice veți avea nevoie (cunoscând tipurile de forme pe care un singur segment cubic le pot găzdui) și apoi trasând tangente la aceste segmente pentru a afla unde puteți plasa punctele de control. Iată o diagramă care explică ce vorbesc.

Referindu-se la punctele de control ale curbei Bezier

Am determinat ca pentru a reprezenta forma filei, patru segmente Bezier ar face bine:

  • două reprezentând segmentele de linie dreaptă la fiecare capăt al formei
  • două reprezentând segmentele în formă de S reprezentând fila centrală

Observați că segmentele liniei întrerupte verde și galben acționează ca tangente la segmentele în formă de S, ceea ce mi-a ajutat să estimez unde să loc punctele de control. Rețineți, de asemenea, că am vizualizat piesa ca având o lungime de o unitate, motiv pentru care toate coordonatele sunt fracțiuni ale unuia. Mi-ar fi putut face ușor curba să fie, de exemplu, 100 de puncte (scalarea punctelor de control cu ​​un factor de 100). Independența rezoluției graficii vectoriale înseamnă că aceasta nu este o problemă.

În cele din urmă, am folosit Beziers cubi chiar și pentru segmentele de linie dreaptă pur și simplu pentru comoditate, astfel încât codul să poată fi scris mai concis și uniform.

Am ignorat desenarea punctelor de control ale segmentelor drepte din diagramă pentru a evita dezordinea. Bineînțeles, un bezier cubic care reprezintă o linie are pur și simplu punctele de capăt și punctele de control situate pe linia însăși.

Faptul că dezvoltați acest lucru într-un loc de joacă înseamnă că puteți "rejigra" cu ușurință valorile punctului de control pentru a găsi o formă care vă place și pentru a primi feedback instant.

Noțiuni de bază

Să începem. Puteți folosi același loc de joc ca înainte, adăugând o pagină nouă. Alege Nou> Pagină de joacă pentru copii de la Fişier sau creați un nou loc de joacă dacă preferați.

Înlocuiți orice cod din noua pagină cu următoarele:

"javascript import UIKit

outie_coords: [(x: CGFloat, y: CGFloat)] = [(1,0 / 9,0), 2,0 / 9,0, 1,0 / 3,0, 37,0 / 60,0, / 6,0,0 / 3), (1,0 / 2, 1,0 / 3), (5,0 / 6,0,0 / 3), (23,0 / 60,0) ), (8,0 / 9,0), (1,0, 0)]

permiteți dimensiunea: CGFloat = 100 let outie_points = outie_coords.map CGPointApplyAffineTransform (CGPointMake ($ 0.x, $ 0.y), CGAffineTransformMakeScale (dimensiune, dimensiune)

permite calea = UIBezierPath () path.moveToPoint (CGPointZero)

pentru i în 0.stride (prin: outie_points.count - 3, prin: 3) path.addCurveToPoint (outie_points [i + 2], controlPoint1: outie_points [i], controlPoint2:

cale"

Generarea tuturor celor patru părți folosind transformările geometrice

Rețineți că am decis să facem calea noastră cu 100 de puncte, aplicând o transformare de scalare către puncte.

Vedem următorul rezultat utilizând funcția "Quick Look":

Până acum, bine. Cum generăm patru laturi ale piesei de puzzle? Răspunsul este (după cum puteți ghici), folosind transformări geometrice. Aplicând o rotație de 90 de grade, urmată de o traducere adecvată cale de mai sus, putem genera cu ușurință restul laturilor.

Caveat: O problemă cu umplerea interioară

Există o avertizare aici, din păcate. Transformarea nu se va alătura automat segmentelor individuale. Chiar dacă silueta piesei noastre de piese arata bine, interiorul ei nu va fi umplut și ne vom confrunta cu probleme folosind-o ca o mască. Putem observa acest lucru la locul de joacă. Adăugați următorul cod:

"javascript permite transformarea = CGAffineTransformTranslate (CGAffineTransformMakeRotation (CGFloat (-M_PI / 2)), 0, dimensiune)

la temppath = path.copy () ca! UIBezierPath

lasati foursided = UIBezierPath () pentru i in 0 ... 3 temppath.applyTransform (transform) foursided.appendPath (temppath)

foursided“

Quick Look ne arată următoarele:

Observați cum interiorul piesei nu este umbrit, indicând faptul că nu a fost umplut.

Puteți afla comenzile de desen folosite pentru a construi un complex UIBezierPath prin examinarea lui debugDescription proprietate în loc de joacă.

Rezolvarea problemei de umplere

Geometria se transformă pe UIBezierPath lucrați destul de bine pentru cazul obișnuit de utilizare, adică atunci când deja aveți o formă închisă sau forma pe care o transformați este deschisă intrinsec și doriți să generați versiuni transformate geometric ale acestora. Cazul nostru de utilizare este diferit. Calea acționează ca o subterană într-o formă mai mare pe care o construim și a cărei interior intenționăm să o umplem. E un pic mai complicat.

O abordare ar fi mizeria cu interioarele căii (folosind CGPathApply () funcția de la API-ul Core Graphics) și conectați manual segmentele împreună pentru a ajunge la o singură, închisă și umplută în mod corespunzător.

Dar această opțiune se simte puțin hackish și de aceea am optat pentru o abordare diferită. Aplicăm transformările geometrice în punctele înseși, prin intermediul CGPointApplyAffineTransform () funcția, aplicând exact aceeași transformare pe care am încercat să o folosim cu un moment în urmă. Apoi utilizăm punctele transformate pentru a crea subcala, care este atașată la forma generală. La sfârșitul tutorialului, vom vedea un exemplu în care putem aplica corect o transformare geometrică pe calea Bezier.

Generarea variațiilor marginii piesei

Cum generăm fila "innie"? Am putea aplica din nou o transformare geometrică cu un factor de scalare negativ în direcția y (inversarea formei), dar am optat să o fac manual prin simpla inversare a coordonatelor y ale punctelor outie_points.

În ceea ce privește fila cu marginile plane, în timp ce aș fi putut folosi pur și simplu un segment de linie dreaptă pentru ao reprezenta, pentru a evita necesitatea specializării codului pentru cazuri distincte, am stabilit pur și simplu coordonatele y ale fiecărui punct în outie_points la zero. Acest lucru ne oferă:

javascript lăsați innie_points = outie_points.map CGPointMake ($ 0.x, - $ 0.y) permiteți flat_points = outie_points.map CGPointMake ($ 0.x, 0)

Ca exercițiu, puteți genera curbe Bezier din aceste margini și le puteți vizualiza utilizând Quick Look.

Acum știți suficient pentru mine să vă împing cu întregul cod, care leagă totul împreună într-o singură funcție.

Înlocuiți tot conținutul paginii de joacă cu următoarele:

"importul javascript UIKit import XCPlayground

enum Edge caz Outie caz Innie caz plat

func jigsawPieceMaker (mărimea dimensiunii: CGFloat, margini: [Edge]) -> UIBezierPath

Funcția incrementalPathBuilder (firstPoint: CGPoint) -> (CGPoint)) -> UIBezierPath let path = UIBezierPath () path.moveToPoint (firstPoint) return puncte în assert (points.count% 3 == 0) pentru i în 0. (de exemplu: points.count - 3, by: 3) path.addCurveToPoint (puncte [i + 2], controlPoint1: puncte [i], controlPoint2: points [i + (1,0 / 9,0), (2,0 / 9,0), (1,0 / 3,0), (37,0 / (1,0 / 6, 1,0 / 3), (1,0 / 2, 1,0 / 3), (2,0 / 3,0) , (7.0 / 9, 0), (8.0 / 9, 0), (1.0, 0)] outie_points = outie_coords.map CGPointApplyAffineTransform (CGPointMake ($ 0.x, $ 0.y)) CGAffineTransformMakeScale  permite innie_points = outie_points.map CGPointMake ($ 0.x, - $ 0.y) permite flat_points = outie_points.map CGPointMake ($ 0.x, 0) var shapeDict: [Edge: [CGPoint] : outie_points, .Innie: innie_points, .Flat: flat_points] lasa transform = CGAffineTransformTranslate (CGAffineTransformMakeRotation (CGFl oval (-M_PI / 2)), 0, mărime) let path_builder = incrementalPathBuilder (CGPointZero) var cale: UIBezierPath! pentru margine în margini path = path_builder (shapeDict [margine]!) pentru (e, pts) în formăDict let tr_pts = pts.map CGPointApplyAffineTransform ($ 0, transform) shapeDict [e] = tr_pts ) calea de intoarcere  

lăsați piece1 = jigsawPieceMaker (dimensiune: 100, margini: [.Innie, .Outie, .Flat, .Innie])

permiteți bucătării2 = jigsawPieceMaker (dimensiune: 100, margini: [.Innie, .Innie, .Innie ,.Innie]) piece2.applyTransform (CGAffineTransformMakeRotation (CGFloat (M_PI / 3)

"

Există doar câteva lucruri mai interesante în codul pe care aș dori să le clarific:

  • Noi folosim un enum pentru a defini diferite forme de margine. Stocăm punctele într-un dicționar care folosește valorile de enumerare ca taste.
  • Împărțim substraturile (care constau din fiecare margine a formei piesei cu patru fețe) în incrementalPathBuilder (_) funcție, definită intern la jigsawPieceMaker (dimensiune: margini :) funcţie.
  • Acum că piesa de puzzle este umplută corect, după cum vedem în ieșirea Quick Look, putem folosi în siguranță applyTransform (_ :) metoda de a aplica o transformare geometrică în formă. De exemplu, am aplicat o rotație de 60 de grade la a doua piesă.

Concluzie

Sper că te-am convins că abilitatea de a genera grafic o grafică vectorială poate fi o abilitate utilă de a avea în arsenalul tău. Sperăm că veți fi inspirați să vă gândiți (și să codificați) alte aplicații interesante pentru grafica vectorială pe care le puteți încorpora în propriile aplicații.

Cod