artykuły

Delphi - Transformacje grafiki - Odbicia lustrzane

21:36
sob, 12 marzec 2005
Tekst z dużą dokładnością opisuje sposób lustrzanego odbijania obrazu, prowadzi za rękę przy pisaniu filtra wykonującego zarówno odbicia pionowe jak i poziome.

Wstęp

Witam wszystkich serdecznie! Dzisiaj zajmiemy się efektem, który powinien być dostępny praktycznie w każdej aplikacji graficznej. Nie chodzi mi jednak o dublowanie już istniejących funkcji, ale o wgląd w istotę ich działania. Podczas pracy nad pewną grą natknąłem się na problem, który zafascynował mnie łatwością rozwiązania. W programowaniu rzadko bywa tak, iż od razu rozwiązujemy ujawniony problem. Przeważnie trzeba przejść długą drogę, prowadzącą często do skorzystania z klawisza Delete ;) Dziś chciałbym przybliżyć Wam temat odbijania grafiki (w poziomie lub pionie).

Piszemy program

Zdarza się, że potrzebujemy czasem odwrócić obiekt graficzny poprzez tzw. lustrzane odbicie. Poruszając kwestię lustrzanego odbicia, musimy rozpatrzyć dwa przypadki, w których: a) chcemy odbić obiekt w poziomie b) chcemy odbić obiekt w pionie

PRZYPADEK A (ODBICIE W POZIOMIE)

Obrazek pierwotny > Obrazek odbity poziomo
  1. Uruchom Delphi'ego.
  2. Z palety "Standard" wybierz przycisk (Button) i połóż go na formie.
  3. Z palety "Additional" wybierz komponent Image i połóż go na formie.
  4. Przejdź do palety Dialogs, znajdź komponent OpenPictureDialog i również umieść go na formularzu.
  5. Teraz wypadałoby pozmieniać domyślnie wartości przypisane komponentom. Czynimy to klikając (jednokrotnie) w pierwszej kolejności na przycisk, następnie przechodząc do Inspektora Obiektów (Object Inspector) i odnajdując właściwość Caption. Zmieniamy wartość tej właściwości na "Odbij obrazek".
  6. Zaznaczamy teraz komponent Image i w Inspektorze Obiektów odnajdujemy właściwość Align (na samej górze), zmieniając ją na alClient (to pozwoli komponentowi Image zajmować cały obszar na naszej formie) oraz właściwość Name, zmieniając ją na "obrazek".
  7. Zaznaczamy jeszcze komponent OpenPictureDialog1 i w Inspektorze Obiektów zmieniamy jego właściwość Name na "Open" (bez cudzysłowów).
  8. Gdy ukończyliśmy powyższe czynności, powinniśmy przejść do Edytora kodu. Czynimy to klikając dwukrotnie na przycisk. Po chwili edytor kodu czeka na nas z nowo-wygenerowaną procedurą. Procedure tę uzupełniamy następująco:
  9. przed słówkiem begin dopisujemy słówko var i deklarujemy zmienne: var polowa_szerokosci : integer; x, y : integer; kolor1 : TColor; kolor2 : TColor;
  10. Po słówku begin wpisujemy kolejno instrukcje: if not open.Execute Then Exit; Powyższa instrukcja otwiera okienko, dzięki któremu możemy otworzyć plik. Powyższa konstrukcja (z warunkiem) zakańcza nam całą procedurę w przypadku, gdy wystąpi błąd (okienko otwierania pliku się nie otworzy) lub użytkownik przyciśnie przycisk Anuluj. Można zapisać to prościej, bez warunku (zabezpieczenia), w następujący sposób:open.Execute;wtedy jednak, gdy użytkownik naciśnie na przycisk Anuluj (zamykając okno bez wybrania pliku) pojawi się nam błąd przy kolejnej linii. przechodzimy do kolejnej, drugiej linijki: obrazek.Picture.LoadFromFile(open.FileName); powyższa instrukcja jest odpowiedzialna za załadowanie pliku do komponentu "obrazek" z lokalizacji jaką wybrał użytkownik (open.FileName). Kolejna linia to: polowa_szerokosci := (obrazek.picture.Width-1) div 2; w powyższej instrukcji następuje przypisanie połowy szerokości wczytanego obrazka do zmiennej polowa_szerokosci. Dlaczego odejmujemy 1 od szerokości obrazka? Ponieważ pierwszy piksel osi X obrazka to 0. Jeśli numeracja zaczynałaby się od 1, a nie od 0 wtedy nie musielibyśmy nic odejmować. Operator div dzieli bez reszty. Czyli np. 5 div 2 da nam 2, a 12 div 7 zwróci 1 (niezaokrąglone). Przejdźmy do kolejnej linii: for y:=0 To obrazek.picture.Height-1 Do for x:=0 To polowa_szerokosci Do begin Kolejną linię stanowią dwie pętle. Jest to konstrukcja typowa dla różnorakich filtrów graficznych. Pierwsza pętla przechodzi nam przez oś Y, druga zaś przez połowę osi X.kolor1 := obrazek.picture.bitmap.Canvas.Pixels[x,y];W powyższej linii następuje pobranie koloru z lewej strony obrazka i zapisanie go w zmiennej kolor1. Zmienna x zmienia się od 0 do połowy szerokości obrazka.kolor2 := obrazek.picture.bitmap.Canvas.Pixels[obrazek.picture.Width-1-x,y];W powyższej linii następuje pobranie koloru z prawej strony obrazka i zapisanie go w zmiennej kolor2. Jak wiemy zmienna X zmienia się od 0 do połowy szerokości obrazka. Jeśli więc odejmiemy od szerokości obrazka (pomniejszonej o 1) wartość zmiennej x to otrzymamy pozycję zmieniającą się od prawej strony do połowy obrazka.


    Zobrazowanie działania obrazek.picture.bitmap.Canvas.Pixels[x,y] := kolor2; Ta instrukcja rysuje po lewej stronie piksel o kolorze pobranym z prawej strony. obrazek.picture.bitmap.Canvas.Pixels[obrazek.picture.Width-1-x,y] := kolor1; Tutaj na odwrót. Ta instrukcja rysuje po prawej stronie piksel o kolorze pobranym z lewej strony. end; I zakończenie pętli. Koniec.

