Scroll

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:

  1. Utworzymy czcionkę, którą będziemy rysować tekst.
  2. Pobierzemy wymiary tego tekstu w pixelach.
  3. Utworzymy bitmapę przeznaczoną na tekst.
  4. Ustalimy kolor tekstu i kolor tła (który potem będzie przezroczysty).
  5. Wypiszemy tekst w bitmapie.

Rysowanie:

  1. 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

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *