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