Pascal


Jak zmienić wygląd kursora w trybie graficznym?

Czasami chcielibyśmy, aby w trybie graficznym zamiast standardowej strzałki pojawił się inny wzór. Jest to możliwe dzięki assemblerowi. Aby zmienić wygląd kursora graficznego skorzystamy z przerwania 33h.

Przypatrzmy się jak ono wygląda:

INT 33 - MS MOUSE v3.0+ - DEFINIUJ KURSOR GRAFICZNY
AX = 0009h
BX = kolumna hot spot'u (-16 do 16) (współrzędna X)
CX = wiersz hot spot'u (-16 do 16) (współrzędna Y)
ES:DX -> opis masek

Hot spot to punkt pozycji, czyli ten piksel naszego wzoru, który będzie wskazywać pozycję naszego kursora (przy strzałce jest to czubek tej strzałki).

Wzór kursora zapisany jest jako grafika o wymiarach 16x16 pikseli, przy czym dzięki masce możemy dokładnie określić jak będą się zachowywały poszczególne piksele, czy będą wypełnione na biało, czarno, czy też będą przeźroczyste.

Opis masek, który wrzucimy pod ES:DX składa się z ciągu 32 słów (1 słowo to 2 bajty). Pierwsze 16 słów to opis maski ekranu a kolejne 16 to opis maski kursora. Jest więc to tablica 32 elementowa, a każdy element jest typu WORD (ang. word - słowo).

Wyświetlanie kursora przebiega następująco

Piksele przykryte przez kursor są AND'owane (koniunkcja) z maską ekranu, a następnie XOR'owane (alternatywa wyłączna) z maską kursora.

Wygląda to zapewne trochę zawile, ale zaraz postaram się to dokładnie wyjaśnić.

Hot spot ustawimy na końcu. Najpierw zajmijmy się maskami ekranu i kursora. Jak już mówiłem opis maski to 16 słów. Każde słowo to 2 bajty, a więc 16 bitów (2 razy po 8). Każdy bit odpowiada jednemu pikselowi naszej maski. Żeby lepiej sobie to wyobrazić jest to tak jakby tablica 16x16 bitów określająca, które piksele należą do maski. Nasz kursor ma dokładnie takie same wymiary - 16x16. Jeśli chcemy, aby dany piksel należał do maski musimy ustawić odpowiedni bit na 1, a jeśli ma nie należeć do maski to oczywiście na 0.

Przykładowa maska może wyglądać tak:

0000000000000000
0100000000000000
0110000000000000
0111000000000000
0111100000000000
0111110000000000
0111111000000000
0111111100000000
0111111110000000
0111110000000000
0110110000000000
0100011000000000
0000011000000000
0000011000000000
0000001100000000
0000000000000000

Można powiedzieć, że jest to utworzony pewien wzór - jedynki wskazują na piksele włączone do maski. Każda linia składa się jak widać z 16 bitów, a więc jest to jedno słowo. Najmłodszy bit w każdym słowie (skrajny prawy) odpowiada za najbardziej wysunięty na prawo piksel w danej linii. A więc najstarszy bit (skrajny lewy) pierwszego słowa naszego opisu maski odpowiada za lewy górny róg naszego wzoru.

Zatem czym różni się maska ekranu od maski kursora?

Zauważmy, że nasz kursor to w zasadzie prostokąt o wymiarach 16x16, my jednak chcemy żeby nie miał on kształtu prostokąta tylko np. strzałki, ręki, krzyżyka, klepsydry, itp. Zatem niektóre piksele muszą być w tym prostokącie przeźroczyste. I do tego właśnie służy maska ekranu. Włączamy do maski te piksele, które mają być przeźroczyste (ustawiamy odpowiednie bity). Z kolei w masce kursora zaznaczamy, które piksele będą tworzyły kursor.

Przykładowo, jeśli chcemy utworzyć wzór w kształcie strzałki, to do maski kursora wstawiamy piksele, które utworzą kształt strzałki, a wszystkie pozostałe piksele wstawiamy do maski ekranu.

Dodatkowo, jeśli jakiś piksel nie będzie należał ani do maski ekranu ani do maski kursora pozostanie on cały czas czarny (lub będzie miał kolor tła ustawionego za pomocą SetBkColor), co umożliwia stworzenie czegoś w rodzaju obwódki dookoła kursora. Jest to bardzo pomocne, gdyż kursor jaki tworzymy będzie miał zawsze biały kolor, zatem bez obwódki o innym kolorze nie byłby widoczny na białym tle.

Może to trochę skomplikowane, ale wystarczy zapamiętać, że:

Piksele należące do maski kursora będą cały czas białe (utworzą kursor).

Piksele należące do maski ekranu będą przeźroczyste i pokażą to, co jest pod kursorem.

