12, ## Documents ##, Delphi 4 dla każdego


Rozdział 12.
Programowanie grafiki i multimediów


Programowanie grafiki i multimediów reprezentuje przyjemniejszą część pracy programistycznej. W tym rozdziale omawiany jest wstęp do programowania grafiki i multimediów z wykorzystaniem Delphi. W przypadku programowania grafiki większość tego wprowadzenia opiera się na analizie klas TCanvas i TBitmap.

Zaczniemy od spojrzenia na najprostsze techniki wyświetlania grafiki w Delphi. Później poznasz interfejs urządzeń graficznych Windows (GDI) i składające się na niego komponenty. W międzyczasie zapoznasz się z różnymi procedurami rysowania linii i kształtów, a także z różnymi sposobami wyświetlania bitmap. W dalszej części rozdziału omówione zostaną tzw. bitmapy pozaekranowe i korzyści płynące z ich zastosowania. Sekcje programowania multimediów dotyczą sposobów odtwarzania plików dźwiękowych przy wykorzystaniu interfejsu Windows API. Dowiesz się także, w jaki sposób odtwarza się pliki dźwiękowe MIDI i wideo AVI przy użyciu klasy TMediaPlayer.

Grafika w prosty sposób

Programowanie grafiki niekoniecznie musi być trudne. Czasami wszystko, co trzeba zrobić, sprowadza się do wyświetlenia obrazu lub kształtu w formularzu. Biblioteka VCL udostępnia gotowe komponenty przeznaczone do tych właśnie celów. Przed przystąpieniem do prawdziwego programowania grafiki przyjrzymy się niektórym z tych komponentów.

Komponent Shape (na zakładce Additional Palety Komponentów) umożliwia dodawanie do formularza figur geometrycznych o różnych kształtach. Jego użycie jest proste, wystarczy umieścić go w formularzu i ustawić według własnych potrzeb właściwości pędzla (Brush), pióra (Pen) i kształtu (Shape). Teraz można przystąpić do rysowania okręgów, elips, kwadratów i prostokątów (również z zaokrąglonymi narożnikami). Właściwość Brush wpływa na kolor tła, właściwość Pen zmienia kolor i grubość krawędzi figur.

Do wyświetlenia bitmapy w formularzu służy komponent Image. Znajduje on wiele zastosowań przy operacjach graficznych, włączając w to tworzenie tła formularza w postaci bitmapy. Właściwość Picture klasy TImage jest obiektem klasy TPicture. Wyboru obrazu można dokonać na etapie projektowania poprzez Inspektor Obiektów lub poprzez ładowanie w trakcie pracy programu. Poniższy przykład pokazuje, jak można zmienić rysunek w trakcie pracy programu:

Image1.Picutre.Bitmap.LoadFromFile('tlo.bmp');

Właściwość Stretch określa, czy rysunek będzie rozciągany lub kompresowany w celu dopasowania go rozmiaru komponentu. Właściwość Center decyduje o tym, czy bitmapa będzie wycentrowana względem komponentu. Właściwość AutoSize może posłużyć do zmuszenia komponentu do dopasowania własnych rozmiarów do rozmiaru rysunku.

Wspomnę również o komponencie PaintBox. Udostępnia on prostokątny obszar w postaci tzw. płótna (ang. canvas), stanowiącego arenę wszelkich operacji graficznych. Płótno to reprezentowane jest przez jego właściwość Canvas, będącą obiektem klasy TCanvas; klasa ta odpowiedzialna jest za większość operacji graficznych wykonywanych przez Delphi - poświęcam jej następną sekcję niniejszego rozdziału.

Kontekst urządzenia i klasa TCanvas

Windows stosuje termin kontekst urządzenia do opisania obszaru (płótna), na którym można tworzyć grafikę. Kontekst urządzenia może być wykorzystany do rysowania na wielu powierzchniach, w szczególności:

To tylko niektóre przykłady. Istnieją również inne, mniej oczywiste konteksty urządzeń (np. menu), ale te przedstawione powyżej będą najważniejsze z Twojego punktu widzenia.

Korzystanie z kontekstów urządzeń na poziomie API może być dosyć kłopotliwe. Po pierwsze, trzeba uzyskać uchwyt do kontekstu urządzenia od systemu Windows. Następnie trzeba utworzyć szereg różnych obiektów wymaganych przez kontekst (pióra, pędzle, czcionki itp.). Dopiero po tych czynnościach można przystąpić do rysowania. Po skończeniu rysowania trzeba dopilnować, aby obiekty, wybrane uprzednio dla kontekstu, zostały zwolnione przed samym zwolnieniem kontekstu urządzenia. Jeżeli nie zostanie to zrobione, aplikacja korzystająca z tego kontekstu spowoduje zagubienie pamięci - zajętej przez nie zwolnione obiekty i niedostępnej w żaden sposób aż do końca sesji Windows.

Jak więc widać, zabawa z kontekstami urządzeń jest na ogół mało przyjemna.

Dobrą wiadomością jest jednak to, że biblioteka VCL upraszcza cały ten proces, udostępniając klasę TCanvas. Oto krótki przykład - poniższy fragment kodu korzysta z interfejsu API, aby narysować na ekranie koło o niebieskiej krawędzi i czerwonym wnętrzu:

procedure TForm1.Button1Click(Sender: TObject);

var

DC : HDC;

Brush, OldBrush: HBrush;

Pen, OldPen : HPen;

begin

DC:=GetDC(Handle);

Brush:=CreateSolidBrush(RGB(255,0,0));

Pen:=CreatePen(PS_SOLID, 1, RGB(0,0,255));

OldBrush:=SelectObject(DC, Brush);

OldPen:=SelectObject(DC, Pen);

Ellipse(DC, 20,20,120,120);

SelectObject(DC, OldBrush);

SelectObject(DC, OldPen);

ReleaseDC(Handle, DC);

end;

Kod ten nie wygląda znowu tak strasznie, ale mimo wszystko łatwo można zapomnieć o przywróceniu poprzednich ustawień obiektów po zakończeniu rysowania. Kiedy coś takiego się zdarzy, w aplikacji wystąpi gubienie zasobów.

Teraz zobacz, jak wygląda równoważny kod napisany za pomocą biblioteki VCL:

Canvas.Brush.Color:= clRed;

Canvas.Pen.Color:= clBlue;

Canvas.Ellipse(20,20,120,120);

Kod ten jest nie tylko krótszy i czytelniejszy, ale również o wiele pewniejszy. Klasa TCanvas sama dba o to, aby w miarę potrzeby zwolnić zasoby, zwalniając programistę z tego obowiązku. Jest więc narzędziem o wiele poręczniejszym niż funkcje API.

Najważniejsze właściwości i metody klasy TCanvas przedstawione są w tabelach 12.1 i 12.2; większość z nich opiszę dokładniej w dalszej części rozdziału.

Tabela 12.1. Główne właściwości klasy TCanvas

Właściwość

Opis

Brush

Zawiera kolor pędzla lub wzór stosowany do wypełniania figur.

ClipRect

Określa prostokątny wycinek płótna, do którego dodatkowo ograniczone jest tworzenie grafiki. Właściwość tylko do odczytu.

CopyMode

Określa sposób tworzenia grafiki w kontekście bieżącej zawartości obszaru (normalnie, inwersyjne, xor itd.)

Font

Określa rodzaj czcionki stosowanej przez płótno do wypisywania tekstu.

Handle

Zawiera uchwyt, stanowiący kontekst urządzenia (HDC) płótna, stosowany podczas bezpośrednich wywołań funkcji API.

Pen

Określa styl i kolor linii rysowanych na płótnie.

PenPos

Zawiera bieżącą pozycję rysowania wyrażoną przez współrzędne x i y.

Pixels

Reprezentuje poszczególne piksele płótna w postaci macierzy

Tabela 12.2. Główne metody klasy TCanvas

Metoda

Opis

Arc

Rysuje łuk korzystając z bieżących ustawień pióra.

BrushCopy

Wyświetla bitmapę z przezroczystym tłem.

CopyRect

Kopiuje fragment obrazu na płótno.

Draw

Kopiuje obraz z pamięci na płótno.

Ellipse

Rysuje elipsę, korzystając z bieżących ustawień pióra (dla krawędzi) i pędzla (dla wypełnienia wnętrza).

FloodFill

Wypełnia obszar na płótnie zgodnie z bieżącymi ustawieniami pędzla.

LineTo

Rysuje odcinek linii prostej od bieżącego punktu do pozycji wyznaczonej przez parametry x i y.

MoveTo

Ustawia nową pozycję bieżącego punktu rysowania.

Pie

Rysuje wycinek koła - zgodnie z bieżącymi ustawieniami pióra i pędzla.

Polygon

Rysuje wielokąt na podstawie danych z tablicy punktów i wypełnia go zgodnie z bieżącymi ustawieniami pędzla.

Polyline

