furious programming
dziś, 16:58

Kolejna ciekawostka na temat Pascala.

Czy wiesz, co oznacza zapis ^M, jeśli nie mówimy o typie wskaźnikowym? Albo zapis ^7 lub ^!? Jest to bardzo stara funkcjonalność, starsza ode mnie, na dodatek wciąż wspierana (zapewne ze względu na wsteczną kompatybilność). Przykład użycia:

const
  MAGIC = ^M;

Dlaczego to się kompiluje? Czym jest to M? Co zawiera stała? Sprawdźmy:

Write(MAGIC);

Hmm… pusta konsola… No to sprawdźmy rozmiar:

Write(SizeOf(MAGIC));

W konsoli wyświetla 1, czyli jeden bajt. Sprawdźmy więc wartość liczbową tego ”czegoś”, rzutując na typ liczbowy:

Write(UInt8(MAGIC));

Wyświetla 13 – czego tak? Może sprawdźmy inny znak:

const
  MAGIC = ^J;

Wyświetla 10. A coś z interpunkcji?

const
  MAGIC = ^!;

Wyświetla 97, dla kropki 110, a dla spacji (wpisanej bezpośrednio) wyświetla 96 – skonfundowani? ;)


Już tłumaczę – operator ^ ma kilka znaczeń. Pierwsza i dziś powszechnie wykorzystywana jego funkcjonalność to możliwość zapisu typu wskaźnikowego, gdzie token po operatorze to identyfikator typu bazowego. Można deklarować osobny typ danych (w sekcji type) lub bezpośrednio w linii deklaracji zmiennej (w bloku var):

type
  TPointerToInt = ^Integer;
var
  PointerToInt: ^Integer;

Druga i równie często używana funkcjonalność to odwołanie się do zawartości zmiennej wskaźnikowej:

PointerToInt^ := $100;

Trzecią, zapomnianą i dość magiczną funkcjonalnością tego operatora (czego przykład podałem na początku tego wpisu) jest zwrócenie znaku na podstawie tokenu po jego prawej stronie. Tokenem tym musi być znak – nie kod numeryczny, nie literał, po prostu znak (i tylko jeden). Znaki jakie zwracane są z takich fraz mają specyficzną kolejność:

[A, a] = 1
[Z, z] = 26
 
[0]    = 112
[9]    = 121

Nie pasują do żadnego zbioru znaków. Skoro fraza zwraca znak, to i możliwe jest tworzenie z nich łańcuchów:

var
  MagicString: String = ^f^r^e^e^ ^p^a^s^c^a^l;

W łańcuchu znajdą się głównie znaki kontrolne. Zapis równoważny z powyższym – ale zapisany w znany wszystkim sposób – poniżej:

var
  MagicString: String = #6#18#5#5#96#16#1#19#3#1#12;

Zapewne mało kto dziś wie o tym ukrytym ficzerze, a jeszcze węższe grono go wykorzystuje. Sam natknąłem się na taki zapis stosunkowo niedawno i na pierwszy rzut oka nie zrozumiałem o co chodzi. Jednak wczoraj przypomniał mi się tamten kod (bo znów natknąłem się na tego typu zapis) i postanowiłem dogłębnie zbadać sprawę – magia rozgryziona.

Tak więc jeśli kiedykolwiek analizować będziecie stare kody napisane w Pascalu, to już nie powinien Was zdziwić poniższy zapis.

const
  CR  = ^M;
  LF  = ^J;
  TAB = ^I;

#pascal #free-pascal

czysteskarpety

czekam na ciekawostki o CSS :]

furious programming

nieprędko – CSS to nie moja bajka… :D

furious programming
2017-09-19 19:55

Generyki we Free Pascalu coraz bardziej zaczynają mnie denerwować – straszanie toporny jest ten mechanizm… Pierwsza wkurzająca rzecz to sposób używania zmiennych, przechowujących referencje do list generycznych.


Dla przykładu – chcemy użyć listy generycznej do przechowywania instancji poniższej klasy:

type
  TEntry = class(TObject)
  {..}
  public
    Data: Integer;
  end;

Teraz deklaracja zmiennej dla listy. W stylu Delphi – ale z użyciem dostępnego typu kontenera – było by to tak jak poniżej, ale wyrzuci błąd kompilacji (treść w komentarzu):

var
  Entries: TFPGObjectList<TEntry>;  // Error: Generics without specialization cannot be used as a type for a variable

