Technical Article

Vektorinė grafika PDF su Delphi: keliai ir gradientai

Dauguma Delphi kodų, susijusių su PDF, vertina šį formatą kaip konteinerį dviem dalykams: teksto atkarpoms ir keliems patalpintiems taškiniams paveikslėliams. Šis požiūris yra teisingas tiek, kiek jis apima, tačiau jis palieka nenaudojamą pačią pajėgiausią formato dalį. PDF puslapis yra nuo skiriamosios gebos nepriklausoma 2D drobė, sukurta remiantis tuo pačiu vaizdavimo modeliu kaip ir PostScript. Ji gali piešti linijas, kreives, užpildytas sritis, gradientus ir pasikartojančius raštus – visa tai kaip vektorius, kurie išlieka ryškūs bet kokiu masteliu ir yra spausdinami visa įrenginio skiriamąja geba. Jei piešiate logotipą, diagramą, vandens ženklą ar sertifikato rėmelį, vektorinis kelias beveik visada yra tinkamas primityvas, be to, jis yra mažesnis ir aiškesnis nei rastrinis vaizdas, kurį vietoj to pasirenka daugelis programų

Šiame straipsnyje apžvelgiamas vektorinis modelis, kaip jį apibrėžia ISO 32000-1, ir parodomi atitinkami PDFlibPas iškvietimai. Tikslas – padaryti specifikaciją konkretesnę, nes API glaudžiai su ja susijusi, o vieno supratimas padeda suprasti kitą

Puslapis yra kelių mašina

ISO 32000-1 §8.5 aprašo grafiką dviem etapais, kurie niekada nepersidengia. Pirmiausia sukuriate kelią (angl. path), kuris yra gryna geometrija be matomo rezultato. Tada nupiešiate tą kelią viena operacija, kuri apibrėžia jo kontūrą, užpildo jo vidų arba atlieka abu veiksmus. Kūrimo metu puslapyje nieko neatsiranda. Kelias yra abstrakti taškų ir segmentų seka, laikoma grafikos būsenoje, kol piešimo operatorius ją sunaudoja, o tada ji yra atvaizduojama ir pašalinama

Kelią sudaro vienas arba keli subkeliai. Subkelias prasideda taške ir auga pridedant segmentus: tiesias linijas, kubines Bezjė kreives, o kai kuriose platformose ir ištisus stačiakampius, pridedamus kaip atskirus uždarus subkelius. PDFlibPas programoje atidarote kelią su StartPath, kuris nustato pradinį tašką, tada išplečiate jį su AddLineToPath ir AddCurveToPath. Kiekvienas iškvietimas pastumia numanomą dabartinį tašką, zodžiu, kitas segmentas tęsiasi nuo ten, kur baigėsi paskutinis. ClosePath nubrėžia paskutinį tiesų segmentą atgal į subkelio pradžią, o tai yra svarbu kontūro brėžimui, nes sukuria tikrą linijos jungtį ties uždarymo viršūne, o ne du laisvus galus

// A closed quadrilateral, stroked then filled
PDF.SetLineColor(0, 0, 0);
PDF.SetFillColor(0.6, 0.8, 1.0);
PDF.SetLineWidth(1.5);

PDF.StartPath(150, 100);           // open the path at the first vertex
PDF.AddLineToPath(220, 140);
PDF.AddLineToPath(180, 210);
PDF.AddLineToPath(110, 170);
PDF.ClosePath;                     // straight segment back to (150, 100)
PDF.DrawPath(2);                   // 2 = fill and stroke; path is consumed

Kreivėms naudojamas AddCurveToPath, kuris priima du Bezjė valdymo taškus ir galinį tašką: AddCurveToPath(CtAX, CtAY, CtBX, CtBY, EndX, EndY). Kreivė eina nuo dabartinio taško iki (EndX, EndY), pakeliui traukiama link dviejų valdymo taškų. Apskritimo lankai galimi naudojant AddArcToPath(CenterX, CenterY, TotalAngle), kur spindulys imamas iš atstumo tarp dabartinio taško ir centro, o variklis išveda lanką kaip Bezjė segmentų grandinę. Stačiakampiai turi trumpinį AddBoxToPath(Left, Top, Width, Height), kuris prideda pilną uždarą stačiakampį kaip atskirą subkelią be ankstesnio StartPath

