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.

 

Menu

  1. Konwersja formatu JPG do BMP.

  2. Konwersja Formatu BMP do JPG.

  3. Odbicie bitmapy w pionie.

  4. Odbicie bitmapy w poziomie.

  5. Odwrócenie kolorów w bitmapie.

  6. Pozbywanie się migotania w obiekcie 'Image' - sposób prosty.

  7. Pozbywanie się migotania w obiekcie 'Image' - sposób skomplikowany.

  8. Przesuwanie obiektu Image po formularzu.

  9. Przeciąganie zawartości z jednego obiektu Image do drugiego.

  10. Rysowanie na obiektach nie posiadających Canvas.

  11. Wypełnianie obiektów bitmapą.

  12. Zapisywanie kolorów do plików.

  13. przeźroczyste kolory w bitmapach.

  14. Odejmowanie bitmapy od bitmapy.

  15. Umieszczanie bitmapy w zasobach programu.

  16. Wyciąganie ikon z plików.

  17. Umieszczanie grafiki w formacie JPEG w zasobach programu.

  18. Efekt rozjaśnienia obrazka (BITMAPY).

  19. Zmiana kontrastu obrazka (Bitmapy).

  20. Zmiana rozmiaru obrazka.

  21. Rysowanie po ekranie.

  22. Jak pobrać (sprawdzić) ikonę powiązaną z plikiem bez sprawdzania tego w rejestrze?

  23. Konwersja wartości typu TColor na kolor HTML.

  24. Rotacja (obracanie) bitmapy z wykorzystaniem DIB.

  25. Tworzenie Funkcji dla obiektów typy TImage i TBitmap.

  26. Obsługa skanera.

  27. Wypełnianie obszaru kolorem (tzw. wiadro).

  28. Zrzuty ekranu.

  29. Rysowanie dowolnych kształtów myszką.

  30. Grafika jako tło w ListBox.

  31. Fraktal Mandelbrota.

  32. Sprawdzanie głębi bitowej plików graficznych.

  33. Konwersja bitmapy na szarą i monochromatyczną.

  34. Wyświetlanie grafiki z wykorzystaniem biblioteki gdiplus.dll GDI+.

  35. Prosta konwersja kolorów WEB, RGB, TColor.

 

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:

//--------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
 DoubleBuffered = true;
}
//--------------------------------


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.

...powrót do menu. 

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:

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
//--------------------------------
private:
void __fastcall NewPoz(TMessage &Msg); // 'NewPoz' jest nazwą umowną i może być dowolna...
Controls::TWndMethod helppanel;

public:
    __fastcall TForm1(TComponent* Owner); // ten wpis już się znajduje w sekcji public...
//--------------------------------


// wpis do pliku źródłowego np. Unit1.cpp
//--------------------------------
 __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
{
helppanel = Panel1->WindowProc;
Panel1->WindowProc = NewPoz;
}
//--------------------------------
void __fastcall TForm1::NewPoz(TMessage &Msg)
{
   if (Msg.Msg == WM_ERASEBKGND)
     {
       Msg.Result = 0;
       return;
     }
       helppanel(Msg);
}
//--------------------------------


 
 Tak więc tyle wystarczy, żeby obiekt 'Image' nie migał w trakcie przesuwania go po formularzu, podany przykład zadziała niezależnie od wersji Borland C++ Builder. A jak przesuwać obiekt 'Image'? To już zupełnie inna porada.
graf2.zip rozmiar: 35 kB...ściągnij pliki.

...powrót do menu. 

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
//--------------------------------
private:
int pozX, pozY;
bool go;
//--------------------------------

  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
//--------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
pozX = X;
pozY = Y;
go = true;
}
//--------------------------------
void __fastcall TForm1::Image1MouseUp(TObject *Sender, TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
go = false;
}
//--------------------------------
void __fastcall TForm1::Image1MouseMove(TObject *Sender, TShiftState Shift,
        int X, int Y)
{
if(go){
Image1->Left = (Image1->Left - pozX) + X;
Image1->Top = (Image1->Top - pozY) + Y;
        }
}
//--------------------------------

  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
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
go = false;
}
//--------------------------------

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.

...powrót do menu. 

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.
//--------------------------------
private:
void __fastcall Przechwyt(TObject *Source, TImage *image);

//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Przechwyt(TObject *Source, TImage *image)
{
image->Picture = dynamic_cast<TImage *>(Source)->Picture;
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Image1DragOver(TObject *Sender, TObject *Source,
        int X, int Y, TDragState State, bool &Accept)
{
Accept = Source->ClassNameIs("TImage");
}
//--------------------------------
void __fastcall TForm1::Image2DragOver(TObject *Sender, TObject *Source,
        int X, int Y, TDragState State, bool &Accept)
{
Accept = Source->ClassNameIs("TImage");
}
//--------------------------------
void __fastcall TForm1::Image1DragDrop(TObject *Sender, TObject *Source,
        int X, int Y)
{
Przechwyt(Source, Image1);
}
//--------------------------------
void __fastcall TForm1::Image2DragDrop(TObject *Sender, TObject *Source,
        int X, int Y)
{
Przechwyt(Source, Image2);
}
//--------------------------------

No i teraz można przesuwać obrazki z jednego 'Image' do drugiego mteodą przeciągnij - upuść.

...powrót do menu. 

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.
//--------------------------------
#ifndef Unit1H
#define Unit1H
//--------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
#include <ExtCtrls.hpp>
#include <inifiles.hpp>
   // importowany plik inifiles.hpp
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// tutaj nastąpuje zapamiętanie koloru i zapisanie go do pliku.
Integer L = ColorToRGB(clRed);
TIniFile *ini = new TIniFile(ExtractFileDir(Application->ExeName) + "\\PlikIni.ini");
ini->WriteInteger("COLOR", "kolor1", L);
ini->Free();
}
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
// tutaj następuje zmiana koloru formularza na kolor wczytany z pliku.
TIniFile *ini = new TIniFile(ExtractFileDir(Application->ExeName) + "\\PlikIni.ini");
Integer L = ini->ReadInteger("COLOR", "kolor1", 0);
Form1->Color = (TColor)L;
ini->Free();
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
// tutej nastąpuje zapamiętanie koloru i zapisanie go do pliku.
Integer L = ColorToRGB((TColor)RGB(30, 200, 100));
TIniFile *ini = new TIniFile(ExtractFileDir(Application->ExeName) + "\\PlikIni.ini");
ini->WriteInteger("COLOR", "kolor1", L);
ini->Free();
}
//--------------------------------

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).

...powrót do menu. 

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
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <jpeg.hpp>
//import pliku jpeg.hpp
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------

