Lekce 4 - Vektorová grafika v C# .NET WPF - Úvod
V dnešní lekci si popíšeme co je to vektorová grafika a jak se liší oproti grafice rastrové. Dále si ukážeme, jak se k této problematice staví .NET framework a WPF. Poté si zkusíme přes XAML nakreslit pár jednoduchých objektů.
Vektorová vs. rastrová grafika
Velmi laicky můžeme říci, že vektorový obrázek si můžeme zvětšit jak chceme a nikdy se nám nerozmaže nebo nerozpixeluje. To je velmi lákavé, protože takový obrázek je plně responzivní a stále 100% ostrý, navíc zabírá velmi málo paměti a naše aplikace pak vypadá na všech zařízeních stejně hezky. Není divu, že např. SVG ikony jsou dnes na webu velmi populární. A i my budeme vektorové ikony ve svých WPF aplikacích preferovat.
Vektorový obrázek
Obrázek v sobě obsahuje matematický zápis souřadnic bodů (vrcholů) a jejich spojnic (hran), který vytváří požadovaný obrazec (např. trojúhelník, kruh, čtverec...). Kombinováním a prolínáním těchto obrazců se poté vytvářejí obrazce složitější. Dále se ve vektorových obrázcích specifikují informace jako tloušťka čáry, barva výplně atd. Z toho vyplývá, že s jakýmkoliv zvětšením či zmenšením obrazce se mění jen poměr vzdáleností mezi vrcholy.
Rasterizace
A jak se křivky vykreslí? To, co poté vidíme na obrazovce, se musí samozřejmě znovu "rasterizovat", neboli vykreslit na 2D ploše našeho monitoru:

Jak sami vidíte, vykreslení vektorového obrázku na obrazovku vytváří při malém počtu pixelů "schody" neboli aliasing. WPF za nás však použije pokročilejší algoritmy, tzv. anti-aliasing, aby byly jednotlivé křivky obrázku vyhlazené.
Rastrový obrázek
Naopak rastrový obrázek, např ve formátu JPEG (když jej zjednodušíme a pomineme ztrátový algoritmus komprese), se skládá pouze z pixelů a jejich barvy. Pokud takový obrázek zvětšíme, základní algoritmus zdvojí počet pixelů, ale ty vycházejí pouze z původní informace, a tedy bude obraz ošklivě rozkostičkovaný a v lepším případě rozmazaný. O jelikož se hustota pixelů obrazovek stále zvyšuje, můžeme si být jistí, že uživatelé naši aplikaci zvětšovat budou.
Ukažme si, jak to vypadá, když se zvětší rastrový obrázek:

A zde příklad zvětšení vektorového obrázku:

Kdy použít vektorovou a kdy rastrovou grafiku?
Takže nyní máme představu, jaký je rozdíl mezi vektorovou a rastrovou grafikou. Nemůžeme říci, že jedna je lepší než druhá. Zjednodušeně můžeme říci, že na ikony bychom měli používat vektorové obrázky a na fotografie dostatečně kvalitní obrázky rastrové. Shrňme si nyní do pár bodů základní rozdíly:
Vektorová grafika
- Jednodušší grafika jako ikony
- Jakýkoliv grafický prvek, který se bude dynamicky zmenšovat či zvětšovat
- Možnost i případné animace
- Lze i dynamicky měnit barvu jednotlivých prvků v obrazci
- Menší velikost souboru s obrázkem
- Náročnější na výpočetní výkon
Rastrový obrázek
- Tam, kde se jedná o reálnou fotografii, či složité obrazce
- Tam, kde bude obraz vždy v nativním rozlišení, a nebude se s ním zoomovat
- Kde je třeba detailního, fotorealistického obrázku se stíny a nasvícením, ale není výhodné zatěžovat GPU/CPU velmi složitou scénou
- Vyšší nároky na velikost souboru
- Výpočetní nenáročnost
Jak ve WPF pracovat s vektorovou grafikou
Jak možná již tušíte, standardně bez dalších rozšiřujících balíčků WPF ve Visual Studiu nepodporuje externí soubory vektorové grafiky. To jsou např.:
.svg- Scalable Vector Graphics.eps, .ps - PostScript.ai- Adobe Illustrator Artwork.cdr- Corel Draw.pdf- Portable Document Format
Zejména soubor SVG je v dnešní době etalon, který se uchytil ve webové
grafice, a umí mimo jiné i animace. Bohužel je podporován až v novější
verzi Creator's Update (tedy verze OS Windows 10 1709 z konce roku 2017) pod UWP
(Universal Windows Platform), kde pro něj vznikla nová třída
SvgImageSource.
Jak tedy na vektorovou grafiku ve WPF?
XAML
Řešením je XAML, který v základu využívá nástroje na
kreslení jako <Rectangle>, <Line>,
<Ellipse> a další, které se vykreslují jako vektorové
objekty. S kreslením tvarů jsme se v našem kurzu již setkali. Nyní si
ukážeme i další tvary a jak do XAML zkonvertovat SVG
soubory.
Vytvoření aplikace
Začneme pracovat na aplikaci, která bude ve výsledku vypadat takto:

Vytvořte si nový projekt a přepněte se do XAML části:

Zde si vytvoříme <Grid>, a ten rozdělíme na 9
rovnoměrných "chlívečků" s těmito parametry:
<Grid.ColumnDefinitions> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="1*"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="1*"/> <RowDefinition Height="1*"/> <RowDefinition Height="1*"/> </Grid.RowDefinitions>
To nám rozdělí pracovní plochu na 3 * 3 chlívečky, které si budou
díky parametru * udržovat poměr s velikostí okna. Okno by mělo
vypadat v návrháři takto (objekty přidáme samozřejmě později):

Vložení jednotlivých prvků
Nyní si ukažme, jak vykreslíme základní tvary.
Rectangle
Jako první vložíme obdélník <Rectangle>, vyplněný
zelenou barvou a se žlutým okrajem:
<Rectangle Stroke="Bisque" StrokeThickness= "5" Fill="Aquamarine" Margin="10" Grid.Row="0" Grid.Column="0" />
Výsledek:

Stroke je barva okraje, StrokeThickness je síla
čáry okraje v pixelech. Fill je barva výplně,
Margin je odskok od okrajů, Grid.Row a
Grid.Column určuje, ve kterém z našich "chlívečků" bude
obrazec umístěný.
Rectangle - Čtverec
Druhý obdélník je speciální v tom, že je to vlastně čtverec. Hlídá
si zde svou aktuální šířku, a tu nastavuje i pro výšku. Toho dosáhneme
přiřazením Binding:
<Rectangle StrokeThickness= "5" Margin="10" Grid.Row="1" Grid.Column="0" Width="{Binding ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Mode=Self}}" Stroke="Black">
Výsledek:

Binding může nahrazovat konstantní hodnotu v kterémkoliv parametru
proměnnou nebo referencí na jinou proměnou, v našem případě na aktuální
výšku obdélníku:
Width="{Binding ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Mode=Self}}"
Ellipse
Poté tu máme <Ellipse>, kde je nastavení stejné jako v
případě čtverce, ale okořenil jsem to efekty přechodů, jak pro výplň
Fill, tak pro okraje Stroke:
<Ellipse StrokeThickness= "5" Margin="10" Grid.Row="0" Grid.Column="1"> <Ellipse.Stroke> <LinearGradientBrush> <GradientStop Color="#FFFFFF03" Offset="0"/> <GradientStop Color="#FF000CFF" Offset="1"/> </LinearGradientBrush> </Ellipse.Stroke> <Ellipse.Fill> <RadialGradientBrush> <GradientStop Color="#FF0633FF" Offset="0"/> <GradientStop Color="Red" Offset="1"/> <GradientStop Color="#FFFFF500" Offset="0.7"/> <GradientStop Color="#FF3AFF00" Offset="0.403"/> <GradientStop Color="#FFFF16B5" Offset="0.837"/> </RadialGradientBrush> </Ellipse.Fill> </Ellipse>
Výsledek:

Pro čáru Stroke využíváme lineární přechod, tedy
postupný přechod, v tomto případě mezi dvěma barvami, od spodní modré,
po horní žlutou. Color nastavuje logicky barvu, a
Offset výchozí pozici na barevné škále – neboli jaké místo
bude pro danou barvu vyhrazeno. Postupuje se po 0.1 krocích nebo
jemnějších, maximální hodnota je 1. Výplň Fill
jsme pro změnu nastavili na přechod několika barev, tentokráte na kruhový
přechod.
Ellipse - Kruh
Nyní opět pomocí Bindingu nakreslíme místo elipsy kruh.
Postup je totožný jako u našeho čtverce:
<Ellipse x:Name="ellipse" Stroke="Black" Fill="AliceBlue" Margin="10" Grid.Row="1" Grid.Column="1" Width="{Binding ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Mode=Self}}"/>
Pokračovat budeme příště.