Rysuje linię łamaną na podstawie punktów z tablicy i bieżących ustawień pióra. Linia nie jest automatycznie domykana.

Rectangle

Rysuje prostokąt, korzystając z bieżących ustawień pióra (dla krawędzi) i pędzla (dla wypełnienia wnętrza).

RoundRect

Rysuje wypełniony prostokąt z zaokrąglonymi narożnikami.

StretchDraw

Kopiuje bitmapę z pamięci na płótno. Bitmapa jest rozciągana lub skracana w zależności od rozmiaru obszaru przeznaczenia.

TextExtent

Zwraca szerokość i wysokość (w pikselach) łańcucha przekazanego przez parametr Text. Szerokość jest obliczana na podstawie bieżącej czcionki płótna.

TextHeight

Zwraca wysokość (w pikselach) łańcucha przekazanego przez parametr Text. Szerokość jest obliczana na podstawie bieżącej czcionki płótna.

TextOut

Wypisuje tekst na płótnie od określonego położenia, korzystając z bieżącej czcionki.

TextRect

Wypisuje tekst w ramach ograniczonego obszaru.

Powyższe właściwości i metody reprezentują jedynie małą część funkcjonalności kontekstu urządzenia Windows, niemniej jednak dzięki nim będziesz w stanie wykonać 80% zadań związanych z tworzeniem grafiki. Zanim jednak przejdziemy do szczegółowego omawiania klasy TCanvas, musisz wcześniej dowiedzieć się co nieco na temat obiektów graficznych stosowanych przy programowaniu w Windows.

Obiekty GDI

Interfejs urządzeń graficznych Windows (GDI) składa się z wielu typów obiektów, które definiują funkcjonowanie kontekstu urządzenia. Najczęściej stosowanymi obiektami GDI są pióra, pędzle i czcionki. Inne obiekty GDI to palety, bitmapy i regiony. Przyjrzyjmy się najpierw piórom, pędzlom i obiektom, by późnij przejść do obiektów bardziej skomplikowanych.

Pióra, pędzle i czcionki

Pióra, pędzle i czcionki są prostymi obiektami. Zajmiemy się każdym z nich z osobna, aby przekonać się, w jaki sposób korzysta z nich klasa TCanvas.

Pióra

Pióro definiuje obiekt, którego przeznaczeniem jest rysowanie linii. Może to być prosta linia rysowana od jednego punktu do drugiego lub krawędź rysowana wokół prostokątów, elips i wielokątów. Dostęp do pióra, będącego obiektem klasy TPen, następuje poprzez właściwość Pen klasy TCanvas. Właściwości klasy TPen zostały przedstawione w tabeli 12.3.

Tabela 12.3. Właściwości klasy TPen

Właściwość

Opis

Color

Ustala kolor linii.

Handle

Zawiera kontekst urządzenia pióra (HDC). Stosowany podczas bezpośrednich odwołań do GDI.

Mode

Określa sposób w jaki linia będzie rysowana w kontekście bieżącej zawartości obszaru (normalny, inwersyjny, xor, itd.).

Style

Określa styl pióra. Może to być styl ciągły, kropkowy, kreskowy,
kropkowo-kreskowy, czysty lub inny.

Width

Zawiera grubość linii w pikselach.

Właściwości te używane są w sposób oczywisty. Poniższy przykład rysuje czerwoną linię w stylu kreskowym:

Canvas.Pen.Color:= clRed;

Canvas.Pen.Style:= psDash;

Canvas.MoveTo(20,20);

Canvas.LineTo(120,20);

Aby przetestować ten fragment kodu, umieść przycisk w formularzu, a następnie dodaj kod do wnętrza funkcji obsługującej zdarzenie OnClick tego przycisku. Kiedy klikniesz na przycisku, w formularzu zostanie narysowana linia.

0x01 graphic

Prezentowane w niniejszym rozdziale przykładowe aplikacje cechują się jednym niedostatkiem, stanowiącym cenę płaconą za ich prostotę: otóż - jeżeli formularz aplikacji zostanie zasłonięty przez inne okno, a następnie odsłonięty, brak będzie na nim rysunku uprzednio się tam znajdującego. Jest to konsekwencja nieoprogramowania zdarzenia OnPaint - zdarzenie to generowane jest każdorazowo, gdy zachodzi potrzeba odświeżenia zawartości okna (np. właśnie przy jego odsłonięciu). Jeżeli więc chcesz, by dana aplikacja funkcjonowała zgodnie ze standardami Windows (zachowując swą grafikę), umieść kod tworzący grafikę w treści procedury obsługującej zdarzenie OnPaint.

