Wyświetlanie obrazków BMP

Jeśli przejrzeliście mój poprzedni kurs związany z grafiką, to umiecie już coś samodzielnie narysować.

Ale przecież w Internecie (i nie tylko) jest tyle ciekawych rysunków, nie mówiąc już o tych, które moglibyście stworzyć dla jakiegoś specjalnego celu, na przykład swojej własnej gry. Dlatego teraz pokażę, jak takie rysunki wyświetlać. Ze względu na prostotę formatu, wybrałem pliki typu BMP. Plik, który chcecie wyświetlić powinien mieć rozmiar 320x200 pikseli w 256 kolorach (jak pamiętamy, taki rysunek pasuje jak ulał do trybu graficznego 13h).
Wszystkie operacje na pliku zostały już przez mnie szczegółowo opisane w jednej z części mojego kursu, więc tutaj nie będziemy poświęcać im zbyt wiele uwagi.
Ale przejdźmy wreszcie do interesujących nas szczegółów.

Powinniście zaopatrzyć się w cokolwiek, co opisuje format BMP. Informacje, z których będę tutaj korzystał, znalazłem w Internecie (niestety, nie pamiętam już gdzie, ale możecie poszukać na Wotsit.org).
A oto nagłówek pliku BMP (składnia języka Pascal niestety, info: Piotr Sokolowski, 6 maja 1998r):


(przeskocz opis nagłówka)
	Type
 	 TBitMapHeader =
  	  Record
		bfType :             Word; (dwa bajty)
   		bfSize :             LongInt; (cztery bajty)
   		bfReserved :         LongInt;
   		bfOffBits :          LongInt;
   		biSize :             LongInt;
   		biWidth :            LongInt;
   		biHeight :           LongInt;
   		biPlanes :           Word;
   		biBitCount :         Word;
   		biCompression :      LongInt;
   		biSizeImage :        LongInt;
   		biXPelsPerMeter :    LongInt;
   		biYPelsPerMeter :    LongInt;
   		biClrUsed :          LongInt;
   		biClrImportant :     LongInt;
  	  End;

Gdzie:

Ale spokojnie - nie musicie znać tych wszystkich pól, bo my nie będziemy wszystkich używać. Ściśle mówiąc, nie będziemy używać ani jednego z tych pól!
No to po co to wszystko?
Po to, aby znać długość nagłówka pliku (54 bajty), który ominiemy przy analizie pliku.

Po nagłówku idzie paleta 256 kolorów * 4 bajty/kolor = kolejny 1kB. Jeśli macie jakieś wątpliwości co do tego 1 kilobajta, to słusznie. Oczywiście, do opisu koloru wystarczą 3 bajty (odpowiadające kolorom czerwonemu, zielonemu i niebieskiemu - RGB), co daje razem 768 bajtów. Co czwarty bajt nie zawiera żadnej istotnej informacji i będziemy go pomijać (zmienna z).

Zaraz po palecie idzie obraz, piksel po pikselu. Niestety, nie jest to tak logiczne ustawienie, jak byśmy sobie tego życzyli. Otóż, pierwsze 320 bajtów to ostatni wiersz obrazka, drugie 320 - przedostatni, itd. Dlatego trzeba będzie troszkę pokombinować.

