W tym tutorialu zajmiemy się rysowaniem przesuwanego tekstu. Punktem wyjścia jest tekst „Grafika w oknie – bitmapy i blittowanie”. Bez zbędnych wstępów przejdźmy od razu do rzeczy. Oto w jaki sposób będziemy rysować scrolla:
Inicjalizacja:
- Utworzymy czcionkę, którą będziemy rysować tekst.
- Pobierzemy wymiary tego tekstu w pixelach.
- Utworzymy bitmapę przeznaczoną na tekst.
- Ustalimy kolor tekstu i kolor tła (który potem będzie przezroczysty).
- Wypiszemy tekst w bitmapie.
Rysowanie:
- W każdej klatce animacji będziemy blittować odpowiedni fragment bitmapy z tekstem na bitmapę lpBackBitmap.
Przejdźmy do punktu pierwszego. Czcionkę tworzymy za pomocą funkcji CreateFont. Nie będę tutaj opisywał jej parametrów, gdyż nie jest to przedmiotem tego tutoriala. Uchwyt czcionki zwrócony przez tę funkcję zapamiętujemy w zmiennej hFont.
invoke CreateFont,14,8,0,0,FW_NORMAL,0,0,0,DEFAULT_CHARSET,\ 0,0,0,0,addr Verdana mov hFont,eax
Do pobierania wysokości i szerokości tekstu w pixelach służy funkcja GetTextExtentPoint32. Wymaga ona następujących parametrów:
HDC hdc - uchwyt device contextu LPCTSTR lpString - adres tekstu, którego wymiary mają być podane int cbString - ilość znaków tego tekstu LPSIZE lpSize - adres struktury SIZE, w której zostaną zapisane wymiary tekstu
Ponieważ SIZE jest słowem kluczowym Macro Assemblera, struktura SIZE nosi w nim nazwę SIZEL i definiuje się ją następująco:
SIZEL STRUCT x DWORD ? y DWORD ? SIZEL ENDS
Zadeklarujmy zatem tekst, który będzie scrollowany oraz strukturę SIZEL.
.data ScrollTekst db "Przykładowy scrollowany tekst...\ bla, bla, bla...",0 .data? WymiaryTekstu SIZEL <>
Mamy już wszystkie parametry wymagane przez GetTextExtentPoint32 oprócz hdc. Utwórzmy zatem device context podobnie jak w poprzednich tutorialach i wybierzmy dla niego czcionkę, którą przed chwilą utworzyliśmy (musimy tak zrobić, ponieważ funkcja GetTextExtentPoint32 odczytuje z podanego przez nas device contextu wszystkie niezbędne jej właściwości tekstu, w tym również czcionkę).
invoke CreateCompatibleDC,0 mov hScrollDC,eax invoke SelectObject,eax,hFont
Teraz możemy już wywołać GetTextExtentPoint32.
invoke GetTextExtentPoint32,hScrollDC,addr ScrollTekst,\ sizeof ScrollTekst-1,addr WymiaryTekstu
A skoro znamy wymiary tekstu, możemy już utworzyć bitmapę (analogicznie jak robiliśmy to do tej pory).
mov bmi.bmiHeader.biSize,sizeof BITMAPINFOHEADER mov eax,WymiaryTekstu.x mov bmi.bmiHeader.biWidth,eax mov eax,WymiaryTekstu.y neg eax mov bmi.bmiHeader.biHeight,eax mov bmi.bmiHeader.biPlanes,1 mov bmi.bmiHeader.biBitCount,32 mov bmi.bmiHeader.biCompression,BI_RGB invoke CreateDIBSection,hScrollDC,addr bmi,\ DIB_RGB_COLORS,addr lpScrollBitmap,0,0 mov hScrollBitmap,eax invoke SelectObject,hScrollDC,eax
Ustawmy teraz kolor tekstu i tła. Służą do tego funkcje SetTextColor oraz SetBkColor. Każdej z nich musimy przekazać uchwyt DC oraz kolor w formacie 00BBGGRRh.
invoke SetTextColor,hScrollDC,0000FF00h invoke SetBkColor,hScrollDC,00000000h
Teraz możemy narysować tekst w naszej bitmapie. Służy do tego funkcja TextOut.
HDC hdc - uchwyt DC int nXStart int nYStart - współrzędne miejsca w którym ma się zacząć rysowanie tekstu LPCTSTR lpString - adres stringa do narysowania int cbString - ilość znaków w tym stringu invoke TextOut,hScrollDC,0,0,addr ScrollTekst,sizeof ScrollTekst-1
Teraz, gdy bitmapę z tekstem mamy już gotową, możemy przejść do rysowania. Najpierw jednak zadeklarujmy zmienną ScrollPos, która będzie przechowywać aktualną pozycję scrolla. Na początku niech będzie ona równa szerokości okna, czyli WND_WIDTH (zaraz wyjaśni się dlaczego tak). W każdej klatce animacji będziemy ją zmniejszać o jeden.
.data ScrollPos dd WND_WIDTH
Popatrzmy teraz na poniższy rysunek. Czerwony prostokąt oznacza bitmapę ze scrollem (lpScrollBitmap), a granatowy lpBackBitmap. Żółtą ramką oznaczono fragment lpScrollBitmap, który jest w danym momencie widoczny. Na zielono zaznaczyłem również wartości odpowiadające odpowiednim zmiennym (tylko na niektórych rysunkach, bo na pozostałych mi się nie chciało :p) (tych zmiennych będziemy używać za chwilę). Rysunek przedstawia trzy przypadki, które musimy rozpatrzyć. Dodatkowo w każdym z tych przypadków widać co się dzieje gdy bitmapa ze scrollem jest krótsza niż okno (lewa strona rysunku) oraz gdy lpScrollBitmap jest dłuższa niż okno (prawa strona rysunku). W pierwszym przypadku scroll „wchodzi” na scenę, w drugim jest widoczny cały lub też zajmuje całe okno, natomiast w trzecim „schodzi” ze sceny.
Do blittowania scrolla będziemy używać procedury TransBltPart32, która blittuje fragment jednej bitmapy na inną, uwzględniając kolor przezroczysty. Jej kod znajduje się w archiwum dołączonym do tego tekstu. Wymaga ona następujących parametrów:
DstBits:dword - bitmapa docelowa SrcBits:dword - bitmapa źródłowa SrcWidth:dword - szerokość bitmapy źródłowej DstWidth:dword - szerokość bitmapy docelowej DstX:dword - DstY:dword - współrzędne punktu w którym ma zostać umieszczony fragment bitmapy źródłowej SrcX:dword SrcY:dword - współrzędne lewego-górnego narożnika prostokątnego fragmentu bitmapy źródłowej SrcW:dword - jego szerokość... SrcH:dword - ...i wysokość ColorKey:dword - kolor przezroczysty
DstBits, SrcBits, SrcWidth i DstWidth są nam znane. DstX, SrcX i SrcW będziemy musieli obliczać za każdym razem, ponieważ te wartości zmieniają się podczas ruchu scrolla. DstY ustawimy na 88, żeby scroll był na środku okna. SrcY zawsze będzie równa 0, a SrcH zawsze będzie wynosić tyle ile wysokość bitmapy ze scrollem. ColorKey ustawimy na 00000000h ponieważ taki jest kolor tła tekstu.
Zastanówmy się jak obliczyć wartości DstX, SrcX i SrcW na podstawie zmiennej ScrollPos, która – przypomnijmy – na początku jest równa WND_WIDTH i zmniejsza się o jeden przy każdej klatce animacji. Popatrzmy jeszcze raz na rysunek. Widzimy, że w pierwszym przypadku:
SrcX = 0 DstX = ScrollPos SrcW = WND_WIDTH - DstX
W drugim przypadku jest już gorzej, ponieważ zmienne mają inne wartości gdy bitmapa ze scrollem jest krótsza niż okno a inne gdy jest dłuższa.
a) lpScrollBitmap krótsza niż okno:
SrcX = 0 DstX = ScrollPos SrcW = szerokość lpScrollBitmap = WymiaryTekstu.x
b) lpScrollBitmap dłuższa niż okno: (w tym przypadku ScrollPos będzie mniejsze od 0)
SrcX = -ScrollPos DstX = 0 SrcW = WND_WIDTH
Trzeci przypadek jest już prosty (tutaj również ScrollPos<0):
SrcX = -ScrollPos DstX = 0 SrcW = WymiaryTekstu.x + ScrollPos
Dodatkowo jeśli okaże się, że SrcW jest mniejsze od 1, to znak, że scroll wyjechał już z okna i trzeba zacząć od początku.
Analizując powyższe zapiski dostrzeżemy, że przypadek 1 i 2a oraz 3 i 2b są do siebie bardzo podobne. Całość można więc zapisać w ten sposób (pseudokod):
if (ScrollPos>=0) { SrcX = 0 DstX = ScrollPos SrcW = WND_WIDTH - DstX if (SrcW>WymiaryTekstu.x) SrcW=WymiaryTekstu.x } else { SrcX = -ScrollPos DstX = 0 SrcW = WymiaryTekstu.x + ScrollPos if (SrcW>WND_WIDTH) SrcW=WND_WIDTH }
Teraz przepiszmy to na assemblera:
dec ScrollPos ;zmniejszamy ScrollPos o 1 ;eax-DstX ;ecx-SrcX ;edx-SrcW cmp ScrollPos,0 jl @F ;ScrollPos>=0 xor ecx,ecx ;SrcX=0 mov eax,ScrollPos ;DstX=ScrollPos mov edx,WND_WIDTH sub edx,eax ;SrcW=WND_WIDTH - DstX cmp edx,WymiaryTekstu.x jna blit ;if (SrcW>WymiaryTekstu.x) SrcW=WymiaryTekstu.x mov edx,WymiaryTekstu.x jmp blit @@: ;ScrollPos<0 xor eax,eax ;DstX=0 mov ecx,ScrollPos neg ecx ;SrcX=-ScrollPos mov edx,WymiaryTekstu.x add edx,ScrollPos ;SrcW=WymiaryTekstu.x + ScrollPos cmp edx,WND_WIDTH jna @F ;if (SrcW>WND_WIDTH) SrcW=WND_WIDTH mov edx,WND_WIDTH @@: cmp edx,1 jnle blit ;jeżeli SrcW<=1 zacznij scrolla od początku mov ScrollPos,WND_WIDTH blit: ;blittowanie scrolla invoke TransBltPart32,lpBackBitmap,lpScrollBitmap,\ WymiaryTekstu.x,WND_WIDTH,eax,88,ecx,0,edx,\ WymiaryTekstu.y,00000000h
W ten sposób otrzymaliśmy poruszający się napis.
Załącznik:
czajnick_gdiscroll.rar