No dobrze, dodajmy magiczne słówko spezialize:

var
  Entries: spezialize TFPGObjectList<TEntry>;

Taki kod zostanie poprawnie skompilowany, jednak nie mam zielonego pojęcia skąd mam teraz wydłubać konstruktor, aby utworzyć instancję takiej listy. Nie mam określonego typu danych (jawnej klasy), więc trzeba by jakiejś magii, może ze słówkiem specialize, może z generic, a może z czymś innym, jednak różne konfiguracje zawodzą.

Pewnym obejściem jest po prostu zadeklarowanie osobnego typu, z którego będzie możliwe wywołanie konstruktora:

type
  TEntries = specialize TFPGObjectList<TEntry>;
var
  Entries: TEntries;
begin
  Entries := TEntries.Create();
  {..}

No i fajnie – kod się kompiluje, listę da się utworzyć. Po problemie? Nie… :]


Tak utworzona klasa TEntries co prawda potrafi już przechowywać obiekty klasy TEntry, jednak dostęp do nich jest nieco utrudniony. Załóżmy, że chcemy uzyskać dostęp do pierwszego obiektu listy i np. wpisać dane do zmiennej TEntry.Data. Odpowiedni zapis wygląda tak:

Entries[0].Data := $FF; // lub Entries.Items[0].Data := $FF;

Kod jest poprawny, kompiluje się, działa. Gdzie jest problem? W domyślnej właściwości Items. W klasie TFPGObjectList zdefiniowana jest w taki sposób, że zwraca lub modyfikuje T, czyli nie wiadomo co:

property Items[Index: Integer]: T read Get write Put; default;

Kompilator nie widzi problemu i najwyraźniej podczas kompilacji pod to T podstawia sobie klasę, której obiekty moja lista przechowuje. Jednak mechanizm kompletowania kodu gubi się – po klepnięciu kropki po nawiasach z indeksem elementu, powinno pojawić się okienko completion box i coś podpowiedzieć, ale wyrzuca błąd:

Entries[0].  // Error: illegal qualifier . found

Zapewne mechanizm ten dalej widzi typ elementu jako T (no bo tak jest zdefiniowany w klasie bazowej), więc nie może nic podpowiedzieć, a że pisanie kodu bez funkcji kompletowania jest niewygodne, dlatego też trzeba to naprawić. Znów małe obejście – można nadpisać właściwość Items, konkretyzując typ na jakim ma operować:

type
  TEntries = class(spezialize TFPGObjectList<TEntry>)
  public
    property Items[AIndex: Integer]: TEntry read Get write Put; default;
  end;

Na szczęście można skorzystać z istniejących metod pełniących rolę akcesora i mutatora, czyli metod Get i Put, a całość ustawić jako właściwość domyślną, całkowicie przykrywając poprzedniczkę. Plus jest też taki, że w okienku do kompletowania kodu będzie sugerowało naszą (skonkretyzowaną) właściwość Items, a do tej bazowej nie będzie dawać dostępu.

Nazwa takiej właściwości może być inna – nie musi to być akurat Items.


No, w tym momencie da się mieć własną listę generyczną, da się utworzyć jej instancję, kod będzie się kompilował i kompletowanie kodu nie będzie się dławić. Mam nadzieję, że w przyszłości coś się w tej materii zmieni, bo nie jest to zbyt wygodne w obsłudze. Już za kilka tygodni opublikowana zostanie wersja 1.8 środowiska – zobaczymy co nowego się pojawi.

#free-pascal #lazarus

furious programming

@i486: masz z tym jakiś problem?

@kAzek: to pisze ktoś, kto niedawno wrócił z bana za wyzywanie ludzi od „miernot”.

@vpiotr: znam ten artykuł, czytałem ich wiele na ten temat. Generyków używam, choć nie mam do nich przekonania, ze względu na wymienione kombinacje i trochę bugów w środowisku. Zwróć też uwagę na to, że ten artykuł nie wyjaśnia mojego jedynego pytania z tego wpisu (jak utworzyć listę generyczną, nie deklarując wcześniej konkretnego typu danych). Delphi w temacie generyków jest znacznie lepsze. :]

Azarien

Jednak mechanizm kompletowania kodu gubi się – no to jest już problem samego IDE, nie kompilatora… generyki w Delphi może i są lepsze, ale w FPC były pierwsze. A w {$MODE DELPHI} nie działają te delphiowe?

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

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