Dvi užpildymo taisyklės ir kodėl jos nesutampa

Kai užpildote kelią, kuris kertasi su savimi arba turi vidinę kilpą, atvaizdavimo programai reikia taisyklės, kad nuspręstų, kurios sritys yra formos viduje, o kurios yra skylės. ISO 32000-1 §8.5.3.3 skyrius apibrėžia dvi taisykles, ir jos gali skirtingai nudažyti tą pačią geometriją. Nenulinio apvijų skaičiaus (angl. nonzero winding) taisyklė skaičiuoja žymėtus susikirtimus spindulio, nukreipto iš bandomojo taško į begalybę, pridedant po vieną kiekvienam segmentui, kuris kertasi iš kairės į dešinę, ir atimant po vieną kiekvienam, kuris kertasi į kitą pusę; taškas yra viduje, kai bendra suma nėra nulis. Lyginio-nelyginio (angl. even-odd) taisyklė nepaiso krypties ir tiesiog skaičiuoja susikirtimus, laikydama tašką viduje, kai skaičius yra nelyginis

Klasikinis atvejis, kai jos skiriasi, yra forma su skyle – baronka arba poveržlė. Nubrėžkite išorinę ribą ir vidinę ribą jos viduje. Taikant lyginio-nelyginio taisyklę, vidinė kilpa visada išpjauna skylę, nes bet kuris taškas tarp dviejų ribų yra kertamas vieną kartą, o bet kuris taškas vidinėje kilpoje – du kartus. Pagal nenulinio apvijų skaičiaus taisyklę skylė atsiranda tik tuo atveju, jei vidinė kilpa sukasi priešinga kryptimi nei išorinė; sukite jas ta pačia kryptimi, ir apvijos sustiprins viena kitą, o ne panaikins, ir vidinė sritis užsipildys vientisai. Penkiakampė žvaigždė, nubrėžta kaip vienas susikertantis kontūras, rodo tą patį pasidalijimą: lyginis-nelyginis palieka centrinį penkiakampį tuščią, o nenulinis apvijų skaičius jį užpildo

PDFlibPas parenka taisyklę pagal iškvietimą, kurį naudojate piešimui, o ne pagal vėliavėlę. DrawPath užpildo pagal nenulinio apvijų skaičiaus taisyklę; DrawPathEvenOdd užpildo pagal lyginio-nelyginio taisyklę. Abu priima tą patį sveikojo skaičiaus režimą: 0 braižo tik kontūrą, 1 tik užpildo, o 2 užpildo ir braižo kontūrą. Lyginio-nelyginio taisyklė yra lengvesnis įrankis skylėms išpjauti būtent todėl, kad jums nereikia valdyti subkelio krypties

// Same two boxes, two fill rules, two different results.
// Nonzero winding: both boxes wind the same way, so the inner one
// does NOT cut a hole and the whole outer box fills solid.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 100, 200, 120);   // outer
PDF.AddBoxToPath(140, 130, 120,  60);   // inner
PDF.DrawPath(1);                         // 1 = fill, nonzero winding

// Even-odd: the inner box is crossed an even number of times,
// so it punches a clean rectangular hole through the outer box.
PDF.SetFillColor(0.2, 0.4, 0.8);
PDF.AddBoxToPath(100, 300, 200, 120);   // outer
PDF.AddBoxToPath(140, 330, 120,  60);   // inner cut-out
PDF.DrawPathEvenOdd(1);                  // 1 = fill, even-odd

Ašiniai gradientai keičia spalvą išilgai linijos