Style kropkowe i kreskowe pióra mogą być stosowane jedynie przy linii o grubości jednego piksela. Styl psClear może zostać wykorzystany do eliminacji linii, jaką interfejs GDI rysuje wokół krawędzi obiektów takich jak prostokąty, elipsy i wypełnione wielokąty.

0x01 graphic

Możesz poeksperymentować z różnymi właściwościami klasy TPen, umieszczając w formularzu komponent typu Shape i modyfikując jego właściwość Pen. Operacja taka jest szczególnie użyteczna, kiedy chce się oglądać na bieżąco efekty zmian wartości właściwości Mode klasy TPen.

Pędzle

Pędzel jest obiektem służącym do wypełniania wnętrza obszarów - wszystkie rysowane elipsy, prostokąty, wielokąty itp. zostaną wypełnione zgodnie z bieżącymi ustawieniami pędzla. Ustawienia te nie ograniczają się li tylko do koloru (jak można by mniemać na podstawie potocznego znaczenia słowa „pędzel”) lecz obejmują również wzór („deseń”) wypełnienia, bądź to w jednej z predefiniowanych postaci, bądź też w postaci określonej przez wskazaną bitmapę.

W ramach klasy TCanvas pędzel reprezentowany jest przez właściwość Brush klasy TBrush, której właściwości przedstawia tabela 12.4

Tabela 12.4. Właściwości klasy TBrush

Właściwość

Opis

Bitmap

Bitmapa określająca wzór wypełnienia. W przypadku Windows 95 bitmapa ta nie może przekroczyć rozmiaru 8×8 pikseli.

Color

Kolor wypełnienia.

Handle

Kontekst urządzenia (HDC) pędzla. Stosowany przy bezpośrednich
odwołaniach do GDI.

Style

Styl pędzla (jednolity, wymazujący (clear) lub jeden z predefiniowanym wzorów).

Domyślną wartością właściwości Style jest bsSolid, co oznacza styl jednolity. Wypełnianie obszarów wzorem wymaga nadania właściwości Style odpowiedniej wartości (bsHorizontal, bsVertical, bsFDiagonal, bsBDiagonal, bsCross, bsDiagCross). Poniższy przykład rysuje koło wypełnione wzorem w kratę obróconą o 45 stopni. Efekt wykonania kodu przedstawiony został na rysunku 12.1.

Canvas.Brush.Color:= clBlue;

Canvas.Brush.Style:= bsDiagCross;

Canvas.Ellipse(20,20,220,220);

Rysunek 12.1.

Koło wypełnione przykładowym wzore

0x01 graphic

Podczas stosowania pędzla ze wzorem właściwość Color definiuje kolor linii, które tworzą określony wzór. Z nieznanego powodu biblioteka VCL automatycznie wymusza przezroczyste tło podczas wypełniania wzorem. Oznacza to, że kolor tła pędzla będzie taki sam jak kolor tła okna, na którym rysowana jest figura.

Przyjrzyj się jeszcze raz rysunkowi 12.1, a zobaczysz, że kolor tła koła jest taki sam jak kolor formularza (chociaż w rysunku monochromatycznym niełatwo to zauważyć). Jeżeli chcesz określić kolor tła, musisz pominąć VCL i odwołać się bezpośrednio do API. Oto przykład kodu, który ustala wypełnienie w postaci niebieskiego wzoru na białym tle:

Canvas.Brush.Color:=clBlue;

Canvas.Brush.Style:=bsDiagCross;

SetBkMode(Canvas.Handle, OPAQUE);

SetBkColor(Canvas.Handle, clWhite);

Canvas.Ellipse(20, 20, 220, 220);

Teraz kolor tła pędzla będzie biały - co widać na rysunku 12.2.

Kolejną interesującą cechą pędzli jest opcja tła w postaci bitmapy. Najpierw przyjrzyj się poniższemu fragmentowi kodu:

Canvas.Brush.Bitmap:=TBitmap.Create;

Canvas.Brush.Bitmap.LoadFromFile('tlo.bmp');

Canvas.Ellipse(20, 20, 220, 220);

Canvas.Brush.Bitmap.Free;

Rysunek 12.2.

Niestandardowe tło wypełnienia

0x01 graphic