furious programming
2017-09-08 02:43

Ostatnio dość dużo dłubię w grafice – i przy jej tworzeniu, i przy programowaniu.

Jedną z takich dłubanin było malowanie tekstu z obramowaniem. Co prawda istnieje natywny dla systemu sposób takiego dekorowania tekstu (BeginPath i EndPath), jednak ma on pewne ograniczenia. Niewątpliwie jednym z najbardziej denerwujących jest brak wsparcia antialiasingu. Szkoda, bo dociąganie kobylastej biblioteki graficznej (lub kopiowanie z niej tego co interesujące) tylko po to, by namalować kilka napisów, nie brzmi zbyt rozsądnie.

Rozwiązanie znalazłem metodą prób i błędów, i choć nietypowe, może nieco dziwne, to zdaje egzamin. Chodzi o to, aby malować tekst bez tła. Przypisać kolor obramowania do Font.Color i namalować cztery razy docelowy tekst – odpowiednio o piksel wyżej i w lewo, wyżej i w prawo, niżej w lewo i niżej w prawo. Po tym wszystkim przywrócić ustawiony wcześniej kolor i namalować docelowy tekst w zadanej pozycji (już bez przesunięcia).


Załóżmy, że chcemy namalować na oliwkowym tle biały tekst z ciemniejszym obramowaniem. Najpierw wypełniamy obszar pod spodem standardową metodą, np. Canvas.FillRect:

Canvas.Brush.Color := $0034765E;
Canvas.FillRect(ClientRect);

Co da poniższy efekt:

0.png

Następnie ustawiamy właściwość Font:

Canvas.Font.Name := 'Ubuntu';
Canvas.Font.Size := 10;
Canvas.Font.Color := clWhite;
Canvas.Font.Style := [fsBold];

wpisujemy kolor obramowania do właściwości Pen (można go przekazać w parametrze – jak kto woli):

Canvas.Pen.Color := $00245241;

i na koniec wołamy metodę malującą tekst z obramowaniem:

Canvas.DrawTextOutlined(PosX, PosY, '4programmers.net');

Metoda działa w dwóch krokach – najpierw maluje cztery napisy z przesunięciem, tworząc ciemniejsze wypełnienie:

1.png

a na koniec maluje tekst w zadanym kolorze na wierzchu. Gotowy efekt poniżej:

2.png

Tekst w powiększeniu wygląda następująco:

3.png

Teraz kwestia kodu – w klasie TCanvas nie istnieje metoda DrawTextOutlined – stworzyłem dla niej helper. Cały kod niżej:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawTextOutlined(AX, AY: Integer; const AText: String);
  end;
 
  procedure TCanvasHelper.DrawTextOutlined(AX, AY: Integer; const AText: String);
  var
    LOldBrushStyle: TBrushStyle;
    LOldFontColor: TColor;
  begin
    LOldBrushStyle := Self.Brush.Style;
    LOldFontColor := Self.Font.Color;
 
    Self.Brush.Style := bsClear;
    Self.Font.Color := Self.Pen.Color;
 
    Self.TextOut(AX - 1, AY - 1, AText);
    Self.TextOut(AX + 1, AY - 1, AText);
    Self.TextOut(AX - 1, AY + 1, AText);
    Self.TextOut(AX + 1, AY + 1, AText);
 
    Self.Font.Color := LOldFontColor;
    Self.TextOut(AX, AY, AText);
 
    Self.Brush.Style := LOldBrushStyle;
  end;

Zaletą jest to, że w ten sposób można namalować tekst zarówno o braku dodatkowych atrybutów, jak również tekst wytłuszczony, pochylony, podkreślony czy przekreślony. Drugą zaletą jest automatyczne dostosowanie się do systemowych ustawień antialiasingu – jeśli usługa rozmywania tekstu jest wyłączona to obramowanie będzie twarde, ale w dalszym ciągu widoczne.

Niestety, ale metoda ta ma też wady – nie nadaje się do malowania grubszego obramowania (przesunięcia większego niż 1px), co przy większych literach jest raczej konieczne. O ile ogólnie jest to możliwe, to wiązałoby się z malowaniem tekstu dla obramowania o wiele więcej razy, co może negatywnie odbić się na wydajności renderowania. Mimo wszystko do prostych zastosowań wystarczy – mnie wystarczyło.


W bardzo podobny sposób możliwe jest też malowanie tekstu z cieniem pod spodem. Różnica polega na tym, że tekst cienia wystarczy namalować raz, zamiast cztery razy. To jak daleko ma się znajdować cień i jak duży ma być, można zdefiniować w dodatkowych parametrach. Idąc za ciosem, niżej przykład metody malującej taki tekst:

type
  TCanvasHelper = class helper for TCanvas
  public
    procedure DrawTextShadowed(AX, AY, AOffsetX, AOffsetY: Integer; const AText: String);
  end;
 
  procedure TCanvasHelper.DrawTextShadowed(AX, AY, AOffsetX, AOffsetY: Integer; const AText: String);
  var
    LOldBrushStyle: TBrushStyle;
    LOldFontColor: TColor;
  begin
    LOldBrushStyle := Self.Brush.Style;
    LOldFontColor := Self.Font.Color;
 
    Self.Brush.Style := bsClear;
 
    Self.Font.Color := Self.Pen.Color;
    Self.TextOut(AX + AOffsetX, AY + AOffsetY, AText);
 
    Self.Font.Color := LOldFontColor;
    Self.TextOut(AX, AY, AText);
 
    Self.Brush.Style := LOldBrushStyle;
  end;

I znów – ustawiamy font oraz kolor dodatkowy dla cienia (przykładowo tak jak wcześniej) i wołamy metodę:

Canvas.DrawTextShadowed(PosX, PosY, 2, 2, '4programmers.net');

Efekt działania metody poniżej:

4.png

Co w powiększeniu przedstawia się następująco:

5.png

To nie wszystko – przykład zawiera cień o takim samym rozmiarze jak tekst właściwy, jednak nic nie stoi na przeszkodzie, aby cień był większy lub mniejszy, sprawiając wrażenie głębi (im wizualnie głębiej ma być cień, tym większy i z większym offsetem powinien być malowany).

Można się tym bawić bez końca, więc na cieniu zakończmy ten wpis. ;)

#free-pascal #lazarus

Azarien

szybkie góglanie wykazuje, że ludzie nie mają zbyt pochlebnego zdania na temat wydajności CopyRect, zwłaszcza w połączeniu z przezroczystością. jeśli to na tym tracisz wydajność, spróbowałbym czegoś innego np. funkcji WinApi AlphaBlend (nie wiem jak z jej wydajnością) albo czegoś o czym wiadomo że jest wydajne (Direct3D, OpenGL, czy choćby starego DirectDraw)

furious programming

Porzuciłem ten moduł właśnie ze względu na konieczne modyfikacje i optymalizacje – za dużo z tym roboty, a funkcjonalność zbyt mała. Na pewno CopyRect nie jest najlepszym rozwiązaniem, jednak zależało mi na tym, aby nie babrać się nadmiernie w winapi i nie dociągać dodatkowych bibliotek.

Teraz to nawet nie mam na czym sprawdzić tego AlphaBlend – pliki fontów usunąłem dawno temu, łącznie z konsolowym narzędziem do ich generowania. Pozostanie więc ciekawostką i przestrogą, aby nie być „mądrzejszym od telewizora”.

furious programming
2016-08-17 22:24

Kilka razy już trafiłem w sieci na materiały zawierające wskazówki dotyczące rozwoju i modernizacji dialektu Free Pascala; No i muszę przyznać, że część z propozycji jest w mojej ocenie super - wprowadzenie sugerowanych elementów do składni mogło by ułatwić pisanie kodu i w dużej mierze go skrócić; Poniżej opiszę kilka z nich - te, na których wdrożenie będę czekał z niecierpliwością; Punkty poprzedzone cyferkami to propozycje znalecione w sieci, natomiast poprzedzone literkami to moje własne propozycje/przemyślenia (choć być może ktoś już o tym wspomniał);

1. Fuzja bloków try finally i try except

Pierwszym pomysłem, który bardzo mi się spodobał, dotyczy połączenia wspomnianych bloków w jeden; Umożliwiło by to skrócenie kodu, zachowanie jednego poziomu wcięcia dla kodu poszczególnych sekcji oraz zwiększeniu uniwersalności tych bloków; W chwili obecnej istnieje możliwość połączenia tych bloków, jednak trzeba je zagnieździć:

try
  try
    // instrukcje do wykonania
    // możliwość zaistnienia wyjątków
  except
    // obsługa wyjątków
  end;
finally
  // zwolnienie zasobów
end;

W rezultacie połączenia wspomnianych bloków, dostępna była by poniższa struktura kodu:

try
  // instrukcje do wykonania
  // możliwość zaistnienia wyjątków
except
  // obsługa wyjątków
finally
  // zwolnienie zasobów
end;

Jak dla mnie pomysł na duży plus;

2. Określenie skoku iteratora pętli for

Jak powszechnie wiadomo, bieżąca składnia tytułowej pętli jest dość uboga; W porównaniu do języków z rodziny C, pętla for w Pascalu nie umożliwia określenia skoku (wartości inkrementacji lub dekrementacji) iteratora - zawsze jest to 1; Mało tego - do inkrementacji iteratora używa się słówka to, a do dekrementacji słówka downto; Ktoś więc wpadł na pomysł, aby obecną sytuację zmienić i rozszerzyć nagłówek pętli o jeszcze jeden element - wartość skoku; Ta wartość miała by być określana na końcu nagłówka, po słówku step;

Obecnie można kontrolować wartość skoku, jednak trzeba skorzystać z pętli while lub repeat; Przykład użycia:

while Iterator <= 100 do
begin
  // instrukcje do wykonania
  Iterator += 10;
end;

Rozwinięcie pętli for było by bardzo pomocne, co widać w poniższym przykładzie:

for Iterator := 0 to 100 step 10 do
  // kod do wykonania

3. Deklaracja iteratora pętli w jej nagłówku

Zapewne wszyscy znają taki zabieg z języka C lub podobnych - w Pascalu też by pasowało tak móc; Deklaracja zmiennej iteratora w nagłówku pętli wiele by się nie różniła od obecnej postaci - do nagłówka było by dodane słówko var i typ:

for var Iterator: Integer := 0 to 100 step 10 do
  // instrukcje do wykonania

Odpadnie konieczność deklaracji i definicji zmiennej iteratora; Póki co i tak ułatwieniem jest możliwość nadania zmiennej wartości już w linii deklaracji; Powyższy przykład dodatkowo wzbogaciłem sekcją step, aby uwidocznić znaczenie proponowanych zmian; W mojej ocenie oba pomysły elegancko się uzupełniają;

4. Grupowe porównywanie wartości

Ten pomysł jest o tyle ciekawy, że mógłby znacznie skrócić linie umożliwiające pozyskanie wartości logicznej z porównania hardkodowanych wartości, zmiennych i stałych; Mowa tutaj o sprytnym posłużeniu się operatorami porównywania (równy, większy, mniejszy itd.); Załóżmy, że w instrukcji warunkowej potrzebujemy sprawdzić wartości dwóch parametrów - górną i dolną granicę, a także ich wzajemny stosunek; Dziś takie warunki można zapisać w poniższy sposób:

procedure Foo(AFirst, ASecond: Integer);
begin
  if (AFirst > 0) and (ASecond > AFirst) and (ASecond < 100) then
    // kod do wykonania

Pierwszy parametr musi być większy od 0, drugi musi być większy od pierwszego i mniejszy od wartości 100; To co dziś możliwe to zapis trzech osobnych sekcji i koniunkcja wyników, jak widać powyżej; Natomiast całość można znacznie skrócić i zwiększyć czytelność, posługując się "porównaniem grupowym":

procedure Foo(AFirst, ASecond: Integer);
begin
  if 0 < AFirst < ASecond < 100 then
    // kod do wykonania

Funkcjonalność całkiem przydatna, bo od razu widać zależności pomiędzy wartościami;

A. Grupowe nadawanie wartości zmiennym

Tego ficzeru mi brakuje - czasem potrzebuję hurtem wpisać taką samą wartość do kilku zmiennych i muszę to robić osobno; Przykład - trzy pointery, do których wpisywane są takie wartości nil (że niby głowa, ogon i ostatni użyty):

ptrHead := nil;
ptrTail := nil;
ptrLast := nil;

Trochę redundantne, a jeśli zmiennych będzie więcej, kod się wydłuży i będzie do d**y; Co by się przydało? Grupowe nadanie wartości - przecinkiem oddzielić zmienne i na końcu zapisać przypisanie wartości:

ptrHead, ptrTail, ptrLast := nil;

albo na wzór zbiorowego przypisania z języka C:

ptrHead := ptrTail := ptrLast := nil;

