// Plik nagłówkowy np. Unit1.cpp //--------------------------------------------------------------------------- TColor kpFractal(double cX, double cY) { double x = 0, y = 0; byte n = 0; for(; n < 255; n++) { double xNext = x * x - y * y + cX; double yNext = 2 * x * y + cY; if(xNext * xNext + yNext * yNext > 4) break; else { x = xNext; y = yNext; } } TColor kolor; if(n == 255) kolor = clBlack; else kolor = TColor(255, 255 - n, n); return kolor; } //--------------------------------------------------------------------------- void __fastcall Mandelbrot(TCanvas *Canvas, TObject *Temp) { int kMax = reinterpret_cast<TControl *>(Temp)->ClientWidth; int lMax = reinterpret_cast<TControl *>(Temp)->ClientHeight; double cXmin = 0.20, cYmin = 0.54; double cXmax = 0.22, cYmax = 0.55; for(int l = 0; l < lMax; l++) { for(int k = 0; k < kMax; k++) { double cX, cY; cX = cXmin + k * (cXmax - cXmin) / kMax; cY = cYmin + l * (cYmax - cYmin) / lMax; Canvas->Pixels[k][l] = kpFractal(cX, cY); } } } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDblClick(TObject *Sender) { Mandelbrot(Canvas, this); ![]() } //--------------------------------------------------------------------------- |
// Plik nagłówkowy np. Unit1.cpp //--------------------------------------------------------------------------- #include <memory> ![]() void __fastcall TForm1::Button3Click(TObject *Sender) { int x; String pf; if(Image1->Picture->Graphic->GetNamePath() == "TBitmap") ![]() { x = Image1->Picture->Bitmap->PixelFormat; switch(x) { case 1: pf = "1 bit"; break; case 2: pf = "4 bity"; break; case 3: pf = "8 bitów"; break; case 4: pf = "15 bitów"; break; case 5: pf = "16 bitów"; break; case 6: pf = "24 bity"; break; case 7: pf = "32 bity"; break; case 8: pf = "Inne (nierozpoznane)"; break; } } if(Image1->Picture->Graphic->GetNamePath() == "TJPEGImage") ![]() { std::auto_ptr<TJPEGImage> jpg(new TJPEGImage()); jpg->Assign(Image1->Picture); ![]() x = jpg->PixelFormat; switch(x) { case 0: pf = "24 bit"; break; case 1: pf = "8 bity"; break; } } Label1->Caption = "Głębia kolorów grafiki: " + pf; } //--------------------------------------------------------------------------- |
Jak wszystkim wiadomo do obsługi plików graficznych w środowisku BCB służy komponent TImage bazujący głównie na klasie TCanvas, która obsługuje bitmapy, żeby skorzystać z plików w formacie JPEG trzeba włączać do projektu plik jpeg.hpp, ale o obsłudze innych formatów graficznych bez dodatkowych bibliotek nie ma co marzyć. Problem ten można częściowo rozwiązać poprzez wykorzystanie biblioteki gdiplus.dll GDI+. Co to jest GDI?
(ang. Graphic Device Interface - graficzny interfejs urządzenia) - wewnętrzny język graficzny systemu operacyjnego Windows wykorzystywany do prezentowania grafiki na ekranie monitora i drukowanie na drukarce. Zapewnia to wierne oddanie zawartości ekranu na drukarce oraz znaczne poprawienie szybkości druku w porównaniu z drukarkami, w których wydruki muszą być przetwarzane przez procesor drukarki. Wraz z pojawieniem się Windows XP GDI jest zastępowany GDI+.
Obsługiwane formaty plików to: BMP, GIF, JPEG,
PNG, TIFF, and EMF.
Korzystanie z tej biblioteki wymaga uprzedniego przygotowania projektu,
trzeba przede wszystkim włączyć do niego plik gdiplus.h i bibliotekę
gdiplus.lib. Niestety nie wiem jak jest z dostępnością tych
bibliotek w darmowych środowiskach BCB, tzw. Personal, w wersjach
komercyjnych począwszy od wersji 6.0 powinny być.
Tworzymy nowy projekt, zapisujemy, a następnie w pliku
nagłówkowym (np. Unit1.h) w sekcji include włączmy plik #include "gdiplus.h",
potem z menu Project | Add to project... włączmy do projektu bibliotekę
gdiplus.lib, powinna się znajdować w katalogu z zainstalowanym środowiskiem
BCB, np. ...\Borland\CBuilder6\Lib\psdk. Następnie w pliku nagłówkowym w
sekcji private lub public deklarujemy obiekt klasy Gdiplus oraz zmienną typu
unsigned LONG_PTR.
W starszych wersjach środowiska BCB może zajść konieczność włączenia dodatkowych plików w sekcji include:
#define STRICT
#include <windows.h>
#include <algorithm>
using std::min;
using std::max;
#include <gdiplus.h>
//-------------------------------- |
Plik źródłowy np. Unit1.cpp
//--------------------------------------------------------------------------- |
void __fastcall TForm1::Button1Click(TObject *Sender) |
Typ wyliczeniowy RotateFilpType:
typedef enum { RotateNoneFlipNone = 0, Rotate90FlipNone = 1, Rotate180FlipNone = 2, Rotate270FlipNone = 3, RotateNoneFlipX = 4, Rotate90FlipX = 5, Rotate180FlipX = 6, Rotate270FlipX = 7, RotateNoneFlipY = Rotate180FlipX, Rotate90FlipY = Rotate270FlipX, Rotate180FlipY = RotateNoneFlipX, Rotate270FlipY = Rotate90FlipX, RotateNoneFlipXY = Rotate180FlipNone, Rotate90FlipXY = Rotate270FlipNone, Rotate180FlipXY = RotateNoneFlipNone, Rotate270FlipXY = Rotate90FlipNone } RotateFlipType; |
Można podawać tylko numer typu np: imagePNG->RotateFlip(1);
Chcąc obrócić grafikę o dowolny kąt trzeba go zdefiniować w funkcji RotateTransform. Funkcja akceptuje wartości z zakresu od 0 do 360. Pewnym problemem jest tutaj wyśrodkowanie obróconego obrazu, gdyż nie mam pojęcia gdzie znajduje się punkt obrotu, z testów wynika, że się przemieszcza w zależności od kąta obrotu:
void __fastcall TForm1::Button1Click(TObject *Sender) |
Wczytywanie plików zawierających piramidę obrazków - klatki.
Kolejna rzecz to wczytywanie plików zawierających piramidę obrazków, czyli
takich jak np. animowane GIF'y, można wczytać każdą klatkę oddzielnie, działa to
również w przypadku plików TIF, ale ja próbowałem stworzyć taki plik w
Photoshop'ie i co prawda zapisywał mi on pliki TIF zarówno z warstwami jak i
ramkami, jednak nie udało mi się pobrać z nich pojedynczych klatek. Pliki TIF
zawierające tylko jedną warstwę wczytywały się prawidłowo, ale tych z warstwami,
nie dało się w ogóle wczytać, być może takie pliki tworzy się w jakiś szczególny
sposób. W przykładzie wczytany zostanie animowany plik GIF i po każdym
kliknięciu w przycisk Button1 będzie wyświetlana jego kolejna klatka, aż do
osiągnięcia ostatniej, gdy licznik przekroczy liczbę klatek w pliku zostanie
wyświetlony komunikat. W przykładzie plik graficzny jest wczytywany za każdym
kliknięciem przycisku, ale to oczywiście nie jest wcale tutaj konieczne, można
go wczytać razem z uruchamianym programem, to o czym pisałem wyżej.
void __fastcall TForm1::Button1Click(TObject *Sender) |
Krótkie wyjaśnienie:
GetFrameDimensionsCount()
- pobiera liczbę wymiarów klatek w obiekcie Gdiplus::Image
GetFrameDimensionList(GUID *dimensionIDs, UINT count)
- pobiera identyfikatory wymiarów klatek i zapisuje je do tablicy
dimensionIDs, count określa liczbę elementów w tablicy
GetFrameCount(const GUID *dimensionID)
- pobiera liczbę klatek w Gdiplus::Image
SelectActiveFrame(const GUID *dimensionID, UINT frameIndex)
-określa która klatka ma ba być aktywna, czyli którą klatkę odrysuje funkcja
DrawImage, gdzie dimensionID to wskaźnik do tablicy przechowującej
poszczególne klatki wraz z ich wymiarami, a frameIndex to numer
klatki, która ma być aktywna. Liczenie klatek zaczyna się od 0;
Jak widać w przykładzie wszystkie klatki z pliku graficznego są przechowywane w tablicy pDimensionIDs, zmienna m_nFrameCount przechowuje informacje o całkowitej liczbie klatek, natomiast funkcja SelectActiveFrame określa która klatka ma być w danym momencie aktywna, czyli widoczna. W kodzie znalazła się funkcja klasy TCanvas FillRect, jej zadaniem jest zamazywanie wcześniej wyświetlanej klatki przed nową, dodatkowo jeżeli ładujemy klatkę zawierającą przezroczystość to dzięki tej funkcji zostanie ona zachowana o ile ustawimy w Image1 właściwość Transparent na true.
Od takiego kodu tylko krok do wyświetlenia animowanego GIF'a, i tutaj niestety
przyznaję ze smutkiem nie wiem jak pobrać z pliku GIF czas wyświetlania
poszczególnych klatek. Znalazłem co prawda funkcję Gdiplus::FrameDimensionTime,
ale użycie jej w programie kończy się błędem, dlatego w przykładzie czas pracy
animacji zostanie określony ręcznie. Umieszczamy na formularzu komponent Timer1,
gdyż to w nim będzie się odbywała animacja. obiekt Gdiplus::Image zadeklaruje
tutaj jako globalny w sekcji public pliku nagłówkowego podobnie jak wszystkie
inne obiekty, które można zdefiniować przed użyciem.
private: |
__fastcall TForm1::TForm1(TComponent*
Owner) |
Tyle wystarczy, żeby animacja GIF działała, trzeba jednak pamiętać, że takie rozwiązanie jest mniej efektywne niż obsługa plików GIF z wykorzystaniem stworzonych specjalnie do tego celu bibliotek.
Teraz nadszedł czas
na wczytywanie plików z zasobów. Na początek prosta operacja wczytywania plików
w formacie GIF i PNG z zasobu. Format pliku nie ma tutaj większego znaczenia,
istotne jest tylko. żeby w zasobach umieścić plik w formacie obsługiwanym przez
GDI+.
Plik zasobu tworzy się w prosty sposób, jak w każdym innym przypadku. Wszystkie
pliki graficzne niezależnie od formatu są umieszczane jako typ RCDATA. W tym
celu uruchamiamy notatnik i umieszczamy w nim taki wpisy dla każdego z plików:
ID_GIF RCDATA "plik.gif"
ID_PNG RCDATA "plik.png"
Trzeba sobie oczywiście przygotować odpowiednio pliki plik.gif i plik.png i
zapisać je w katalogu z programem. Plik zasobów zapisujemy również w katalogu z
programem pod nazwą zasob.rc, przy czym nazwa jest dowolna
(jednowyrazowa) lecz rozszerzenie jest tutaj istotne. Następnie poprzez menu
Project | Add to project... włączamy do projektu plik zasoby.rc. Podczas
kompilacji programu zostanie on przetworzony i przerobiony na plik zasoby.res i
ten plik zostanie automatycznie włączony w zasoby programu.
Dla potrzeb programu stworzyłem funkcję, która będzie wczytywała zasób w oparciu
o nazwę identyfikator i będzie zwracała wartość typu IStream, bezpośrednio do
funkcji FromStream będącej składnikiem obiektu typu Gdiplus::Image.
IStream*
LoadFromStream(char *resType) |
Umieściłem w kodzie
przełącznik lswitch tylko w celu pokazania jak można przemiennie
wczytywać pliki z zasobów.
Od tego kodu pozostaje już tylko krok do wczytania z pliku animowanego GIF'a, w
zasadzie wystarczy połączyć ze sobą kod na wczytanie grafiki z zasobu z kodem na
animowanie:
private: |
__fastcall TForm1::TForm1(TComponent*
Owner) |
Wszystkie przedstawione przykłady nie wyczerpują możliwości.
Konwersja plików z jednego formatu na inny.
Na zakończenie przykład konwersji pliku z formatu TIF na format PNG. Nie będę omawiał tutaj poszczególnych elementów kodu:
int GetEncoderClsid(const WCHAR*
format, CLSID* pClsid) |
Kluczem do określenia formatu na który konwertujemy grafikę jest tutaj funkcja GetEncodeClsid, która pobiera jako pierwszy argument wskaźnik określający na jaki format chcemy skonwertować grafikę, drugi argument zwraca do funkcja Save typ konwertowanego pliku i funkcja Save zapisuje nowy plik pod tym formatem. Wskaźniki mogą przyjmować następujące wartości: image/bmp; image/jpeg; image/gif; image/emf; image/png; image/tiff. Należy przy tym pamiętać, żeby funkcji Save podać właściwe rozszerzenie dla pliku.
// Plik nagłówkowy np. Unit1.cpp //--------------------------------------------------------------------------- void __fastcall TForm1::Button1Click(TObject *Sender) { Form1->Color = WebColorStrToColor("FF0000"); // kolor WEB do TColor } //--------------------------------------------------------------------------- void __fastcall TForm1::Button2Click(TObject *Sender) // kolor WEB do RGB kolor { TColor kolor = WebColorStrToColor("FF0000"); // kolor WEB to TColor TColorRef rgbVal = ColorToRGB(kolor); // TColor to RGB Form1->Color = (TColor)rgbVal; Label1->Caption = Format("Kolor RGB: r - %d; g - %d; b - %d", OPENARRAY(TVarRec, (GetRValue(rgbVal), GetGValue(rgbVal), GetBValue(rgbVal)))); } //--------------------------------------------------------------------------- void __fastcall TForm1::Button3Click(TObject *Sender) { Label1->Caption = ColorToWebColorStr(clRed); // kolor TColor do kolor WEB } //--------------------------------------------------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { Label1->Caption = RGBToWebColorStr(RGB(255, 0, 0)); // kolor RGB do kolor WEB } //--------------------------------------------------------------------------- void __fastcall TForm1::Button4Click(TObject *Sender) { int kolorRGB = ColorToRGB(clYellow); // kolor TColor do RGB kolor int r = GetRValue(kolorRGB); int g = GetGValue(kolorRGB); int b = GetBValue(kolorRGB); TVarRec vr[] = {r, g, b}; Label1->Caption = Format("Składowe RGB: R=%d; G=%d; B=%d", vr, 3); Form1->Color = (TColor)kolorRGB; } //--------------------------------------------------------------------------- |