furious programming
2017-09-12 21:39

Poprzedni mój wpis na temat bazgrołów graficznych z poziomu kodu nawet się spodobał, więc postanowiłem umieścić nowy – w podobnej tematyce. Dziś pokażę sposób renderowania obrazu z otoczką. Sposób ten wykorzystuję we własnych kontrolkach – zwykłych przyciskach oraz w przyciskach zakładek. Również daje się "motywować", więc kolor obrazu i tła jak najbardziej możliwe są do określenia.

O ile malowanie tekstu z obramowaniem było małym oszustwem (wielokrotnym malowaniem ciemnego tekstu pod spodem), to w przypadku grafiki już tak łatwo nie będzie. Co prawda można – podobnie jak wcześniej – wiele razy namalować obrazek pod spodem, imitując tym samym otoczkę, jednak na pewno nie będzie to sposób efektywny. Dlatego też trzeba podejść do tematu nieco od innej strony.

Jednym z ciekawszych rozwiązań jest wielowarstwowe renderowanie. Gotowy obraz będzie wynikiem wypełnienia tła, przemalowania w locie grafiki wzorca, jego malowania na płótnie, oraz finalnie namalowania drugiej grafiki, zawierającej półprzezroczystą otoczkę.


Etap 1. Przygotowanie grafik

Najpierw należy się zająć grafikami. Pierwszy krok to przygotowanie grafiki wzorca. Aby móc w pełni obsługiwać kolorowe tła, wzorzec musi obsługiwać kanał alfa, dlatego też przygotujmy sobie 32-bitowy obraz PNG. Do jego stworzenia skorzystam z programu Inkscape. Wybierzmy obraz – ja skorzystam z loga 4p (bez tytułu):

0.png

W dowolnym programie do obróbki grafiki rastrowej (np. Paint.NET), przyciemniam ją maksymalnie:

1.png

Teraz otwieram Inkscape i przeciągając plik z obrazem, upuszczam go na dokumencie:

2.png

Aby stworzyć wektorowy kształt na jego podstawie, wystarczy obraz zaznaczyć i skorzystać z narzędzia Path\Trace Bitmap…. Domyślne ustawienia wystarczą – klikamy Ok i mamy wektorową odbitkę:

3.png

Obraz można było wcześniej przyciemnić, ale też można to zrobić w Inkscape – jak kto woli. Teraz czas na otoczkę – zaznaczamy obraz i korzystamy z opcji Path\Linked Offset. Rozciągamy obramowanie na zadaną grubość:

4.png

Obramowanie jest koloru czarnego, z ustawionym kanałem alfa na 85 – po to, aby otoczka zawsze przyciemniała kolor tła (w przeciwnym razie była by bezużyteczna). Kolejny krok to już eksport grafik do docelowych plików – najpierw wzorzec (wypełnienie). W tym celu ustawiam otoczkę na całkowicie przezroczystą i eksportuję całość do pliku:

5.png

Teraz otoczka – najpierw należy ją oddzielić od wypełnienia. Zaznaczamy ją, przywracamy poprzedni stan kanału alfa (tutaj: wartość 85) i używamy opcji Path\Combine – otoczka stanie się osobnym obiektem. Teraz zaznaczamy oba obiekty i korzystamy z opcji Path\Exclusion. W wyniku tych działań otrzymamy samo dziurawe obramowanie (dla podglądu rozsunąłem obiekty):

6.png

Aby wyeksportować samą otoczkę, można albo usunąć główny obiekt (ten czarny), albo ustawić mu maksymalną przezroczystość. Po wszystkim eksportujemy dziada do drugiego pliku:

7.png

W efekcie mamy dwa pliki – jeden z czarnym wzorcem, a drugi z samą przyciemniającą otoczką (oba w tym samym rozmiarze):

8.png


Etap 2: Programowanie

Obrazki są gotowe, więc czas na kod. Co prawda można już oba namalować (jeden nad drugim) jednak mamy tylko jeden kolor – czarny. Potrzebujemy więc metody, która najpierw pomaluje grafikę wzorca na zadany kolor, a dopiero potem namaluje ją na docelowym płótnie. Wydaje się skomplikowane, no bo jak wypełnić kolorem grafikę o niewiadomych kształtach, w dodatku z antialiasingiem? Nic prostszego – pomalować każdy jej piksel. ;)

Ale nie tak prędko – pomalować należy każdy piksel grafiki, nie ruszając kanału alfa. Dlatego też skorzystamy z poczciwego ScanLine i dobierzemy się do składowych każdego piksela. Do tego celu przydadzą się odpowiednie typy danych (ich deklaracja może nie być konieczna):

type
  TRGBTriple = packed record
    B, G, R: UInt8;
  end;
 
type
  TRGBQuadRec = packed record
    B, G, R, A: UInt8;
  end;
 
type
  PRGBQuadArr = ^TRGBQuadArr;
  TRGBQuadArr = packed array [0 .. MaxInt div SizeOf(TRGBQuadRec) - 1] of TRGBQuadRec;