Było by dobrze, gdyby taka konstrukcja możliwa była również w sekcji deklaracji zmiennych:

var
  ptrHead, ptrTail, ptrLast: Pointer = nil;

Póki co składnia w ogóle nie przewiduje zbiorowego nadawania tej samej wartości dla kilku zmiennych, a to niedobrze;

B. Deklaracja zmiennych w dowolnym miejscu bloku kodu

To chyba najbardziej wyczekiwana przeze mnie funkcjonalność, choć szansa że zostanie kiedyś wdrożona jest raczej nikła; Mowa o możliwość deklaracji zmiennych w sposób zaczerpnięty z języka C; Raczej dotyczyć miała by tylko deklaracji zmiennych, choć nie wykluczam, że dla stałych też miało by to sens; Dzięki temu, możliwe było by alokowanie pamięci dla lokalnych zmiennych tylko wtedy, kiedy spełnione będą wymagane warunki, głównie w ciałach zagnieżdżonych ifów lub innych bloków grupujących; Póki co pamięć dla zmiennych alokowana jest na samym początku, przez wykonaniem kodu np. funkcji;

Przykład kawałka procedury, która coś tam robi z wejściowym ciągiem - kod możliwy do kompilacji dziś:

procedure Foo(const AParam: String);
var
  intLIdx, intRIdx: Integer;
begin
  if AParam <> '' then
  begin
    intLIdx := 1;
    intRIdx := Length(AParam);
 
    // walidacja ciągu wejściowego
  end;
end;

Jak widać zmienne zadeklarowane są w sekcji var i pamięć dla nich zostanie zaalokowana bez względu na zawartość parametru; Jeśli ciąg wejściowy będzie pusty, pamięć zostanie zaalokowana niepotrzebnie; O ile w powyższym przykładzie to tylko osiem bajtów (pod 32-bitowym kompilatorem), to przy większej ilości zmiennych i obszerniejszych typach danych, może to już mieć większe znaczenie;

Deklaracja zmiennych wewnątrz ciała instrukcji warunkowej (jakkolwiek to brzmi) była by czytelniejsza:

procedure Foo(const AParam: String);
begin
  if AParam <> '' then
  begin
    var intLIdx: Integer = 1;
    var intRIdx: Integer = Length(AParam);
 
    // walidacja ciągu wejściowego
  end;
end;

Przy czym możliwość nadania wartości zmiennym w linii ich deklaracji była by konieczna (inaczej sobie tego nie wyobrażam); W podobnym przypadku, gdy kilka zmiennych ma otrzymać taką samą wartość początkową, wpasowuje się pomysł z punktu A., czyli grupowe nadawanie wartości kilku zmiennym; Inny dziwny przykład:

procedure Foo(AProcessValue: Boolean);
begin
  // instrukcje początkowe
 
  if AProcessValue then
  begin
    var ptrHead, ptrTail, ptrLast: Pointer = nil;
 
    // dalsze instrukcje do wykonania
  end;
end;

W tym przykładzie widać dwie propozycje - deklaracja trzech zmiennych dopiero po spełnieniu określonego warunku, a także nadanie wszystkim zmiennym takiej samej wartości; Uroczo;

Podsumowanie

Część ze znalezionych w sieci propozycji została zaakceptowana przez gromowładnych, między innymi te wymienione na początku tego wpisu; Sugestii było i jest znacznie więcej, jednak niektóre są bezsensowne i w sumie od razu zostały odrzucone; Propozycje opisane w tym wpisie są w mojej ocenie dobre i mogły by być przydatne;

Będę czekał na wszelkie nowinki związane ze składnią Free Pascala, choć nie wykluczam, że wdrożenie nowości może zająć sporo czasu; Wiadomo, że propozycje to jedno, a przebudowa kompilatora to drugie; Mimo wszystko fajnie by było, gdyby natywny dialekt FPC nie był dłużej rozwijany pod kątem kompatybilności z Delphi, bo to kula u nogi; Tym bardziej, jeśli składnia pozwalałaby na większą swobodę, możliwość poprawy czytelności kodu oraz jego skrócenia; Pomarzyć zawsze można :)

#free-pascal

furious programming

@Azarien: mają też ~operator trójargumentowy...:

Console.WriteLine(if result then 'yes' else 'no');
Azarien

@furious programming: ciekawe. identyczną składnię ma Ada.