Potem w zdarzeniu przycisku 'Button1' - 'OnClick' umieszczamy całą procedurę konwersji:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   if(OpenDialog1->Execute()){
     TJPEGImage *jpg = new TJPEGImage();
     jpg->LoadFromFile(OpenDialog1->FileName);  //wczytanie pliku w formacie jpg.

     Graphics::TBitmap * bmp = new Graphics::TBitmap();

     bmp->Width = jpg->Width;
     bmp->Height = jpg->Height;

     bmp->Canvas->Draw(0, 0, jpg);  //przerysowanie grafiki w formacie jpg do obiektu bmp.

     Image1->Picture->Bitmap = bmp;

     bmp->Free();  // usunięcie obiektu bmp z pamięci.
     jpg->Free();  // usunięcie obiektu jpg z pamięci.
                                              }
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
   if(SaveDialog1->Execute())
   Image1->Picture->SaveToFile(SaveDialog1->FileName);
}
//--------------------------------

...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.

...powrót do menu. 

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;
Image1->Canvas->FillRect(Rect(0, 0, Image1->Width, Image1->Height);

...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.
//--------------------------------
private:
 Graphics::TBitmap *bmp;
//--------------------------------

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
//--------------------------------
void __fastcall
TForm1::FormShow(TObject *Sender)
{
  bmp = new Graphics::TBitmap;
  bmp->LoadFromFile("nazwa_pliku"); /* Zamiast frazy nazwa_pliku należy wprowadzić ścieżkę dostępu i nazwę pliku którym zamierzamy wypełnić formularz.*/

  Image1->Canvas->Brush->Bitmap = bmp;
  Image1->Canvas->FillRect(Rect(0,0,Width,Height));
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  delete bmp;
}
//--------------------------------

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.

...powrót do menu. 

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.
//--------------------------------
private:
  TControlCanvas *FCanvas;
//--------------------------------

Następnie przechodzimy do pliku źródłowego, i tworzymy definicję zadeklarowanego obiektu 'FCanvas ':

// Plik źródłowy np. Unit1.cpp
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
   FCanvas = new TControlCanvas;
   FCanvas->Control = Panel1;
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Graphics::TBitmap *bmp = new Graphics::TBitmap;
  bmp->LoadFromFile("nazwa_pliku");

  FCanvas->Brush->Bitmap = bmp;
  FCanvas->FillRect(Rect(0, 0, Panel1->Width, Panel1->Height));

  delete bmp;
}
//--------------------------------

...powrót do menu. 

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.
//--------------------------------
private:
void __fastcall Mirror(TImage *img);

//--------------------------------

Następnie przechodzimy do pliku żródłowego (np. Unit1.cpp) i tworzymy definicję zadeklarowanej funkcji 'Mirror':

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Mirror(TImage *img)
{
 Graphics::TBitmap *bmp = img->Picture->Bitmap;
 if(!bmp->Empty)
    {
     bmp->PixelFormat = pf32bit;
     int W = bmp->Width;
     for(int i = 0; i < bmp->Height; i++)
      {
        int *Line = (int *) bmp->ScanLine[i];
          for(int j = 0; j <= ((W & (-2)) - 1) / 2; j++)
        {
            int Temp = Line[j];
            Line[j] = Line[W - j - 1];
            Line[W - j - 1] = Temp;
        }
      }
     img->Invalidate();     }
} //--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Mirror(Image1);
}
//--------------------------------

...powrót do menu. 

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.
//--------------------------------
private:
void __fastcall Flip(TImage *img);

//--------------------------------

Następnie przechodzimy do pliku źródłowego (np. Unit1.cpp) i tworzymy definicję zadeklarowanej funkcji 'Flip':

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Flip(TImage *img)
{
  Graphics::TBitmap * bmp = img->Picture->Bitmap;
  if(!bmp->Empty)
    {
      bmp->PixelFormat = pf32bit;
      int H = bmp->Height;
      int x = bmp->Width * 4;
      void *Buffer = malloc(x);

        for(int i = 0; i <= ((H & (-2)) - 1) / 2; i++)
            {
              Move(bmp->ScanLine[i], Buffer, x);
              Move(bmp->ScanLine[H - i - 1], bmp->ScanLine[i], x);
              Move(Buffer, bmp->ScanLine[H - i - 1], x);
            }
            delete Buffer;
            img->Invalidate();
    }
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Flip(Image1);
}
//--------------------------------

...powrót do menu. 

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.
//--------------------------------
private:
void __fastcall Invert(TImage *img);

//--------------------------------

Następnie przechodzimy do pliku żródłowego (np. Unit1.cpp) i tworzymy definicję prototypu 'Invert':

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Invert(TImage *img)
{
  Graphics::TBitmap *bmp = img->Picture->Bitmap;
  if(!bmp->Empty)
    {
      bmp->PixelFormat = pf32bit;
      for(int i = 0; i < bmp->Height; i++)
          {
            RGBQUAD *Pixel = (RGBQUAD *) bmp->ScanLine[i];
            for(int j = 0; j < bmp->Width; j++, Pixel++)
              {
                Pixel->rgbRed = (BYTE)~Pixel->rgbRed;
                Pixel->rgbGreen = (BYTE)~Pixel->rgbGreen;
                Pixel->rgbBlue = (BYTE)~Pixel->rgbBlue;
              }
          }
    img->Invalidate();
    }
}
//--------------------------------

Teraz wystarczy już tylko wywołać funkcję np. w zdarzeniu 'OnClick' obiektu 'Button1':

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  Invert(Image1);
}
//--------------------------------

...powrót do menu. 

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
//--------------------------------
private:
        Graphics::TBitmap *nakladka;

//--------------------------------

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
//--------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 nakladka = new Graphics::TBitmap();
 nakladka->LoadFromFile("stempel.bmp");
 nakladka->Transparent = true;
 nakladka->TransparentColor = nakladka->Canvas->Pixels[1][1]; //<-- patrz objaśnienie.
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Graphics::TBitmap *bmp = new Graphics::TBitmap();

 bmp->LoadFromFile("test.bmp");
 bmp->Transparent = true;
 bmp->TransparentColor = bmp->Canvas->Pixels[1][1];
 Image1->Canvas->Draw(0, 0, bmp);

 delete bmp;
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 Image1->Canvas->Draw(X, Y, nakladka);
}
//--------------------------------

Lepiej będzie wyglądało jeżeli kursor myszki będzie wypadał w środku "stempla":

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Image1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 int a = nakladka->Width/2;
 int b = nakladka->Height/2;
 Image1->Canvas->Draw(X - a, Y - b, nakladka);
}
//--------------------------------

Przedstawioną technikę można z powodzeniem wykorzystać przy tworzeniu prostych gier.