Pierwsza linia kodu tworzy obiekt klasy TBitmap i przypisuje go do właściwości Bitmap pędzla. Domyślnie właściwości Bitmap nie jest przypisywana żadna wartość, dlatego trzeba samemu utworzyć obiekt i przypisać go. W drugiej linii następuje załadowanie bitmapy z pliku. Nie może ona być większa niż 8 na 8 pikseli. Można użyć większej bitmapy, ale wtedy zostanie ona przycięta do rozmiaru 8×8. Trzecia linia rysuje elipsę. Po jej narysowaniu bitmapa jest zwalniana - jest to niezbędne, ponieważ czynność ta nie jest wykonywana przez bibliotekę VCL. Zaniedbanie tej operacji spowoduje zagubienie fragmentu pamięci w programie. Koło wypełnione wzorem pochodzącym z bitmapy przedstawione zostało na rysunku 12.3.

Rysunek 12.3.

Wypełnienie wzorem pochodzącym z bitmapy

0x01 graphic

Pędzel może być również wykorzystywany do wyczyszczenia obszaru, co oznacza jego wypełnienie kolorem „przezroczystym”, czyli faktyczne odsłonięcie tła, na którym obiekt został narysowany. W tym celu należy nadać właściwości Style wartość bsClear. Jeżeli - przykładowo - wewnątrz koła z rysunku 12.13 narysujemy mniejsze i wypełnimy je kolorem „przezroczystym”, otrzymamy obraz prezentowany na rysunku 12.4.

Odpowiada za to poniższy fragment kodu:

Canvas.Pen.Width:=1;

Canvas.Brush.Bitmap:=TBitmap.Create;

Canvas.Brush.Bitmap.LoadFromFile('tlo.bmp');

Rysunek 12.4.

Efekt wypełnienia kolorem przezroczystym

0x01 graphic

Canvas.Ellipse(20, 20, 220, 220);

Canvas.Brush.Style:=bsClear;

Canvas.Pen.Width:=5;

Canvas.Ellipse(70,70,170,170);

Canvas.Brush.Bitmap.Free;

Bezpośrednie odwołanie się do API (z wykorzystaniem właściwości Handle) umożliwia wykonanie również innych operacji z wykorzystaniem pędzli. Jednak w większości wypadków wystarczy skorzystać z klasy TBrush.

Czcionki

Czcionki nie są dla Ciebie niczym nowym; używałeś ich już w trakcie wcześniejszej lektury tej książki. Czcionki stosowane przez klasę TCanvas nie różnią się niczym od czcionek stosowanych w formularzach czy innych komponentach. Właściwość Font klasy TCanvas jest identyczna jak właściwość o tej nazwie należąca do dowolnego komponentu. Żeby zmienić czcionkę dla płótna, zrób tak:

Canvas.Font.Name:= 'Courier New';

Canvas.Font.Size:= 14;

Canvas.Font.Style:= Canvas.Font.Style+ [fsBold];

Canvas.TextOut(20, 20, 'Test');

O wykorzystaniu czcionek będzie jeszcze mowa w sekcji „Wypisywanie tekstu”.

Bitmapy i palety

Zazwyczaj bitmapy i palety występują razem. W Delphi obiekt bitmapy jest zaszyty w klasie TBitmap. Dzięki niej ładowanie i wyświetlanie bitmap staje się rzeczą prostą. Miałeś już okazję przekonać się, jak działa klasa TBitmap w programie Jumping Jack w rozdziale ósmym „Tworzenie aplikacji w Delphi”. Klasa ta znajduje zastosowanie w wielu różnych sytuacjach. Niektórym z nich przyjrzymy się w dalszej części tego rozdziału podczas omawiania rysowania bitmap, a także bitmap przechowywanych w pamięci. TBitmap jest klasą skomplikowaną, dlatego omówimy tylko jej najważniejsze właściwości i metody.

Palety są jednym z najbardziej zagmatwanych aspektów programowania dla Windows. W większości przypadków paleta jest utrzymywana przez obiekt klasy TBitmap, nie musisz więc martwić się o nią. Pozwól, że zamiast tłumaczenia jak ważne są palety, przedstawię Ci przykład.

Stwórz nową aplikację, a następnie wpisz poniższy kod do wnętrza funkcji obsługującej zdarzenie OnPaint lub użyj zdarzenia kliknięcia na przycisku. Wpisz odpowiednią ścieżkę dostępu do pliku HANDSHAK.BMP (plik ten powinieneś znaleźć w katalogu Borland Shared\ Files\Images\Splash\256Color). Oto kod:

var

Bitmap : TBitmap;

begin

Bitmap:= TBitmap.Create;

{ Bitmap.IgnorePalette:=True;}

Bitmap.LoadFromFile('handshak.bmp');