Vientisa užpildymo spalva yra viena reikšmė visoje srityje. Gradientas spalvą keičia tolygiai, o paprasčiausias tipas yra ašinis, arba tiesinis, gradientas. ISO 32000-1 §8.7.4.5 apibrėžia jį kaip Type 2 ašinį šešėliavimą (angl. axial shading): nurodote du taškus, kurie apibrėžia ašį, pradinę spalvą pirmame taške ir galutinę spalvą antrame taške, o atvaizdavimo programa interpoliuoja spalvą išilgai tos ašies. Kiekvienas užpildytos srities taškas įgauna savo statmenos projekcijos į ašį spalvą, todėl gradientas eina juostomis stačiu kampu į liniją tarp dviejų taškų

PDFlibPas programoje gradientas yra įvardytas dokumento išteklius, kurį sukuriate vieną kartą ir pasirenkate kaip aktyvius dažus. NewRGBAxialShader jį užregistruoja. Parašas yra NewRGBAxialShader(ShaderName, StartX, StartY, StartRed, StartGreen, StartBlue, EndX, EndY, EndRed, EndGreen, EndBlue, Extend): du ašies galiniai taškai, RGB reikšmės kiekviename gale kaip reikšmės nuo 0 iki 1, bei vėliavėlė Extend. Kai Extend nustatyta į 1, galutinės spalvos tęsiasi kaip vientisas užpildas už ašies galinių taškų, o to paprastai ir norisi, kad srities kampai už ašies ribų neliktų nenupiešti; 0 palieka juos nepakeistus. Kai šešėliavimo objektas (angl. shader) sukurtas, susiejate jį su SetFillShader užpildytoms sritims, SetLineShader brėžiamiems kontūrams arba SetTextShader tekstui. Susiejimas išlieka aktyvus po to einantiems piešimo iškvietimams, todėl kitas piešiamas kelias naudoja gradientą, o ne vientisą spalvą

// Define a vertical gradient once: blue at the bottom to white at the top.
PDF.NewRGBAxialShader('panelGrad',
  0, 100,   0.10, 0.25, 0.55,    // start point and start RGB
  0, 260,   1.00, 1.00, 1.00,    // end point and end RGB
  1);                            // 1 = extend ends as solid color

// Select the gradient as the fill, then paint a rectangle with it.
PDF.SetFillShader('panelGrad');
PDF.AddBoxToPath(80, 100, 300, 160);
PDF.DrawPath(1);                 // 1 = fill, now filled by the shader

Ašis čia yra vertikali, nuo y=100 iki y=260 esant fiksuotam x, todėl spalvų juostos eina horizontaliai, o stačiakampis kinta nuo mėlynos apačioje iki baltos viršuje. Kadangi šešėliavimas yra susietas pavadinimu, vienas apibrėžimas gali užpildyti bet kokį skaičių formų puslapyje, o grįžimas prie vientisos spalvos yra tiesiog dar vienas SetFillColor iškvietimas prieš kitą kelią

Pasikartojantys raštai kartoja langelį

Kai gradientas švelniai keičia vieną spalvą, pasikartojantis raštas (angl. tiling pattern) kartoja nedidelį meno kūrinį visoje srityje. ISO 32000-1 §8.7.3.1 apibrėžia pasikartojantį raštą kaip rašto langelį – nepriklausomą turinio dalį, kurią atvaizdavimo programa kopijuoja fiksuotame tinklelyje, kad padengtų piešiamą sritį. Taip sukuriate brūkšniavimą inžineriniam užpildymui, pasikartojantį prekės ženklo motyvą už antraštės arba tekstūrinį foną, kuris išlieka ryškus kaip vektorius ir beveik nieko nesveria, nesvarbu, kokia didelė būtų sritis, nes langelis yra išsaugomas vieną kartą ir naudojamas visur