...powrót do menu. 

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Graphics::TBitmap *BMP1 = new Graphics::TBitmap();
 Graphics::TBitmap *BMP2 = new Graphics::TBitmap();
 Graphics::TBitmap *BMP3 = new Graphics::TBitmap();

 BMP1->Assign(Image2->Picture->Bitmap);
 BMP2->Assign(Image3->Picture->Bitmap);

 BMP3->Width = BMP1->Width;
 BMP3->Height = BMP1->Height;

 for(int i = 0; i < BMP1->Width; i++)
 {
  for(int j = 0; j < BMP1->Height; j++)
  {
   BMP3->Canvas->Pixels[i][j] = (TColor)((BMP2->Canvas->Pixels[i][j] - BMP1->Canvas->Pixels[i][j]) + RGB(0,0,255));
  }
 }

 Image1->Picture->Bitmap->Assign(BMP3);

 delete BMP1, BMP2, BMP3;
}
//--------------------------------

...i to już wszystko na ten temat.

...powrót do menu. 

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Image1->Picture->Bitmap->LoadFromResourceName((int)HInstance, "BITMAP_1");
}
//--------------------------------

Można również wczytywać, tak utworzone zasoby do dynamicznie utworzonego obiektu typy Graphics::TBitamp:

// Plik źródłowy np. Unit1.cpp
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Graphics::TBitmap *bmp = new Graphics::TBitmap;
 bmp->LoadFromResourceName((int)HInstance, "BITMAP_1")
}
//--------------------------------

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
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Image1->Picture->Bitmap->LoadFromResourceName((int)HInstance, "PLIK");
}
//--------------------------------

Kilka uwag na zakończenie:

...powrót do menu. 

Wyciąganie ikon z plikó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
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <shellapi.h>

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 int X = 20, Y = 20;
 if(OpenDialog1->Execute())
 {
  String IFile = OpenDialog1->FileName;
  for(int i = 0; i < 200; i++)
  {
   try
   {
    TImage *Ikona = new TImage(this);
    Ikona->Parent = this;
    Ikona->Width = 32;
    Ikona->Height = 32;

    Ikona->Picture->Icon->Handle = ExtractIcon(Application->Handle, IFile.c_str(), i);
    Ikona->Picture->Icon->SaveToFile(ExtractFilePath(Application->ExeName) + "Ikony\\Ikona" + IntToStr(i) + ".ico");
    Ikona->Left = X;
    Ikona->Top = Y;
   }
   catch(...){;}

   Application->ProcessMessages();

   if(X < 510) X += 34;
   else
   {
    X = 20;
    Y += 34;
   }
  }
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
 {
  String IFile = OpenDialog1->FileName;
  int i;
  try
  {
   i = StrToInt(InputBox("Numer ikony","Podaj numer ikony", 0));
  }
  catch (...)
  {
   Application->MessageBox("Podaj wartość liczbową!", "Nieprawidłowe dane!", MB_OK | MB_ICONSTOP);
   return;
  }
  try
  {
   Image1->Picture->Icon->Handle = ExtractIcon(Application->Handle, IFile.c_str(), i);
  }
  catch (...)
  {
   Application->MessageBox(("Plik nie zawiera ikony z numerem: '" + IntToStr(i) + "'.").c_str(), "Błąd!", MB_OK | MB_ICONINFORMATION);
  }
  if(Image1->Picture->Icon->Handle == NULL)
  Application->MessageBox(("Plik nie zawiera ikony z numerem: '" + IntToStr(i) + "'.").c_str(), "Błąd!", MB_OK | MB_ICONINFORMATION);
 }
}
//---------------------------------------------------------------------------

...powrót do menu. 

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
#define MYRES_RH

#define ID_JPEG 1000
#endif

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"

ID_JPEG RCDATA "IMAGE1.JPG"

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
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <jpeg.hpp>
#include <memory>
#include "Myres.rh"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
private:
        void __fastcall
ViewJPEG(TImage *Image, unsigned short ID);
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::ViewJPEG(TImage *Image, unsigned short ID)
{
 HRSRC rsrc = FindResource(HInstance, MAKEINTRESOURCE(ID), RT_RCDATA);
 if(!rsrc) return;

 DWORD Size = SizeofResource(HInstance, rsrc);
 HGLOBAL MemoryHandle = LoadResource(HInstance, rsrc);

 if(MemoryHandle == NULL) return;

 BYTE *MemPtr = (BYTE *)LockResource(MemoryHandle);

 std::auto_ptr<TMemoryStream>stream(new TMemoryStream);
 stream->Write(MemPtr, Size);
 stream->Position = 0;

 std::auto_ptr<TJPEGImage> JImage(new TJPEGImage());
 JImage->LoadFromStream(stream.get());

 Image->Width = JImage->Width;       
 Image->Height = JImage->Height;     
 Image->Picture->Assign(JImage.get());

}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 ViewJPEG(Image1, ID_JPEG);
}
//---------------------------------------------------------------------------

Ściągnij źródło: ViewJpeg.zip