Canvas.Draw(0,0, Bitmap);

Bitmap.Free;

end;

Zwróć uwagę, że jedna z linii ukryta jest w komentarzu. Uruchom program, a zobaczysz bitmapę wyświetloną w formularzu. Teraz usuń znaki komentarza z linii kodu. Linia ta nakazuje zignorowanie informacji o palecie w trakcie wyświetlania bitmapy. Uruchom ponownie program - tym razem powinieneś zauważyć, że wszystkie kolory bitmapy są nieprawidłowe (sytuacja taka może się jednak nie zdarzyć, jeżeli Twoja karta graficzna jest ustawiona na wyświetlanie barw z rozdzielczością większą niż 256). Przyczyną tej sytuacji jest ignorowanie palety kolorów - a to właśnie ona gwarantuje, że do palety systemowej zostaną przekazane prawidłowe barwy.

Obiekty bitmap i palet odgrywają ważną rolę w operacjach graficznych. Zrozumienie zagadnień związanych z tym tematem może zająć trochę czasu, dlatego nie popadaj w zwątpienie, kiedy coś nie wyjdzie Ci za pierwszym razem.

Regiony

Regiony są wydzielonymi obszarami płótna, do których ograniczone są wszelkie operacje graficzne związane z tym płótnem. Klasa TCanvas zawiera co prawda właściwość ClipRect, ale - po pierwsze, może ona definiować jedynie obszar prostokątny, po drugie - jest przeznaczona tylko do odczytu.

W celu zdefiniowania regionu należy odwołać się do funkcji Windows API. Weźmy pod uwagę poprzedni przykład, modyfikując go odrobinę w celu zilustrowania, jak funkcjonują regiony:

var

Bitmap : TBitmap;

Rgn : HRGN;

begin

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile('handshak.bmp');

Rgn:= CreateRectRgn(50,50,250,250);

SelectClipRgn(Canvas.Handle, Rgn);

Canvas.Draw(0,0, Bitmap);

Bitmap.Free;

end;

Teraz, po uruchomieniu programu, przekonasz się, że wyświetlana jest tylko część bitmapy. Funkcja SelectClipRgn definiuje region w kształcie prostokąta o współrzędnych 50,50, 250 i 250. Bitmapa jest nadal wyświetlana w takim samym położeniu, jak uprzednio, chociaż teraz widoczna jest tylko jej część - ta, która mieści się wewnątrz regionu; cała jej reszta jest po prostu ignorowana.

Regiony mogą przyjmować również kształty różne od prostokątnych. Przeredagujmy poprzedni przykład, czyniąc go trochę bardziej interesującym - zmień linię definiującą region na następującą:

Rgn:= CreateEllipticRgn(30, 30, 170, 170);

Uruchom ponownie swój program - tym razem bitmapa jest ograniczona przez okrąg (rysunek 12.5).

Rysunek 12.5.

Bitmapa ograniczona regionem w kształcie okręgu

0x01 graphic

Spróbujmy teraz utworzyć jeszcze jeden region o innym kształcie - zmodyfikuj kod programu do następującej postaci:

const

Punkty : array[0..3] of TPoint =

((X:80;Y:0), (X:0;Y:80), (X:80;Y:160), (X:160;Y:80));

var

Bitmap : TBitmap;

Rgn : HRGN;

begin

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile('handshak.bmp');

Rgn:= CreatePolygonRgn(Punkty, 4, ALTERNATE);

SelectClipRgn(Canvas.Handle, Rgn);

Canvas.Draw(0,0, Bitmap);

Bitmap.Free;

end;

Tym razem zdefiniowany region posiada kształt wielokąta - tablica Punkty zawiera definicję jego wierzchołków. Z tablicy tej korzysta funkcja CreatePolygonRgn definiująca region. Możliwe jest użycie dowolnej ilości wierzchołków.

Uruchom ponownie program, aby obejrzeć efekt końcowy (rysunek 12.6).

Rysunek 12.6.

Obszar ograniczony w kształcie wielokąta

0x01 graphic

0x01 graphic

Powyższy fragment kodu przedstawia również sposób deklarowania stałej tablicy rekordów:

const

Punkty : array[0..3] of TPoint =

((X:80,Y:0), (X:0;Y:80), (X:80;Y:160), (X:160;Y:80));