PDFlibPas sukuria rašto langelį iš užfiksuoto puslapio turinio. Užfiksuojate puslapį arba sritį su CapturePage, paverčiate užfiksavimą įvardytu raštu naudodami NewTilingPatternFromCapturedPage(PatternName, CaptureID), o tada pasirenkate tą raštą kaip dabartinį užpildą su SetFillTilingPattern(PatternName). Nuo to momento bet koks jūsų užpildomas kelias yra nudažomas pasikartojančiu langeliu, o ne vientisa spalva – tiksliai taip, kaip veikia šešėliavimo užpildymas, bet kaip dažų šaltinį naudojant pasikartojantį langelį. Ši seka yra sudėtingesnė nei vienas iškvietimas, todėl, jei užfiksavimo veiksmas nepažįstamas, vertinkite raštą kaip dviejų etapų operaciją: pirmiausia sukurkite užfiksuotą langelį, o prieš piešdami norimą padengti sritį susiekite jį kaip užpildą pagal pavadinimą

Primityvų sujungimas

Dalys jungiasi tiesiogiai. Užpildytas Bezjė objektas yra kreivių kelias, nupieštas su DrawPath. Tas pats kontūras, nupieštas su DrawPathEvenOdd pridėjus vidinę kilpą, rodo skylę, kurią apvijų užpildymas būtų uždaręs. Gradientu užpildytas stačiakampis yra dėžutė, susieta su šešėliavimu. Žemiau pateiktame pavyzdyje visi trys piešiami iš eilės, kad viename puslapyje būtų matomas skirtumas tarp dviejų užpildymo taisyklių, o po jais padedamas gradiento skydelis

// 1. A filled Bezier shape (nonzero winding).
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 480);
PDF.AddCurveToPath(160, 560, 240, 560, 280, 480);   // top lobe
PDF.AddCurveToPath(240, 420, 160, 420, 120, 480);   // bottom lobe
PDF.ClosePath;
PDF.DrawPath(1);                                     // 1 = fill

// 2. The same outline, plus an inner loop, filled even-odd to show a hole.
PDF.SetFillColor(0.85, 0.30, 0.25);
PDF.StartPath(120, 300);
PDF.AddCurveToPath(160, 380, 240, 380, 280, 300);
PDF.AddCurveToPath(240, 240, 160, 240, 120, 300);
PDF.ClosePath;
PDF.MovePath(180, 300);                              // new subpath: the hole
PDF.AddArcToPath(200, 300, 360);                     // a full circle
PDF.ClosePath;
PDF.DrawPathEvenOdd(1);                              // hole is punched out

// 3. A rectangle filled with an axial gradient.
PDF.NewRGBAxialShader('footerGrad',
  60, 100,  0.95, 0.55, 0.10,
  60, 200,  0.20, 0.10, 0.40,
  1);
PDF.SetFillShader('footerGrad');
PDF.AddBoxToPath(60, 100, 340, 100);
PDF.DrawPath(1);

Verta prisiminti dvi detales. Piešimo iškvietimas nustato užpildymo taisyklę, todėl pasirinkimas tarp DrawPath ir DrawPathEvenOdd yra pasirinkimas tarp nenulinio apvijų skaičiaus ir lyginio-nelyginio, o formoms su skylėmis lyginio-nelyginio taisyklė išlaisvina jus nuo mąstymo apie subkelio kryptį. Grafikos būsena yra nuskaitoma piešimo momentu: nustatykite spalvas, linijos plotį ir šešėliavimo susiejimą prieš piešimo iškvietimą, nes tai yra būsena, kurią skaito variklis. Pirmiausia sukurkite, sukonfigūruokite būseną, o pieškite paskiausiai – ir vektorinis modelis kiekvieną kartą veiks nuspėjamai

Natūralūs tolesni žingsniai yra vektorių ir teksto skaitymas iš esamo dokumento, kuris aprašytas mūsų straipsne apie teksto, vaizdų ir šriftų išgavimą, bei to paties piešimo modelio atvaizdavimas Windows įrenginio kontekste ekrano peržiūrai ir spausdinimui, kuris aprašytas spausdinimo ir peržiūros apžvalgoje. Čia aprašyti kelio, šešėliavimo ir rašto iškvietimai platinami kaip Delphi PDF biblioteka kartu su teksto, vaizdo, formų ir pasirašymo API, aprašytomis kitur šiame tinklaraštyje