Do właściwego namalowania wzorca – tak jak poprzednio – użyjemy helpera dla klasy TCanvas:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawGraphicPattern(AX, AY: Integer; APattern: TPortableNetworkGraphic; AColor: TColor);
  end;
 
  procedure TCanvasHelper.DrawGraphicPattern(AX, AY: Integer; APattern: TPortableNetworkGraphic; AColor: TColor);
  var
    LLine: PRGBQuadArr;
    LLineIdx, LPixelIdx: Integer;
    LColor: TRGBTriple;
  begin
    RedGreenBlue(AColor, LColor.R, LColor.G, LColor.B);
    APattern.BeginUpdate();
 
    for LLineIdx := 0 to APattern.Height - 1 do
    begin
      LLine := APattern.ScanLine[LLineIdx];
 
      for LPixelIdx := 0 to APattern.Width - 1 do
      begin
        LLine^[LPixelIdx].R := LColor.R;
        LLine^[LPixelIdx].G := LColor.G;
        LLine^[LPixelIdx].B := LColor.B;
      end;
    end;
 
    APattern.EndUpdate();
    Self.Draw(AX, AY, APattern);
  end;

Zapewne nasuwa się pytanie – dlaczego operuję bezpośrednio na APattern, nie używając grafiki pomocniczej?

Odpowiedź jest prosta – bo mogę. Dopóki nie zmienia się wartości kanału alfa, obraz może być wielokrotnie przemalowywany na dowolne kolory i nie będzie to miało żadnych negatywnych skutków. Drugi plus jest taki, że brak kopiowania obrazu przyspieszy proces renderowania.

Przykładowe wywołanie metody (malowanie tła pomijam – jest takie samo jak w poprzednim wpisie, oliwkowe):

//FPattern: TPortableNetworkGraphic;
Canvas.DrawGraphicPattern(PosX, PosY, FPattern, clWhite);

Rezultat:

9.png

Teraz pozostaje jeszcze otoczka. Tu nie trzeba żadnych dodatków – standardowa metoda Canvas.Draw wystarczy. Małe przypomnienie – skoro obrazy są tego samego rozmiaru, muszą być namalowane w tej samej pozycji, aby obramowanie pasowało do wzorca. Po namalowaniu otoczki mamy wszystko gotowe:

10.png

Tu podany jest sposób działający niezależnie – najpierw malowany jest pattern w zadanym kolorze (jedna metoda), a następnie malowana jest otoczka (druga metoda). Nic nie stoi na przeszkodzie, aby napisać jedną metodę, która wykonywać będzie obie te czynności. Wystarczy drugi parametr typu TPortableNetworkGraphic:

procedure DrawGraphicPattern(AX, AY: Integer; APattern, AOutline: TPortableNetworkGraphic; AColor: TColor);

oraz kod malujący otoczkę, umieszczony na końcu:

  Self.Draw(AX, AY, APattern);
  Self.Draw(AX, AY, AOutline);
end;

Sposób ten nie jest wrażliwy na kolor tła, więc można używać dowolnych kolorów, bez ingerencji w grafiki czy kod renderujący:

11.png


PS: Do ikonki z otoczką najlepiej będzie pasować tekst z otoczką. :]

12.png

#free-pascal #lazarus

kate87

Bosh piękne. Chce jeszcze:)

czysteskarpety

panie ale to roboty jest, jednak zostanę przy png :)

furious programming

@kate87: jak będę miał coś ciekawego do pokazania to na pewno o tym napiszę. :]

@czysteskarpety: ale z czym? Wbrew pozorom, przygotowanie tych dwóch grafik zajmuje mi – komuś niezbyt ogarniającemu grafikę – dosłownie dwie minuty. Natomiast kod metody malującej pisze się raz – też nie dłużej niż pięć minut.

W swoim projekcie wykorzystuję taką metodę, jednak w ciekawszy sposób. Przycisk posiada nie dwie, a pięć grafik (w postaci zgrupowanych właściwości), w kolejności są to:

  • Normal – dla normalnego stanu,
  • Hover – po najechaniu myszą,
  • Inactive – grafika przeznaczona dla specjalnego trybu, w którym formularz wyświetlany jest w odcieniach szarości,
  • Disabled – dla kontrolki zablokowanej (Enabled na False),
  • Pattern – wzorzec (przeznaczenie jak we wpisie wyżej).

Do tego jest dodatkowa właściwość – ImageCombined typu Boolean – której stan określa sposób renderowania obrazu przycisku.

Jeśli ustawiona jest na False, kontrolka maluje jedynie tło oraz odpowiedni obrazek (któryś z czterech pierwszych, w zależności od stanu). A jeśli jest na True to wypełnia tło, maluje Pattern w zadanym kolorze (jaśniejszym niż tło lub białym, w zależności od ”hover”, a także w kolorze lub odcieniach szarości, w zależności od właściwości InterfaceActive), a następnie maluje któryś z pierwszych czterech obrazów, w których należy umieścić przyciemniające obramowanie – mocno przyciemniające dla Normal i Hover, a słabiej dla Inactive i Disabled.

Dzięki temu na odblokowanym formularzu przycisk malowany jest w kolorach, a na zablokowanym w odcieniach szarości (wszystkie pozostałe kontrolki również). I wygląda to wyśmienicie. :]