Zanim jeszcze zaczniemy, należy się przyjrzeć, których portów (choć to samo można uzyskać wywołując odpowiednie przerwanie) i dlaczego będziemy używać (patrzymy do pliku ports.lst w Spisie Przerwań Ralfa Brown'a):


(przeskocz opis portów)
	03C8  RW  (VGA,MCGA) PEL address register (write mode)
                 Sets DAC in write mode and assign start of color register
                 index (0..255) for following write accesses to 3C9h.
                 Don't read from 3C9h while in write mode. Next access to
                 03C8h will stop pending mode immediatly.
	03C9  RW  (VGA,MCGA) PEL data register
                 Three consequtive reads (in read mode) or writes (in write
                 mode) in the order: red, green, blue. The internal DAC index
                 is incremented each 3rd access.
                  bit7-6: HiColor VGA DACs only: color-value bit7-6
                  bit5-0:                        color-value bit5-0

Czyli najpierw na port 3C8h idzie numer rejestru dla danego koloru (rejestrów jest 256 i kolorów też), a potem na 3C9h idą trzy wartości kolorów: czerwonego, zielonego i niebieskiego, których połączenie daje nam żądany kolor.

Ale dobierzmy się wreszcie do kodu:


(przeskocz program)
; Program wyświetla na ekranie kolorowy rysunek o rozmiarze
; 320x200 w 256 kolorach, umieszczony w pliku.
;
; nasm -O999 -o bmp1.com -f bin bmp1.asm
;
; Autor: Bogdan D., bogdandr (at) op (kropka) pl
;
; na podstawie kodu podpisanego "Piotr Sokolowski",
; napisanego w języku Pascal

org 100h

start:
	mov	ax, 13h
	int	10h	; uruchamiamy tryb graficzny 13h - 320x200x256

	mov	ax, 3d00h		; otwieramy plik tylko do odczytu
	mov	dx, nazwa_pliku
	int	21h

	jnc	otw_ok

	mov	ah, 9
	mov	dx, blad_plik		; wyświetlane, gdy wystąpił błąd
	int	21h

err:
	mov	ax, 4c01h		; wyjście z kodem błędu=1
	int	21h

otw_ok:
	mov	bx, ax			; bx = uchwyt do pliku
	mov	ah, 3fh			; czytanie z pliku
	mov	cx, 54			; wyrzucamy 54 bajty nagłówka
	mov	dx, smieci
	int	21h
	jc	err

; wczytywanie palety z pliku:


	xor	si, si			; indeks do tablicy "paleta"

czytaj_pal:
	mov	ah, 3fh			; czytanie z pliku
	mov	cx, 4			; czytam po 4 bajty - do b,g,r i z.
					; ("z" nas nie interesuje)
	mov	dx, b
	int	21h
	jc	err

					; ustawiamy paletę:
	mov	al, [r]
	shr	al, 2
	mov	[paleta+si], al		; paleta[si] = [r] / 4

	mov	al, [g]
	shr	al, 2
	mov	[paleta+si+1], al	; paleta[si] = [g] / 4

	mov	al, [b]
	shr	al, 2
	mov	[paleta+si+2], al	; paleta[si] = [b] / 4

	add	si, 3			; przejdź o 3 miejsca dalej -
					; na kolejne wartości RGB

	cmp	si, 256*3		; sprawdź, czy nie zapisaliśmy
					; już wszystkich kolorów
	jb	czytaj_pal

; wysyłanie palety do karty graficznej:

	xor	ax, ax
	xor	si, si			; SI = indeks do palety

	mov	dx, 3c8h		; port karty graficznej
wyslij_palete:
	out	dx, al			; wysyłamy numer rejestru,
					; wszystkie od 0 do 255

	inc	dx			; DX = port 3C9h

	push	ax
					; zapisujemy kolorki:
					; czerwony, zielony, niebieski.

	mov	al, [paleta+si]		; AL = czerwony
					; (patrz: pętla czytaj_pal)
	out	dx, al

	mov	al, [paleta+si+1]	; AL = zielony
	out	dx, al

	mov	al, [paleta+si+2]	; AL = niebieski
	out	dx, al

	pop	ax

	add	si, 3			; przejdź do następnych kolorów

	dec	dx			; DX z powrotem 3C8h

	inc	ax			; wybierzemy kolejny rejestr koloru
					; w karcie graficznej

	cmp	ax, 256			; sprawdź, czy już koniec pracy
	jb	wyslij_palete

; wczytywanie obrazka:

	mov	ax, 0a000h
	mov	ds, ax		; czytaj bezpośrednio do pamięci ekranu

	mov	dx, 64000-320		; DX = adres ostatniego wiersza

	mov	cx, 320			; z pliku czytamy po 320 bajtów

obrazek:
	mov	ah, 3fh
	int	21h			; czytaj 320 bajtów prosto na ekran
	jc	err

	sub	dx, 320			; przejdź do wcześniejszego wiersza
	jnc	obrazek			; dopóki nie musimy pożyczać
					; do odejmowania. Pożyczymy dopiero
					; wtedy, gdy DX < 320 - a to się
					; zdarzy dopiero, gdy DX = 0, czyli
					; przerobiliśmy cały obrazek i ekran.
					; Wtedy kończymy pracę.


; koniec programu:

	mov	ah, 3eh
	int	21h			; zamknij plik
	jc	err

	xor	ah, ah
	int	16h			; czekamy na klawisz

	mov	ax, 3
	int	10h			; powrót do trybu tekstowego

	mov	ax, 4c00h
	int	21h

; dane:


nazwa_pliku	db	"rys1.bmp",0
blad_plik	db	"Blad operacji na pliku!$"

smieci:		times	54	db 0
paleta:		times	768	db 0
b		db 0
g		db 0
r		db 0
z		db 0
kolor		db 0

Mam nadzieję, że kod jest dość jasny. Nawet jeśli znacie asemblera tylko w takim stopniu, w jakim to jest możliwe po przeczytaniu mojego kursu, zrozumienie tego programu nie powinno sprawić Wam więcej kłopotów niż mnie sprawiło przetłumaczenie go z Pascala.



Spis treści off-line (Alt+1)
Spis treści on-line (Alt+2)
Ułatwienia dla niepełnosprawnych (Alt+0)