Visual 6
Grafika
GDI+
GDI+ (Graphics Device Interface) jest reprezentowany przez przestrzeń nazw System.Drawing w .NET Framework. Dostarcza klas pozwalających na wykorzystanie możliwości graficznych systemu. GDI+ wprowadza między innymi następujące mechanizmy:
antyaliasing,
pędzle gradientowe,
krzywe sklejane (splines),
macierze transformacji,
mieszanie kolorów kanału alfa (alpha blending).
Przestrzenie nazw GDI+
Wszystkie klasy (typy) w bibliotece .NET zgrupowane są w przestrzeniach nazw.
Przestrzeń nazw jest zbiorem klas dostarczających podobnej funkcjonalności, np.
klasy związane z formami, zgrupowane sa w przestrzeni nazw Windows.Forms, klasy
związane z obsługą dostępu do baz danych w przestrzeni nazw Data.
Interfejs GDI+ jest zdefiniowany w następujących przestrzeniach nazw:
System.Drawing
System.Drawing.Design
System.Drawing.Printing
System.Drawing.Imaging
System.Drawing.Drawing2D
System.Drawing.Text
System.Drawing
Przestrzeń nazw System.Drawing dostarcza podstawowej funkcjonalności interfejsu
GDI+. Zawarta jest w niej definicja podstawowych klas Brush,Pen,Graphics,Font itd. Klasa Graphics odgrywa ważną rolę w interfejsie GDI+ i zawiera metody służące do rysowania na urządzeniu graficznym. Poniższa tabela zawiera wykaz wybranych
klas przestrzeni nazw System.Drawing:
Tabela 1. Wybrane klasy przestrzeni nazw System.Drawing
Tabela 2. Wybrane struktury przestrzeni nazw System.Drawing
Struktura |
Opis |
Color |
reprezentuje kolor ARGB
|
Point, PointF |
reprezentuje punkt na płaszczyźnie, współrzędne x,y w przypadku Points , a typu Integer, w przypadku PointF typu Double
|
Rectangle, RectangleF |
Reprezentuje prostokąt opisany przez współrzędne lewego górnego rogu oraz prawego dolnego rogu
|
Size |
reprezentuje rozmiar szerokość oraz wysokość
|
System.Drawing.Design
Jest to przestrzeń w której umieszczone są klasy wspomagaj ące tworzenie nowych
Kontrolek aktualnie zawarte są w niej dwa typy klas: Edytory oraz narzędzia.
W skład edytorów wchodzą: BitmapEditor, FontEditor, ImageEditor, a
w skład narzędzi: ToolBoxItem, ToolBoxItemCollection.
System.Drawing.Drawing2D
Zawiera klasy oraz enumeratory przydatne do bardziej zaawansowanej grafiki wektorowej
na płaszczyźnie. Zawiera między innymi klasy reprezentujące gradientowy pędzel, macierz, ścieżki graficzne. Kilka podstawowych klas wchodzących w skład tej przestrzeni nazw przedstawia poniższa tabela:
Tabela 3. Wybrane klasy przestrzeni nazw System.Drawing.Drawing2D
Klasa |
Opis |
GraphicsPath |
reprezentuje zbiór połączonych linii oraz krzywych
|
LinearGradientBrush |
pędzel z liniowym gradientem
|
Matrix |
macierz reprezentująca transformacje geometryczne
|
:
Tabela 4. Wybrane enumeratory przestrzeni nazw System.Drawing.Drawing2D
Enumerator |
Opis |
QualityMode |
jakość rysowanych obiektów interfejsu GDI+
|
SmoothingMode |
sposób wygładzania krawędzi interfejsu GDI+
|
System.Drawing.Imaging
Dostarcza zaawansowanych funkcji GDI+ do manipulowania obrazami. Kodeki różnych
formatów graficznych.
System.Drawing.Printing
Definiuje klasy pozwalające aplikacji na komunikację z drukarką. Poniższa tabelka zawiera zestaw podstawowych klas wchodzących w skład tej przestrzeni nazw.
.
Tabela 5. Wybrane klasy przestrzeni nazw System.Drawing.Printing
Klasa |
Opis |
PageSettings |
ustawienia strony
|
PaperSize |
rozmiar papieru
|
PreviewPageInfo |
informacje o podglądzie wydruku dla pojedynczej strony
|
PrintController |
kontroluje wydruk dokumentu
|
PrintDocument |
wysyła wynik na drukarkę
|
PrinterResolution |
ustawia rozdzielczość drukarki
|
PrinterSettings |
ustawienia drukarki |
System.Drawing.Text
Większość klas związanych z zarządzeniem czcionkami znajduje się w przestrzeni
nazw System.Drawing. Przestrzeń System.Drawing.Text dostarcza zaawansowane
mechanizmy typograficzne. Obecnie zdefiniowane są w niej trzy klasy FontCollection,
InstalledFontCollection oraz PrivateFontCollection.
Klasa Graphics
Klasa Graphics jest jądrem interfejsu GDI+. Nieważne jaką operację graficzną chcemy
Wykonać, musimy w końcu skorzystać z klasy Graphics. Jest kilka sposobów otrzymania obiektu klasy Graphic do wykorzystania w aplikacji. Jedną z nich jest otrzymanie go jako parametru metody OnPaint() formy, która jest metodą obsługującą komunikat Paint. Metoda ta posiada argument typu System.Windows.Forms.PaintEventArgs zawierający obiekt typu Graphics.
Private Sub Form1_Paint(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Dim g As Graphics = e.Graphics
End Sub
Kiedy już mamy obiekt Graphics, to możemy wywoływać dowolne jego metody służące
Np. do malowania linii, elips, prostokątów, wielokątów, obrazków. Poniższa tabelka
zawiera wykaz niektórych metod klasy Graphics.
Tabela 6. Wybrane metody klasy Graphics
Metoda |
Opis |
DrawArc |
rysowanie łuku
|
DrawBezier, DrawBeziers, DrawCurve |
rysowanie krzywych Beziera
|
DrawEllipse |
rysowanie elipsy lub okręgu
|
DrawImage |
rysowanie obrazu
|
DrawLine |
rysowanie linii
|
DrawPath |
rysowanie obiektów zawartych w GraphicsPath
|
DrawPolygon |
rysowanie wielokąta
|
DrawRectangle |
rysowanie prostokąta
|
DrawString |
rysowanie łańcucha znaków
|
FillEllipse |
wypełniona elipsa
|
FillPath |
wypełnione obiekty zdefiniowane w GraphicsPath |
FillPolygon |
wypełniony wielokąt
|
FillRectangle |
wypełniony prostokąt
|
Jeżeli np. chcemy narysować elipsę, możemy posługując się obiektem Graphics wywołać metodę DrawEllipse, która posiada kilka wersji.
Private Sub Form1_Paint(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Dim g As Graphics = e.Graphics
' rysowanie elipsy
g.DrawEllipse(Pens.Black, 10, 10, 30, 30)
End Sub
Powyższy kod spowoduje namalowanie elipsy wpisanej w prostokąt o współrzędnych
lewego górnego rogu (10, 10) oraz szerokości 30 i wysokości 30, a więc namalowany
zostanie okrąg.
Obiekty graficzne
Do malowania w GDI+ są używane obiekty graficzne. Np., aby namalować prostokąt
wypełniony kolorem, potrzebny nam jest obiekt reprezentujący kolor, oraz sposób
wypełnienia. Zdefiniowane są cztery podstawowe obiekty, które są wykorzystywane podczas pracy z GDI+.
Tabela 7. Podstawowe obiekty graficzne GDI+
W GDI+, każdy z tych obiektów reprezentowany jest jako klasa.
Klasa Pen
Pióro reprezentowane jest przez obiekt klasy Pen i służy do rysowaniałinii o zadanej grubości oraz stylu. Aby posłużyć si ę piórem możemy skorzystać z jednego z predefiniowanych piór zdefiniowanych w klasie Pens lub utworzyć własny obiekt.
Klasa Pen zawiera cztery konstruktory umożliwiające utworzenie nowego obiektu, a
różniące się parametrami. Możemy skonstruować nowy obiekt przekazując konstruktorowi
informacje na temat:
koloru pióra,
koloru i grubości pióra,
pędzla,
pędzla i grubości pióra.
Przykładowy kod:
Dim p1 As Pen = new Pen(Color.Red)
Dim p2 As Pen = new Pen(Color.Red,5)
Dim p3 As Pen = new Pen(Brushes.Red)
Dim p4 As Pen = new Pen(Brushes.Red,5)
Struktura Color
Struktura Color reprezentuje kolor w postaci ARGB:
Tabela 8. Struktura Color
Składowa |
Opis |
A |
kanał alfa |
B |
składowa niebieska koloru |
G |
składowa zielona koloru |
R |
składowa czerwona koloru |
Możemy skonstruować kolor składający sięz dowolnej kombinacji składowych RGB:
Dim c As Color = Color.FromArgb(255,0,255)
Klasa Font
Klasa reprezentuje fonty, które tworzymy w celu namalowania napisu. Klasa Font pozwala na stworzenie fontów różnego typu, stylu, rozmiaru. Klasa posiada szereg
konstruktorów pozwalaj ących na tworzenie obiektów.
Style dost ępne dla tworzonych fontów przedstawione są w tabeli poniżej:
Tabela 9. Style czcionki FontStyle
Przykład utworzenia nowego obiektu Font:
Dim f As Font = new Font(”Times New Roman”,24)
Zegar analogowy
Nasz program będzie wyświetlał zegar analogowy. Część odpowiedzialna za pobieranie daty i godziny będzie identyczna jak w przypadku zegara cyfrowego. Istotna różnicą bedzie sposób wyświetlania i malowania zegara.
Rys.1. Zegar analogowy.
Najpierw utworzymy nowy projekt w języku VB.NET. Umieszczamy na formie komponent Timer. Zegar składa się z następujących elementów:
tarcza
liczby reprezentujące godziny
wskazówki (godzina, minuta, sekunda)
Musimy dokonać odpowiednich ustawień kontrolki Timer. W edytorze właściwości przestawiamy właściwość Enable na True, a Interval na 1000.
Najpierw napiszemy kod odpowiedzialny za malowanie tarczy zegara w postaci metody
DrawClock, wywoływanej w obsłudze zdarzenia Paint. Zaznaczamy formę i w edytorze właściwości przełączamy się na widok zdarzeń (Events), odszukujemy zdarzenie Paint i klikamy dwukrotnie myszka po prawej stronie.
Rysowanie tarczy zegara
Rysowanie tarczy zegara sprowadza siędo narysowania dwóch elips: jednej wypełnionej
Kolorem i drugiej w postaci konturu jak obwódki. Do rysowania elips posłużymy się metodami FillEllipse oraz DrawEllipse. Składowa klasy Form o nazwie ClientRectangle zawiera prostokąt definiujący obszar roboczy okna.
Deklarujemy zmienną p typu Pen, przypisujemy na nią pióro koloru czarnego, a
następnie modyfikujemy właściwość Width pióra, oznaczającą grubość pióra. Elipsa
obwódki malowana jest tym nowym piórem. Jako ostatni element malujemy wypełnioną elipsę w cetralnym punkcie tarczy.
Private Sub DrawClock(ByVal g As Graphics)
Dim b As Brush
b = Brushes.GhostWhite
g.FillEllipse(b, 10, 10, _
Me.ClientRectangle.Width - 20, _
Me.ClientRectangle.Height - 20)
Dim p As Pen = New Pen(Color.Black)
p.Width = 4
g.DrawEllipse(p, 10, 10, _
Me.ClientRectangle.Width - 20, _
Me.ClientRectangle.Height - 20)
Dim midX As Integer = ClientRectangle.Width / 2
Dim midY As Integer = ClientRectangle.Height / 2
g.FillEllipse(Brushes.Black, midX - 5, midY - 5, 10, 10)
End Sub
Wygląd aplikacji
Po dodaniu wywołania naszej metody DrawClock w obsłudze zdarzenia Paint w
następujący sposób: DrawClock(e.Graphics), kiedy uruchomimy program na ekranie
powinniśmy dostać to, co przedstawia rysunek 2
Rys.2. Zegar analogowy: tarcza zegara
Godziny
Kolejnym krokiem będzie namalowanie na tarczy zegara liczb reprezentujących godziny.
Najpierw jednak przypomnienie z matematyki.
Tarcza naszego zegara jest elipsą. Elipsę możemy opisać następującym równaniem:
(1)
gdzie a oraz b to długości półosi elipsy.
Rys.3. Geometra elipsy
Równanie (1) można zapisać w postaci parametrycznej:
(2)
Układ współrzędnych związany z oknem aplikacji ma punkt (0, 0) w górnym lewym
narożniku okna, wartości x rosną w prawo, natomiast y do dołu. Metoda rysująca elipsę dostaje parametry opisujące prostokąt, wewnątrz którego rysowana jest elipsa.
Rys.4. Elipsa w układzie współrzędnych okna
Możemy teraz wyznaczyć współrzędne do umieszczenia godzin, ponieważ wszystkie
malowane obiekty z użyciem GDI+ są wpisywane w prostokąt, tak samo jest w przypadku metody rysującej napis. Musimy wyznaczyć współrzędne prostokąta wewnątrz, którego umieścimy napis. Należy również uwzględnić, fakt, że godziny powinny być odpowiednio wyrównywane, np na górze zegara znajduje się godzina dwunasta, napis składający się z dwóch cyfr. Natomiast na dole godzina szósta, czyli jedna cyfra, cyfra reprezentująca godzinę szóstą powinna być wycentrowana względem godziny dwunastej. Godziny nie będą umieszczone dokładnie na obrzeżu tarczy zegara, lecz trochę wewnątrz. Środek elipsy, która jest tarczą zegara nie znajduje się w punkcie (0, 0), musimy więc uwzględnić ten fakt przy obliczaniu współrzędnych godzin. Ponieważ środek tarczy zegara znajduje się w środku okna, promienie elipsy mają odpowiednio wartość ClientRect.Width/2, oraz ClientRect.Height/2, godziny umieścimy na obrzeżu elipsy o mniejszych romieniach.
Do wycentrowania napisów będzie nam potrzebna ich szerokość w pikselach. Szerokość liter nie jest stała, do obliczenia miejsca zajmowanego przez napis posłużymy się metodą MeasureString z klasy Graphics, która zwróci nam między innymi szerokość napisu w pikselach. Metoda ta pobiera jako parametr napis, oraz czcionkę, jaką chcemy go namalować.
Jedna godzina to 30o, więc wykonamy pętlę dwanaście razy zmieniając kąt co 30o. Pętla będzie wykonywana dla zmiennej n zmieniającej się od 1 do 12, ponieważ pierwszą rysujemy godzinę pierwszą, rozpoczniemy od kąta -60o, tak jak pokazano na rys. 5.
Rys. 5. Rysowanie godzin na tarczy zegara analogowego
Uzupełnijmy kod dopisując metodę DrawNumbers
Private Sub DrawNumbers(ByVal g As Graphics)
Dim angle As Double
Dim ww As Double
Dim hh As Double
Dim x As Double
Dim y As Double
Dim sc As Double
Dim sh As Double
Dim ss As SizeF
Dim n As Integer
Dim midX As Integer
Dim midY As Integer
Dim s As String
angle = -60.0
For n = 1 To 12
midX = ClientRectangle.Width / 2
midY = ClientRectangle.Height / 2
ww = (ClientRectangle.Width - 50) / 2.0
hh = (ClientRectangle.Height - 50) / 2.0
x = ww * Math.Cos(angle * Math.PI / 180.0)
x = x + midX
y = hh * Math.Sin(angle * Math.PI / 180.0)
y = y + midY
s = Convert.ToString(n)
ss = g.MeasureString(s, Me.Font))
sc = ss.Width / 2
sh = (ss.Height / 2) - 1
g.DrawString(s, Me.Font, Brushes.Black, _
x - sc, y - sh, New StringFormat())
angle = angle + 30
Next
End Sub
Kolejmym krokiem jest zmodyfikowanie metody Paint, tak aby wywołać w niej naszą nowo napisaną metodę DrawNumbers:
Private Sub Form1_Paint(ByVal sender As System.Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles MyBase.Paint
Dim g As Graphics = e.Graphics
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
DrawClock(g)
DrawNumbers(g)
End Sub
Po dokonaniu tych zmian i uruchomieniu naszej aplikacji na ekranie powinniśmy
zobaczyć coś podobnego do widoku zaprezentowanego na rys 6.
Rys. 6. Zegar analogowy wygląd aplikacji (tarcza z godzinami)
Wskazówki
Ostatnim krokiem jest namlowanie wskazówek sekundnika, minut, oraz godzin. Możemy malować je w sposób podobny do tego jaki został użyty przy malowaniu godzin. Zacznijmy najpierw od sekundnika, ponieważ pełen obieg wskazówki sekundnika oznacza upływ minuty, sekunda jest 1/60 częścią minuty, wskazówka sekundnika, będzie malowana w 60 różnych położeniach, stopień o jaki należy ją obrócić po upływie sekundy to 360/60 = 6.
Do rysowania wskazówek zastosujemy jednak inną metodę niż zaprezentowaną przy
rysowaniu godzin. Skorzystamy z klasy GraphicsPath, oraz klasy reprezentuj ącej
macierze w przestrzeni dwu wymiarowej Matrix.
Wskazówki potraktujemy jako odcinki o początku w środku tarczy, a współrzędne końca zostaną wyznaczone na podstawie aktualnej godziny, minuty oraz sekundy. Zacznijmy od sekund. Wiemy, że po upływie sekundy wskazówkę musimy obrócić o 6o, czyli jeśli upłynęło np. 15 sekund, to kąt jaki tworzy wskazówka z osią OY naszego układu współrzędnych wynosi 90o. Tak więc, mając punkt p = (x1, y1) możemy obrócić go względem środka układu współrzędnych, a nowe współrzędne punktu po obrocie wyrażają następujące wzory:
Możemy zapisać powyższy układ równań w postaci macierzowej:
gdzie M jest macierzą obrotu. W naszym przypadku
Macierz M opisuje obrót punktu względem środka układu współrzdnych. Aby obrócić punkt wokół innego punktu, wystarczy wprowadzić nowy lokalny układ współrzędnych posiadający środek w punkcie względem którego chcemy dokonać obrotu. Sprowadzi się to do przesunięcia (translacji) punktu do środka układu współrz ędnych, dokonania obrotu i ponownego przemieszczenia. Mówiąc krótko, do dokonania złożenia trzech transformacji: translacji, obrotu oraz translacji.
Możemy sobie ułatwić życie i zamiast wyznaczać trzy macierze opisujące translacje oraz obrót skorzystać z z klasy Matrix, posiada ona metodęo nazwie RotateAt, która wyznaczy za nas macierz opisuj ącą obrót względem danego punktu o zadany kąt.
Rys. 7. Obrót punktu względem innego punktu
Korzystaj ąc z klasy DataTime, odczytamy bieżącą godzinę, składowe Hour, Minute
oraz Second określają aktualną godzine, minutę oraz sekundę. Znając liczbę sekund,
która upłynęla w bieżącej minucie, możemy policzyć kąt o jaki wskazówka sekundnika
musi zostać obrócona względem położenia pełnej minuty:
.
Napiszmy nową metodę o nazwie DrawHands, która na razie będzie rysowała tylko
wskazówkę sekundnika:
Private Sub DrawHands(ByVal g As Graphics)
Dim midX As Integer
Dim midY As Integer
Dim d As DateTime
Dim angles As Double
Dim ps As Point
Dim mTransform As Math.table
Dim gps As GraphicsPath
midX = (ClientRectangle.Left + ClientRectangle.Right) / 2
midY = (ClientRectangle.Top + ClientRectangle.Bottom) / 2
d = DateTime.Now
angles = d.Second * 6.0
mTransform = New Math.table()
mTransform.RotateAt(angles, New PointF(midX, midY))
ps = New Point(midX, 15)
gps = New GraphicsPath()
gps.AddLine(midX, midY, ps.X, ps.Y)
gps.Transform(mTransform)
g.DrawPath(Pens.Red, gps)
End Sub
Najpierw wyznaczamy punkt, względem, którego dokonamy obrotu, jego współrz ędne
to (midX, midY ), czyli środek tarczy zegara. Następnie pobieramy aktualną godzinę, wyznaczamy kąt obrotu, oraz tworzymy nową macierz. Korzystając z metody RotateAt budujemy macierz obrotu o kąt angles względem punktu (midX, midY). Deklarujemy punkt, który będzie obracany, następnie korzystając z klasy GraphicsPath, dokonamy obrotu linii o współrz ędnych pocz ątku (midX, midY ) oraz współrzędnych końca (midX, 15). Przypomnijmy sobie, że układ współrzędnych okna posiada oś OY skierowaną ku dołowi, punkt (midX, 15) leży więc o 15 pikseli od górnej krawędzi okna. Następnie zastosujemy metodę Transform klasy GraphicsPath, która zastosuje transformację opisaną przez macierz mTransform do wszystkich elementów zawartych w GraphicsPath. W naszym przypadku ponieważ zawiera ona tylko jedną linię, dokona obrotu tylko tej jednejłinii. Na zakończenie skorzystamy z metody DrawPath klasy Graphics aby namalować zawartość GraphicsPath.
Visual 6.doc 6/15
Obiekt |
Opis |
Brush |
używany do wypełniania zamkniętych obszarów kolorem, wzorem lub mapą bitową
|
Pen |
używany do rysowania linii, wielokątów, łuków i innych kształtów
|
Font |
opisuje czcionkę użytą do namalowania napisu
|
Color |
używany do opisu koloru w GDI+
|
Styl |
Opis |
Bold |
tekst pogrubiony |
Italic |
tekst pochylony |
Regular |
tekst normalny |
Strikeout |
tekst przekreślony |
Underline |
tekst podkreślony |
Klasa |
Opis |
Bitmap, Image |
mapa bitowa i obraz
|
Brush, Brushes |
pędzel służący do malowania wypełnionych figur
|
Font, FontFamily |
klasa reprezentująca czcionki
|
Graphics |
klasa reprezentująca urządzenie graficzne i dostarczająca metod służących do rysowania
|
Pen |
klasa reprezentująca pióro służące do malowania linii oraz krzywych
|
SolidBrush |
klasa reprezentująca pędzel do malowania figur wypełnionych jednolitym kolorem
|