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:
w obszarze okna użytkownika lub jego ramce
na Pulpicie
w pamięci
na drukarce lub innym urządzeniu wyjściowym
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, |
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.
|
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.
|
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 |
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
|
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 |
|
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.
|
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 |
|
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:
Pierwszy parametr zawiera uchwyt kontekst urządzenia, na którym dokonywane jest wyświetlanie/wypisywanie tekstu. Dla płótna - klasy TCanvas - uchwyt ten ukrywa się pod właściwością Handle.
Rysunek 12.9. Przykłady użycia funkcji DrawText |
|
Drugi parametr jest łańcuchem określającym wyświetlany tekst.
Trzeci parametr określa liczbę początkowych znaków tekstu, które zostaną uwzględnione; wartość -1 oznacza wyświetlanie wszystkich znaków.
Czwarty parametr - typu TRect - określa prostokąt, w którym umiejscowiony będzie wypisywany tekst; parametr ten przekazywany jest przez zmienną, ponieważ w niektórych przypadkach jest on modyfikowany przez funkcję (patrz opis znacznika DT_CALCRECT w jednym z poniższych akapitów).
Kluczem do zachowania się funkcji DrawText jest jej końcowy parametr. Służy on do przekazania znaczników używanych w trakcie wyświetlania tekstu. W tym przykładzie użyte zostały znaczniki DT_SINGLELINE, DT_VCENTER i DT_CENTER. Informują one Windows o tym, że tekst znajduje się w jednej linii i powinien być wyśrodkowany zarówno w poziomie, jak i w pionie. Istnieje prawie 20 znaczników które można przekazać pod postacią tego parametru - nie będę tutaj omawiał każdego z nich, dlatego po kompletną ich listę odsyłam Cię do systemu pomocy Win32 API.
Powyższy przykład prezentuje jeden z najczęstszych powodów wykorzystania funkcji DrawText: wyśrodkowanie tekstu - w poziomie, w pionie, bądź w obydwu kierunkach. Cecha ta jest niezwykle przydatna chociażby podczas rysowania specyficznego (owner- drawing) elementów list lub opcji menu.
Jednym z interesujących znaczników funkcji DrawText jest DT_END_ELLPSIS. Jeżeli wypisywany tekst jest zbyt długi, aby zmieścić się w ustalonym prostokącie, Windows obetnie go i doda na jego końcu informujący o tym wielokropek - oto przykład:
var
R: TRect;
beign
R:=Rect(20,100,120,150);
DrawText(Canvas.Handle,
'Ten tekst jest za długi, aby się tu zmieścić.',
-1, R, DT_END_ELLIPSIS);
end;
Wykonanie tego kodu zaowocuje wyświetleniem na ekranie następującego tekstu:
Ten tekst jest za dł...
Możesz stosować ten znacznik za każdym razem, kiedy będziesz przewidywał, iż tekst może nie zmieścić się w przeznaczonym dla niego prostokącie.
Kolejnym nieocenionym znacznikiem jest DT_CALCRECT. Powoduje on obliczenie wysokości prostokąta niezbędnej do pomieszczenia w jego wnętrzu określonego tekstu. Przy zastosowaniu tego znacznika Windows oblicza i zwraca niezbędną wysokość, ale nie wyświetla tekstu. Programista informuje Windows o szerokości, jaką powinien posiadać prostokątny obszar, a system na tej podstawie oblicza wysokość niezbędną do pomieszczenia tekstu. W rzeczywistości modyfikowane są również współrzędne lewego dolnego narożnika wspomnianego prostokąta (cała ta komunikacja odbywa się w ramach czwartego parametru wywołania). Jest to szczególnie użyteczne w przypadku wypisywania tekstu składającego się z wielu linii.
Poniższy przykład ilustruje zastosowanie znacznika DT_CALCRECT:
var
R: TRect;
S: string;
beign
R:=Rect(20,50,150,200);
S:='Jest to bardzo długi tekst, który zostanie umieszczony '+
'w kilku liniach tekstu.';
DrawText(Canvas.Handle, PChar(S), -1, R, DT_CALCRECT
or DT_WORDBREAK);
Canvas.Brush.Style:= bsSolid;
Canvas.Rectangle(R.left, R.top, R.right, R.bottom);
Canvas.Brush.Style:=bsClear;
DrawText(Canvas.Handle, PChar(S), -1, R, DT_WORDBREAK);
end;
Zauważ, że w drugim parametrze funkcji DrawText łańcuch S jest rzutowany na typ PChar. Jest to niezbędne, ponieważ funkcja DrawText wymaga przekazania jej tekstu w postaci wskaźnika do tablicy znaków, a nie w postaci zwykłego łańcucha (string).
|
Rzutowanie łańcucha na typ PChar dopuszczalne jest jedynie w przypadku długich łańcuchów, gdyż tylko one mają odpowiednią do tego strukturę. Jeżeli używasz „tradycyjnych” łańcuchów ShortString - na przykład w celu zgodności projektu z Delphi 1 - nie możesz wykonywać tego rzutowania, lecz musisz posłużyć się pomocniczą tablicą znaków i funkcją StrPCopy konwertującą krótki łańcuch do postaci łańcucha z zerowym ogranicznikiem. Zasadę tę ilustruje poniższy fragment programu: var Temp : array[0..50] of Char; R: TRect; S: ShortString; Begin … R:=Rect(20,50,150,200);
S := Jest to bardzo długi tekst, który StrPCopy(Temp, S); DrawText(Canvas.Handle, Temp, -1, R, DT_CALCRECT or DT_WORDBREAK); … Ponieważ tak się składa, że funkcja StrPCopy zwraca jako wynik adres docelowej tablicy znaków, powyższy przykład można nieco uprościć: var Temp : array[0..50] of Char; R: TRect; S: ShortString; begin … R:=Rect(20,50,150,200);
S := Jest to bardzo długi tekst, który zostanie DrawText(Canvas.Handle, StrPCopy(Temp, S), -1, R, DT_CALCRECT or DT_WORDBREAK); … |
Umieść ten kod we wnętrzu funkcji obsługującej zdarzenie OnPaint formularza. Uruchom program kilkakrotnie, modyfikując każdorazowo długość wyświetlanego łańcucha. Zauważ, że niezależnie od tego, jak długi łańcuch wpiszesz, prostokątny obszar jest zawsze dokładnie dopasowany do tekstu. Rezultat tego ćwiczenia można obejrzeć na rysunku 12.9.
|
Jeżeli potrzebujesz wykorzystać jeszcze bardziej zaawansowane możliwości wyświetlania tekstu, możesz skorzystać z funkcji DrawTextEx. Więcej szczegółów na jej temat znajdziesz w systemie pomocy Win32 API. |
|
Rysowanie tekstu za pomocą pomocy funkcji DrawText przebiega wolniej, niż w przypadku funkcji TextOut - jeżeli więc pewne operacje muszą być wykonane z odpowiednią szybkością, należy zrezygnować z funkcji DrawText na rzecz funkcji TextOut. Sytuacja taka zmusza co prawda programistę do większego nakładu pracy, ale prawdopodobnie wzrośnie dzięki temu szybkość programu. Na szczęście jednak w przypadku większości operacji graficznych, różnica szybkości pomiędzy funkcjami DrawText i TextOut okazuje się niezauważalna. |
Rysowanie bitmap
Wbrew pozorom rysowanie bitmap w Delphi 4 nie należy do czynności trudnych. Do rysowania bitmap przeznaczonych jest kilka metod klasy TCanvas; najczęściej używaną z nich jest funkcja Draw. Metoda ta w prosty sposób rysuje bitmapę (będącą potomkiem klasy TGraphic) na płótnie we wskazanym położeniu.
Przykłady jej użycia widziałeś już do tej pory kilkakrotnie - oto kolejny:
var
Bitmap : TBitmap;
begin
Bitmap :=TBitmap.Create;
Bitmap.LoadFromFile('c:\winnt\winnt256.bmp');
Canvas.Draw(0,0, Bitmap);
Bitmap.Free;
end;
W powyższym kodzie tworzony jest obiekt klasy TBitmap, ładowany jest plik o nazwie WINNT245.BMP, po czym następuje jego wyświetlenie w lewym górnym rogu formularza. Używaj metody Draw wtedy, gdy chcesz wyświetlić bitmapę bez żadnych modyfikacji.
Kiedy wymagana jest zmiana rozmiaru bitmapy, należy posłużyć się metodą StretchDraw. Funkcja ta wymaga dwóch parametrów: prostokątnego obszaru oraz rysunku, który ma być w nim umieszczony. Jeżeli wyspecyfikowany obszar jest większy niż oryginalny rozmiar bitmapy, bitmapa ta zostanie rozciągnięta. Jeżeli obszar jest zbyt mały, bitmapa zostanie zmniejszona. Oto przykład:
var
Bitmap : TBitmap;
R : TRect;
begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile('c:.bmp');
R:=Rect(0, 0, 100, 100);
Canvas.StretchDraw(R, Bitmap);
end;
|
Funkcja StretchDraw w żaden sposób nie próbuje zachować oryginalnego współczynnika proporcjonalności bitmapy (ang. aspect ratio); zapewnienie właściwych proporcji spoczywa więc na programiście. |
Kolejną metodą operującą na bitmapach jest CopyRect. Umożliwia ona określenie obszaru źródła i przeznaczenia. To z kolei pozwala na podzielenie bitmapy na sekcje w trakcie jej wyświetlania. Rozważmy dla przykładu następujący fragment programu:
var
Bitmap : TBitmap;
Src : TRect;
Dst : TRect;
I, X, Y : Integer;
Paski : Integer;
RozmiarPaska: Integer;
OldPal : HPalette;
begin
Bitmap:= TBitmap.Create;
Bitmap.LoadFromFile('factory.bmp');
Paski:=6;
RozmiarPaska:=(Bitmap.Height div Paski);
OldPal :=SelectPalette(Canvas.Handle, Bitmap.Palette, True);
for I:=0 to Pred(Paski) do
begin
Src:=Rect(0, i*RozmiarPaska,
Bitmap.Width, (i*RozmiarPaska) + RozmiarPaska);
X:= Random(Width - Bitmap.Width);
Y:= Random(Height - RozmiarPaska);
Dst:=Rect(X, Y, X+Bitmap.Width, Y + RozmiarPaska);
Canvas.CopyRect(Dst, Bitmap.Canvas, Src);
end;
SelectPalette(Canvas.Handle, OldPal, True);
Bitmap.Free;
end;
Powyższy kod ładuje bitmapę, dzieli ją na paski, które następnie wyświetla w losowych miejscach formularza (rysunek 12.10). Umieść ten kod we wnętrzu funkcji obsługującej zdarzenie OnPaint formularza i uruchom program. Zasłoń formularz innym oknem, a następnie przywołaj go ponownie na pierwszy plan. Każde odświeżenie formularza powoduje przerysowanie wszystkich pasków.
Rysowanie 12.10. Fragmenty bitmapy wyświetlone losowo na ekranie za pomocą funkcji CopyRect |
|
Na pierwszy rzut oka dzielenie na fragmenty bitmapy takiej jak FACTORY.BMP może wydawać pozbawione sensu. Niemniej jednak, istnieje powszechnie stosowana technika programowania grafiki, polegająca na tworzeniu jednej dużej bitmapy, składającej się z wielu małych rysunków, a następnie kopiowaniu tylko potrzebnego rysunku na ekran. W takiej sytuacji metoda CopyRect okazuje się niezastąpiona.
|
W poprzednim przykładzie do ustawienia właściwości Palette formularza wykorzystana została funkcja SelectPalette. Klasa TCanvas - nie wiedzieć dlaczego - nie posiada takiej właściwości, dlatego ustawienie palety dla formularza wymaga odwołania się do biblioteki Windows API. Pominięcie tej operacji zaowocowałoby użyciem nieprawidłowych kolorów dla pasków bitmap wyświetlanych w formularzu. Metoda CopyRect stosuje odmienny mechanizm wyświetlania bitmapy na płótnie, dlatego podjęcie tego dodatkowego kroku jest w tym przypadku niezbędne. |
I na zakończenie jeszcze jedna, warta wzmianki, metoda wyświetlania bitmapy - nosi ona nazwę BrushCopy i oferuje kopiowanie fragmentów bitmapy w połączeniu z zamianą wybranego jej koloru przez kolor pędzla związanego z płótnem. Umożliwia to osiągnięcie efektów specjalnych - na przykład uzyskiwanie bitmap częściowo przezroczystych. Szczegóły dotyczące tej technologii możesz znaleźć w systemie pomocy w sekcji opisującej metody klasy TCanvas.
Bitmapy w pamięci operacyjnej
Często stosowanym rozwiązaniem jest tworzenie bitmapy w pamięci - i wyświetlanie jej na płótnie komponentu dopiero po skompletowaniu, za pomocą metody Draw. Z racji swej natury bitmapy takie nazywane są potocznie bitmapami pozaekranowymi (ang. offscreen bitmaps); ich wykorzystanie pozwala na uniknięcie nieprzyjemnego migotania, które powstaje na ekranie wskutek wyświetlania szybko zmieniających się porcji danych.
Bitmapy pozaekranowe stosowane są szeroko we wszelkiego rodzaju animacjach, a także w nowej popularnej technologii Microsoftu o nazwie DirectX.
Korzystania z bitmap pozaekranowych sprowadza się do trzech zasadniczych zagadnień:
Utworzenie bitmapy w pamięci.
Wykonanie rysunku w bitmapie pamięciowej.
Skopiowanie bitmapy z pamięci na ekran.
Tworzenie bitmapy pamięciowej
Utworzenie bitmapy pamięciowej jest bardzo proste - robiłeś to już kilkakrotnie w trakcie czytania tego rozdziału. Utworzenie obiektu klasy TBitmap jest równoważne stworzeniu bitmapy pamięciowej. Dotychczas obiekt ten ładowany był zawartością pliku; poniższy przykład ilustruje natomiast utworzenie bitmapy, ustalenie jej rozmiarów i wykonanie prostego rysunku:
var
Bitmap : TBitmap;
I,X,Y,W,H : Integer;
Czerwony, Zielony, Niebieski : Integer;
begin
Bitmap:=TBitmap.Create;
Bitmap.Width:=500;
Bitmap.Height:=500;
for I:=0 to 19 do begin
X:= Random(400);
Y:= Random(400);
W:= Random(100) + 50;
H:= Random(100) + 50;
Czerwony := Random(255);
Zielony := Random(255);
Niebieski := Random(255);
Bitmap.Canvas.Brush.Color:= RGB(Czerwony,Zielony,Niebieski);
Bitmap.Canvas.Rectangle(X,Y,W,H);
end;
Canvas.Draw(0,0, Bitmap);
Bitmap.Free;
end;
Umieść na formularzu przycisk i zastosuj powyższy kod w roli procedury obsługi zdarzenia OnClick. Każdorazowe kliknięcie na tym przycisku spowoduje wyświetlenie zestawu dwudziestu prostokątów w losowych położeniach, rozmiarach i kolorach. Rysunek wykonywany jest w bitmapie pamięciowej, ta zaś - przenoszona na płótno (za pośrednictwem metody Canvas.Draw).
Jeżeli Twój komputer pracuje aktualnie w trybie 256 kolorów, kolory będą miały formę kombinowaną (dithered) - dzieje się tak dlatego, iż na potrzeby niniejszego przykładu nie została zaimplementowana paleta kolorów.
|
W chwili tworzenia bitmapy pamięciowej liczba stosowanych przez nią kolorów ustawiana jest na wartość zgodną z bieżącymi ustawieniami wyświetlania grafiki w komputerze. Jeżeli więc komputer pracuje w trybie 256-kolorowym (albo w trybie 32K bądź 16M kolorów) bitmapa również będzie 256-kolorowa (albo -odpowiednio - korzystać będzie z 32K bądź 16M kolorów). |
Zapisywanie bitmapy pamięciowej
Zapisywanie bitmapy pamięciowej do pliku nie jest niczym nadzwyczajnym:
Bitmap.SaveToFile('test.bmp');
Na tym kończy się cały proces. W rzeczywistości w ten właśnie sposób można łatwo zbudować własny program zrzucający do pliku zawartość ekranu - należy w tym celu załadować bitmapę pamięciową zawartością Pulpitu, co ilustruje poniższy przykład:
Listing 12.1. Procedura przychwytująca obraz
procedure TForm2.CaptureBtnClick(Sender: TObject);
var
DtCanvas : TCanvas;
Bitmap : TBitmap;
NumColors : Integer;
LogPal : PLogPalette;
Src, Dst : TRect;
begin
{ Utworzenie obiektu klasy TCanvas dla kontekstu urządzenia Pulpitu }
DtCanvas := TCanvas.Create;
DtCanvas.Handle := GetDC(0);
{ Utworzenie nowego obiektu klasy TBitmap i ustawienie}
{ jego rozmiaru zgodnie z wymiarami formularza. }
Bitmap := TBitmap.Create;
Bitmap.Width := Width;
Bitmap.Height := Height;
{ Utworzenie palety na podstawie płótna formularza}
{ i przypisanie jej do właściwości Palette }
{ bitmapy. }
NumColors := GetDeviceCaps(Canvas.Handle, SizePalette);
GetMem(LogPal, SizeOf(TLogPalette) +
(NumColors - 1) * SizeOf(TPaletteEntry));
LogPal.palVersion := $300;
LogPal.palNumEntries := NumColors;
GetSystemPaletteEntries(
Canvas.Handle, 0, NumColors, LogPal.palPalEntry);
Bitmap.Palette := CreatePalette(LogPal^);
FreeMem(LogPal);
{ Skopiowanie fragmentu ekranu z płótna }
{ Pulpitu do bitmapy. }
Src := BoundsRect;
Dst := Rect(0, 0, Width, Height);
Bitmap.Canvas.CopyRect(Dst, DtCanvas, Src);
{ Zapisanie bitmapy na dysk. }
Bitmap.SaveToFile('form.bmp');
{ Wyczyszczenie i wyjście z programu }
Bitmap.Free;
DtCanvas.Free;
ResultsBtn.Enabled := True;
|
Powyższy fragment kodu ilustruje ponadto jeszcze jedną ciekawą operację - mianowicie zdefiniowanie palety dla formularza wyświetlającego grafikę. Kod ten stanowi pascalową mutację przykładu, który był częścią artykułu napisanego przeze mnie dla magazynu C++ Builder Developer's Journal. Jeżeli nawet nie jesteś zainteresowany literaturą dotyczącą C++, to być może zainteresuje Cię czasopismo o nazwie Delphi Developer's Journal, którego darmowy egzemplarz możesz zamówić pod adresem http://www.cobb.com/ddj. |
Przykładowy program
korzystający z bitmapy pamięciowej
Poniższa aplikacja ilustruje użycie bitmapy pamięciowej do prostej animacji.
Kliknięcie w pierwszy z trzech przycisków formularza spowoduje uruchomienie procesu sprowadzającego się do każdorazowego rysowania „na piechotę” finalnego obrazu po zmianie jego lokalizacji - co jest rozwiązaniem najprostszym, lecz zdecydowanie mało efektywnym, szczególnie przy skomplikowanych rysunkach.
Znacznie efektywniejszą metodą wykonania rzeczonej animacji jest jednokrotne wykonanie rysunku na bitmapie pamięciowej i kopiowanie tejże bitmapy na płótno po każdej zmianie lokalizacji - czego rezultat można zaobserwować klikając w drugi przycisk.
Trzeci przycisk służy do zatrzymania animacji. Formularz działającego programu przedstawiony jest na rysunku 12.11.
Rysunek 12.11. Formularz programu MemBmp |
|
Program 12.2. MemBmpU.pas
unit MemBmpU;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
DisplayText = 'TurboPower Software Co.';
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
Done : Boolean;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
I : Integer;
begin
Canvas.Font.Name := 'Arial Bold';
Canvas.Font.Size := 16;
Canvas.Brush.Color := clSilver;
Done := false;
while not Done do begin
for I := -Canvas.TextWidth(DisplayText) to Pred(Width) do begin
Sleep(1);
Application.ProcessMessages;
if (Done) then
Break;
Canvas.Font.Color := clGray;
Canvas.Brush.Style := bsClear;
Canvas.TextOut(i + 2, 12, DisplayText);
Canvas.Font.Color := clBlack;
Canvas.Brush.Style := bsClear;
Canvas.TextOut(i, 10, DisplayText);
Canvas.Font.Color := clSilver;
Canvas.TextOut(i + 2, 12, DisplayText);
Canvas.TextOut(i, 10, DisplayText);
end;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
Bitmap : TBitmap;
I : Integer;
begin
Bitmap := TBitmap.Create;
Bitmap.Width := Width;
Bitmap.Height := 40;
Bitmap.Canvas.Font.Name := 'Arial Bold';
Bitmap.Canvas.Font.Size := 16;
Bitmap.Canvas.Brush.Color := clSilver;
Bitmap.Canvas.FillRect(Rect(0, 0, Width, 40));
Done := False;
while not Done do begin
for I := -Bitmap.Canvas.TextWidth(DisplayText) to Pred(Width) do begin
Application.ProcessMessages;
if (Done) then
Break;
Sleep(1);
Bitmap.Canvas.Font.Color := clGray;
Bitmap.Canvas.Brush.Style := bsClear;
Bitmap.Canvas.TextOut(2, 12, DisplayText);
Bitmap.Canvas.Font.Color := clBlack;
Bitmap.Canvas.Brush.Style := bsClear;
Bitmap.Canvas.TextOut(0, 10, DisplayText);
Canvas.Draw(I, 0, Bitmap);
end;
end;
Bitmap.Free;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
Done := True;
end;
end.
Programowanie multimediów
Pod niepozornym określeniem „programowanie multimediów” kryje się szeroki krąg możliwości - przede wszystkim „zwykły” dźwięk, dźwięk MIDI, sekwencje wideo w standardzie AVI oraz animacje. Nie chciałbym, aby programowanie multimediów zostało mylone z programowaniem gier - to ostatnie co prawda zdecydowanie opera się multimediach, lecz jest czymś więcej niż tylko np. dodawaniem dźwięku do aplikacji. W tej sekcji pokrótce omówione zostaną zagadnienia multimediów i możliwości ich wykorzystania poprzez zastosowanie narzędzi dostarczonych razem z Delphi. Nie będzie tu jednak mowy interfejsach programowania multimediów czy grafiki, takich jak OpenGL czy Direct Draw - informacji o ich wykorzystaniu musisz poszukać w innych książkach, z których osobiście polecam„Delphi 4 Unleashed” (ISBN 0-672-312-859).
Odtwarzanie dźwięków za pomocą funkcji Windows API
Zwykle nie omawiałbym szczegółowo funkcji Windows API, ponieważ w większości wypadków znaleźć można lepszy sposób na wykonanie tych samych zadań przy użyciu biblioteki VCL. Niemniej jednak w przypadku odtwarzania pliku typu wave nie ma prostszej funkcji niż PlaySound, należąca do Win32 API. Odtwarzanie dźwięku przy jej pomocy jest bardzo proste. Po pierwsze do listy modułów (uses) trzeba dodać nazwę MmSystem, następnie wystarczy wywołać funkcją PlaySound z odpowiednimi parametrami, na przykład:
PlaySound('test.wav', 0, SND_FILENAME);
Jak widać, pierwszy parametr wywołania funkcji służy do określenia pliku zawierającego wzorzec dźwiękowy do odtworzenia. Ostatni parametr służy do przekazania znaczników decydujących o sposobie odtworzenia dźwięku. Jeżeli odtworzony ma być plik audio znajdujący się na dysku, ostatnim parametrem funkcji powinien być znacznik SND_ FILENAME (można użyć również innych znaczników - o tym za chwilę).
Oprócz plików dyskowych funkcja PlaySound jest również w stanie odtwarzać dźwięki systemowe - wystarczy w tym celu jako pierwszy parametr podać alias odpowiedniego dźwięku systemowego oraz umieścić znacznik SND_ALIAS w roli ostatniego parametru, na przykład:
PlaySound('SystemAsterisk', 0, SND_ALIAS);
Powyższe wywołania spowoduje odtworzenie dźwięku skojarzonego ze zdarzeniem systemu o nazwie „Gwiazdka” (o ile skojarzenie takie zostało utworzone). Listę zdarzeń możliwych do powiązania z dźwiękami można obejrzeć w ustawieniach dźwięków w Panelu Sterowania. Listę aliasów dla zdarzeń można znaleźć w Rejestrze Windows pod kluczem HKEY_CURRENT_USER.
Jeżeli odnalezienie wskazanego dźwięku jest niemożliwe, Windows odtworzy dźwięk domyślny (ding.wav w przypadku standardowego schematu dźwiękowego). Można uniknąć odtworzenia dźwięku domyślnego przez wyspecyfikowanie znacznika SND_ NODEFAULT. Przykładowo, jeśli chciałbyś, aby odtworzony został dźwięk systemowy, a w przypadku jego braku nie był odtwarzany dźwięk domyślny, mógłbyś użyć następującego kodu:
PlaySound('MailBeep', 0, SND_ALIAS or SND_NODEFAULT);
Zauważ, że znaczniki SND_ALIAS i SND_NODEFAULT zostały zsumowane.
|
Do odtwarzania dźwięków systemowych - poprzez ich indeksy - można również zastosować funkcję MessageBeep, należącą do biblioteki Win32 API. Więcej informacji na temat tej funkcji znaleźć można w systemie pomocy Win32. |
Istnieją jeszcze dwa inne znaczniki, które warto znać podczas korzystania z funkcji PlaySound:
SND_ASYNC wymusza asynchroniczne odtworzenie dźwięku. W efekcie zastosowania tego znacznika, po rozpoczęciu funkcji odtwarzania dźwięku sterowanie jest natychmiast zwracane do aplikacji, która tę funkcję wywołała. Oznacza to, że dźwięk odtwarzany będzie równolegle z pracą aplikacji.
SND_SYNC sprawia, iż funkcja wywołana zostanie w zwykły sposób - sterowanie zostanie zwrócone do aplikacji dopiero po zakończeniu odtwarzania dźwięku. Jest to ustawienie domyślne, a więc znacznika tego nie trzeba podawać w sposób jawny.
Do kontrolowania sposobu odtwarzania dźwięków przez funkcję PlaySound można zastosować wiele innych znaczników. Kompletnej informacji na ten temat należy szukać w systemie pomocy Win32 pod hasłem PlaySound.
Komponent TMediaPlayer
Biblioteka VCL udostępnia komponent MediaPlayer przeznaczony do prostych operacji multimedialnych. Komponent ten, umieszczony na stronie System Palety Komponentów, jest w stanie odtworzyć pliki wave, pliki MIDI, sekwencje wideo AVI i inne. Ponadto, jego użycie jest bardzo proste. Osobiście, jeżeli program wymaga tylko odtwarzania dźwięków, stosuję funkcję PlaySound. Mimo to, w przypadku operacji bardziej złożonych, lepiej jest skorzystać z komponentu MediaPlayer.
Najoczywistszy sposób użycia komponentu klasy TMediaPlayer to umieszczenie go na formularzu. Po wykonaniu tej operacji wyświetlony zostanie pasek kontrolny odtwarzacza. Pasek ten składa się z przycisków odtwarzania, pauzy, stopu, przejścia wstecz, w przód, kroku i wysunięcia szuflady (rysunek 12.12).
Korzystanie z odtwarzacza, w jego najprostszej formie, jest niezmiernie proste. Całe zadanie sprowadza się do przypisania właściwości FileName nazwy odpowiedniego pliku z danymi multimedialnymi i kliknięcia na przycisku Play (Odtwarzaj). Żadne dodatkowe
Rysunek 12.12. Komponent MediaPlayer umieszczony w formularzu |
|
operacje nie są wymagane, ponieważ MediaPlayer automatycznie rozpoznaje typ pliku (.MID, .WAV, .AVI). Tego typu funkcjonalność w większości przypadków okazuje się jednak niewystarczająca. Żeby zrobić coś bardziej interesującego z komponentem MediaPlayer, trzeba będzie zagłębić się w jego zasady funkcjonowania.
Chociaż w niektórych sytuacjach pasek kontrolny odtwarzacza jest mile widziany, czasem trzeba będzie użyć komponentu MediaPlayer z jego pominięciem. Wykonywanie operacji odtwarzania, zatrzymania, czy przewinięcia na odtwarzaczu może być również realizowane na poziomie kodu. Jeżeli w trakcie pracy programu pasek kontrolny ma być niewidoczny, należy właściwości Visible przypisać wartość False.
Właściwości, metody i zdarzenia komponentu MediaPlayer
Znaczenie większości właściwości klasy TMediaPlayer jest oczywiste, posiada ona jednak i właściwości bardziej skomplikowane. Znaczenie większości z nich przedstawia tabela 12.5.
Tabela 12.5. Główne właściwości klasy TMediaPlayer
Właściwość |
Opis |
AutoOpen |
Określa, czy urządzenie powinno być otwarte bezpośrednio po utworzeniu odtwarzacza. Wartość domyślna: False. |
AutoRewind |
Jeżeli wartością jest True, po zakończeniu odtwarzania wskaźnik pozycji w pliku multimedialnym zostanie przesunięty na jego początek. Wartość domyślna: True. |
DeviceType |
Typ urządzenia multimedialnego. Wartość bsAutoSelect powoduje automatyczny wybór urządzenia, na podstawie rozszerzenia pliku. Wartość domyślna: dtAutoSelect. |
Display |
Służy do określenia komponentu przeznaczonego na okno wyświetlania (dla urządzeń wideo). |
DisplayRect |
Służy do określenia rozmiaru i pozycji okna odtwarzania obrazu dla urządzeń wideo. Rozmiar obrazu wideo jest modyfikowany tak, aby dopasować go do okna. |
EnabledButtons |
Określa, które przyciski odtwarzacza powinny być dostępne. |
Tabela 12.5. cd. Główne właściwości klasy TMediaPlayer
Właściwość |
Opis |
EndPos |
Punkt końcowy danych multimedialnych. Dane są odtwarzane od punktu StartPos do punktu EndPos. Jeżeli wartość tego pola jest nieokreślona, dane będą odtwarzane do końca. Wartość przypisywana polu EndPos zależy od typu danych odtwarzanych. |
Error |
Kod błędu ostatniej operacji. |
ErrorMessage |
Tekstowy opis ostatniego błędu. |
Frames |
Liczba klatek do przesunięcia po wywołaniu jednej z metod Back lub Next lub kliknięciu jednego z przycisków Back lub Next na pasku kontrolnym odtwarzacza. |
Length |
Długość danych multimedialnych. Wartość tego pola zależy od typu odtwarzanych danych i bieżącej wartości pola TimeFormat. |
Mode |
Stan urządzenia, reprezentowany przez jedną z wartości mpNotReady, mpStopped, mpPlaying, mpRecording, mpSeeking, mpPaused lub mpOpen. |
Notify |
Jeżeli wartością jest True, z chwilą zakończenia dowolnej operacji odtwarzacza generowane będzie zdarzenie OnNotify. |
NotifyValue |
Rezultat ostatniej notyfikacji. Możliwe wartości tego pola |
Position |
Aktualna pozycja odtwarzanych danych multimedialnych. |
StartPos |
Punkt startowy w danych multimedialnych. Dane są odtwarzane od punktu StartPos do punktu EndPos. Jeżeli wartość StartPos jest nieokreślona, dane są odtwarzane od samego początku. Wartość |
TimeFormat |
Format czasu przeznaczony do stosowania razem z urządzeniem. Czas może być przedstawiany w postaci milisekund, klatek, bajtów, sampli, ścieżek/minut/sekund, godzin/minut/sekund itd. |
Tracks |
Liczba ścieżek danych multimedialnych (dla urządzeń CD Audio). |
VisibleButtons |
Określa przyciski, które powinny być wyświetlone na pasku kontrolnym odtwarzacza. Wartość domyślna: wszystkie przyciski. |
Wait |
Określa, czy sterowanie powinno być zwrócone do aplikacji natychmiast, czy dopiero po zakończeniu odtwarzania. |
Komponent MediaPlayer zawiera również wiele metod. Wiele z nich wykonuje takie same zadania, jak przyciski paska kontrolnego. Główne metody komponentu zostały zestawione w tabeli 12.6.
Komponent MediaPlayer posiada tylko jedno istotne zdarzenie - OnNotify. Jest ono generowane po zakończeniu każdego polecenia pod warunkiem, że właściwość Notify jest ustawiona na wartość True. Sprawdzenie wartości właściwości Error i NotifyValue pozwala określić czy dana operacja zakończyła się sukcesem.
Tabela 12.6. Główne metody klasy TMediaPlayer
Metoda |
Opis |
Back |
Cofa bieżącą pozycję danych o liczbę kroków wskazywaną przez właściwość Frames. |
Close |
Zamyka urządzenie. |
Eject |
Wysuwa szufladę, jeżeli urządzenie posiada taką możliwość (np. urządzenie Audio CD). |
Next |
Powoduje przejście do początku następnej ścieżki, jeżeli dane urządzenie dopuszcza tę operację. |
Open |
Otwiera określone urządzenie (metoda stosowana wtedy, gdy właściwość AutoOpen posiada wartość False). |
Pause |
Zawiesza odtwarzanie lub nagrywanie. |
Play |
Rozpoczyna odtwarzanie. |
Previous |
Powoduje przejście do początku poprzedniej ścieżki. |
Resume |
Wznawia akcję (odtwarzania lub nagrywania) zawieszoną poleceniem Pause. |
Rewind |
Ustawia bieżącą pozycję danych multimedialnych na ich początku. |
Save |
Zapisuje dane multimedialne do pliku określonego przez właściwość FileName. |
StartRecord |
Rozpoczyna nagrywanie danych. |
Step |
Przechodzi do przodu o ustaloną liczbę ramek, reprezentowaną przez |
Stop |
Zatrzymuje bieżącą akcję (odtwarzanie lub nagrywanie). |
Dźwięk w standardzie wave
Odtwarzanie dźwięków w standardzie wave jest jedną z podstawowych i najczęściej wykonywanych operacji multimedialnych. Odtworzenie pliku dźwiękowego w sposób synchroniczny mogłoby mieć następującą postać:
Player.Wait := True;
Player.FileName :='test.wav';
Player.Open;
Player.Play;
Zwróć uwagę na przypisanie właściwości Wait wartości True - to właśnie wymusza odtworzenie pliku w sposób synchroniczny. Jest to niezbędne na przykład w sytuacji, gdy pliki dźwiękowe mają być odtwarzane kolejno jeden za drugim:
Player.FileName :='Sound1.wav';
Player.Open;
Player.Wait:=True;
Player.Play;
Player.FileName :='Sound2.wav';
Player.Open;
Player.Wait:=True;
Player.Play;
Zauważ, że właściwość Wait jest ustawiana na wartość True przed odtworzeniem każdego z plików - jest to konieczne, gdyż każde z tych odtwarzań resetuje właściwość Wait do wartości False.
Jeżeli właściwość Wait nie zostanie ustawiona na wartość True, kilka milisekund po rozpoczęciu odtwarzania pierwszego pliku rozpocznie się odtwarzanie drugiego (i jednocześnie anulowanie pierwszego - nie jest to więc recepta na polifonię). Wartość False właściwości Wait jest natomiast wykorzystywana od odtwarzania pojedynczego pliku dźwiękowego w tle działającej aplikacji.
Odtworzenie wybranego fragmentu pliku jest możliwe po wcześniejszym ustawieniu wartości pól StartPos i EndPos. Poniższy przykład otwiera plik dźwiękowy i odtwarza jego dwusekundowy fragment, rozpoczynając od pierwszej i kończąc na trzeciej sekundzie.
Player.FileName:= 'test.wav'
Player.Open;
Player.StartPos:=1000;
Player.EndPos:=3000;
Player.Play;
Wartości pól StartPos i EndPos wyrażane są w milisekundach, będących jednostką domyślną dla urządzeń odtwarzających dźwięk w standardzie wave.
|
Ustawienie jednego z pól StartPos lub EndPos na wartość nieprawidłową sprawi, iż plik nie zostanie odtworzony. Do nieprawidłowych wartości można zaliczyć wartość StartPos przekraczającą wartość EndPos, a także wielkość EndPos przekraczającą całkowitą długość danych multimedialnych. |
Ustawienie głośności dźwięku
Ustawienie głośności dźwięku typu wave jest operacją względnie prostą, ale żeby tego dokonać trzeba odwołać się do biblioteki Windows API. Do pobrania i ustawienia poziomu dźwięku służą dwie funkcje o nazwach waveOutGetVolume i waveOutSetVolume.
Głośność dźwięku jest pamiętana jako liczba typu integer. Starsze słowo określa głośność prawego kanału, a młodsze - lewego. Jeżeli urządzenie nie posiada zdolności niezależnego ustawiania głośności kanałów, młodsze słowo określa głośność każdego z obydwu kanałów, starsze zaś jest ignorowane.
Wartość 0 oznacza brak dźwięku, natomiast wartość szesnastkowa $FFFF określa głośność maksymalną. Poniższy przykład ustawia głośność dźwięku obu kanałów na 50%:
waveOutSetVolume(0, $80008000);
Kolejny przykład ustawia głośność maksymalną:
waveOutSetVolume(0, $FFFFFFFF);
W obydwu przypadkach założyłem, że urządzenie odtwarzające posiada numer 0 (taką wartość ma pierwszy parametr); jest to prawdą w większości instalacji.
Za pomocą funkcji waveOutSetVolume nie można, niestety, ustawić tzw. głównego poziomu głośności (master volume) - szczegółowa analiza tego zagadnienia wykracza jednak poza ramy niniejszej książki.
Nagrywanie dźwięku w standardzie wave
Nagrywanie dźwięku w standardzie wave nie jest tak proste, jak mogłoby się wydawać - nie sprowadza się bowiem do prostego wywołania metody StartRecording.
Zapisanie nowego pliku dźwiękowego wymaga wcześniejszego otwarcia istniejącego pliku wave o takich samych parametrach nagrywania, jakimi cechować powinien się plik tworzony. Następnie można nagrać nowe dane dźwiękowe, zmienić wartość właściwości FileName (na nazwę nowego pliku) i na końcu zapisać go. Jest to trochę nieporęczne, ale działa.
Załóżmy dla przykładu, że dysponujesz plikiem o nazwie DUMMY.WAV, który został zapisany w 8-bitowym formacie wave o częstotliwości próbkowania 22050 Hz i jednym kanale (tego typu plik można z łatwością stworzyć przy pomocy programu Windows - Rejestrator dźwięków). W takim przypadku proces nagrywania dźwięku, rozpoczynany kliknięciem na przycisku, można zrealizować w następujący sposób:
procedure TForm1.StartBtnClick(Sender : TObject);
begin
with MediaPlayer do begin
{Przypisanie właściwości FileName nazwy pliku dummy.wav}
{aby móc pobrać parametry nagrywania.}
FileName:='dummy.wav';
{Otwarcie urządzenia}
Open;
{Rozpoczęcie nagrywania.}
Wait:=False;
StartRecording;
end;
end;
W momencie rozpoczęcia nagrywania sterowanie powraca do aplikacji (Wait ma wartość False). Teraz trzeba zatrzymać proces nagrywania, co może nastąpić na przykład w wyniku kliknięcia innego przycisku:
procedure TForm1.StopBtnClick(Sender : TObject);
begin
with MediaPlayer do begin
{ Zatrzymanie nagrywania }
Stop;
{ Nadanie właściwości FileName nowej nazwy pliku }
FileName:= 'new.wav';
{ Zapisanie i zamknięcie pliku }
Save;
Close;
end;
end;
|
W chwili kiedy powstawała ta książka, podjęcie opisanych wyżej kroków w celu zapisania dźwięku typu wave było niezbędne ze względu na błąd w klasie TMediaPlayer. Możliwe, że błąd ten został naprawiony przed wypuszczeniem Delphi 4 na rynek. Można to sprawdzić, umieszczając komponent MediaPlayer w formularzu i wywołując metodę Record. Jeżeli Delphi nie wygeneruje wyjątku będzie to oznaczało, że błąd został naprawiony. |
Do czynników wpływających na sposób zapisywania dźwięku wave należą: częstotliwość próbkowania, liczba kanałów (mono lub stereo), a także bitów na próbkę (zazwyczaj 8 lub 16). Powszechnie stosowanymi częstotliwościami próbkowania są 8000 Hz, 11025 Hz, 22050 Hz i 44100 Hz. Im wyższa częstotliwość próbkowania, tym wyższa jakość nagrywanego dźwięku. Nagrywanie dźwięku stereo będzie należeć raczej do rzadkości chyba, że zajmujesz się programowaniem gier. Jednak nawet wtedy należy stosować dźwięk stereo tylko wtedy, w gdy zajdzie wyraźna tego potrzeba. Również liczba bitów na próbkę ma wpływ na jakość nagrywanego dźwięku. Liczba bitów na próbkę może wynosić 8 lub 16.
|
Wraz ze wzrostem jakości dźwięku rośnie ilość miejsca na dysku pochłanianego przez plik. Plik typu wave nagrywany w stereo będzie oczywiści dwa razy większy od rozmiaru tego samego pliku, ale nagrywanego jednokanałowo. Podobnie, stosowanie szesnastobitowych próbek sprawi, że rozmiar pliku będzie podwojony (osiem bitów więcej na każdym samplu). Plik nagrany w trybie mono z częstotliwością próbkowania równą 22050 Hz i 8 bitowymi próbkami może zajmować około 200 KB, podczas gdy jego stereofoniczny odpowiednik nagrany z 16-bitowym próbkowaniem o częstotliwości 22050 Hz przyjąłby wtedy rozmiar 800 KB. W większości przypadków zastosowanie dźwięku stereo, próbkowanego 16-bitowo, nie przynosi dostatecznych korzyści w porównaniu z koniecznością zapewnienia dodatkowej przestrzeni dyskowej. Dźwięk w postaci 8-bitowych sampli mono, zapisywanych z częstotliwością 22050 Hz stanowi ofertę kompromisu między jakością dźwięku, a rozmiarem pliku. |
MIDI
Odtwarzanie plików dźwiękowym typu MIDI (pliki te noszą rozszerzenia .MID lub .RMI) nie wymaga dodatkowych objaśnień - należy przypisać nazwę pliku właściwości FileName i wywołać metodę Play.
|
Bardziej zaawansowane możliwości w zakresie operowania dźwiękami MIDI oferują funkcje API o nazwach midiInXXX i midiOutXXX. Ich dokumentacja znajduje się w pliku MMEDIA.HLP znajdującym się w podkatalogu MsHelp katalogu zawierającego współdzielone pliki Borlanda. |
Większość kart muzycznych nie pozwala na jednoczesne odtwarzanie dwóch plików typu wave. Podobnie, większość z nich nie pozwala również na odtwarzanie więcej niż jednego pliku MIDI w tym samym czasie. Niemniej jednak większość kart pozwala na jednoczesne odtwarzanie pliku typu wave i MIDI. Zauważyłeś pewnie ten efekt w grach, gdzie efekty dźwiękowe i muzyka są odtwarzane jednocześnie. Do jednoczesnego odtworzenia pliku MIDI i wave można wykorzystać dwa komponenty MediaPlayer. Ewentualnie, można pozostawić tylko jeden komponent dla plików MIDI, a do odtworzenia niezbędnych plików wave użyć funkcji PlaySound.
Pliki MIDI służą często jako podkład muzyczny do gier. Wykorzystując plik MIDI w ten sposób będziesz zmuszony do ponawiania odtwarzania muzyki po jej zakończeniu. Klasa TMediaPlayer nie posiada niestety wbudowanej możliwości odtwarzania muzyki w sposób ciągły. Do osiągnięcia efektu pętli można jednak wykorzystać zdarzenie OnNotify. Po pierwsze trzeba nakazać komponentowi MediaPlayer, aby informował o zachodzących zdarzeniach:
MediaPlayer.Notify:= True;
Następnie trzeba utworzyć funkcję obsługującą zdarzenie OnNotify. We wnętrzu tej funkcji musi znaleźć się kod, który zrestartuje odtwarzanie pliku po pomyślnym jego zakończeniu. Funkcja ta mogłaby mieć następującą postać:
procedure TForm1.MediaPlayerNotify(Sender : TObject);
begin
with MediaPlayer do
if NotifyValue = nvSuccessful then begin
Position:= 0;
Play;
end;
end;
Na początku następuje sprawdzenie, czy właściwość NotifyValue zawiera wartość nvSuccessful. Jeżeli tak, pozycja pliku zostaje ustawiona na wartość 0, po czym wywoływana jest funkcja Play, w efekcie czego plik jest ponownie odtwarzany od początku. Zadanie okazało się proste do wykonania, niemniej jest tu kilka kwestii, których istnienia warto być świadomym.
Po pierwsze, właściwość Position jest ustawiana na wartość 0, co powoduje przesunięcie wskaźnika położenia w pliku na jego początek. Operacja ta jest zbędna, jeżeli właściwość AutoRewind jest ustawiona na wartość True. Po drugie, trzeba zdawać sobie sprawę, że istnieje kilka akcji komponentu MediaPlayer, które mogą wywołać zdarzenie OnNotify z wartością nvSuccessful właściwości NotifyValue.
Przykładowo, źródłem wartości nvSuccessful może być prosta komenda Stop, wykonana bezbłędnie. Być może będziesz musiał określić stan urządzenia, aby upewnić się, że zdarzenie OnNotify zostało wywołane w wyniku osiągnięcia końca pliku, a nie efekcie zaistnienia innej operacji komponentu MediaPlayer.
Audio CD
Odtwarzanie dźwięku Audio CD za pomocą komponentu TMediaPlayer jest względnie proste. Wystarczy zmienić wartość właściwości DeviceType na dtCDAudio i kliknąć na przycisku Play (lub wywołać metodę o tej nazwie).
Najtrudniejszym do zrozumienia aspektem programowania urządzeń Audio CD są różne formaty czasu. Do zebrania informacji o danej ścieżce lub ustawieniu bieżącej pozycji na określonej ścieżce należy skorzystać z czasu TMSF (czas, minuty, sekundy, ramki). Wartości minut, sekund lub ramek będą ustawiane względem numeru ścieżki. Poniższy przykładowy kod formatuje łańcuch informujący o bieżącej pozycji wewnątrz bieżącej ścieżki:
var
Time: Integer;
Track: Integer;
Minutes: Integer;
Seconds: Integer;
TimeStr: string;
begin
MediaPlayer.TimeFormat:= tfTMSF;
Time:= MediaPlayer.Position;
Track:=mci_TMSF_Track(Time);
Minutes:=mci_TMSF_Minute(Time);
Seconds:= mci_TMSF_Second(Time);
TimeStr:=Format('Track Time: %2.2d:%2.2d',[Minutes,Seconds]);
Label1.Caption := 'Track: '+IntToStr(Track);
Label2.Caption := TimeStr;
end;
Na początku właściwość TimeFormat jest ustawiana na wartość tfTMSF, co daje pewność poprawnego formatu czasu. Następnie bieżąca pozycja jest zapisywana w zmiennej Time. W kolejnych liniach kodu makropolecenia konwersji czasu (należące do Windows) - mci_TMSF_Track, mci_TMSF_Minute, - wydobywają z tej zmiennej różne wartości czasowe (nr ścieżki, minuty i sekundy). Makra te zawarte są w module MMSystem, dlatego aby móc z nich skorzystać, trzeba dodać ten moduł do listy uses. Po wyodrębnieniu indywidualnych jednostek budowany jest łańcuch, który posłuży do wyświetlenia czasu ścieżki. Na samym końcu ścieżka i czas zostają wyświetlone przez dwa komponenty typu Label.
Do zebrania ogólnej informacji o płycie CD służy czas w formacie MSF (minuty, sekundy, ramki). Przykładowo, format ten mógłby posłużyć do określenia bieżącej pozycji na płycie względem jej początku lub do ustawienia bieżącej pozycji np. na 30 minucie płyty niezależnie od tego, na której ścieżce wypadnie to miejsce. Poniższy przykład pokazuje w jaki sposób uzyskać i wyświetlić bieżącą pozycję na płycie (licząca w minutach i sekundach):
var
Time: Integer;
Minutes: Integer;
Seconds: Integer;
TimeStr: string;
begin
MediaPlayer.TimeFormat:= tfMSF;
Time:= MediaPlayer.Position;
Minutes:=mci_MSF_Minute(Time);
Seconds:=mci_MSF_Second(Time);
TimeStr:=Format('Total time: %2.2d:%2.2d',[Minutes, Seconds]);
Label3.Caption:=TimeStr;
end;
Kod przeznaczony dla tej książki zawiera program o nazwie CDPlayer, ilustrujący możliwość wykorzystania komponentu TMediaPlayer do budowy odtwarzacza CD.
Wideo AVI
Odtworzenie sekwencji wideo AVI przy pomocy komponentu klasy TMediaPlayer wymaga wybrania pliku w formacie AVI i wywołania metody Play (lub kliknięcia na przycisku Play). W przypadku standardowych ustawień komponentu MediaPlayer otwarte zostanie oddzielne okno, w którym wideo będzie odtwarzane. Innym rozwiązaniem jest przypisanie właściwości Display dowolnego komponentu okienkowego - wtedy wideo zostanie odtworzone w obszarze klienta tegoż komponentu.
Jeżeli, przykładowo, chciałbyś obejrzeć zawartość pliku wideo na panelu o nazwie AVIPanel, musiałbyś dokonać następującego przypisania:
MediaPlayer.Display:= AVIPanel;
Jeżeli obraz wideo jest większy niż obszar klienta wskazanego komponentu, zostanie on obcięty do rozmiarów tegoż obszaru.
Obraz wideo może być rozciągany lub pomniejszany dzięki właściwości DisplayRect - poniższy kod sprawi, iż obraz wideo zostanie dopasowany do rozmiaru obszaru wyświetlania panelu:
MediaPlayer.DisplayRect:= AVIPanel.ClientRect;
Istnieje wiele typów formatów wideo AVI i nie wszystkie one są możliwe do odtwarzania pod kontrolą każdego systemu. Aby móc zastosować określony typ wideo, trzeba mieć pewność iż wszyscy użytkownicy posiadają zainstalowane sterowniki dla tego formatu wideo. Pewność taką daje stosowanie standardu wideo AVI firmy Microsoft. Użytkownicy niemal na pewno będą posiadać sterowniki dla tego standardu, ponieważ są one dodawane w ramach instalacji Windows.
|
Komponent TAnimate (znajdujący się na stronie Win32 Palety Komponentów) służy do odtwarzania małych sekwencji wideo, jakie stosowane są przez Windows. Za przykład mogą tutaj posłużyć animacje widoczne podczas procesu kopiowania plików w Eksploratorze Windows, a także podczas procesu poszukiwania plików (w oknie dialogowym Znajdź). Komponent Tanimate jest w stanie odtworzyć jedynie nieskompresowane sekwencje wideo AVI lub sekwencje skompresowane metodą RLE. Żadne inne metody kompresji nie są obsługiwane. Ponadto odtwarzana sekwencja nie może zawierać dźwięku. |
|
Komponent MediaPlayer może odtwarzać różne typy sekwencji wideo i animacji, jeżeli tylko zainstalowane zostaną odpowiednie sterowniki. Przykładem mogą być animacje w standardzie Autodesk Animator (AA), oznaczane rozszerzeniem .fli lub .flc. Aby odtworzyć animację tego typu wystarczy właściwości DeviceType nadać wartość dtAutoSelect i wybrać odpowiedni plik. MediaPlayer będzie odtwarzał animacje w tym standardzie pod warunkiem, że sterowniki AA będą zainstalowane w systemie. |
Podsumowanie
Programowanie grafiki może być czynnością bardzo interesującą i przynosić wiele satysfakcji, ale może być również frustrujące. Biblioteka VCL usunęła wiele powodów do frustracji udostępniając klasy TBitmap i TCanvas. Klasy te umożliwiają zajęcie się wizualnymi aspektami programowania grafiki, zamiast martwienia się o takie rzeczy jak ładowanie i zapisywanie plików bitmap. Również wiele radości może dać programowanie multimediów. Wielkie zadowolenie daje napisanie kilku linii kodu, a po kilku sekundach obserwowanie lub słuchanie efektów swojej pracy. Multimedia zdecydowanie ożywiają programy, ale należy uważać aby nie przesadzić. Chociaż nie można stwierdzić iż rozdział ten stanowi dogłębne spojrzenie na programowanie grafiki czy multimediów w Delphi, jest to jednak dobry początek i wprowadzenie do pewnych koncepcji, które z powodzeniem możesz rozwijać dalej samodzielnie.
Warsztat
Warsztat składa się z pytań kontrolnych oraz ćwiczeń utrwalających i pogłębiających zdobytą wiedzę. Odpowiedzi do pytań możesz znaleźć w dodatku A.
Pytania i odpowiedzi
Czy zagadnienia grafiki, omawiane w tym rozdziale, znajdują takie samo zastosowanie w przypadku drukowania, jak miało to miejsce w przypadku wyświetlania na ekranie?
Tak. Jeśli chodzi o Windows nie ma znaczenia czy kontekst urządzenia dotyczy ekranu komputerowego, bitmapy pamięciowej, czy drukarki.
Istnieje metoda Ellipse, ale nigdzie nie można znaleźć metody służącej do rysowania idealnych okręgów. Jak więc rysuje się okręgi?
Do tego celu służy również funkcja Ellipse. Wystarczy zdefiniować prostokąt opisujący elipsę jako kwadrat, a otrzymana figura będzie idealnym okręgiem.
W jaki sposób można zmienić kolor tekstu wyświetlanego przez funkcję DrawText.
Wystarczy zmienić właściwość Color czcionki płótna.
Do czego mogą mi się przydać bitmapy pamięciowe?
Być może w ogóle nie będziesz musiał z nich korzystać. Niemniej jednak, jeżeli kiedykolwiek zauważysz migotanie podczas wyświetlania grafiki, będzie to dobry powód, aby skorzystać z bitmap pamięciowych.
Jaki jest domyślny format czasu dla urządzeń dźwiękowych typu wave?
Urządzenia dźwiękowe typu wave domyślnie używają milisekund (1 sekunda = 1000 milisekund).
Czy możliwe jest jednoczesne odtworzenie więcej niż jednego pliku typu wave?
Nie poprzez komponent MediaPlayer. Interfejs DirectX firmy Microsoft udostępnia możliwości miksowania dźwięku, które sprawiają wrażenia odtwarzania dwóch lub więcej dźwięków jednocześnie. Jeżeli potrzebujesz takich funkcji miksujących dźwięk, sprawdź bibliotekę DirectX (dostępną za darmo w firmie Microsoft).
W jaki sposób można wyświetlić obraz wideo AVI bezpośrednio na własnym formularzu?
Przypisz właściwości Display klasy TMediaPlayer wskazanie na formularza.
Quiz
Jakiego komponentu można użyć do tworzenia grafiki w formularzu?
Która z właściwości klasy TCanvas kontroluje kolor wypełnienia płótna?
Do czego służą regiony?
Jaka funkcja służy do rysowania tekstu na płótnie w wielu liniach?
Która z metod klasy TCanvas może zostać wykorzystana do narysowania bitmapy z przezroczystym tłem?
Która z metod klasy TCanvas służy do przeniesienia całej bitmapy na płótno?
W jaki sposób zapisuje się bitmapę pamięciową do pliku?
Który z komponentów służy do odtwarzania plików dźwiękowych typu wave?
Do czego służy właściwość TimeFormat klasy TMediaPlayer?
Czy komponent MediaPlayer umożliwia nagranie pliku dźwiękowego typu wave?
Ćwiczenia
Napisz program, który po kliknięciu przyciskiem wyświetla na ekranie okrąg.
Napisz program, który rysuje na ekranie losowo ułożone linie, za każdym razem kiedy formularz zostaje wyświetlony (również wtedy gdy formularz został przywołany na pierwszy plan po zasłonięciu).
Stwórz program, który tworzy bitmapę pamięciową, rysuje na niej dowolne figury oraz tekst, a następnie wyświetla ją na płótnie formularza.
Zmodyfikuj program napisany w ćwiczeniu trzecim tak, aby zapisywał stworzoną bitmapę na dysk.
Napisz program który wyświetla wideo AVI na głównym formularzu aplikacji.
Napisz program, który odtwarza plik dźwiękowy w chwili swojego uruchomienia.
Struktury różnych typów łańcuchów i ich wykorzystanie w Delphi 4 opisane są szczegółowo na stronach 66÷80 książki „Delphi 4 Vademecum Profesjonalisty” wyd. HELION 1999 (przyp. red.)
512 Część II
512 \\printsrv\skład\Robin\Delphi 4 dla kazdego\12.doc
\\printsrv\skład\Robin\Delphi 4 dla kazdego\12.doc 473
Rozdzia³ 12. ♦ Programowanie grafiki i multimediów 511