...powrót do menu. 

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
//---------------------------------------------------------------------------
void __fastcall Brightness(Graphics::TBitmap *BMP, int x)
{
 for(int i = 0; i < BMP->Height; i++)
 {
  RGBQUAD *row = (RGBQUAD *) BMP->ScanLine[i];

  for(int j = 0; j < BMP->Width; j++)
  {
   int b = row[j].rgbBlue;
   int g = row[j].rgbGreen;
   int r = row[j].rgbRed;

   r += x;
   g += x;
   b += x;

   b = (b > 255) ?255: (b < 0) ?0:b;
   g = (g > 255) ?255: (g < 0) ?0:g;
   r = (r > 255) ?255: (r < 0) ?0:r;

   row[j].rgbBlue  = (BYTE) b;
   row[j].rgbGreen = (BYTE) g;
   row[j].rgbRed   = (BYTE) r;
  }
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall Brightness(Graphics::TBitmap *BMP, int x)
{
 for(int i = 0; i < BMP->Height; i++)
 {
  RGBQUAD *row = (RGBQUAD *) BMP->ScanLine[i];

  for(int j = 0; j < BMP->Width; j++)
  {
   int b = row[j].rgbBlue;
   int g = row[j].rgbGreen;
   int r = row[j].rgbRed;

   r += x;
   g += x;
   b += x;

   b = (b > 255) ?255: (b < 0) ?0:b;
   g = (g > 255) ?255: (g < 0) ?0:g;
   r = (r > 255) ?255: (r < 0) ?0:r;

   row[j].rgbBlue = (BYTE) b;
   row[j].rgbGreen = (BYTE) g;
   row[j].rgbRed = (BYTE) r;
  }
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Brightness(Image1->Picture->Bitmap, 100);
 Image1->Repaint();
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
private:
       
Graphics::TBitmap *BMP;
//---------------------------------------------------------------------------

Następnie wracamy do pliku źródłowego i w konstruktorze klasy (np. TForm1) definiujemy obiekt BMP:

// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 BMP = new Graphics::TBitmap();
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
 {
  Image1->Picture->LoadFromFile(OpenDialog1->FileName);
  try
  {
   BMP->LoadFromFile(OpenDialog1->FileName);
   BMP->PixelFormat = pf32bit;
  }
  catch(...)
  {
   TJPEGImage *jpg = new TJPEGImage();
   jpg->LoadFromFile(OpenDialog1->FileName);

   BMP->Width = jpg->Width;
   BMP->Height = jpg->Height;

   BMP->Canvas->Draw(0, 0, jpg);
   BMP->PixelFormat = pf32bit;
   jpg->Free();
  }
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::TrackBar1Change(TObject *Sender)
{
 TMemoryStream *Ms = new TMemoryStream;
 Ms->Position = 0;
 BMP->SaveToStream(Ms);
 Ms->Position = 0;
 Image1->Picture->Bitmap->LoadFromStream(Ms);
 delete Ms;

 Brightness(Image1->Picture->Bitmap, TrackBar1->Position);
}
//---------------------------------------------------------------------------

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>

...powrót do menu. 

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
//---------------------------------------------------------------------------
void __fastcall Kontrast(Graphics::TBitmap *Bmp, double Value)
{
 double x = Value * 1.2725;
 double v1 = 255/(255 - 2 * x);
 double v2 = (-255 + 2 * x)/255;

 for(int i = 0; i < Bmp->Height; i++)
 {
  RGBQUAD *row = (RGBQUAD *) Bmp->ScanLine[i];

 for(int j = 0; j < Bmp->Width; j++)
 {
  int r = row[j].rgbRed;
  int g = row[j].rgbGreen;
  int b = row[j].rgbBlue;

  if(x > 0)
  {
   r = (r < x) ? 0 : (r > 255 - x) ? 255 : v1 * (r - x);
   g = (g < x) ? 0 : (g > 255 - x) ? 255 : v1 * (g - x);
   b = (b < x) ? 0 : (b > 255 - x) ? 255 : v1 * (b - x);
  }
  else
      if(x < 0)
      {
       r = (-x + v2) * r;
       g = (-x + v2) * g;
       b = (-x + v2) * b;
      }
   r = (r > 255) ? 255 : (r < 0) ? 0: r;
   g = (g > 255) ? 255 : (g < 0) ? 0: g;
   b = (b > 255) ? 255 : (b < 0) ? 0: b;

   row[j].rgbRed   = (BYTE) r;
   row[j].rgbGreen = (BYTE) g;
   row[j].rgbBlue  = (BYTE) b;
  }
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H

//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <Menus.hpp>
#include <ComCtrls.hpp>
#include <Dialogs.hpp>
#include <ExtCtrls.hpp>
#include <JPEG.hpp>

//---------------------------------------------------------------------------
class TForm1 : public TForm
{
 __published: // IDE-managed Components
        TTrackBar *TrackBar1;
        TImage *Image1;
        TOpenDialog *OpenDialog1;
        TButton *Button1;
        void __fastcall TrackBar1Change(TObject *Sender);
        void __fastcall Button1Click(TObject *Sender);
private: // User declarations
        Graphics::TBitmap *BMP;

public: // User declarations
        __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 BMP = new Graphics::TBitmap;
}
//---------------------------------------------------------------------------

void __fastcall Kontrast(Graphics::TBitmap *Bmp, double Value)
{
 double x = Value * 1.2725;
 double v1 = 255/(255 - 2 * x);
 double v2 = (-255 + 2 * x)/255;

 for(int i = 0; i < Bmp->Height; i++)
 {
  RGBQUAD *row = (RGBQUAD *) Bmp->ScanLine[i];

  for(int j = 0; j < Bmp->Width; j++)
  {
   int r = row[j].rgbRed;
   int g = row[j].rgbGreen;
   int b = row[j].rgbBlue;

   if(x > 0)
   {
    r = (r < x) ? 0 : (r > 255 - x) ? 255 : v1 * (r - x);
    g = (g < x) ? 0 : (g > 255 - x) ? 255 : v1 * (g - x);
    b = (b < x) ? 0 : (b > 255 - x) ? 255 : v1 * (b - x);
   }
   else
       if(x < 0)
       {
        r = (-x + v2) * r;
        g = (-x + v2) * g;
        b = (-x + v2) * b;
       }
   r = (r > 255) ? 255 : (r < 0) ? 0: r;
   g = (g > 255) ? 255 : (g < 0) ? 0: g;
   b = (b > 255) ? 255 : (b < 0) ? 0: b;

   row[j].rgbRed = (BYTE) r;
   row[j].rgbGreen = (BYTE) g;
   row[j].rgbBlue = (BYTE) b;
  }
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::TrackBar1Change(TObject *Sender)
{
 TMemoryStream *pms = new TMemoryStream;
 pms->Position = 0;
 BMP->SaveToStream(pms);
 pms->Position = 0;
 Image1->Picture->Bitmap->LoadFromStream( pms );
 delete pms;

 Kontrast(Image1->Picture->Bitmap, TrackBar1->Position);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenDialog1->Execute())
 {
  Image1->Picture->LoadFromFile(OpenDialog1->FileName);
  try
  {
   BMP->LoadFromFile(OpenDialog1->FileName);
   BMP->PixelFormat = pf32bit;
  }
  catch(...)
  {
   TJPEGImage *jpg = new TJPEGImage();
   jpg->LoadFromFile(OpenDialog1->FileName);

   BMP->Width = jpg->Width;
   BMP->Height = jpg->Height;

   BMP->Canvas->Draw(0, 0, jpg);
   BMP->PixelFormat = pf32bit;
   jpg->Free();
  }
 }
}
//---------------------------------------------------------------------------

...powrót do menu. 

Zmiana rozmiaru obrazka

    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
//---------------------------------------------------------------------------
#include <JPEG.hpp>  //ten wpis należy umieścić w sekcji include
//---------------------------------------------------------------------------

private:
       
TImage *img;
        String tp;
//---------------------------------------------------------------------------

W konstruktorze klasy TForm1 definiujemy obiekt img:

// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 img = new TImage(NULL);
 img->Stretch = false;
 img->AutoSize = true;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
Graphics::TBitmap *ConvertImage(TImage *Cimg)
{
 Graphics::TBitmap * bmp = new Graphics::TBitmap();
 bmp->Width = Cimg->Width;
 bmp->Height = Cimg->Height;

 bmp->Canvas->Draw( 0, 0, Cimg->Picture->Graphic);

 return bmp;
}
//---------------------------------------------------------------------------
void SaveReConvertImage(Graphics::TBitmap *Rbmp, String FileName)
{
 TJPEGImage *jpg = new TJPEGImage();
 jpg->Assign(Rbmp);
 jpg->SaveToFile(FileName);
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::CSpin1Change(TObject *Sender)
{
 // w celu zachowania proporcji obrazka - szerokość
 try
 {
  if(ActiveControl == CSpin1)
  {
   float y = (float)img->Width / ((float)img->Height / (float)CSpin1->Value);
   CSpin2->Value = y;
  }
 }catch(...){;}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::CSpin2Change(TObject *Sender)
{
 // w celu zachowania proporcji obrazka - wysokość
 try
 {
  if(ActiveControl == CSpin2)
  {
   float x = (float)img->Height / ((float)img->Width / (float)CSpin2->Value);
   CSpin1->Value = x;
  }
 }catch(...){;}
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(OpenPictureDialog1->Execute())
 {
  img->Picture->LoadFromFile(OpenPictureDialog1->FileName);
  Image1->Picture = img->Picture;
  tp = ExtractFileExt(OpenPictureDialog1->FileName);
  CSpin1->Value = img->Height;
  CSpin2->Value = img->Width;
  SavePictureDialog1->FilterIndex = OpenPictureDialog1->FilterIndex;
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 if(SavePictureDialog1->Execute())
 {
  if(tp.LowerCase() == ".jpg" || tp.LowerCase() == ".jpeg")
  {
   img->Picture->Bitmap = ConvertImage(img);
  }
  Graphics::TBitmap *tmp = new Graphics::TBitmap;
  tmp->Width = CSpin2->Value;
  tmp->Height = CSpin1->Value;

  tmp->Canvas->CopyRect(Rect(0, 0, tmp->Width, tmp->Height), img->Canvas, Rect(0, 0, img->Width, img->Height));

  if(tp.LowerCase() == ".jpg" || tp.LowerCase() == ".jpeg")
   SaveReConvertImage(tmp, SavePictureDialog1->FileName);
  else
   tmp->SaveToFile(SavePictureDialog1->FileName);
 
 delete tmp;
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 if(SavePictureDialog1->Execute())
 {
  if(tp.LowerCase() == ".jpg" || tp.LowerCase() == ".jpeg")
  {
   img->Picture->Bitmap = ConvertImage(img);
  }
  Graphics::TBitmap *tmp = new Graphics::TBitmap;
  tmp->Width = CSpin2->Value;
  tmp->Height = CSpin1->Value;

  tmp->Canvas->StretchDraw(Rect(0, 0, tmp->Width, tmp->Height), img->Picture->Bitmap);

  if(tp.LowerCase() == ".jpg" || tp.LowerCase() == ".jpeg")
   SaveReConvertImage(tmp, SavePictureDialog1->FileName);
  else
   tmp->SaveToFile(SavePictureDialog1->FileName);
 
 delete tmp;
 }
}
//---------------------------------------------------------------------------

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->StretchDraw(Rect(0, 0, tmp->Width, tmp->Height), img->Picture->Bitmap);

 tmp->Canvas->CopyMode = cmBlackness;
 tmp->Canvas->CopyRect(Rect(0, 0, tmp->Width, tmp->Height), img->Canvas, Rect(0, 0, img->Width, img->Height));

 tmp->Canvas->CopyMode = cmDstInvert;
 tmp->Canvas->BrushCopy(Rect(0, 0, tmp->Width, tmp->Height), img->Picture->Bitmap, Rect(0, 0, img->Width, img->Height), clBlack);

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.

...powrót do menu. 

Rysowanie po ekranie.

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TCanvas *PulpitCanvas = new TCanvas();
 PulpitCanvas->Handle = GetDC(0);
 PulpitCanvas->Draw(100, 100, Image1->Picture->Bitmap); // można też rysować dowolne figury
 ReleaseDC(0, PulpitCanvas->Handle);
 delete PulpitCanvas;
}
//---------------------------------------------------------------------------

Rysowanie po ekranie nie ma jednak wiele wspólnego z rysowaniem po pulpicie ponieważ obiekt będzie rysowany na powierzchni wszystkich otwartych okien.

...powrót do menu. 

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
  HICON ico;
  WORD idx;
  ico = ExtractAssociatedIcon(HInstance,"C:\\WINDOWS\\system32\\ntimage.gif",&idx);
  Image1->Picture->Icon->Handle = ico;
}

//---------------------------------------------------------------------------

...powrót do menu. 

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TColorRef rgbVal;
 
 if(ColorDialog1->Execute())
 {
   rgbVal = ColorToRGB(ColorDialog1->Color);
   AnsiString tmp = Format("%2x%2x%2x", OPENARRAY(TVarRec,
                           (GetRValue(rgbVal), GetGValue(rgbVal), GetBValue(rgbVal))));
   Label1->Caption = tmp;
  }
}
//---------------------------------------------------------------------------

...powrót do menu. 

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
#include <math.h>
#include "C:\Program Files\Borland\CBuilder6\Examples\StdLib\stlexam.h"

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 //Bitmapa źródłowa
 Graphics::TBitmap *SrcBitmap = new Graphics::TBitmap;
 
 //Bitmapa docelowa
 Graphics::TBitmap *DestBitmap = new Graphics::TBitmap;
 SrcBitmap->LoadFromFile("Nazwa pliku.bmp");
 DestBitmap->PixelFormat = SrcBitmap->PixelFormat;
 
 int
angle = 45; //przykładowy kąt 45°
 //konwersja stopni na radiany
 float radians = (2 * 3.1416 * angle)/360;

 float cosine = (float)cos(radians);
 float sine   = (float)sin(radians);

 float Point1x = (-SrcBitmap->Height * sine);
 float Point1y = (SrcBitmap->Height  * cosine);
 float Point2x = (SrcBitmap->Width   * cosine - SrcBitmap->Height * sine);
 float Point2y = (SrcBitmap->Height  * cosine + SrcBitmap->Width  * sine);
 float Point3x = (SrcBitmap->Width   * cosine);
 float Point3y = (SrcBitmap->Width   * sine);

 float minx = min(0, min(Point1x, min(Point2x, Point3x)));
 float miny = min(0, min(Point1y, min(Point2y, Point3y)));
 float maxx = max(0, max(Point1x, max(Point2x, Point3x)));
 float maxy = max(0, max(Point1y, max(Point2y, Point3y)));

 int DestBitmapWidth  = (int)ceil(maxx - minx);
 int DestBitmapHeight = (int)ceil(maxy - miny);

 DestBitmap->Height = DestBitmapHeight;
 DestBitmap->Width  = DestBitmapWidth;

 //tworzenie DIB dla SrcBitmap

 LPBITMAPINFO srcBmpi = (LPBITMAPINFO)new char[sizeof(BITMAPINFO)];
 ZeroMemory(srcBmpi, sizeof(BITMAPINFO));
 
 srcBmpi->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
 srcBmpi->bmiHeader.biWidth  = SrcBitmap->Width;
 srcBmpi->bmiHeader.biHeight = SrcBitmap->Height;
 srcBmpi->bmiHeader.biPlanes = 1;
 srcBmpi->bmiHeader.biCompression = BI_RGB;

 switch(SrcBitmap->PixelFormat)
 {
  case pf24bit: srcBmpi->bmiHeader.biBitCount=24; break;
  case pf32bit: srcBmpi->bmiHeader.biBitCount=32; break;
 }

 int SrcNumberOfBytesPerRow =
((((SrcBitmap->Width * srcBmpi->bmiHeader.biBitCount) + 31)&~31)/8);

 HDC ScreenDC = GetDC(NULL);

 //gpobranie rozmiaru obrazu
 GetDIBits(ScreenDC, SrcBitmap->Handle, 0, srcBmpi->bmiHeader.biHeight,
 NULL, srcBmpi, DIB_RGB_COLORS);

 if(srcBmpi->bmiHeader.biSizeImage == 0)
 {
  srcBmpi->bmiHeader.biSizeImage =
  SrcNumberOfBytesPerRow * SrcBitmap->Height;
 }

 char* srcbits = new char[srcBmpi->bmiHeader.biSizeImage];

 GetDIBits(ScreenDC, SrcBitmap->Handle, 0, srcBmpi->bmiHeader.biHeight,
 srcbits, srcBmpi, DIB_RGB_COLORS);

 LPBITMAPINFO destBmpi = (LPBITMAPINFO)new char[sizeof(BITMAPINFO)];
 ZeroMemory(destBmpi, sizeof(BITMAPINFO));

 destBmpi->bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
 destBmpi->bmiHeader.biWidth  = DestBitmap->Width;
 destBmpi->bmiHeader.biHeight = DestBitmap->Height;
 destBmpi->bmiHeader.biPlanes = 1;
 destBmpi->bmiHeader.biCompression = BI_RGB;
 destBmpi->bmiHeader.biBitCount = srcBmpi->bmiHeader.biBitCount;

 int DestNumberOfBytesPerRow =
 ((((DestBitmap->Width * destBmpi->bmiHeader.biBitCount) + 31)&~31)/8);

 GetDIBits(ScreenDC, DestBitmap->Handle, 0, destBmpi->bmiHeader.biHeight,
 NULL, destBmpi, DIB_RGB_COLORS);
 
 if(destBmpi->bmiHeader.biSizeImage == 0)
 {
  destBmpi->bmiHeader.biSizeImage =
  DestNumberOfBytesPerRow * DestBitmap->Height;
 }

 char* destbits = new char[destBmpi->bmiHeader.biSizeImage];

 for(int y = 0; y < DestBitmapHeight; y++)
 {
  for(int x = 0; x < DestBitmapWidth; x++)
  {
   int SrcBitmapx  = (int)((x + minx) * cosine + (y + miny) * sine);
   int SrcBitmapy  = (int)((y + miny) * cosine - (x + minx) * sine);
   if(SrcBitmapx>  = 0 && SrcBitmapx < SrcBitmap->Width && SrcBitmapy > =0 &&
      SrcBitmapy<SrcBitmap->Height)
   {
    for(int i = 0; i < srcBmpi->bmiHeader.biBitCount / 8; i++)
    {
     *(destbits+(DestNumberOfBytesPerRow*y) +
     (x*srcBmpi->bmiHeader.biBitCount/8+i)) =
     *(srcbits+(SrcNumberOfBytesPerRow*SrcBitmapy) +
     (SrcBitmapx*srcBmpi->bmiHeader.biBitCount / 8 + i));
    }
   }
  }
 }

 SetDIBits(ScreenDC,DestBitmap->Handle, 0,
 destBmpi->bmiHeader.biHeight, destbits, destBmpi, DIB_RGB_COLORS);
 Image1->Picture->Bitmap = DestBitmap;

 delete DestBitmap;
 delete SrcBitmap;
 delete []srcbits;
 delete []destbits;
}
//---------------------------------------------------------------------------

...powrót do menu. 

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
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 DoubleBuffered = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Zoom1Click(TObject *Sender)
{
 Image1->AutoSize = false;
 VertScrollBar->Visible = false;
 HorzScrollBar->Visible = false;
 const int x     = (Image1->Width * 10) / 100; // 10%
 const int y     = (Image1->Height * 10) / 100; // 10%
 Image1->Width  += x;
 Image1->Height += y;

 int MaxX = Screen->WorkAreaWidth  - Left;
 int MaxY = Screen->WorkAreaHeight - Top;

 LockWindowUpdate(Handle);
 ClientWidth = Image1->Width;
 ClientHeight = Image1->Height;

 if(Width >= MaxX)
  Form2->Width = MaxX;

 if(Height >= MaxY)
  Height = MaxY;

 VertScrollBar->Visible = true;
 HorzScrollBar->Visible = true;
 LockWindowUpdate(0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Zoom2Click(TObject *Sender)
{
 Image1->AutoSize = false;
 const int x     = (Image1->Width * 10) / 100; // 10%
 const int y     = (Image1->Height * 10) / 100; // 10%
 Image1->Width  -= x;
 Image1->Height -= y;

 int MaxX = Screen->WorkAreaWidth  - Left;
 int MaxY = Screen->WorkAreaHeight - Top;

 LockWindowUpdate(Handle);
 ClientHeight = Image1->Height;
 ClientWidth  = Image1->Width;

 if(Width > MaxX)
  Width = MaxX;
 if(Height > MaxY)
  Height = MaxY;
 LockWindowUpdate(0);
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------

bool MoveImg = false;
int PosY = 0, PosX;
//---------------------------------------------------------------------------
void __fastcall TForm2::Image1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 MoveImg = true;
 PosY = Y;
 PosX = X;
}
//---------------------------------------------------------------------------
void __fastcall TForm2::Image1MouseMove(TObject *Sender, TShiftState Shift,
        int X, int Y)
{
 if(MoveImg && (ClientWidth <= Image1->Width || ClientHeight <= Image1->ClientHeight))
 {
  if(PosY > Y)
   VertScrollBar->Position += PosY - Y;
  else
   VertScrollBar->Position -= Y - PosY;

  if(PosX > X)
   HorzScrollBar->Position += PosX - X;
  else
   HorzScrollBar->Position -= X - PosX;
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm2::Image1MouseUp(TObject *Sender, TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
 MoveImg = false;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
private:
        Graphics::TBitmap *bmp;
//---------------------------------------------------------------------------


// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 bmp = new Graphics::TBitmap;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
static int x = 0;
static int y = 0;
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 int test;
 if(bmp->Width >= bmp->Height)
  test = bmp->Width/2 - (Image1->Width * 4) / 100;
 else
  test = bmp->Height/2 - (Image1->Height * 4) / 100;
 if(x < test)
 {
  x += (Image1->Width * 4) / 100;
  y += (Image1->Height * 4) / 100;

  Graphics::TBitmap *tmp = new Graphics::TBitmap;
  tmp->Width = bmp->Width;
  tmp->Height = bmp->Height;
  tmp->Canvas->CopyRect(Rect(0, 0, tmp->Width, tmp->Height), bmp->Canvas, Rect(x, y, bmp->Width - x, bmp->Height - y));
  // Image1->Canvas->StretchDraw(Rect(0, 0, Image1->Width, Image1->Height), tmp);
  Image1->Canvas->Draw(0, 0, tmp);
  delete tmp;
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 if(x > 0)
 {
  x -= (Image1->Width * 4) / 100;
  y -= (Image1->Height * 4) / 100;

  Graphics::TBitmap *tmp = new Graphics::TBitmap;
  tmp->Width = bmp->Width;
  tmp->Height = bmp->Height;
  tmp->Canvas->CopyRect(Rect(0, 0, tmp->Width, tmp->Height), bmp->Canvas, Rect(x, y, bmp->Width - x, bmp->Height - y));
  //Image1->Canvas->StretchDraw(Rect(0, 0, Image1->Width, Image1->Height), tmp);
  Image1->Canvas->Draw(0, 0, tmp);
  delete tmp;
 }
}
//---------------------------------------------------------------------------
Graphics::TBitmap *ConvertImage(TImage *Cimg)
{
 Graphics::TBitmap *bmp = new Graphics::TBitmap();
 bmp->Width = Cimg->Picture->Width;
 bmp->Height = Cimg->Picture->Height;

 bmp->Canvas->Draw(0, 0, Cimg->Picture->Graphic);

 return bmp;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
 if(OpenPictureDialog1->Execute())
 {
  Image1->Picture->LoadFromFile(OpenPictureDialog1->FileName);
  String img = Image1->Picture->Graphic->GetNamePath();

  if(img == "TJPEGImage")
  {
   bmp = ConvertImage(Image1);
   Image1->Picture->Bitmap->Assign(bmp);
  }
  else
   bmp->Assign(Image1->Picture->Bitmap);

  Image1->Picture->Bitmap = NULL;
  //Image1->Canvas->StretchDraw(Rect(0, 0, Image1->Width, Image1->Height), bmp);
  Image1->Canvas->Draw(0, 0, bmp);
}
}
//---------------------------------------------------------------------------

Te dwie metody nie wyczerpują oczywiście wszystkich sposobów na wykonanie ZOMM'u, to tylko mój pomysł na to i niekoniecznie najlepszy.

...powrót do menu. 

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
//---------------------------------------------------------------------------
#include <memory>
#include "eztwain.h"

int m_Scale;
int m_Width;
int m_Height;
void __fastcall TForm1::Button4Click(TObject *Sender)
{
 if(HANDLE BMPHandle = TWAIN_AcquireNative(0, 0))
 {
  try
  {
   Graphics::TBitmap* Bitmapa = Image1->Picture->Bitmap;
   PBITMAPINFOHEADER Info = (PBITMAPINFOHEADER)GlobalLock(BMPHandle);
   m_Width = 1000 * Info->biWidth/Info->biXPelsPerMeter;
   m_Height = 1000 * Info->biHeight/Info->biYPelsPerMeter;
   Bitmapa->Palette = TWAIN_CreateDibPalette(BMPHandle);
   Bitmapa->Width = Info->biWidth;
   Bitmapa->Height = Info->biHeight;
   TWAIN_DrawDibToDC(Bitmapa->Canvas->Handle, 0, 0, Bitmapa->Width, Bitmapa->Height, BMPHandle, 0, 0);
  }
  __finally
  {
   TWAIN_FreeNative(BMPHandle);
  }
 }
}
//---------------------------------------------------------------------------

Wynik skanowania (zeskanowany obrazek) zostanie wyświetlony na obiekcie Image1 więc trzeba pamiętać o umieszczeniu tego obiektu na formularzu.

...powrót do menu. 

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
//---------------------------------------------------------------------------
#include <JPEG.hpp>
#include <memory>

void __fastcall TForm1::Button4Click(TObject *Sender)
{
 if(SavePictureDialog1->Execute())
 {
  std::auto_ptr<TJPEGImage> Jpeg(new TJPEGImage());
  Jpeg->Assign(Image1->Picture->Bitmap);
  Jpeg->SaveToFile(SavePictureDialog1->FileName);
 }
}
//---------------------------------------------------------------------------

Można też tak:

// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
#include <JPEG.hpp>
void __fastcall TForm1::Button4Click(TObject *Sender)
{
 if(SaveDialog1->Execute())
 {
  TJPEGImage *jpg = new TJPEGImage();
  jpg->Assign(Image1->Picture->Bitmap);
  jpg->CompressionQuality = 30; //określenie stopnia kompresji od 1 do 100.
  jpg->Compress(); //kompresja
  jpg->SaveToFile(SaveDialog1->FileName);
  delete jpg;
 }
}
//---------------------------------------------------------------------------

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.

...powrót do menu. 

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);

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseUp(TObject *Sender, TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
 Image1->Canvas->Brush->Color = clRed;
 Image1->Canvas->FloodFill(X, Y, clBlack, fsBorder);
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseUp(TObject *Sender, TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
 Image1->Canvas->Brush->Color = clRed;
 Image1->Canvas->FloodFill(X, Y, Image1->Canvas->Pixels[X][Y], fsSurface);
}
//---------------------------------------------------------------------------

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.

...powrót do menu. 

Zrzuty ekranu.

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TCanvas *pCanvas = new TCanvas();
 Graphics::TBitmap *Bmp = new Graphics::TBitmap;

 pCanvas->Handle = GetDC(0);
 Bmp->Width  = Screen->Width;
 Bmp->Height = Screen->Height;
 Bmp->Canvas->CopyRect(Rect(0, 0, Bmp->Width, Bmp->Height), pCanvas, Rect(0, 0, Bmp->Width, Bmp->Height));

 Bmp->SaveToFile("c:\\ekran.bmp");
 ReleaseDC(0, pCanvas->Handle);

 delete pCanvas, Bmp;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TCanvas *pCanvas = new TCanvas();
 Graphics::TBitmap *Bmp = new Graphics::TBitmap;

 HWND okno = FindWindow(NULL, "Form1");
 pCanvas->Handle = GetWindowDC(okno);
 TRect oR;
 GetWindowRect(okno, &oR);
 Bmp->Width = oR.Width();
 Bmp->Height = oR.Height();
 Bmp->Canvas->CopyRect(Rect(0, 0, Bmp->Width, Bmp->Height), pCanvas, Rect(0, 0, Bmp->Width, Bmp->Height));

 Bmp->SaveToFile("c:\\ekran.bmp");
 ReleaseDC(0, pCanvas->Handle);

 delete pCanvas, Bmp;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 TCanvas *pCanvas = new TCanvas();
 Graphics::TBitmap *Bmp = new Graphics::TBitmap;

 HWND okno = GetForegroundWindow();
 pCanvas->Handle = GetWindowDC(okno);
 TRect oR;
 GetWindowRect(okno, &oR);
 Bmp->Width = oR.Width();
 Bmp->Height = oR.Height();
 Bmp->Canvas->CopyRect(Rect(0, 0, Bmp->Width, Bmp->Height), pCanvas, Rect(0, 0, Bmp->Width, Bmp->Height));

 Bmp->SaveToFile("c:\\ekran.bmp");
 ReleaseDC(0, pCanvas->Handle);

 pCanvas->Free(); Bmp->Free();
}
//---------------------------------------------------------------------------


...powrót do menu. 

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 PaintBox1->Canvas->Pen->Color = clRed;
 PaintBox1->Canvas->Pen->Width = 5;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
bool draw = false;
TPoint OldPos;

void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 draw = true;
 OldPos = Point(X, Y);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox1MouseUp(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 draw = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender,
        TShiftState Shift, int X, int Y)
{
 if(draw)
 {
  PaintBox1->Canvas->MoveTo(OldPos.x, OldPos.y);
  OldPos = Point(X, Y);
  PaintBox1->Canvas->LineTo(X, Y);
 }
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 PaintBox1->Canvas->Pen->Color = clRed;
 PaintBox1->Canvas->Pen->Width = 5;
 PaintBox1->Canvas->Brush->Color = clYellow;
 PaintBox1->Canvas->FillRect(PaintBox1->BoundsRect);
}
//---------------------------------------------------------------------------

A teraz kod zapisujący zawartość PaintBox1 do pliku *.bmp:

// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 using Graphics::TBitmap;
 TBitmap *Bmp = new TBitmap;
 Bmp->Width = PaintBox1->Width;
 Bmp->Height = PaintBox1->Height;
 TRect myRect = Rect(0, 0, Bmp->Width, Bmp->Height);
 Bmp->Canvas->CopyRect(myRect, PaintBox1->Canvas, myRect);
 Bmp->SaveToFile("c:\\paintbox.bmp");
 delete Bmp;
}
//---------------------------------------------------------------------------

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
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 DoubleBuffered = true; // dla formularza Form1, dla obiektu Panel byłoby Panel1->DoubleBuffered = true.
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
 Image1->Canvas->Pen->Color = clRed;
 Image1->Canvas->Pen->Width = 5;
 Image1->Canvas->Brush->Color = clYellow;
 Image1->Canvas->FillRect(Image1->BoundsRect);
}
//---------------------------------------------------------------------------
bool draw = false;
TPoint OldPos;

void __fastcall TForm1::Image1MouseDown(TObject *Sender,
        TMouseButton Button, TShiftState Shift, int X, int Y)
{
 draw = true;
 OldPos = Point(X, Y);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseUp(TObject *Sender, TMouseButton Button,
        TShiftState Shift, int X, int Y)
{
 draw = false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Image1MouseMove(TObject *Sender, TShiftState Shift,
        int X, int Y)
{
 if(draw)
 {
  Image1->Canvas->MoveTo(OldPos.x, OldPos.y);
  OldPos = Point(X, Y);
  Image1->Canvas->LineTo(X, Y);
 }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
 Image1->Picture->SaveToFile("c:\\image.bmp");
}
//---------------------------------------------------------------------------


...powrót do menu. 

Grafika jako tło w ListBox.

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
//---------------------------------------------------------------------------
private:
        Graphics::TBitmap *ListBmp;
        Graphics::TBitmap *FillBmp;


// Plik źródłowy np. Unit1.cpp
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
 ListBmp = new Graphics::TBitmap;
 ListBmp->LoadFromFile(ExtractFilePath(ParamStr(0)) + "fantasy.bmp");
 FillBmp = new Graphics::TBitmap;
}
//---------------------------------------------------------------------------
void FillBitmap(Graphics::TBitmap *FillBmp, Graphics::TBitmap *ListBmp, TListBox *ListBox)
{
 FillBmp->Width  = ListBox->Width;
 FillBmp->Height = ListBox->Items->Count * ListBox->ItemHeight;
 FillBmp->Canvas->Brush->Bitmap = ListBmp;
 FillBmp->Canvas->FillRect(Rect(0, 0, FillBmp->Width, FillBmp->Height));
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListBox1DrawItem(TWinControl *Control, int Index,
        TRect &Rect, TOwnerDrawState State)
{
 String tekst = ListBox1->Items->Strings[Index];
 unsigned short x = (ListBox1->ItemHeight - ListBox1->Canvas->TextHeight(tekst))/2 - 1;

 if(State.Contains(odSelected))
 {
  ListBox1->Canvas->Brush->Color = clYellow;
  ListBox1->Canvas->Font->Color = clHighlightText;
  ListBox1->Canvas->Brush->Style = bsSolid;
  Frame3D(ListBox1->Canvas, Rect, (TColor)0x00C6E7FF, (TColor)0x000086FF, x/2);
 }
 else
  ListBox2->Canvas->Brush->Style = bsClear;

 FillBitmap(FillBmp, ListBmp, ListBox1);

 TRect ARect;
 ARect.left   = Rect.left;
 ARect.top    = Index * ListBox2->ItemHeight;
 ARect.right  = Rect.right;
 ARect.bottom = (Index * ListBox2->ItemHeight) + ListBox2->ItemHeight;

 ListBox1->Canvas->CopyRect(Rect, FillBmp->Canvas, ARect);
 ListBox1->Canvas->FillRect(Rect);
 ListBox1->Canvas->TextOut(Rect.Left + x, Rect.Top + x, ListBox1->Items->Strings[Index]);
}
//---------------------------------------------------------------------------

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.

...powrót do menu. 

Zdarzenie OnDrawItem obiektu ListBox1. Tutaj odbywa się cały proces wypełniania tła obiektu grafiką.
Fragment kodu odpowiedzialny za ustawienie koloru zaznaczania wybranej pozycji na ListBox1.
Zmienna potrzebna do wykonania obliczeń wyrównujących tekst w pionie.
Funkcja rysująca trójwymiarową ramkę wokół zaznczania. Nie jest potrzebna, ale ładnie wygląda.
Tutaj zostaje wprowadzony tekst do ListBox1 po tym jak został przysłonięty grafiką i tutaj jest wyrównywany w pionie, z wykorzystaniem zmiennej x
Funkcja przechwytująca grafikę z obiektu ListBmp i dostosowująca rozmiar obiektu FillBmp do rozmiaru ListBox1.
Konstrukotr klasy TForm1. Tutaj zostają umieszczone definicje obiektów ListBmp i FillBmp i tutaj zostaje wczytana grafika do obiektu ListBmp.
Funkcja ExtractFilePath(ParamStr(0)) pobiera ścieżkę dostępu do pliku graficznego znajdującego się w tym samym katalogu co program, można oczywiście podać inną bezwzględną ścieżkę dostęu do pliku, np.: c:\\myimages\\fantasy.bmp