Następuje tutaj deklaracja tablicy rekordów typu TPoint i zainicjalizowanie jej elementów wartościami. Typ TPoint składa się z dwóch pól X i Y. Zwróć uwagę na konstrukcję przypisania: najpierw pojawia się nazwa pola, następnie znak dwukropka i na końcu wartość przypisywana polu (np. X:80). Wartości nadawane są polom X i Y połączonym w pary ze średnikiem jako znakiem rozdzielającym. Istnieją cztery takie pary, ponieważ z tylu elementów składa się tablica. Jest to jedyny sposób deklarowania i inicjalizacji stałej tablicy rekordów.

Stosowanie regionów może okazać się użyteczne w przypadku niektórych typów operacji graficznych; chociaż być może będziesz potrafił się bez nich obyć, to jednak w niektórych sytuacjach okazują się one niezastąpione.

Proste operacje graficzne

W trakcie studiowania tej książki napotkałeś już niektóre proste procedury graficzne. Wiesz już na przykład, że metoda Rectangle służy do rysowania kwadratów i prostokątów, metoda Ellipse rysuje okręgi i owale, a funkcje LineTo i MoveTo służą do kreślenia odcinków linii prostej. Istnieje również metoda Arc rysująca łuki i metoda Pie rysująca wycinki koła. Wszystkie one wydają się w miarę proste, nie ma więc większego sensu zagłębianie się w ich szczegóły. Zamiast tego zajmijmy się bardziej interesującymi (i kłopotliwymi) operacjami graficznymi, na które można natknąć się w trakcie tworzenia aplikacji w Delphi.

Wypisywanie tekstu

Wypisywanie tekstu nie wydaje się być operacją trudną - czy jest tak w rzeczywistości? Pośród wielu ciekawych aspektów tej czynności istnieją takie, których nieznajomość może w znacznym stopniu utrudnić Ci pracę.

Metody TextOut i TextRect

Metoda TextOut jest najprostszym środkiem służącym wypisywaniu tekstu; tak naprawdę niewiele można powiedzieć na jej temat. Wystarczy przekazać jej współrzędne X i Y oraz tekst do wyświetlenia - np.

Canvas.TextOut(20,20, 'Joshua Reisdorph');

i gotowe - powyższa instrukcja powoduje wyświetlenie napisu Joshua Reisdorph, współrzędne 20, 20 odnoszą się do lewego górnego rogu prostokąta zawierającego wypisywany tekst. Najlepiej zilustruje to poniższy przykład:

Canvas.TextOut(20,20, 'To jest test');

Canvas.MoveTo(20,20);

Canvas.LineTo(100,20);

Powyższy fragment kodu powoduje wyświetlenie tekstu od pozycji 20,20, a następnie rysuje linię od tego samego miejsca do pozycji o współrzędnych 100,20. Efekt działania tego kodu przedstawia rysunek 12.7. Jak widzisz, linia widnieje nad górną krawędzią tekstu.

Rysunek 12.7.

Tekst wyświetlony za pomocą metody TextOut

0x01 graphic

Stosuj metodę TextOut, kiedy będziesz chciał wypisać tekst nie wymagający dokładnego pozycjonowania.

Metoda TextRect, również wypisująca podany tekst w podanym położeniu, umożliwia ponadto określenie prostokątnego obszaru ograniczającego - każdy fragment tekstu, który wysunie się poza tę granicę, zostanie obcięty. Poniższy fragment kodu daje pewność, iż wyświetlonych zostanie nie więcej niż 100 pikseli tekstu:

Canvas.TextRect( Rect(20,50,120,70),

20,50,

'To jest bardzo długi tekst, który może zostać obcięty,');

Obydwie metody - TextOut i TextRect - mogą wyświetlać jedynie pojedyncze linie tekstu. Nie są wykonywane żadne funkcje formatujące tekst.

0x01 graphic

Wypisywanie tekstu zawierającego znaki tabulacji umożliwia funkcja Windows API o nazwie TabbedTextOut.

Tło tekstu

Przyjrzyj się rysunkowi 12.7 i zauważ, że tekst posiada białe tło - nie wygląda to najlepiej na szarym formularzu. Kolor tła tekstu pochodzi od bieżącego ustawienia pędzla (domyślnie białego). Zaradzenie sytuacji widocznej na rysunku 12.7 wymaga podjęcia jednej z dwóch akcji: zmiany koloru pędzla lub uczynienia tła tekstu przezroczystym.

Zmiana koloru tła tekstu jest stosunkowo prosta. Problem w tym, jakiego koloru należy użyć. W tym przypadku kolor tła tekstu może być taki sam, jak kolor formularza, wystarczy więc dokonać następującego przypisania:

Canvas.Brush.Color:= Color;

