O, jaki piękny kod!
Nie wiem czy taki piękny, ale robi robotę i działa na tyle szybko, że renderowanie dziesiątków obrazków z rozciągnięciem i modulacją kanałów nie powoduje utraty responsywności nawet na moim, raczej słabym laptopie.
Dzięki... tylko, że ja naprawdę nie jestem ekspertem od grafiki.
Ja raczej też nie (chyba). Jeśli chodzi o zabawę z obrazami, to myśl o nich jak o wielkich blokach pamięci, które zawierają dane kanałów RGB(A), w formie UInt8
. Możesz robić z danymi kanałów co tylko chcesz — w końcu to tylko ciągi liczb, na których można wykonywać dowolne operacje matematyczne.
Myślę, że jeśli nie za bardzo ogarniasz to jak bawić się grafikami, to lepiej będzie jeśli skorzystasz z propozycji od @Paweł Dmitruk — niewiele kodu, łatwe w użyciu klasy.
Proszę o rozjaśnienie, jak tego cuda użyć - napisałeś to pięknie, ale dla ludzi, którzy wiedzą o co chodzi.
Funkcja ta służy do namalowania jednego PNG na drugim. W parametrze ABuffer
przekazuje się obraz-tło — może to być bufor tła komponentu, albo tła komórki grida (jak u mnie). Parametr AStamp
to mała grafika, która zostanie wyrenderowana na obrazie ABuffer
— u mnie to grafika sprajtu, u Ciebie grafika konkretnej ikonki. ADestRect
to obszar docelowy — obszar w obrazie ABuffer
, w którym zostanie wyrenderowany AStamp
. Jego rozmiar możesz policzyć dowolnie, z zachowaniem proporcji lub bez, w zależności od tego jaki efekt chcesz uzyskać. Jeśli proporcje ADestRect
będą inne niż proporcje obrazu AStamp
, to zostanie on wyrenderowany z rozciągnięciem.
Parametr AAlpha
u mnie służy do tego, aby można było sprawdzić jak będzie wyglądał sprajt np. w trakcie zanikania obiektu. Jeśli przyjmie on wartość 255
, to AStamp
zostanie namalowany na ABuffer
z oryginalną przezroczystością. Natomiast jeśli będzie to mniej, to AStamp
będzie półprzezroczysty w zadanym stopniu. Dzięki temu mogę renderować sprajty na tle w formie szachownicy, uwzględniając oryginalny kanał alpha sprajtów oraz dodatkową przezroczystość (określaną suwakiem w edytorze).
Jednak tutaj trzeba zaznaczyć, że ta funkcja przeznaczona jest do renderowania jednego obrazu na drugim. Nieważne jaki format, byle oba były 32-bitowe — w pamięci wszystkie obrazy istnieją w formie bitmap, czyli bloków z danymi kanałów pikseli. Ja korzystam obecnie z klas TPortableNetworkGraphic
, ale bez problemu mogłyby to być klasy TBitmap
.
Czy byłbyś tak uprzejmy i zademonstrował jak użyć tego kodu?
Z ikonami nie miałem do tej pory czynienia, a klasa TIcon
to niestety smutny żart. Tak to jest, jak się wymusza ścieżkę dziedziczenia na potrzeby LCL/VCL i używa klasy bazowej pojedynczego obrazu do reprezentacji zbioru obrazów o różnych wymiarach i formatach (bo tym jest format .ico
).
Z tego co widziałem, jakoś da się zmieniać aktywny format ikony (chyba ustawiając TIcon.Current
na interesujący nas indeks grafiki) i wtedy ma się do niej dostęp z poziomu TIcon.BitmapHandle
lub można przekopiować taką bitmapę do pomocniczej bitmapki, za pomocą TempBitmap.Assign(MyIcon)
lub za pośrednictwem strumienia pomocniczego. Ogólnie dramat, ale raczej da się to zrobić.
Mając zgraną konkretną ikonkę do bitmapy pomocniczej, można tę bitmapkę namalować na innej (zwykłym Canvas.Draw
lub używając ScanLine
, aby mieć możliwość manipulowania pikselami) lub bezpośrednio na płótnie komponentu. Sam wszędzie używam tylnych buforów, bo renderowanie na płótnie nie dość, że nie daje możliwości szybkiego dostępu do danych jego pikseli, to w dodatku wymusza odmalowywanie płótna, co znacząco spowalnia proces renderowania. Dlatego stworzyłem własny grid, którego każda komórka ma bitmapkę jako tylny bufor, dzięki czemu odmalowanie komórki nie ma nic wspólnego z płótnem komponentu, a jeśli komponent musi zostać odmalowany, to wtedy tylko maluję tylne bufory na płótnie, co jest wysoce wydajne.
Mogę ci pokazać kod wyjęty z któregoś edytora, ale będzie go trochę. Jeśli potrzebujesz wyrenderować grafikę w formie podglądu w jakimś komponencie, to możesz użyć mojego kodu. Sam też korzystam z takiego podglądu — zaznaczony w drzewie plik jest wyświetlany w PaintBox
pod drzewkiem:
Jest to mniej więcej to czego potrzebujesz. Ale żeby można było użyć podanej wcześniej funkcji, PaintBox
też posiada tylny bufor w formie bitmapki — najpierw renderowane jest tło (np. szachownica lub jednolity kolor), następnie na tym renderowany jest sprajt, z rozciągnięciem. W zdarzeniu PaintBox.OnPaint
jedyne co robię to PaintBox.Canvas.Draw(Buffer, 0, 0)
i to samo dzieje się w gridach.
Jeśli chcesz samodzielnie manipulować pikselami, czyli przede wszystkim renderować jeden obraz na drugim z rozciągnięciem, to bez używania tylnych buforów, całość będzie działać bardzo wolno. U mnie to szczególnie istotne, bo przesuwanie suwaków ma natychmiast kolorować zawartość komórek gridów i zmieniać przezroczystość, dlatego renderowanie musi działać błyskawicznie. Poza tym, jak widać wyżej na gifie, mam wsparcie animacji w aktywnej komórce grida oraz opcję renderowania linii hot spotu, więc wydajność ma kluczowe znaczenie. No i dzięki zastosowaniu wszędzie tylnych buforów, ruszanie suwakami jest płynne, komórki bardzo szybko reagują na zmiany, a animowana komórka grida nie gotuje procesora — zużycie czasu CPU utrzymuje się na poziomie 1-2%, dla całego procesu edytora.