Piksele nie należące do żadnej maski będą cały czas czarne lub koloru ustawionego tła (utworzą obwódkę).

Jeśli piksel będzie należał zarówno do maski ekranu jak i maski kursora, wtedy piksel pod kursorem będzie miał odwrotny kolor.

W jaki sposób odbywa się wygenerowanie kursora? Wyświetlając go na ekranie komputer postępuje następująco - dla każdego piksela znajdującego się pod kursorem (czyli tym prostokącikiem 16x16) wykonuje następujące operacje:

  • Pobiera kolor piksela narysowanego na ekranie.
  • Wykonuje operację AND na kolorze tego piksela i liczbie złożonej z samych 1 (w systemie binarnym), jeśli bit maski ekranu dla tego piksela jest ustawiony na 1 lub liczbie złożonej z samych 0 jeśli bit maski ekranu dla tego piksela jest ustawiony na 0.
  • Następnie wynik XOR'uje z: liczbą złożoną z samych 1 jeśli bit maski kursora dla tego piksela jest ustawiony na 1 lub liczbą złożoną z samych 0 jeśli bit maski kursora dla tego piksela jest ustawiony na 0

Najlepiej będzie to przedstawić rozpatrując każdy przypadek dla pojedynczego piksela:

W Pascalu w module graph wykorzystywane jest 16 kolorów, a więc do jego zapisu wystarczą 4 bity. Kolor biały ma wartość 1111 (dziesiętne 15), a kolor czarny 0000 (dziesiętne 0). Popatrzmy, co dzieje się z pojedynczym pikselem o dowolnym kolorze (w naszym przykładzie będzie to kolor o wartości 0110) jeśli zostanie przykryty przez kursor:

A.

Jeśli dany piksel ma należeć tylko do maski kursora: (maska kursora: bit = 1; maska ekranu: bit = 0)

  • Kolor piksela: 0110
  • 0110 AND 0000 = 0000
  • 0000 XOR 1111 = 1111

Otrzymaliśmy wynik 1111, czyli dziesiętne 15, a więc bez względu jaki kolor miał nasz piksel pod kursorem to teraz i tak będzie biały.

B.

Jeśli dany piksel ma należeć tylko do maski ekranu: (maska kursora: bit = 0; maska ekranu: bit = 1)

  • Kolor piksela: 0110
  • 0110 AND 1111 = 0110
  • 0110 XOR 0000 = 0110

Jak widać wynikiem jest kolor naszego piksela, a więc jego kolor się nie zmieni (uzyskamy efekt jakby nasz kursor był w tym miejscu przeźroczysty).

C.

Jeśli dany piksel nie należy do żadnej maski: (maska kursora: bit = 0; maska ekranu: bit = 0)

  • Kolor piksela: 0110
  • 0110 AND 0000 = 0000
  • 0000 XOR 0000 = 0000

Uzyskaliśmy wynik 0000, czyli kolor czarny, a więc piksel w tym miejscu naszego kursora będzie zawsze czarny.

D.

Jeśli dany piksel należy do obu masek: (maska kursora: bit = 1; maska ekranu: bit = 1)

  • Kolor piksela: 0110
  • 0110 AND 1111 = 0110
  • 0110 XOR 1111 = 1001

Wynikiem jest kolor o wartości 1001, nie jest to zatem ani kolor tego piksela, ani też biały lub czarny. Jest to po prostu kolor, którego bity mają odwrotne wartości.

Na koniec pozostaje ustawić hot spot, czyli punkt wskazujący położenie kursora. Co ciekawe może on leżeć poza kursorem! Jego położenie określamy względem lewego górnego piksela kursora, który ma współrzędne (0,0). Jeśli chcemy aby hot spot znajdował się o jeden piksel na lewo od górnego lewego rogu kursora, ale w tym samym wierszu to ustawiamy go na (-1,0), gdzie -1 to oczywiście kolumna, a 0 to wiersz. Gdy chcemy, aby hot spot znajdował 3 piksele na prawo i 2 piksele w dół ustawiamy współrzędne na (3,2) itd.

A teraz jak to zrealizować w Pascalu?

Aby móc w miarę wygodnie tworzyć nowe kursory wybrałem następujący sposób:

Tworzymy typ MaskString = Array[1..16] of String [16], czyli w zasadzie tablicę 16x16 elementów typu char. Maska jest tutaj przechowywana w formie znaków ASCII. Poszczególne elementy tablicy zawierają łańcuchy znaków opisujące kolejne linie kursora, a każdy ze znaków tego łańcucha informuje, do której maski zaliczyć dany piksel. Aby wstawić piksel do maski kursora ustawiamy znak na '*' a do maski ekranu znak '.', piksele bez maski to znak ' ' (spacja). Definiowanie nowego kursora może wyglądać tak:

Standard :MaskString = ( ('.. '), ('.*. '), ('.**. '), ('.***. '), ('.****. '), ('.*****. '), ('.******. '), ('.*******. '), ('.********. '), ('.*****.... '), ('.**.**. '), ('.*. .**. '), (' . .**. '), (' .**. '), (' .**. '), (' .. '));

Widać od razu, że będzie to kursor w kształcie strzałki z czarną obwódką dookoła.

W osobnej tabeli ustawiony jest hot spot, który dla tego kursora ma współrzędne (-1,-1).

Tworzymy także typ MaskBitmap = Array[1..32] of Word, który zawiera nasze maski - kursora i ekranu - w postaci liczbowej i który wykorzystamy przy korzystaniu z przerwania 33h (tablica tego typu będzie wrzucona do ES:DX).

Elementy tej tablicy o indeksach 1..16 zawierają opis maski ekranu, a elementy o indeksach 17..32 - opis maski kursora. Wszystkie elementy tej tablicy mają wielkość słowa, czyli 16 bitów i opisują kolejne linie naszego prostokąta zawierającego kursor. Jedno słowo zawiera informacje o pikselach jednej linii, a każdy bit tego słowa odpowiada za 1 piksel (gdzie najmłodszy bit odpowiada skrajnemu prawemu pikselowi).

Teraz należy przekonwertować typ MaskString na MaskBitmap

Na początek ustawiamy wszystkie bity w masce ekranu na 1 (czyli elementy tablicy typu MaskBitmap o indeksach 1..16 na wartość FFFFh), a bity maski kursora na 0 (czyli wszystkie elementy tej samej tablicy, ale o indeksach 17..32 na 0). Teraz dla każdego znaku poszczególnych stringów w tabeli typu MaskString wykonujemy:

  • Jeśli mamy znak '*' wstawiamy go na odpowiedniej pozycji do maski kursora i wyłączamy z maski ekranu (zmieniamy odpowiedni bit maski kursora z 0 na 1 i zmieniamy bit maski ekranu z 1 na 0).
  • Jeśli mamy znak '.' wyłączamy piksel z maski ekranu (zmieniamy odpowiedni bit maski ekranu z 1 na 0) - teraz dany piksel nie należy do żadnej maski, czyli będzie tworzył czarną obwódkę.

Reszta pikseli (znaki ' ') będzie należeć tylko do maski ekranu, a więc będą przeźroczyste.

Jak zmienić bit z 0 na 1 lub odwrotnie?

Wystarczy zwiększyć lub zmniejszyć liczbę z tabeli MaskBitmap o odpowiednią wielokrotność 2, odpowiadająca numerowi pozycji bitu w liczbie (poszczególne bity numerujemy od prawej zaczynając od 0).

Przykładowo mamy liczbę 0011 (dziesiętne 3) i chcemy ustawić jej pierwszy najstarszy bit (ten na pozycji 3). Wystarczy, że dodamy do tej liczby liczbę 2^3 czyli 8 (3 + 8 = 11, a 11 binarnie to 1011 - ustawiliśmy najstarszy bit), a teraz chcemy w tej liczbie zmienić skrajny prawy bit (najmłodszy o pozycji 0 na wartość 0) - odejmujemy od naszej liczby liczbę 2^0 czyli 1 (11 - 1 = 10, a 10 to w postaci binarnej właśnie 1010 - zmieniliśmy najmłodszy bit).

Trzeba pamiętać o tym, że jeśli zmienimy kolor tła procedurą SetBkColor, to jednocześnie zmieni się kolor obwódki. Zatem jeśli chcemy mieć czarną obwódkę, a np. szare tło, to tło należy utworzyć poprzez narysowanie wypełnionego na szaro prostokąta wielkości całego ekranu (procedura Bar).

Jak zwykle dołączam kody źródłowe. Plik Cursors.pas zawiera kod źródłowy modułu umożliwiający zmianę kursorów (dodałem kilka wzorów) - można go łatwo przerobić dodając własne kursory. Dodałem także moduł Mouse.tpu konieczny do uruchomienia programu demonstracyjnego - Demo.pas.

Gdybyście mieli jakieś pytania - piszcie na maila.

Dodał: ifrost​, www
Dział: Pascal


 

ComputerSun.pl na FaceBooku
Polecamy lekturę:

Word 2007 PL. Pierwsza pomoc



X

Zapisz się na biuletyn serwisu ComputerSun.pl, aby otrzymać poradnik:

Zabezpieczanie sieci bezprzewodowych. Przydatne wskazówki jak chronić sieć domową przed intruzami

Imię:  
Email:
Tak, akceptuję Politykę Prywatności