W większości wypadków kod ten spełni swoje zadanie, ale w niektórych sytuacjach może okazać się niewystarczający. Byłoby znacznie łatwiej, gdybyś mógł ustawić kolor tła tekstu na kolor przezroczysty:

var

OldStyle: TBrushStyle;

begin

StaryStyl:= Canvas.Brush.Style;

Canvas.Brush.Style:= bsClear;

Canvas.TextOut(20,20, 'To jest krótki test');

Canvas.Brush.Style:= StaryStyl;

end;

Na początku zachowywany jest bieżący styl pędzla, następnie jest on ustawiany jako przezroczysty (bsClear); po wyświetleniu tekstu przywracany jest styl pierwotny. Powinieneś przyzwyczaić się do zachowywania istniejących ustawień (w tym wypadku - stylu pędzla) i odtwarzania ich po skończonej operacji. W związku z powyższym przykładem - mało prawdopodobna jest potrzeba pozostawienia przezroczystego stylu pędzla, dlatego przywrócenie mu poprzedniego stylu jest dobrym rozwiązaniem.

Korzystanie z przezroczystego tła posiada również inne zalety. Powiedzmy, że chcesz wyświetlić jakiś tekst na tle bitmapy. W takim wypadku zdecydowanie nie możesz użyć jednolitego tła. Ilustracją tego problemu jest poniższy fragment kodu (plik FACTORY.BMP można znaleźć w katalogu Borland Shared \Images\Splash\256Color\):

var

OldStyle: TBrushStyle;

Bitmap : TBitmap;

begin

Bitmap:= TBitmap.Create;

Bitmap.LoadFromFile('factory.bmp');

Canvas.Draw(0,0, Bitmap);

Canvas.Font.Name:='Arial Bold';

Canvas.Font.Size:=13;

StaryStyl:= Canvas.Brush.Style;

Canvas.Brush.Style:= bsClear;

Canvas.TextOut(20,20, 'Przezroczyste tło');

Canvas.Brush.Style:= StaryStyl;

Canvas.TextOut(20,20, 'Jednolite tło');

Bitmap.Free;

end;

Kod ten wyświetla bitmapę w formularzu. Następnie na obszarze bitmapy wypisywany jest tekst z przezroczystym tłem. Później następuje ponowne wypisanie tekstu, ale tym razem z tłem jednolitym. Rezultat wykonania tego kodu został przedstawiony na rysunku 12.8. Jak widać wykorzystanie przezroczystego tła daje znacznie lepszy efekt graficzny.

Rysunek 12.8.

Tekst wyświetlony na obszarze bitmapy, na tle przezroczystym i jednolitym

0x01 graphic

Kolejny podwód przemawiający za stosowaniem przezroczystego tła tekstu został zilustrowany w rozdziale trzynastym w sekcji „Rysowanie specyficzne paneli na pasku statusu”. Na pasku statusu umieszczany jest tekst o trójwymiarowym wyglądzie, powstający przez narysowanie go białym kolorem, a następnie powtórzenie kolorem czarnym z minimalnym przemieszczeniem. Jedyny sposób realizacji tego zadania opiera się na zastosowaniu przezroczystego tła. Jak widzisz, czasami użycie przezroczystego tła jest jedynym sposobem osiągnięcia efektu, którego oczekujesz.

Funkcja DrawText

Funkcja biblioteki API Windows - DrawText - daje znacznie większe możliwości rysowania tekstu niż metoda TextOut. Z niewiadomego powodu klasa TCanvas nie posiada metody obudowującej tę funkcję, dlatego konieczne jest odwołanie się do nie w sposób bezpośredni. Na początku przyjrzyjmy się przykładowi jej użycia, później przejdziemy do omówienia jej ogromnych możliwości:

var

R: TRect;

begin

R:=Rect(20,20,220,80);

Canvas.Rectangle(20,20,220,80);

DrawText(Canvas.Handle,

'Przykład użycia funkcji DrawText.',

-1, R,DT_SINGLELINE or DT_VCENTER or DT_CENTER);

end;

Działanie tego kodu (a także następujących dalej przykładów) zostało przedstawione na rysunku 12.9.

Na początku funkcja Rect inicjalizuje zmienną typu TRect, reprezentującą prostokątny obszar; Tenże obszar rysowany jest następnie na płótnie. Ma to na celu zobrazowanie rozmiaru prostokąta, na którym za chwilę będzie wyświetlany tekst - oczywiście przy użyciu funkcji DrawText. Przyjrzyjmy się dokładniej jej parametrom: