GRAFIKA.
Grafika na ekranie lub na papierze - to w rzeczywistości to samo, o ile korzystasz z opartej na idei "płótna" architektury graficznej C++ Builder. Kreśląc obiekty graficzne, pracujemy w układzie współrzędnych kartezjańskich. W C++ Builder początek współrzędnych zaczyna się w lewym górnym rogu, oś Y biegnie z góry do dołu i zawiera się tylko w zakresie liczb dodatnich podobnie jest z osią X, która biegnie z lewej strony do prawej. Płótno C++ Buildera przypomina papier milimetrowy oczywiście w przenośni ponieważ linie nie są na nim narysowane, chyba że sami je sobie wykreślimy. Płótno jest obiektem klasy 'TCanvas' i jak każdy obiekt w C++ Builder ma swoje właściwości i metody. Płótno samo w sobie jest jednak bezużyteczne dlatego używa się go w innych obiektach takich jak np. formularz, Image, drukarka. Niezależnie od tego jaki obiekt posiada płótno (TCanvas) kreślenie grafiki zawsze odbywa się tak samo, tak więc do rysowania na dowolnym obiekcie zawsze będzie służył ten sam kod. Opis obiektu TCanvas znajduje się w dziale: opis obiektów - Canvas. Porady zamieszczone w tym dziale odwołują się bezpośrednio lub pośrednio do obiektu Canvas.
Pozbywanie się migotania w obiekcie 'Image' - sposób prosty
Podczas przesuwania objektu 'Image' po powierzchni formularza widać jak obiekt migocze, wygląda to nie ciekawie i może być bardzo uciążliwe. Istnieje bardzo prosty sposób pozbycia się tego problemu, jednak nie wiem czy zadziała w 'Borland C++ Builder 1 i 3', jednak z z całą pewnością sprawdzi się w 'Borland C++ Builder 4'. Rozwiązanie problemu opiera się na ustawieniu podwójnego buforowania dla formularza (po szczegóły odsyłam do działu: opis obiektów - Form). Jak to działa? To proste podczas przesuwania obrazka po formularzu musi być on odświeżany by wyświetlić nowe położenie obiektu 'Image', żeby odświeżyć obrazek trzeba go wczytać do pamięci. Po włączeniu podwójnego buforowania do pamięci ładowane są kopie wszystkich obiektów, które się na nim
znajdują, a więc podczas odświeżania nie muszą być ponownie wczytywane do pamięci. W ten sposób można redukować migotanie prawie wszystkich obiektów znajdujących się na formularzu. Istnieje jednak i zła strona tego rozwiązania, a mianowicie zwiększa się zapotrzebowanie programu na pamięć RAM:
//-------------------------------- |
W tym przykładzie przed właściwością 'DoubleBuffered' nie umieszczono odwołania do 'Form1'. Jeżeli przed właściwością nie ma nazwy obiektu którego ona dotyczy to 'Builder' domyślnie traktuje to jako właściwość formularza. Tak więc można by napisać: Form1->DoubleBuffered = true i nie byłoby to błędem.
Pozbywanie się migotania w obiekcie 'Image' - sposób skomplikowany
W tym przykładzie obiekt 'Image' nie będzie przesuwany bezpośrednio lecz pośrednio za pośrednictwem obiektu Panel. W tym celu należy umieścić na formularzu obiekt 'Panel' a następnie na Panelu trzeba umieścić obiekt 'Image'. W Inspektorze Objektów na zakładce właściwości (Object Inspector | Properties) niektóre właściwości Panelu należy zmienić według podanego wzoru:
BevelInner = bvNone;
BevelOuter = bvNone;
FullRepaint = false;
Panel powinien mieć taki sam rozmiar jak obiekt 'Image'. Teraz przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji 'private:' umieszczamy nastęujący wpis: 'void __fastcall NewPoz(TMessage &Msg);', oraz: 'Controls::TWndMethod helppanel; ' następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i dodajemy wpisy pokazane w tabelce poniżej:
// wpis do pliku nagłówkowego np. Unit1.h |
// wpis do pliku źródłowego np. Unit1.cpp |
Przesuwanie objektu Image po formularzu.
Przesuwanie objektu 'Image' po formularzu jest w zasadzie bardzo proste. W tym celu w pliku nagłówkowym (np. Unit1.h) w sekcji private: tworzymy dwa rodzaje zmiennych int i bool:
// Plik nagłówkowy np. Unit1.h |
Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i w zdarzeniach objektu 'Image' - OnMouseDown - lewy przycisk myszki wciśnięty, OnMouseMove - przesuwanie wskaźnika myszki, OnMouseUp - lewy przycisk myski zostaje zwolniony umieszczamy wpisy podane w przykładzie:
// Plik źródłowy np. Unit1.cpp |
Teraz pozostało już tylko jedno, a mianowicie trzeba w momencie uruchamiania programu ustawić zmienną 'go' (typ bool) na false. Można to zrobić w zdarzeniu formularza - OnCreate - tworzenie formularza, lub tak jak to jest pokazane w przykładzie:
// Plik źródłowy np. Unit1.cpp |
I to byłoby już wszystko, teraz przy wciśniętym lewym klawiszu myszy można będzie przesuwać obiekt 'Image' po formularzu, natomiast po uwolnieniu klawisza opcja przesuwania zostanie wyłączona, właśnie do tego celu służy zmienna 'go'. Gdyby się pozbyć zmiennej 'go' z programu to po przesunięciu wskaźnika nad obiekt 'Image' program przechwycił by komunikat i uruchomił zdarzenie OnMouseMove i obrazek byłby popychany przez wskaźnik myszki.
Przeciąganie zawartości z jednego objektu Image do drugiego.
W celu przesunięcia jednego 'Image' do drugiego należy najpierw zmienić właściwość 'DragMode' komponentów 'Image' na 'dmAutomatic', a następnie zdefiniować w pliku nagłówkowym (np. Unit1.h), w sekcji 'private:' nowe zdarzenie:
// Plik nagłówkowy np. Unit1.h. |
Następnie w pliku źródłowym (np. Unit1.cpp) należy wstawić obsługę nowo zdefiniowanego zdarzenia:
// Plik źródłowy np. Unit1.cpp |
Teraz w zdarzeniach OnDragDrop oraz OnDragOver dla obydwu (lub więcej) obiektów 'Image' należy umieścić wpisy podane w przykładzie:
// Plik źródłowy np. Unit1.cpp |
No i teraz można przesuwać obrazki z jednego 'Image' do drugiego mteodą przeciągnij - upuść.
Zapisywanie kolorów do plików.
Pisząc programy graficzne, może zajść konieczność zapamiętania przez program kolorów do późniejszego wykorzystania. W zasadzie jest to operacja bardzo prosta, żeby nie komplikować sprawy proponuję posłużyć się tylko dwoma komponentami Button z których jeden będzie zapisywał kolor do pliku *.ini, a drugi będzie go odczytywał z tegoż pliku. W tym celu przechodzimy najpierw do pliku nagłówkowego (np. Unit1.h) i w sekcji #include importujemy plik inifiles.hpp:
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i w zdarzeniach komponentów 'Button1' i 'Button2' - 'OnClick' umieszczamy odpowiednie procedury:
// Plik źródłowy np. Unit1.cpp |
W podanym przykładzie do pliku został zapisany kolor czerwony (clRed), ale można jako kolor podawać składową RGB:
// Plik źródłowy np. Unit1.cpp |
Jak widać w powyższym przykładzie kolor jest inicjowany z trzech składowych R - red = 30, G - green = 200, B - blue =100 (czerwony, zielony, niebieski).
Konwersja JPG do BMP.
Obsługa obrazów w formacie JPG pojawia się dopiero w wersji BCB 4, żeby obsługiwać pliki jpg we wcześniejszych wersjach BCB należy ściągnąć i zainstalować pakiet TJPEGImage.
W celu skonwertowania formatu JPG do BMP posłużymy się metodą 'Canvas->Draw'. Tworzymy nowy projekt i umieszczamy na formularzu pięć komponentów: 'Image1', 'Button1', 'Button2', 'OpenDialog1' i 'SaveDialog1'. Przechodzimy do komponentu 'Image1' i ustawiamy jego właściwość 'AutoSize' na true, potem do komponentu 'OpenDialog1' i w jego właściwośći 'DefaultExt' wpisujemy: jpg. Podobnie postępujemy z komponentem 'SaveDialog1' wpisując w jego właściwości 'DefaultExt': bmp.
Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i w sekcji include dodajemy wpis: #include <jpeg.hpp>:
// Plik źródłowy np. Unit1.cpp |
Potem w zdarzeniu przycisku 'Button1' - 'OnClick' umieszczamy całą procedurę konwersji:
// Plik źródłowy np. Unit1.cpp |
Następnie w zdarzeniu 'OnClick' przycisku 'Button2' umieszczamy procedurę zapisywania przekonwertowanej grafiki jpg do pliku w formacie bmp:
// Plik źródłowy np. Unit1.cpp |
...i to by było w zasadzie wszystko, można jeszcze dodać filtry do obiektów 'OpenDialog1' i 'SaveDialog1' tak, żeby po otwarciu okna dialogowego były widoczne tylko pliki w formatach jpg i bmp.
Wypełnianie obiektów bitmapą.
Istnieje bardzo prosty sposób na wypełnienie bitmapą obiektów posiadających klasę Canvas. Wystarczy posłużyć się funkcją 'FillRect', która wypełnia zaznaczoną powierzchnię.
Czym wypełnia?
Tym co zostanie zdefiniowane wewnątrz funkcji 'Brush'. Jeżeli zdefiniujemy kolor np.:
Image1->Canvas->Brush->Color = clRed; |
...to w tym przypadku obiekt Image1 zostanie wypełniony czerwonym kolorem (clRed).
Pokażę jednak jak wypełnić np. formularz 'Form1' bitmapą wczytaną z pliku. W tym celu przechodzimy najpierw do pliku nagłówkowego (np. Unit1.h) i w sekcji private umieszczamy deklaracją obiektu typu Graphics o nazwie bmp:
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku żródłowego (np. Unit1.cpp) i w zdarzeniu 'OnShow' formularza 'Form1' definiujemy obiekt 'bmp' zadeklarowany w pliku nagłówkowym, potem ładujemy bitmapę z pliku, no a potem ustawiamy właściwość 'Brush' dla forularza i wywołujemy funkcję 'FillRect':
// Plik źródłowy np. Unit1.cpp |
Należy jeszcze pamiętać o usunięciu obiektu 'bmp' z pamięci gdy już nie będzie potrzebny, a nie będzie potrzebny po zamknięciu programu dlatego usuniemy go w zdarzeniu 'OnClose' formularza 'Form1':
// Plik źródłowy np. Unit1.cpp |
I na koniec jeszcze dwie uwagi. Po pierwsze jeżeli bitmapa jest mniejsza od formularza, to zostanie powielona wielokrotnie i rozmieszczona sąsiadująco, po drugie funkcja FillRect jest częcią klasy TCanvas więc można jej używać tylko w obiektach, które posiadają właściwość Canvas, aczkolwiek istnieje sposób na dołączanie klasy TCanvas do obiektów które jej nie posiadają - patrz porada: rysowanie na obiektach nie posiadających Canvas.
Rysowanie na obiektach nie posiadających Canvas.
Rysowanie na powierzchni obiektów jest możliwe tylko wtedy gdy obiekty dziedziczą klasę TCanvas, jednak nie wszystkie obiekty posiadają właściwość Canvas. Otóż, żeby rysować na takich obiektach należy stworzyć obiekt typu TControlCanvas, który przekieruje kontrolę klasy TCanvas na wybrany przez nas obiekt. W przykładzie pokażę jak rysować na obiekcie 'Panel1'.
W tym celu przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private deklarujemy nowy obiekt typu 'TControlCanvas':
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku źródłowego, i tworzymy definicję zadeklarowanego obiektu 'FCanvas ':
// Plik źródłowy np. Unit1.cpp |
Klasa Canvas została przypisana do obiektu Panel1 i teraz można już rysować na tym obiekcie. Można to zrobić wywołując obiekt FCanvas. W przykładzie Panel1 zostanie wypełniony bitmapą po wywołaniu zdarzenia 'OnClick' obiektu 'Button1':
// Plik źródłowy np. Unit1.cpp |
Odbicie bitmapy w poziomie.
Stworzenie lustrzanego odbicia w poziomie, grafiki w formacie bmp jest w zasadzie bardzo proste. Należy posłużyś się w tym celu funkcją 'ScalLine' będącą właściwością klasy TBitmap. Załużmy, że w obiekcie Image1 umieściliśmy jakąś grafikę w formacie bmp i teraz chcemy ją odbić w poziomie. W tym celu utworzymy funkcję o nazwie Mirror (nazwa jest dowolna) i jako parametr przekażemy jej wskaźnik do obiektu typu TImage. Przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private tworzymy deklarację funckcji 'Mirror':
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku żródłowego (np. Unit1.cpp) i tworzymy definicję zadeklarowanej funkcji 'Mirror':
// Plik źródłowy np. Unit1.cpp |
A teraz, żeby odbić bitmapę wystarczy np. w zdarzeniu 'OnClick' obiektu 'Button1' wywołać funkcję 'Mirror' przekazując jej jako parametr obiekt zawierający grafikę, np. 'Image1':
// Plik źródłowy np. Unit1.cpp |
Odbicie bitmapy w pionie.
W celu odbicia w pionie grafiki, w formacie bmp posłużymy się funkcją 'ScanLine' będącą właściwością klasy 'TBitmap'. W tym celu utworzymy funkcję 'Flip' (nazwa jest dowolna), która jako parametr będzie pobierała wskażnik do obiektu typu TImage. Przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private umieszczamy deklarację funkcji 'Flip':
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i tworzymy definicję zadeklarowanej funkcji 'Flip':
// Plik źródłowy np. Unit1.cpp |
A teraz, żeby odbić bitmapę wystarczy np. w zdarzeniu 'OnClick' obiektu 'Button1' wywołać funkcję 'Flip' przekazując jej jako parametr obiekt zawierający grafikę, np. 'Image1':
// Plik źródłowy np. Unit1.cpp |
Odwrócenie kolorów w bitmapie.
W celu stworzenia negatywu grafiki w formacie bmp, stworzymy funkcję 'Invert'. W tym celu przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private tworzymy prototyp funckcji 'Invert':
// Plik nagłówkowy np. Unit1.h. |
Następnie przechodzimy do pliku żródłowego (np. Unit1.cpp) i tworzymy definicję prototypu 'Invert':
// Plik źródłowy np. Unit1.cpp |
Teraz wystarczy już tylko wywołać funkcję np. w zdarzeniu 'OnClick' obiektu 'Button1':
// Plik źródłowy np. Unit1.cpp |
Przeźroczyste kolory w bitmapach.
Chcąc uzyskać przeźroczystość grafiki w obiekcie TImage wystarczy ustawić jego właściwość Transparent na true. Co jednak w sytuacji gdy chcemy np. umieścić grafikę bezpośrednio na formularzu, lub gdy zechcemy umieścić jakiś rysunek na innym rysunku, ale tak żeby nie było widać tła. Istnieje możliwość zdefiniowania kolorów przeźroczystych w plikach graficznych typu bitmapa. Zanim zaczniemy eksperymentować proponuję przegotować sobie trzy bitmapy z których jedna będzie stanowiła tło obiektu Image1, druga zostanie umieszczona na tym tle, ale z pominięciem koloru, który uczynimy przeźroczystym, no i trzecia grafika to będzie coś w rodzaju "stempla", który będzie stawiany w miejscu kliknięcia myszką na obiekcie Image1. Grafika powinna być zapisana w co najmniej 24 bitowej głębi kolorów. Windows NT/2000/XP radzą sobie doskonale z plikami w 8 bitowej głębi kolorów, lecz w
Windows 95/98 nie uda się uzyskać przeźroczystości w plikach zapisanych w 8 bitowej głębi kolorów.
Tworzymy nowy projekt i umieszczamy na formularzu komponenty Image1 i Button1, następnie przechodzimy do pliku nagłówkowego (np. Unit1.h) i w sekcji private deklarujemy obiekt typu TBitmap:
// Plik nagłówkowy np. Unit1.h |
Do pliku nagłówkowego już więcej wracać nie będziemy. Przechodzimy do pliku źródłowego i w konstruktorze klasy np. TForm1 definiujemy zadeklarowany obiekt:
// Plik źródłowy np. Unit1.cpp |
Objaśnienie:
Metoda Canvas->Pixels[X][Y] pobiera kolor piksela, a parametry X i Y określają odpowiednio jego położenie w poziomie i w pionie. Następnie wybrany kolor zostaje przekazany do funkcji TransparentColor i staje się kolorem przeźroczystym.
Teraz do obiektu Image1 należy wczytać przygotowaną wcześniej grafikę mającą stanowić tło. Nie potrzeba do tego żadnego kodu ponieważ komponent TImage posiada już właściwość Picture.
Następnie w zdarzeniu OnClick obiektu Button1 umieszczamy instrukcję, która umieści w obiekcie Image1 kolejny plik graficzny:
// Plik źródłowy np. Unit1.cpp |
Pozostało już tylko umieścić w zdarzeniu OnMouseDown obiektu Image1 instrukcję obsługującą "stemplowanie" obiektu Image1 grafiką wczytaną do obiektu nakladka:
// Plik źródłowy np. Unit1.cpp |
Lepiej będzie wyglądało jeżeli kursor myszki będzie wypadał w środku "stempla":
// Plik źródłowy np. Unit1.cpp |
Przedstawioną technikę można z powodzeniem wykorzystać przy tworzeniu prostych gier.
Odejmowanie bitmapy od bitmapy.
Można odjąć dwie liczby od siebie i podać wynik, ale czy można odjąć jeden obrazek od drugiego i również podać wynik. Otóż można, cała operacja polega na odejmowaniu koloru pojedynczego piksela jednego obrazka od koloru pojedynczego piksela drugiego obrazka. Jeżeli obydwa obrazki będą identyczne to jako wynik otrzymamy jednolite tło (w jednym kolorze) koloru czarnego ponieważ kolor czarny na palecie RGB reprezentowany jest przez wartości Red = 0, Green = 0, Bleu = 0. Tutaj znajduje się przykład wykorzystania tej techniki. Nie znalazłem żadnego praktycznego zastosowania dla tego kodu, może się co najwyżej przydać do sprawdzania różnic pomiędzy dwoma obrazami w formacie BMP.
Umieszczamy na formularzu trzy obiekty Image1,
Image2, Image3 oraz przycisk Button1. Do obiektów Image2 i Image3 wczytujemy grafikę w formacie BMP (najlepiej w 24 bitowej głębi kolorów), przy czym obrazy powinny się nieznacznie między sobą różnic (znacznie również mogą, lecz wtedy efekt będzie słabo widoczny). Następnie w zdarzeniu OnClick dla przycisku Button1 umieszczamy kod odejmujący obrazki od siebie i przedstawiający wynik w obiekcie Image1:
// Plik źródłowy np. Unit1.cpp |
...i to już wszystko na ten temat.
Umieszczanie bitmapy w zasobach programu.
Projekt dowolnego programu tworzonego w BCB składa się z wielu plików, takich jak np. *.bpf, *.cpp, *.h, *.dfm i *.res. W tej poradzie moja uwaga skupia się na pliku *.res, w którym to umieszczone są takie zasoby jak np ikona programu. Tworząc aplikację BCB automatycznie tworzy plik zasobu o takiej samej nazwie jak nazwa projektu. Zawartość tak utworzonego pliku można łatwo przejrzeć wybierając menu Project | Resources. Powinno wyskoczyć okno dialogowe o nazwie Project resources:
Jak widać na rysunku w takim pliku znajdują się nazwa projektu, w tym przypadku jest to Project1, oraz ikona aplikacji. Domyślna ikona aplikacji zawsze nosi nazwę MAINICON. Oprócz tych standardowych zasobów, korzystając z tego okna można włączyć do projektu takie zasoby jak bitmapy, ikony, kursory statyczne oraz dane użytkownika.
W celu wprowadzenia do pliku *.res nowych zasobów należy w oknie dialogowym Project Resources, w dowolnym miejscu, wywołać menu kontekstowe, a następnie z menu New wybrać typ zasobu, który nas interesuje:
Żeby skorzystać w programie z tak włączonej do projektu bitmapy, trzeba posłużyć się nazwą zasobu:
W zasobach widocznych na rysunku zostało umieszczonych sześć bitmap, w przykładzie posłużę się obiektem Image1 i wczytam do niego zasób o nazwie BITMAPA_1:
// Plik źródłowy np. Unit1.cpp |
Można również wczytywać, tak utworzone zasoby do dynamicznie utworzonego obiektu typy Graphics::TBitamp:
// Plik źródłowy np. Unit1.cpp |
Możliwe jest również modyfikowanie i usuwanie dodanych zasobów. Wystarczy tylko zaznaczyć interesujący nas zasób i wywołać menu kontekstowe, a potem odpowiednią opcję.
To jednak nie wszystko, można tworzyć własne pliki zasobów, w tym celu trzeba w katalogu, w którym umieściliśmy nasz projekt utworzyć plik z rozszerzeniem *.rc, można to zrobić za pomocą notatnika. Do tak utworzonego pliku wpisujemy identyfikator bitmapy oraz jej nazwę. Identyfikator bitmapy powinien składać się z nazwy pliku bez rozszerzenia oraz z wyrazu BITMAP. Jeżeli bitmapa nie znajduje się w tym samym katalogu co projekt, to trzeba podać pełną ścieżkę dostępu do niej. Przykładowy plik zasobów może wyglądać tak:
PLIK BITMAP "plik.bmp" |
Tak utworzony plik można zapisać pod dowolną nazwą, np. Myres.rc. Następny krok polega na skompilowaniu tak utworzonego zasobu w formacie tekstowym do zasobu w formacie binarnym z rozszerzeniem
*.res. Najprościej będzie posłużyć się konsolą DOS dostępną w każdej wersji Windows'a.
W linii komend wpisujemy nazwę programu wchodzącego w skład pakietu BCB - Brcc32.exe, a po nim podajemy ścieżkę dostępu do kompilowanego zasobu:
By móc korzystać z nowo utworzonego zasobu ( w przykładzie myres.res) trzeba go włączyć do projektu za pomocą polecenia menu Project | Add to project i w oknie dialogowym, które się ukaże zmieniamy opcję Pliki typu na Compiled resource (*.res), a następnie wybieramy utworzony plik zasobów. W ten oto sposób włączyliśmy własne zasoby do projektu, dalej postępujemy w sposób opisany na samym początku, czyli np:
// Plik źródłowy np. Unit1.cpp |
Kilka uwag na zakończenie:
w zasobach można umieszczać pliki graficzne tylko w formacie *.bmp,
nie należy umieszczać w zasobach plików o dużych rozmiarach, ponieważ rozmiar aplikacji zostanie powiększony o rozmiar bitmapy, a to wydłuży czas uruchamiania programu,
jeżeli chcemy umieścić w zasobach tylko jeden lub dwa pliki to najlepiej skorzystać ze standardowo tworzonego zasobu projektu. Tworzenie własnych zasobów ma sens tylko wtedy, gdy włączamy do programu dużą ilość bitmap i chcemy utrzymać porządek w zasobach,
tworząc własne zasoby, należy wpisywać identyfikator pliku jak i wyraz BITMAP wielkimi literami, nazwa pliku może być ujęta w cudzysłów, ale jest to konieczne tylko wtedy, gdy nazwa ta składa się z kilku wyrazów,
nadając nazwy zasobom i plikom do nim włączanym najlepiej jest stosować nazwy jednowyrazowe i nie dłuższe niż osiem liter, to pozwoli uniknąć zbędnych problemów.
Wszystkie aplikacje i niektóre sterowniki (patrz pliki z rozszerzeniem *.dll) zawierają ikony, np plik shell32.dll znajdujący się w katalogu ...\Windows\System32\ zawiera wszystkie ikony z których korzysta system operacyjny, istnieje prosty sposób przeglądania ikon zawartych w tych plikach. Można to zrobić za pomocą funkcji
ExtractIcon, żeby skorzystać z tejże funkcji należy włączyć do projektu plik shellapi.h i najlepiej jest to zrobić w pliku źródłowym w sekcji include:
// Plik źródłowy np. Unit1.cpp |
W przedstawionym niżej przykładzie, pokaże jak za pomocą dynamicznie tworzonego obiektu typu TImage wyciągnąć, pokazać i zapisać do plików wszystkie ikony z wybranego pliku. W tym celu umieszczamy na formularzu obiekty OpenDialog1, Button1 i w zdarzeniu OnClick wywołujemy funkcję ExtractIcon:
// Plik źródłowy np. Unit1.cpp |
Zaprezentowany przykład to nieco rozbudowany model pokazujący jak można wykorzystać funkcję ExtractIcon. Niżej pokażę jak wykorzystać tą funkcję do wyciągnięcia pojedynczej ikony i pokazania. W tym celu umieszczamy na formularzu obiekty OpenDialog1, Button1 i Image1, po czym w zdarzeniu OnClick wpisujemy instrukcje umożliwiające wyciągnięcie pojedynczej ikony poprzez podanie jej numeru, a następnie pokazanie jej w obiekcie Image1:
// Plik źródłowy np. Unit1.cpp |
Umieszczanie grafiki w formacie JPEG w zasobach programu.
W poradzie 14 pokazałem w jaki sposób można umieszczać bitmapy w zasobach programu, jednakże bitmapy mają duże rozmiary więc wadą takiego rozwiązania jest to, że rośnie rozmiar aplikacji, dlatego znaczniej lepiej jest umieścić w zasobach plik graficzny w formacie JPEG. Umieszczenie pliku w zasobach nie jest skomplikowane, trzeba tylko spełnić kilka warunków. Tak więc przede wszystkim w katalogu w którym znajduje się nasz projekt aplikacji trzeba umieścić plik JPEG, załóżmy że plik nosi nazwę Image1.jpeg. Następnie otwieramy notatnik i tworzymy plik Myres.rh (nazwa dowolna, ale rozszerzenie pozostaje *.rh) i zapisujemy w nim następujące informacje:
#ifndef MYRES_RH |
Tekst wyróżniony na zielono to identyfikator pliku, nazwa identyfikatora jest dowolna, jednak należy trzymać się jej konsekwentnie ponieważ teraz utworzymy drugi plik który znany już z porady 14, któremu nadam nazwę Myres.rc. W tym pliku umieszczamy identyfikator pliku graficznego, który musi być taki sam jak ten zdefiniowany w pliku Myres.rh, po identyfikatorze umieszczamy nazwę zasobu (w tym przypadku RCDATA) no i na koniec nazwę pliku graficznego (w tym przypadku Image1.jpeg), a tak wygląda treść pliku Myres.rc:
#include "myres.rh" |
Po utworzeniu pliku Myres.rc trzeba go skompilować do postaci Myres.res. Opisałem to szczegółowo w poradzie 14, ale powtórzę to jeszcze raz. Otwieramy konsolę DOS dostępną w każdej wersji Windows'a i w linii komend wpisujemy nazwę programu wchodzącego w skład pakietu BCB - Brcc32.exe, a po nim podajemy ścieżkę dostępu do kompilowanego zasobu, np: brcc32 myres.rc.
Gdy plik Myres.rh i Myres.res są już gotowe trzeba dołączyć je do projektu. W tym celu przechodzimy do pliku źródłowego (np Unit1.cpp) i w sekcji include wstawiamy następujące instrukcje:
// Plik źródłowy np. Unit1.cpp |
Proszę zwrócić uwagę, że oprócz utworzonego pliku Myres.rh w sekcji include zostały włączone zasoby znajdujące się w bibliotekach jpeg.hpp i memory.h. Pozostało jeszcze włączenie do projektu pliku zasobów Myres.res, w tym celu trzeba z menu wybrać Project | Add to project i w oknie dialogowym wskazać plik Myres.res.
Zasoby zostały już dodane do projektu, i jak to wcześniej powiedziałem nie było to skomplikowane. Dużo więcej kłopotów może sprawić wyciągnięcie grafiki JPEG z dodanych zasobów i pokazanie ich np w obiekcie Image1. Dlatego, żeby to Wam uprościć napisałem funkcję, która się wszystkim zajmie. Funkcje nazwałem ViewJPEG (nazwa dowolna). Najpierw trzeba umieścić deklarację w pliku nagłówkowym (np Unit1.h) w sekcji private lub public:
// Plik źródłowy np. Unit1.h |
Po utworzeniu deklaracji przechodzimy do pliku źródłowego i tworzymy definicję funkcji ViewJPEG. Funkcja pobiera jako argumenty adres do obiektu typu TImage na którym będzie wyświetlana grafika oraz numer identyfikatora, odwołującego się do konkretnej grafiki w zasobach:
// Plik źródłowy np. Unit1.cpp |
Nie będę wyjaśniał jak to działa, bo to nieco skomplikowane. Pozostało już tylko wywołanie funkcji ViewJPEG, można to zrobić np w zdarzeniu OnClick dla przycisku Button1:
// Plik źródłowy np. Unit1.cpp |
Ściągnij źródło: ViewJpeg.zip
Efekt rozjaśnienia obrazka (bitmapy).
Wszystkie programy przeznaczone do obróbki grafiki komputerowej posiadają funkcję umożliwiającą ściemnianie lub rozjaśnianie grafiki. Jak zapewne wszystkim już wiadomo obraz wyświetlony na ekranie monitora składa się z pojedynczych pikseli, a każdy piksel reprezentuje jakiś kolor. Otóż o czym może nie wszyscy wiedzą kolory te, a mogą ich być miliony, są generowane z trzech kolorów podstawowych
R - red (czerwony),
G - green (zielony),
B - blue (niebieski),
czyli system RGB. Każda składowa może przyjmować wartość od 0 do 255, co daje ponad 16,7 miliona kombinacji, a co za tym idzie - kolorów.
Żeby zmienić jasność grafiki, należy zbudować taki algorytm, który będzie zmniejszał lub zwiększał wartość składowych RGB dla każdego piksela wchodzącego w skład bitmapy. Oto przykład gotowej funkcji zawierającej niezbędny algorytm:
// Plik źródłowy np. Unit1.cpp |
for(int j = 0; j < BMP->Width; j++) |
row[j].rgbBlue = (BYTE) b; |
Przykład został podzielony na trzy części. W części pierwszej wykorzystano dostęp do poszczególnych pikseli bitmapy za pomocą funkcji ScanLine, w drugiej części następuje zmiana wartości poszczególnych składowych (RGB) poprzez ich zwiększanie o wartość przypisaną do zmiennej x, następuje tutaj również sprawdzenie czy składowe RGB pozostają w zakresie 0 - 255. W części trzeciej następuje przypisanie nowych wartości poszczególnym pikselom. Funkcja jest gotowa do użycia. Załóżmy, że mamy w obiekcie Image1 wczytany jakąś grafikę w formacie *.bmp (bitmapa) i chcemy ją rozjaśnić. W tym celu posłużę się zdarzeniem OnClick dla przycisku Button1:
// Plik źródłowy np. Unit1.cpp |
Przedstawiona metoda ma dwie zasadnicze wady, po pierwsze nie można płynnie zmieniać jasności np. co 1 punkt, po drugie jeśli rozjaśnimy obrazek to już nie będzie można powrócić do stanu początkowego inaczej niż tylko poprzez ponowne wczytanie grafiki.
W celu rozwiązania tych problemów posłużymy się obiektem TrackBar1, tak więc umieszczamy go na formularzu i zmieniamy jego właściwości: Min = -254, Max = 254. Tyle wystarczy, teraz przechodzimy do pliku nagłówkowego (np. Unit1.h) i deklarujemy nowy obiekt typu TBitmap:
// Plik źródłowy np. Unit1.h |
Następnie wracamy do pliku źródłowego i w konstruktorze klasy (np. TForm1) definiujemy obiekt BMP:
// Plik źródłowy np. Unit1.cpp |
Obiekt BMP posłuży jako bufor do przechowywania oryginalnego obrazka. Grafikę należy wczytać do obiektu BMP i Image1, można w tym celu posłużyć się obiektem OpenDialog1:
// Plik źródłowy np. Unit1.cpp |
Zdarzenie zostało tak skonstruowane, żeby sprawdzać w jakim formacie jest wczytywana grafika i jeżeli jest to format *.jpg, to jest wykonywana konwersja do formatu *.bmp. I jeszcze jedna bardzo istotna uwaga, otóż funkcja Brightness wykonuje prawidłowo operację rozjaśniania i ściemniania tylko na grafikach w 32 bitowej głębi koloru, dlatego zaraz po wczytaniu grafiki następuje jej konwersja do takiego właśnie formatu.
Teraz sam proces rozjaśniania będzie następował przy przesuwaniu "suwaka" w obiekcie TrackBar1, dlatego wykorzystamy jego zdarzenie OnChange, przy czym do przepisywania zawartości bufora BMP do obiektu Image1 posłuży nam obiekt typu TMemoryStream:
// Plik źródłowy np. Unit1.cpp |
Jeżeli wystąpią problemy z obsługą plików *.jpg, należy włączyć do projektu plik JPEG.hpp, można to zrobić w pliku nagłówkowym lub źródłowym w sekcji include:
#include <jpeg.hpp>
Zmiana kontrastu obrazka (bitmapy).
Niniejsza porada stanowi swego rodzaju rozwinięcie porady efekt rozjaśnienia obrazka (bitmap), prezentuje po prostu kolejny efekt graficzny polegający tym razem na zwiększaniu i zmniejszaniu kontrastu grafiki. Właściwie pewne elementy zostaną powtórzone, a jedyną różnicę będzie stanowił sam algorytm regulujący kontrast, dlatego proponuję najpierw zapoznać się z poprzednią poradą by zrozumieć zastosowany tutaj mechanizm.
Do regulacji kontrastu obrazka posłuży nam specjalnie do tego celu stworzona funkcja Kontrast (nazwa jest dowolna), która po prostu wykonuje operacje matematyczne na wszystkich pikselach, z których składa się grafika. Umieszczamy od razu definicję funkcji Kontrast bezpośrednio w pliku źródłowym (bez deklaracji w pliku nagłówkowym). Definicja funkcji powinna być umieszczona przed wszystkimi
zdarzeniami, które ją wywołują :
// Plik źródłowy np. Unit1.cpp |
Podobnie jak to było w poprzedniej poradzie umieszczamy na formularzu obiekt TrackBar1, który posłuży nam do regulacji kontrastu. Ustawiamy jego właściwości Max = 100 i Min = -100, dalej postępujemy dokładnie tak samo jak w poprzedniej poradzie. Niżej przedstawiam kompletny kod jaki powinien się znaleźć w plikach nagłówkowym i źródłowym. W programie oprócz komponentu TrackBar1 wykorzystano obiekty Image1, Button1 i OpenDialog1:
// Plik nagłówkowy np. Unit1.h |
// Plik źródłowy np. Unit1.cpp |
W tej poradzie pokaże jak przeskalować obrazek, jest to w
zasadzie proste ponieważ wystarczy posłużyć się funkcją CopyRect należącą do
klasy TCanvas, jednak ja przedstawię sposób skalowania, pozwalający na
zachowanie proporcji obrazka, przedstawiona metoda będzie pozwalała również na
skalowanie plików JPEG, poprzez ich konwersję do formatu BMP przed
przeskalowaniem i ponowną konwersję do formatu JPEG przed zapisaniem.
Przygotowujemy sobie komponenty na formularzu według zamieszczonego rysunku:
Następnie ustawiamy właściwości niektórych komponentów:
Image1 : TImage
AutoSize |
false |
automatyczna zmiana rozmiaru obrazka |
IncrementalDisplay |
true |
wyświetlanie przyrostowe |
Stretch |
true |
rozciąganie obrazka do rozmiaru okna |
OpenPictureDialog1 : TOpenPictureDialog
DefaultExt |
*.bmp |
domyślne rozszerzenie dla plików |
+Filter |
Bitmap (*.bmp)|*.bmp|Pliki JPEG|*.jpg;*.jpeg |
SavePictureDialog1 : TSavePictureDialog
DefaultExt |
*.bmp |
domyślne rozszerzenie dla plików |
+Filter |
Bitmap (*.bmp)|*.bmp|Pliki JPEG|*.jpg;*.jpeg |
Teraz przystępujemy do kodowania, przechodzimy do pliku nagłówkowego
(Unit1.h) i w sekcji private deklarujemy nowy obiekt typu TImage i nową
zmienną typu String. Obiekt będzie przechowywał obrazek poddawany
skalowaniu, a zmienna będzie przechowywała informację o typie pliku, czyli czy
jest to plik BMP czy JPEG.
// Plik źródłowy np. Unit1.h |
W konstruktorze klasy TForm1 definiujemy obiekt img:
// Plik źródłowy np. Unit1.cpp |
Teraz utworzymy dwie funkcje, pierwsza ConvertImage będzie
odpowiedzialna za konwersję formatu JPEG do formatu BMP, a druga
SaveConvertImage będzie zapisywała przeskalowany obrazek do pliku o ile
będzie to format JPEG:
// Plik źródłowy np. Unit1.cpp |
Ponieważ te funkcje nie przynależą do klasy TForm1, to muszą być umieszczone
przed zdarzeniami w których są wywoływane. W zdarzeniach OnChange
obiektów CSpin1 i CSpin2 umieścimy kod odpowiedzialny za
zachowanie proporcji obrazka:
// Plik źródłowy np. Unit1.cpp |
W zdarzeniu OnClick przycisku Button1 umieszczamy instrukcję
otwierającą plik z obrazkiem, kod będzie również ustalał rozszerzenie pliku jak
również jego rozmiar:
// Plik źródłowy np. Unit1.cpp |
W zdarzeniu OnClick dla przycisku Button2 nastąpi sprawdzenie z
jakim plikiem mamy do czynienia, potem nastąpi jego konwersja do formatu BMP,
jeśli będziemy mieli do czynienia z plikiem JPEG, następnie zostanie wykonane
skalowanie obrazka, po czym wystąpi kolejna konwersja, jeśli będzie konieczna i
na koniec plik zostanie zapisany:
// Plik źródłowy np. Unit1.cpp |
Skalowanie powinno działać, jednak program nie służy do
konwersji z jednego formatu na drugi, ponieważ konwersja plików i powrót do
właściwego formatu jest dokonywana jeszcze przed zapisem, można oczywiście
przerobić trochę kod i program będzie służył również do konwersji. Skalowanie z
wykorzystaniem funkcji CopyRect jest jednak dalekie do doskonałości,
ponieważ po zmniejszaniu obrazki nie wyglądają najlepiej,
piksele nie układają się tak jak powinny, przy zwiększaniu obrazka jednak
wszystko jest w porządku. Udoskonalimy więc skalowanie tak, żeby niezależnie od
tego czy obrazek jest zwiększany czy zmniejszany wyglądał tak jak należy, w tym
celu wystarczy zastąpić funkcję CopyRect funkcją StretchDraw i
wszystko będzie działać jak należy:
// Plik źródłowy np. Unit1.cpp |
Tym którzy lubią eksperymentować polecam przetestować jeszcze
funkcję CopyMode w połączeniu z funkcjami CopyRect, StretchDraw
lub BrushCopy:
tmp->Canvas->CopyMode =
cmNotSrcCopy; |
tmp->Canvas->CopyMode =
cmBlackness; |
tmp->Canvas->CopyMode =
cmDstInvert; |
Dostępne są następujące wartości funkcji CopyMode:
cmBlackness |
wypełnia płótno Canvas czarnym kolorem. |
cmDstInvert |
odwraca obraz na płótnie i ignoruje źródło, dokładnie to nie wiem jak to działa, chyba chodzi o odwrócenie kolorów. |
cmMergeCopy |
kombinacja obrazu na płótnie i źródle bitmapy poprzez użycie operatora Boolean AND. |
cmMergePaint |
kombinacja odwróconego źródła bitmapy z obrazem na płótnie przez użycie operatora Boolean OR. |
cmNotSrcCopy |
kopiowanie odwróconych kolorów ze źródła na płótno. |
cmNotSrcErase |
kombinacja obrazu na płótnie i źródle bitmapy przez użycie operatora Boolean OR, i odwracenie wyniku. |
cmPatCopy |
kopia wzoru źródła na płótno. |
cmPatInvert |
kombinacja wzoru źródła z obrazem poprzez użycie operatora Boolean XOR na płótnie. |
cmPatPaint |
kombinacja odwróconego źródła bitmapy z źródłem wzoru przez użycie operatora Boolean OR. |
cmSrcAnd |
kombinacja obrazu na płótnie i źródła bitmapy przez użycie operatora Boolean AND. |
cmSrcCopy |
kopiuje bitmapę źródłową na płótno. |
cmSrcErase |
odwraca obraz na płótnie i łączy wynik z źródłem bitmapy przez użycie operatora Boolean AND. |
cmSrcInvert |
kombinacja obrazu na płótnie i bitmapy źródłowej poprzez użycie operatora Boolean XOR. |
cmSrcPaint |
kombinacja obrazu na płótnie i bitmapy źródłowej poprzez użycie operatora Boolean OR. |
cmWhiteness |
wypełnia płótno kolorem białym. |
Do rysowania po ekranie, podobnie jak to ma miejsce w innych przypadkach, służy klasa TCanvas. Trzeba tylko uchwycić kontekst ekranu i wrzucić na niego obiekt typu TCanvas:
// Plik źródłowy np. Unit1.cpp |
Rysowanie po ekranie nie ma jednak wiele wspólnego z rysowaniem po pulpicie ponieważ obiekt będzie rysowany na powierzchni wszystkich otwartych okien.
Jak pobrać (sprawdzić) ikonę powiązaną z plikiem bez sprawdzania tego w rejestrze?
Służy do tego prosta funkcja:
// Plik źródłowy np. Unit1.cpp |
Konwersja wartości typu TColor na kolor HTML.
Taką operację
można przeprowadzić za pomocą klasy TColorRef i funkcji Format, w
poradzie zostanie wykorzystany komponent typu TColorDialog:
// Plik źródłowy np. Unit1.cpp |
Rotacja (obracanie) bitmapy z wykorzystaniem DIB.
Tą poradę znalazłem w sieci i nie testowałem jej więc nie wiem czy działa. Do projektu należy włączyć bibliotekę math.h. Żeby skorzystać z funkcji min i max należy również włączyć bibliotekę stlexam.h przy czym tutaj należy podać pełną ścieżkę dostępu do tej biblioteki i należy ją włączyć poprzez Project | Add to project...
// Plik źródłowy np. Unit1.cpp |
Tworzenie funkcji ZOOM dla obiektów typu TImage i TBitmap.
W tej
poradzie przedstawię dwa sposoby na wykonywanie ZOOM'u - przybliżania i
oddalania - obrazka. Obydwa sposoby będą wykorzystywały obiekt typu TImage,
ponieważ idealnie nada się on do tego typu zadań.
Sposób pierwszy oparty jest na zmianie rozmiaru samego
obiektu Image, jest to chyba najprostszy sposób na wykonanie ZOOM'u. W tym celu
umieszczamy na formularzu obiekt Image1 i ustawiamy jego właściwości Top
i Left na 0. Właściwości AutoSize, Proportional i
Stretch ustawiamy na true. Umieszczamy również na formularzu dwa
przyciski, posłużą nam one do zmiany poziomu przybliżenia. Żeby ZOOM
wykonywany był prawidłowo, musi zmieniać rozmiar Image proporcjonalnie,
ustawienie właściwości Proportional na true powinno tą sprawę
załatwić, ponieważ niezależnie od tego jakie rozmiary podamy dla właściwości
Hight I Width, obiekt Image i tak powinien zachować prawidłowe
proporcje, gdyby jedna tak się nie stało (różnie to bywa) ja zastosują prosty
algorytm na procentową zmianę rozmiarów obiektu. Wraz ze zmianą rozmiarów
obiektu dokonywała się będzie również zmiana rozmiarów formularza na którym
znajduje się Image. Zmiana rozmiarów formularza wymaga jednak zastosowania
bardziej złożonego algorytmu, ponieważ należy zadbać o to, żeby po zmianie
rozmiaru formularz nie przechodził poza prawą i dolną krawędź ekranu,
jednocześnie należy dopilnować, żeby dopóki to możliwe, formularz jednak
zmieniał swoje rozmiary, tak by pokazać na ekranie jak najwięcej obrazka. Jeżeli
rozmiar obiektu Image przekroczy rozmiar Formularza to pokażą się paski
przewijania, ale dopiero gdy formularz osiągnie maksymalne względne położenie na
ekranie, bez przekraczania jego krawędzi. Właściwość AutoSize formularza
ustawiamy na false, właściwość AutoScroll ustawiamy na true. Żeby uniknąć
migotania obrazka można w konstruktorze klasy formularza ustawić funkcję
DoubleBuffered na true.
// Plik źródłowy np. Unit1.cpp |
Wewnątrz
zdarzenia powiększania obrazka, zmiana rozmiaru formularz dokonuje się
kilkakrotnie, może wydawać się to nielogiczne, jednak ma to znaczenie i służy
jak najdokładniejszemu dopasowaniu rozmiaru formularza do rozmiaru Image, żeby
uniknąć migotania całego okna podczas tych zmian umieściłem funkcję blokującą
odświeżanie
LockWindowUpdete(HDC hdc).
Do tego sposobu wykonywania funkcji ZOOM nic już nie dodam, ale dobrym pomysłem
byłoby przesuwanie pasków przewijania po uchwyceniu obiektu Image, działało by
to na zasadzie, że klikamy na obiekcie lewym klawisze myszy i przytrzymując
przesuwamy go po Image i w zależności od tego w którą stronę przesuniemy
wskaźnik myszy w tą samą stronę przesunie się obrazek. Proponowany przeze mnie
sposób nie będzie przesuwał Image po formularzu, lecz będzie sterował położeniem
pasków przewijania powodując w ten sposób przesuniecie Image. Całość zadanie
można zrealizować w trzech zdarzeniach dla obiektu Image OnMouseDown,
OnMouseMove i OnMouseUp:
// Plik źródłowy np. Unit1.cpp |
Przedstawiony
wyżej sposób zadziała niezależnie od typu użytego obrazu, jednak jeżeli chcesz,
żeby obiekt typu TImage obsługiwał pliki *.JPG należy właczyś do pliku
nagłówkowego bibliotekę:
#include <JPEG.hpp>.
Drugi sposób również wykorzystuje obiekt Image, jednak zmiana poziomu
przybliżenia będzie wykonywała się w klasie TCanvas tego obiektu, a to oznacza,
że ten sposób można wykorzystać na wszystkich obiektach obsługujących klasę
TCanvas. Klasa TCanvas wykonuje operacje tylko na bitmapach, co niesie ze sobą
pewne ograniczenia co do wykorzystania grafiki w formacie *.JPG, ale i z tym
można sobie poradzić wystarczy dokonać konwersji. Ten sposób wymaga
przechowywania oryginalnej grafiki w buforze pamięci, wykorzystamy tutaj klasę
TBitmap, należy ją zadeklarować w pliku nagłówkowym i zdefiniować w pliku
źródłowym.
// Plik nagłowkowy np. Unit1.h |
// Plik źródłowy np. Unit1.cpp |
Oprócz trzech
przycisków (jeden do ładowania grafiki) będzie potrzebny również obiekt
OpenPictureDialog (lub OpenDialog), który posłuży na do załadowania grafiki,
a oto kompletny kod:
// Plik źródłowy np. Unit1.cpp |
Te dwie metody nie wyczerpują oczywiście wszystkich sposobów na wykonanie ZOMM'u, to tylko mój pomysł na to i niekoniecznie najlepszy.
Obsługa skanera.
nadesłał:
Scorp1on
Niniejsza
porada powstała na podstawie kodu źródłowego nadesłanego przez Scorp1on.
Przedstawiony sposób obsługi skanera opiera się na gotowym sterowniku
eztw32.dll. Kod obsługujący skaner jest prosty i zmieści się w jednym
zdarzeniu, jednak zanim zaczniemy pisać kod należy przekopiować do katalogu z
naszym projektem pliki: eztw32.dll, eztwain.h i eztw32.lib,
pliki do pobrania stąd. Dodatkowo
bibliotekę eztw32.lib należy włączyć do projektu poprzez menu Project | Add to
project... W sekcji nagłówkowej pliku źródłowego należy włączyć do projektu
biblioteki: #include <memory> i #include "eztwain.h". Po takich przygotowaniach
możemy przystąpić do kodowania:
// Plik źródłowy np. Unit1.cpp |
Wynik skanowania (zeskanowany obrazek) zostanie wyświetlony na obiekcie Image1 więc trzeba pamiętać o umieszczeniu tego obiektu na formularzu.
Konwersja formatu BMP do formatu JPG.
nadesłał: Scorp1on
Przy okazji
przeglądania kodu źródłowego obsługi skanera, zauważyłem że w tym dziale brakuje
właśnie takiej porady. Realizacja tego zadania jest bardzo prosta, a
prezentowany tutaj kod pochodzi z tego samego kodu źródłowego wykorzystanego w
obsłudze skanera:
// Plik źródłowy np. Unit1.cpp |
Można też
tak:
// Plik źródłowy np. Unit1.cpp |
Podsumowując funkcja Assign pobiera jako argument obiekt typu Graphics::TBitmap, więc można jej przekazywać właśnie obiekt tego typu, nie zawsze musi to być Image->Picture->Bitmap, obiekt Image można pomijać jeśli jest zbędny.
Wypełnianie obszaru kolorem (tzw. wiadro).
Klasa TCanvas posiada funkcję FloodFill umożliwiającą wypełnienie wybranego obszaru na "płótnie" wybranym przez nas kolorem, chodzi o tzw. narzędzie wiadro znane chyba wszytkim korzystającym z programów do edycji grafiki. Funkcja FloodFill jest banalnie prosta w użyci i ma dwa tryby pracy przełączane za pomocą stylu listy TFillStyle, są to:
enum TFillStyle {fsSurface,
fsBorder};
void __fastcall FloodFill(int X, int Y, TColor
Color, TFillStyle
FillStyle);
fsSurface - Wypełnia cały obszar wskazywany przez parametr Color. Przerywa gdy napotka inny kolor.
fsBorder - Wypełnia cały obszar, który nie jest wskazywany przez parametr kolor. Przerywa kiedy napotka kolor.
Te
objaśnienia mogą niewiele mówić, dlatego najprościej będzie sprawdzić to na
przykładzie. W tym celu umieszczamy na formularzu obiekt Image1 i ustawiamy jego
właściwość AutoSize na true, następnie wczytujemy do niego przygotowaną przeze
mnie prostą bitmapę, którą można pobrać
tutaj. Teraz pokażę dwa przykładowe kody, należy je wywoływać w
zdarzeniu OnMouseUp dla obiektu Image1. To zdarzenie jest o tyle istotne, że
pozwala sprawdzić w jakiej pozycji nad Image1 znajduje się kursor, a tym samym
można dokładnie określić obszar wypełniany kolorem. Przedstawiam dwa przykładowe
kody dla dwóch stylów:
// Plik źródłowy np. Unit1.cpp |
W podanym przykładzie zostanie wypełniony obszar
"płótna" kolorem czerwonym (clRed) dopóki nie zostanie napotkany kolor czarny (clBlack).
// Plik źródłowy np. Unit1.cpp |
W tym przykładzie zostanie wypełniony kolorem
czerwonym (clRed), kolor wskazywany przez parametr
Color (Image1->Canvas->Pixels[X][Y])
dopóki nie zostanie napotkany inny kolor niż ten wskazywany przez parametr
kolor.
Należy zwrócić uwagę na jeszcze jeden szczegół, otóż funkcja FloodFill
wypełnia wskazany obszar kolorem określonym we właściwości
Brush->Color dla wybranego
obiektu, a nie kolorem wskazywanym przez parametr
Color.
Parametry X i Y określają pozycję w której rozpoczyna się wypełnianie obszaru
kolorem. Istotne jest to, że funkcja wypełnia kolorem zamknięty obszar.
W tej poradzie pokaże jak wykonać zrzuty ekranu. W Internecie można znaleźć wiele tego typu porad pokazujących jak wykonać zrzut ekranu całej zawartości pulpitu. Ja jednak pójdę dalej i pokażę jak wykonać zrzut ekranu całej zawartości pulpitu, pojedynczego okna na podstawie jego nazwy, oraz aktywnego okna. Zrzuty ekranu można wykonać w za pomocą klawiatury wystarczy wcisnąć klawisz 'Prt Scr', a jeżeli chcemy wykonać zrzut aktywnego okna ekranu to należy wcisnąć kombinację klawiszy 'Prt Scr + lewy Alt'. Jeżeli chcemy wykonać zrzut pulpitu za pomocą kodu to należy pobrać uchwyt do pulpitu za pomocą funkcji GetDC, funkcja ta zwraca kontekst do wybranego elementu ekranu określonego przez argument pobierany przez tą funkcję, jeżeli jako argument przekażemy funkcji wartość 0 to uzyskamy uchwyt do całego pulpitu. Jeżeli przekażemy teraz kontekst uchwytu do klasy Canvas poprzez jej wartość Handle, to będziemy mogli skopiować zawartość pulpitu i zapisać w formacie *.bmp.
// Plik źródłowy np. Unit1.cpp |
W ten sposób zostaną przechwycone wszystkie okna na pulpicie włącznie z samym pulpitem, czyli to co akurat widać na ekranie monitora. Jeżeli chcemy przechwycić tylko pojedyncze okno programu, to można to zrobić na dwa sposoby. Pierwszy polega na podaniu nazwy klasy okna, lub nazwy okna, zrzut którego chcemy wykonać, można by się posłużyć również funkcją GetDC, jednak w ten sposób przechwycilibyśmy wybrane okno bez belki tytułowej, dlatego trzeba skorzystać z funkcji GetWindowDC, do pobraniu uchwytu do wybranego okna posłużymy się funkcją FindWindow, przypomnę tutaj, że ta funkcja pobiera dwa argumenty, pierwszy to nazwa klasy okna, a drugi to nazwa okna. W celu pobrania uchwytu wystarczy podać tylko jeden argument, najłatwiej uzyskać nazwę okna programu, bo widać ją na pasku tytułowym okna:
// Plik źródłowy np. Unit1.cpp |
Drugim sposobem na przechwycenie okna programu, jest wykonanie zrzutu aktywnego okna programu, kod jest podobny jak w poprzednim przypadku z tą różnicą, że funkcję FindWindow zastępujemy funkcją GetForegroundWindow, która to pobiera uchwyt do aktywnego okna. Występuje tutaj jednak pewien problem, jeśli chcemy w ten sposób przechwycić aktywne okno programu i robimy to w zdarzeniu OnClick dla jakiegoś przycisku, to w momencie wciśnięcia tego przycisku w naszym programie, aktywnym oknem programu stanie się okno naszego programu, więc to zawsze to okno przechwycimy. Należałoby się tutaj posłużyć hakami systemowymi i przechwytywać okno programu za pomocą zdefiniowanych klawiszy, ale o tym w innej poradzie. Teraz pójdę na łatwiznę i przed wykonaniem kodu odpowiedzialnego za zrzut wybranego okna programu, dodam funkcję Sleep wstrzymującą wykonanie kodu na 3 sekundy, żebym po wciśnięciu przycisku we własnym programie mógł uaktywnić inne okno i w ten sposób je przechwycić. To nie jest profesjonalna metoda, ale skuteczna więc na razie wystarczy:
// Plik źródłowy np. Unit1.cpp |
Rysowanie dowolnych kształtów myszką.
Rysowanie w c++ builder realizuje się z reguły na
obiektach klasy TCanvas, o tym jak rysować za pomocą kodu konkretne figury
geometryczne typu prostokąt czy koło, nie będę w tej poradzie pisał, ponieważ
zakładam, że każdy potrafi to zrobić. Zastanawiałem się dzisiaj, jak można
rysować po dowolnej powierzchni wyposażonej w klasę TCanvas za pomocą myszki,
czyli naciskamy lewy klawisz myszy i przeciągamy ją w dowolnych kierunkach
rysując w ten sposób dowolny kształt, a po zwolnieniu lewego klawisza myszy,
przestaje ona rysować. Zadanie okazało się banalnie proste w realizacji, zanim
jednak do tego przejdę, pokaże jak ustawić kolor i wielkość "pędzla", którym
będziemy rysować, a robi się to poprzez klasę TPen, w którą wyposażona jest
klasa TCanvas.
Rysowanie będziemy realizować na komponencie PainBox1 (zakładka: System) bo
idealnie się do tego nadaje, umieszczamy ten obiekt na formularzu, umieszczamy
również przycisk Button1 i w jego zdarzeniu OnClick ustawiamy kolor i rozmiar
pędzla:
// Plik źródłowy np. Unit1.cpp |
To była ta prostsza części zadania, teraz tworzymy kod
odpowiedzialny za rysowanie. Wymaga to utworzenia dwóch zmiennych globalnych
jednej typu bool, drugiej typu TPoint. Można je zadeklarować w pliku nagłówkowym
w sekcji private lub public, ja jednak umieszczę je w pliku źródłowym poza
jakimkolwiek zdarzeniem. Dla obiektu PaintBox1 tworzymy trzy zdarzenia
OnMouseDown - w tym zdarzeniu następuje włączenie rysowania i ustawienie
początkowej pozycji pędzla, OnMouseUp - w tym zdarzeniu rysowanie zostaje
wyłączone, OnMouseMove - tutaj odbywa się rysowanie:
// Plik źródłowy np. Unit1.cpp |
Jeżeli chodzi o rysowanie
to już wszystko. Gdybyśmy jednak chcieli teraz zapisać zawartość obiektu
PainBox1, no to niestety on nie posiada funkcji zapisu do pliku, jednak można to
zrobić poprzez klasę TBitmap. Zanim jednak zapiszemy i zaczniemy rysować po
obiekcie PaintBox1, dobrze jest wypełnić tło jakimś kolorem, w przeciwnym razie
przy zapisie do pliku, tło zostanie ustawione na białe:
// Plik źródłowy np. Unit1.cpp |
A teraz kod zapisujący zawartość PaintBox1 do pliku
*.bmp:
// Plik źródłowy np. Unit1.cpp |
Ważna uwaga! Nie należy umieszczać na obiekcie
PaintBox1 żadnych obiektów, nie można go również przesłaniać żadnym oknem np.
innego programu, ponieważ to wszystko zostanie przekopiowane i zapisane w pliku.
Takie zachowanie się obiektu PaintBox może stanowić pewną niedogodność, dlatego
należałoby się raczej posłużyć obiektem Image, jednak i on ma swoje wady, otóż
migocze podczas rysowania, można to wyeliminować ustawiając właściwość
DoubleBuffered obiektu na którym został umieszczony komponent Image na true,
czyli jeżeli umieścimy Image na formularzu to ustawiamy dla niego DoubleBuffered
na true, a jeżeli umieścimy np. Image na komponencie Panel to ustawiamy
właściwość DoubleBuffered dla obiektu Panel. Niżej przedstawiam kompletny kod
dla rysowania po Image1:
// Plik źródłowy np. Unit1.cpp |
Ustawienie tła z pliku graficznego w formacie BMP, w
obiekcie typu TListBox1 jest zadaniem stosunkowo prostym, należy ustawić
właściwość Style obiektu np. ListBox1 lbOwnerDrawFixed, można również zwiększyć
wartość właściwości ItemHeight na 21, żeby tekst ładniej się wyświetlał.
Potrzebne będą również dwa obiekty typu TBitmap, które należy zadeklarować w
pliku nagłówkowym w sekcji private. Pierwszy obiekt typu TBitmap - ListBmp
będzie wczytywał grafikę z pliku *.bmp, drugi obiekt FillBmp będzie dostosował
swój rozmiar do rozmiaru obiektu ListBox1, a następnie wypełni się grafiką z
ListBmp, by potem przenieść tą grafikę do obiektu ListBox1 jako tło. Taka
kombinacja jest potrzebna z dwóch powodów, po pierwsze dzięki takiemu
rozwiązaniu przy przewijaniu listy za pomocą pasków przewijania, tło nie będzie
migać i nie będą powstawały artefakty w postaci postrzępionej grafiki, po drugie
jeżeli grafika wczytana do ListBmp będzie mniejsza niż obiekt ListBox1 to
zostanie powielona (powtórzona), czyli będzie rozmieszczana sąsiadująco.
// Plik nagłowkowy np. Unit1.h |
// Plik źródłowy np. Unit1.cpp |
Podsumowując funkcja Assign pobiera jako argument obiekt typu Graphics::TBitmap, więc można jej przekazywać właśnie obiekt tego typu, nie zawsze musi to być Image->Picture->Bitmap, obiekt Image można pomijać jeśli jest zbędny.