The graafika kujundamise praegune suundumus on kasutada palju ümardatud nurki igasugustes kujundites. Seda fakti võime jälgida paljudel veebilehtedel, mobiilseadmetes ja töölauarakendustes. Kõige tähelepanuväärsemad näited on rakenduse nupud, mida kasutatakse klõpsamisel mõne toimingu käivitamiseks. Rangelt ristkülikukujulise kuju asemel, mille nurkades on 90-kraadised nurgad, joonistatakse need sageli ümardatud nurkadega. Ümarad nurgad muudavad kasutajaliidese sujuvamaks ja kenamaks. Ma pole selles täielikult veendunud, kuid mu disainerist sõber ütleb mulle seda.
Kasutajaliideste visuaalsed elemendid on loodud disainerite poolt ja programmeerijal tuleb need vaid õigesse kohta paigutada. Aga mis juhtub, kui peame looma ümarate nurkadega kuju käigu pealt ja me ei saa seda eelkoormata? Mõni programmeerimisraamatukogu pakub piiratud võimalusi ümardatud nurkadega ettemääratud kujundite loomiseks, kuid tavaliselt ei saa neid keerulisematel juhtudel kasutada. Näiteks, Qt raamistik on klass QPainter
, mida kasutatakse kõigi QPaintDevice
-st tuletatud klasside, sealhulgas vidinate, pikslikaartide ja piltide joonistamiseks. Sellel on meetod nimega drawRoundedRect
, mis, nagu nimigi ütleb, joonistab ümardatud nurkadega ristküliku. Kuid kui vajame veidi keerukamat kuju, peame selle ise rakendama. Kuidas saaksime seda teha hulknurga, tasapinnalise kujuga, mida piirab sirgjooneliste segmentide rühm? Kui meil on paberile pliiatsiga joonistatud hulknurk, oleks minu esimene idee kasutada kustutuskummi ja kustutada väike osa joontest igas nurgas ning seejärel ühendada ülejäänud segmendi otsad ringkaarega. Kogu protsessi saab illustreerida alloleval joonisel.
Klass QPainter
on mõned ülekoormatud meetodid nimega drawArc
, mis võivad joonistada ringkaari. Kõik need nõuavad parameetreid, mis määratlevad kaare keskpunkti ja suuruse, algusnurga ja kaare pikkuse. Kuigi pööramata ristküliku jaoks on nende parameetrite vajalikke väärtusi lihtne kindlaks määrata, on see hoopis teine asi, kui tegemist on keerukamate hulknurkadega. Lisaks peaksime seda arvutust korrata iga hulknurga tipu kohta. See arvutus on pikk ja väsitav ülesanne ning inimestel on selle käigus kalduvus igasuguste arvutusvigade tekkimiseks. Kuid tarkvaraarendajate ülesanne on panna arvutid inimestele tööle, mitte vastupidi. Niisiis, siin ma näitan, kuidas arendada lihtsat klassi, mis võib keeruka hulknurga muuta ümarate nurkadega kujundiks. Selle klassi kasutajad peavad lisama ainult hulknurga tipud ja ülejäänud teeb klass. Oluline matemaatiline tööriist, mida selle ülesande jaoks kasutan, on Bezieri kõver .
Bezieri kõverate teooriat kirjeldavaid matemaatilisi raamatuid ja Interneti-ressursse on palju, nii et toon lühidalt asjakohased omadused.
Definitsiooni järgi on Bezieri kõver kõver kahe punkti vahel kahemõõtmelisel pinnal, mille trajektoori juhib üks või mitu kontrollpunkti. Rangelt võttes on kahe punkti vaheline kõver ilma täiendavate kontrollpunktideta ka Bezieri kõver. Kuna selle tulemuseks on kahe punkti vaheline sirgjoon, pole see eriti huvitav ega kasulik.
Bezieri ruutkõveratel on üks kontrollpunkt. Teooria ütleb, et ruutu kandev Bezier kõverub punktide vahel P0 ja P2 kontrollpunktiga Püks on määratletud järgmiselt:
visuaalse taju geštaltpõhimõtted
B (t) = (1 - t)2P0+ 2t (1 - t) Püks+ t2P2, kus 0 ≤ t ≤ 1 (üks)
Millal siis t on võrdne 0 , B (t) annab järele P0 , millal t on võrdne üks , B (t) annab järele P2 , kuid igal teisel juhul väärtus B (t) sõltub ka Püks . Kuna väljend 2t (1 - t) maksimaalne väärtus on t = 0,5 , seal on mõju Püks peal B (t) saab olema suurim. Me võime välja mõelda Püks kui kujuteldav raskusallikas, mis tõmbab funktsiooni trajektoori enda poole. Alloleval joonisel on toodud mõned näited Bezieri ruutkõverate koos nende algus-, lõpp- ja kontrollpunktidega.
Niisiis, kuidas lahendada oma probleem Bezieri kõverate abil? Allpool olev joonis pakub selgitust.
Kui kujutame ette hulknurga tipu ja lühikese osa ühendatud joone segmentide kustutamist selle ümbruses, võime mõelda ühele rea lõigu otsale P0 , teise rea segmendi ots lõpeb P2 ja kustutatud tipp alates Püks . Rakendame sellele punktide komplektile ruutu Bezieri kõverat ja voila, seal on soovitud ümardatud nurk.
Klass QPainter
pole viisi, kuidas joonistada ruutkorralduslikke Bezieri kõveraid. Ehkki seda on võrrandi (1) järgi nullist juurutamine üsna lihtne, pakub Qt teek siiski paremat lahendust. 2D joonistamiseks on veel üks võimas klass: QPainterPath
. Klass QPainterPath
on joonte ja kõverate kogum, mida saab hiljem lisada QPainter
-ga objekt. On mõned ülekoormatud meetodid, mis lisavad Bezieri kõverad praegusele kogu. Eelkõige meetodid quadTo
lisab ruutu Bezieri kõverad. Kõver algab praegusest QPainterPath
punkt ( P0 ), samas Püks ja P2 tuleb edastada quadTo
parameetritena.
QPainter
Meetod drawPath
kasutatakse joonte ja kõverate kogu joonistamiseks QPainterPath
objekt, mis tuleb anda parameetrina, aktiivse pliiatsi ja harjaga.
Nii et vaatame klassi deklaratsiooni:
class RoundedPolygon : public QPolygon { public: RoundedPolygon() { SetRadius(10); } void SetRadius(unsigned int iRadius) { m_iRadius = iRadius; } const QPainterPath& GetPath(); private: QPointF GetLineStart(int i) const; QPointF GetLineEnd(int i) const; float GetDistance(QPoint pt1, QPoint pt2) const; private: QPainterPath m_path; unsigned int m_iRadius; };
Otsustasin alamklassi QPolygon
et ma ei peaks tippude ja muu kraami lisamist ise teostama. Lisaks konstruktorile, mis seab raadiuse mõnele mõistlikule algväärtusele, on sellel klassil veel kaks avalikku meetodit:
SetRadius
meetod määrab raadiuseks antud väärtuse. Raadius on sirgjoone pikkus (pikslites) iga tipu lähedal, mis ümardatud nurga korral kustutatakse (või täpsemalt joonistamata).GetPath
on kõik arvutused. See tagastab QPainterPath
RoundedPolygon
-i lisatud hulknurga punktidest loodud objekt.Privaatse osa meetodid on vaid abimeetodid, mida GetPath
kasutavad.
parim staatilise saidi generaator 2018
Vaatame rakendust ja alustan privaatsetest meetoditest:
float RoundedPolygon::GetDistance(QPoint pt1, QPoint pt2) const { float fD = (pt1.x() - pt2.x())*(pt1.x() - pt2.x()) + (pt1.y() - pt2.y()) * (pt1.y() - pt2.y()); return sqrtf(fD); }
Siinkohal pole palju seletatav, meetod tagastab eukleidilise kauguse antud kahe punkti vahel.
QPointF RoundedPolygon::GetLineStart(int i) const { QPointF pt; QPoint pt1 = at(i); QPoint pt2 = at((i+1) % count()); float fRat = m_uiRadius / GetDistance(pt1, pt2); if (fRat > 0.5f) fRat = 0.5f; pt.setX((1.0f-fRat)*pt1.x() + fRat*pt2.x()); pt.setY((1.0f-fRat)*pt1.y() + fRat*pt2.y()); return pt; }
Meetod GetLineStart
arvutab punkti asukoha P2 viimasest joonisest, kui punktid lisatakse hulknurgale päripäeva. Täpsemalt tagastab see punkti, mis on m_uiRadius
pikslit i
-st tipust kaugemale (i+1)
-nda tipu suunas. (i+1)
-Nda tipu juurde pääsemisel peame meeles pidama, et hulknurgas on ka viimase ja esimese tipu vahel sirgjoon, mis teeb sellest suletud kuju, seega avaldise (i+1)%count()
. See takistab ka meetodil ulatusest välja minemist ja pöördub selle asemel esimese punkti poole. Muutuja fRat
hoiab raadiuse ja i
-nda rea lõigu pikkuse suhet. Samuti on kontroll, mis takistab fRat
väärtusest üle 0.5
. Kui fRat
väärtus oli üle 0.5
, siis kattuksid kaks järjestikust ümardatud nurka, mis põhjustaks halva visuaalse tulemuse.
Punktist reisides Püks kuni P2 sirgjooneliselt ja läbides 30 protsenti distantsist, saame valemi abil määrata oma asukoha 0,7 • Püks+ 0,3 • P2 . Üldiselt, kui saavutame murdosa kogu distantsist, ja a = 1 tähistab täispikkust, praegune asukoht on (1 - α) • P1 + α • P2 .
Nii on GetLineStart
meetod määrab punkti asukoha, mis on m_uiRadius
pikslit i
-nda tipu kaugusel (i+1)
-th suunas.
QPointF RoundedPolygon::GetLineEnd(int i) const { QPointF pt; QPoint pt1 = at(i); QPoint pt2 = at((i+1) % count()); float fRat = m_uiRadius / GetDistance(pt1, pt2); if (fRat > 0.5f) fRat = 0.5f; pt.setX(fRat*pt1.x() + (1.0f - fRat)*pt2.x()); pt.setY(fRat*pt1.y() + (1.0f - fRat)*pt2.y()); return pt; }
See meetod on väga sarnane GetLineStart
See arvutab punkti asukoha P0 (i+1)
-nda tipu jaoks, mitte i
-th. Teisisõnu, kui tõmbame joone GetLineStart(i)
-st kuni GetLineEnd(i)
iga i
kohta vahel 0
ja n-1
, kus n
on hulknurga tippude arv, saaksime kustutatud tippudega hulknurga ja nende lähiümbruse.
Ja nüüd, põhiklassi meetod:
tundlik veebikujundusmeedia päring
const QPainterPath& RoundedPolygon::GetPath() { m_path = QPainterPath(); if (count() <3) { qWarning() << 'Polygon should have at least 3 points!'; return m_path; } QPointF pt1; QPointF pt2; for (int i = 0; i < count(); i++) { pt1 = GetLineStart(i); if (i == 0) m_path.moveTo(pt1); else m_path.quadTo(at(i), pt1); pt2 = GetLineEnd(i); m_path.lineTo(pt2); } // close the last corner pt1 = GetLineStart(0); m_path.quadTo(at(0), pt1); return m_path; }
Selle meetodi korral ehitame QPainterPath
objekt. Kui hulknurgal pole vähemalt kolme tippu, ei ole meil enam tegemist 2D-kujuga ja antud juhul annab meetod hoiatuse ja tagastab tühja tee. Kui punkte on piisavalt, liigume läbi hulknurga kõik sirgjoonelõigud (joonelõikude arv on loomulikult võrdne tippude arvuga), arvutades iga sirgjoone alguse ja lõpu ümardatud vahel nurgad. Nende kahe punkti vahele panime sirgjoone ja eelmise joonelõigu lõpu ning voolu alguse vahele ruutu Bezieri kõvera, kasutades kontrollpunktina praeguse tipu asukohta. Pärast silmust peame tee sulgema Bezieri kõveraga viimase ja esimese rea segmentide vahel, sest silmusesse tõmbasime ühe sirge rohkem kui Bezieri kõverad.
RoundedPolygon
kasutamine ja tulemusedNüüd on aeg vaadata, kuidas seda klassi praktikas kasutada.
QPixmap pix1(300, 200); QPixmap pix2(300, 200); pix1.fill(Qt::white); pix2.fill(Qt::white); QPainter P1(&pix1); QPainter P2(&pix2); P1.setRenderHints(QPainter::Antialiasing); P2.setRenderHints(QPainter::Antialiasing); P1.setPen(QPen(Qt::blue, 2)); P1.setBrush(Qt::red); P2.setPen(QPen(Qt::blue, 2)); P2.setBrush(Qt::red); RoundedPolygon poly; poly << QPoint(147, 187) << QPoint(95, 187) << QPoint(100, 175) << QPoint(145, 165) << QPoint(140, 95) << QPoint(5, 85) << QPoint(5, 70) << QPoint(140, 70) << QPoint(135, 45) << QPoint(138, 25) << QPoint(145, 5) << QPoint(155, 5) << QPoint(162, 25) << QPoint(165, 45) << QPoint(160, 70) << QPoint(295, 70) << QPoint(295, 85) << QPoint(160, 95) << QPoint(155, 165) << QPoint(200, 175) << QPoint(205, 187) << QPoint(153, 187) << QPoint(150, 199); P1.drawPolygon(poly); P2.drawPath(poly.GetPath()); pix1.save('1.png'); pix2.save('2.png');
See lähtekoodi osa on üsna sirgjooneline. Pärast kahe QPixmaps
initsialiseerimist ja nende QPainters
, loome RoundedPolygon
objekt ja täitke see punktidega. Maalikunstnik P1
joonistab korrapärase hulknurga, samal ajal kui P2
joonistab QPainterPath
ümarate nurkadega, mis on loodud hulknurgast. Mõlemad saadud pikslikud salvestatakse nende failidesse ja tulemused on järgmised:
Oleme näinud, et ümarate nurkadega kuju genereerimine polügoonilt pole lõppude lõpuks nii keeruline, eriti kui kasutame head programmeerimisraamistikku, näiteks Qt. Seda protsessi saab automatiseerida klassiga, mida olen siin blogis kontseptsiooni tõestuseks kirjeldanud. Siiski on veel palju arenguruumi, näiteks:
RoundedPolygon
bitikaartide genereerimiseks, mida saab kasutada taustavidina maskina pöörase kujuga vidinate tootmiseks.RoundedPolygon
klass pole rakendamise kiiruse jaoks optimeeritud; Jätsin selle mõiste lihtsamaks mõistmiseks nii, nagu see on. Optimeerimine võib hõlmata paljude vaheväärtuste arvutamist, kui polügoonile lisatakse uus tipp. Samuti, kui GetPath
tagastab viite loodud QPainterPath
-le, võib see määrata lipu, mis näitab, et objekt on ajakohane. Järgmine kõne GetPath
tagastaks ainult sama QPainterPath
objekti ümber arvutamata. Arendaja peaks siiski veenduma, et see lipp kustutatakse iga polügooni tipu iga muutuse korral ja ka iga uue tipu korral, mis paneb mind mõtlema, et optimeeritud klassi oleks parem arendada nullist ja seda ei tuletataks alates QPolygon
. Hea uudis on see, et see pole nii keeruline kui tundub.Kokku on RoundedPolygon
klassi, nagu see on, saab kasutada tööriistana alati, kui soovime lisada a disaineri puudutus meie graafilise kasutajaliidese juurde lennult, ilma et oleks eelnevalt pixmapse või kujundeid ette valmistanud.