Sposób na szybkie malowanie fragmentów jednego obrazka na kanwie komponentu

0

Potrzebuję malować po Canvas komponentu wiele wyciętych fragmentów z grafiki PNG (32-bitowa głębia), zachowując przezroczystość; Malowanych fragmentów może być nawet setki, więc potrzebuję efektywnego algorytmu;

Napisałem więc kod testowy, który do tego celu używa metody TCanvas.CopyRect i wszystko malowane jest prawidłowo; Jednak czas malowania jest tragicznie długi, dlatego że wymieniona metoda wykonuje kupę zbędnych operacji, jak sprawdzanie źródła, rozmiarów fragmentu, a do tego używa funkcji StretchBlt:

procedure TCanvas.CopyRect(const Dest: TRect; SrcCanvas: TCanvas;
  const Source: TRect);
var
  SH, SW, DH, DW: Integer;
Begin
  if SrcCanvas= nil then exit;

  SH := Source.Bottom - Source.Top;
  SW := Source.Right - Source.Left;
  if (SH=0) or (SW=0) then exit;
  DH := Dest.Bottom - Dest.Top;
  DW := Dest.Right - Dest.Left;
  if (Dh=0) or (DW=0) then exit;

  SrcCanvas.RequiredState([csHandleValid]);
  Changing;
  RequiredState([csHandleValid]);

  StretchBlt(FHandle, Dest.Left, Dest.Top, DW, DH,
    SrcCanvas.FHandle, Source.Left, Source.Top, SW, SH, CopyMode);
  Changed;
end;

kod pobrany z modułu canvas.inc ze źródeł LCL

Wszystkie dodatkowe operacje (sprawdzenia) są tak jak napisałem zbędne, dlatego że nie ma mowy o tym, abym czegoś nie dopilnował - podał kanwę wskazującą na Nil, czy błędne rozmiary wycinków; Dlatego też samo sprawdzanie trzeba ominąć; Dodatkowo, wszystkie grafiki PNG posiadają 32-bitową głębię kolorów, malowany fragment nigdy nie będzie rozciągany lub zmniejszany, więc StretchBlt jest nadmiarowy, a nawet sama klasa TPortableNetworkGraphic to także przerost formy nad treścią;

Niestety kanwa komponentu nie daje dostępu do fizycznej pamięci z zapisaną mapą pikseli, jak to można uzyskać za pomocą metody ScanLine w klasie TPortableNetworkGraphic, czy nawet TBitmap; Szkoda, bo można by ręcznie w pętli mieszać składowe pikseli, co było o wiele szybsze, niż używanie do tego tylu metod i zbędnych operacji;


Tak więc potrzebuję sposobu na jak najbardziej optymalne przekopiowanie fragmentu obrazu PNG do kanwy komponentu; Nie musi to być wykonane uniwersalnym (multiplatformowym) kodem - może być to wykonane w WinAPI za pomocą kontekstu urządzenia (DC); Również nie muszę w ogóle korzystać z grafiki PNG - wystarczy mi już zapisana w pliku amorficznym gotowa i nieskompresowana mapa 32-bitowych pikseli - będzie prościej, bo czas ładowania jej do pamięci i tak będzie króciutki;

Ma ktoś jakiś pomysł?

Spróbuję później użyć bezpośrednio funkcji BitBlt - być może będzie szybsza, bo nie będzie rozciągania sprawdzała, no i pozostałych, w moim przypadku zbędnych rzeczy.

2

zobacz to http://www.codeproject.com/Tips/66909/Rendering-fast-with-GDI-What-to-do-and-what-not-to
Ogólnie to najpierw rysujesz na buforze a potem bufor na co tam chcesz

0

Dzięki za link - późno już, więc sprawdzę jutro jak można wykorzystać kod z artykułu;

Wiadome jest, że trzeba użyć bufora pośredniczącego w malowaniu; W moim przypadku wszystko będzie ostatecznie lądować na kanwie komponentu, więc sam komponent musiałby albo w każdej metodzie Paint tworzyć sobie taką bitmapę, albo trzymać ją np. w dodatkowym polu; To akurat nie jest jakimś szczególnym problem;

Tak więc jutro pobawię się, sprawdzę różne sposoby i dam znać.

0

Wracam do problemu, który niestety nie został jeszcze rozwiązany;

Testowałem różne sposoby z buforem pośredniczącym, ale kompletnie nie zmienia to mojej sytuacji; Jeżeli wszystko maluję na bitmapie pomocniczej, a dopiero całą bitmapę na płótnie komponentu, to czas malowania całości jest praktycznie taki sam, jak i bez tej dodatkowej bitmapy; Jeżeli format bitmapy to pf24bit, wszystko jest malowane poprawnie, ale tak samo długo; Jeśli ustawię na pf32bit, to czas nie zmienia się, a w zamian tracę tło - przyjmuje ono jakiś dziwny kolor zamiast tego, który chcę użyć;

Myślałem też nad użyciem bezpośrednio funkcji BitBlt, jednak ta nie pozwala na malowanie fragmentów - maluje zawsze całość; Jednak nadal to nie zmienia faktu, że na pomocniczym płótnie malowanie także odbywa się zbyt długo;

Problemem niestety, ale jest głębia bitów obrazu źródłowego, która wynosi 32-bity... Obsługuje przezroczystość, którą wolałbym zachować, jednak ona jest jedyną przeszkodą; Jeżeli dokładnie tę samą grafikę przerobię na 24-bitową (usunę kanał Alpha) i użyję tego samego kodu co wcześniej, to rysowanie nawet całego ekranu takimi wycinkami trwa ułamek sekundy - nie widać w ogóle (nawet na ułamek sekundy) zamrożenia programu; Robię click i mam cały ekran zapełniony, a z użyciem kanału Alpha trwa to około dwie sekundy;

Tak więc jestem w kropce, bo nie mam zielonego pojęcia, jak z jednej strony zachować przezroczystość, a z drugiej strony móc malować bardzo szybko; Głównie mam na myśli Lazarusa, bo docelowo kod ma pod nim działać; Jeżeli nie ma sposobu, aby LCL umożliwił rysowanie fragmentów z przezroczystością w rozsądnym czasie, to trudno - pójdę na kompromis, dołożę sobie trochę więcej pracy i zrobię wszystko bez przezroczystości... Jednak wtedy będzie możliwe jedynie malowanie na jednolitym tle, a to trochę słabo.


Edit: Problemu w sumie nie rozwiązałem, jednak znalazłem kompromis - gdzie tło będzie jednolite to zastosuję obrazy 24-bitowe (90% przypadków), a w pozostałych przypadkach, w których potrzebował będę namalować małą ilość elementów na niejednolitym tle, zastosuję obraz 32-bitowy;

Czyli - można by rzec - po kłopocie; Dziękuję za zainteresowanie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1