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ě.