Kod tej procedury zamieszczam w całości poniżej: procedure TForm1.Button1Click(Sender: TObject); var polowa_szerokosci : integer; x, y : integer; kolor1 : TColor; kolor2 : TColor; begin if not open.Execute Then Exit; obrazek.Picture.LoadFromFile(open.FileName); polowa_szerokosci := (obrazek.picture.Width-1) div 2; for y:=0 To obrazek.picture.Height-1 Do for x:=0 To polowa_szerokosci Do begin kolor1 := obrazek.picture.bitmap.Canvas.Pixels[x,y]; kolor2 := obrazek.picture.bitmap.Canvas.Pixels[obrazek.picture.Width-1-x,y]; obrazek.picture.bitmap.Canvas.Pixels[x,y] := kolor2; obrazek.picture.bitmap.Canvas.Pixels[obrazek.picture.Width-1-x,y] := kolor1; end; end;Program powinien działać prawidłowo. Rozważmy teraz przypadek B.

PRZYPADEK B (ODBICIE W PIONIE)

Obrazek pierwotny > Obrazek odbity pionowo

Okazuje się, że możemy go napisać w całości poprzez analogię do odbicia w poziomie (odkrywcze, prawda? ;). procedure TForm1.Button1Click(Sender: TObject); var polowa_wysokosci : integer; x, y : integer; kolor1 : TColor; kolor2 : TColor; begin if not open.Execute Then Exit; obrazek.Picture.LoadFromFile(open.FileName); polowa_wysokosci := (obrazek.picture.Height-1) div 2; for y:=0 To polowa_wysokosci Do for x:=0 To obrazek.picture.Width-1 Do begin kolor1 := obrazek.picture.bitmap.Canvas.Pixels[x,y]; kolor2 := obrazek.picture.bitmap.Canvas.Pixels[x,obrazek.picture.Height-1-y]; obrazek.picture.bitmap.Canvas.Pixels[x,y] := kolor2; obrazek.picture.bitmap.Canvas.Pixels[x,obrazek.picture.Height-1-y] := kolor1; end; end; Kolorem czerwonym wyróżniłem kod, który uległ zmianie w stosunku do poprzedniego kodu wykonującego odbicie w poziomie. Jak widzicie, tak naprawdę zmianie uległ tylko jeden fakt - teraz zamiast połowy szerokości obrazka, bierzemy połowę jego wysokości.

Zakończenie

Dziękuję wszystkim za uwagę! Mam nadzieję, że artykuł się spodobał. Jak zwykle do materiału dołączam kod źródłowy, który bez problemu skompilujecie i uruchomicie. A ja póki co żegnam się z Wami, namawiając do skorzystania z odnośnika umieszczonego na końcu artykułu ;P

Źródła: - obrazki zaczerpnięte z nieprzeniknionych zasobów internetu ;)
12345
Delphi - Transformacje grafiki - Odbicia lustrzane Autor opinii: Czytelnicy, data przesłania: 0

Podobne artykuly:

Skomentuj

Aby zamieścić komentarz, proszę włączyć JavaScript - niestety roboty spamujące dają mi niezmiernie popalić.






Komentarze czytelników

    • yavo
    • pon, 26 październik 2009, 20:55
    • A nie można by tak:

      procedure TForm1.SpeedButton1Click(Sender: TObject);
      var Bmp : TBitMap;
      begin
      Bmp :=TBitMap.Create;
      Bmp.Width :=Image1.Width;
      Bmp.Height :=Image1.Height;
      Bmp.Canvas.StretchDraw(Rect(Bmp.Width,0,0,Bmp.Height),Image1.Picture.Bitmap);
      Image1.Picture.Bitmap.Assign(Bmp);
      end;

      Pętle przy dużych grafikach działają jak parowóz a nam potrzebne TGV :)




      Odp: Owszem - można by. Ale muszę zwrócić uwagę, że to nie pętle, ale sposób odwoływania się i podmiany wartości pikseli poprzez tablicę Pixels[x,y] działa jak parowóz. Używając metody ScanLine() (patrz inne artykuły z serii Delphi - Transformacje grafiki) osiągniemy takie same (czasowo) rezultaty co w zaprezentowanym przez Ciebie sposobie. Pixels jest dobre i czytelne dla początkujących (lub małych operacji graficznych), jednak dla pełnego rozwinięcia skrzydeł Delphi - należy używać operacji działających bezpośrednio na wskaźnikach. Wtedy dopiero Delphi pokazuje swoją siłę i szybkość.
Dexter