Wyświetlanie obrazków BMP pod Linuksem

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 (bitmapy). Plik, który wyświetlimy, powinien mieć rozmiar 320x200 pikseli w 256 kolorach (można oczywiście wziąć dowolną inną rozdzielczość, ale trzeba wtedy dobrać tryb graficzny).
Wszystkie operacje na plikach 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, informacja: 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 jednego 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 w programie).

Zaraz po palecie jest 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ć.

W tym artykule też wykorzystam możliwości biblioteki SVGAlib, ze względu na prostotę jej opanowania. Aby móc z niej korzystać, musicie zainstalować pakiety svgalib oraz svgalib-devel lub po prostu samemu skompilować bibliotekę, jeśli pakiety nie są dostępne.

Do działania programów pod X-ami potrzebne mogą być uprawnienia do pliku /dev/console a pod konsolą tekstową - do pliku /dev/mem.

Zwróćcie uwagę na sposób kompilacji poniższego programu. Korzystamy z bibliotek dostępnych dla programistów języka C, więc do łączenia programu w całość najlepiej użyć GCC - zajmie się on dołączeniem wszystkich niezbędnych bibliotek. A skoro używamy gcc, to funkcja główna zamiast _start, musi się nazywać main - tak samo jak funkcja główna w programach napisanych w C. I tak samo, zamiast funkcji wychodzenia z programu, możemy użyć komendy RET, aby zamknąć program.

Ale dobierzmy się wreszcie do kodu (składnia FASM):


(przeskocz program)
; Program wyświetlający obrazek BMP pod Linuksem z wykorzystaniem SVGAlib
;
; Autor: Bogdan D., bogdandr (at) op.pl
;
; kompilacja:
;   fasm bmp.fasm
;   gcc -o bmp bmp.o -lvga

format ELF
section ".text" executable

public	main

G320x200x256	= 5
TEXT		= 0

extrn	vga_setmode
extrn	vga_drawscansegment
extrn	vga_setpalvec


main:
	mov	eax, 5		; otwieranie pliku
	mov	ebx, plik	; adres nazwy
	mov	ecx, 0		; odczyt
	mov	edx, 400o	; - R-- --- ---
	int	80h

	cmp	eax, 0		; czy błąd?
	jng	koniec

	mov	ebx, eax	; EBX = deskryptor

	mov	eax, 19		; zmiana pozycji w pliku
	mov	ecx, 54		; idziemy tyle bajtów...
	mov	edx, 0		; ...od początku pliku
	int	80h

	cmp	eax, 0
	jge	seek_ok

problem:			; tu trafiamy po błędzie obsługi pliku
	push	eax
	mov	eax, 6
	int	80h		; zamykamy plik
	pop	eax

	jmp	koniec

seek_ok:
	xor	esi, esi	; indeks do tablicy "paleta"

czytaj_pal:
	mov	eax, 3		; czytaj z pliku
	mov	ecx, b		; pod ten adres
	mov	edx, 4		; 4 bajty (zmienne b, g, r i z)
	int	80h

	cmp	eax, 0		; czy błąd?
	jl	problem

				; ustawiamy paletę:
	mov	al, [r]
	shr	al, 2		; dzielimy przez 4, ograniczając liczby do
				; przedziału 0-63
	and	eax, 3fh	; zerujemy pozostałe bity
	mov	[paleta+esi], eax     ; paleta[esi] = [r] / 4

	mov	al, [g]
	shr	al, 2
	and	eax, 3fh
	mov	[paleta+esi+1*4], eax ; paleta[esi] = [g] / 4

	mov	al, [b]
	shr	al, 2
	and	eax, 3fh
	mov	[paleta+esi+2*4], eax ; paleta[esi] = [b] / 4

	add	esi, 3*4	; przejdź o 3 miejsca dalej -
				; na kolejne wartości RGB
				; każde miejsce zajmuje 4 bajty

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

wyslij_palete:
	push	dword G320x200x256
	call	vga_setmode	; ustawiamy tryb graficzny:
				; 320x200 w 256 kolorach
	add	esp, 1*4	; zdejmujemy argument ze stosu

	push	dword paleta
	push	dword 256
	push	dword 0
	call	vga_setpalvec	; ustawiamy paletę barw
	add	esp, 3*4	; zdejmujemy argumenty ze stosu

	mov	edi, 200	; tyle linii wyświetlimy
obrazek:
	push	edi		; zachowaj EDI
	mov	eax, 3		; czytaj z pliku
	mov	ecx, kolor	; do tej zmiennej
	mov	edx, 320	; 320 bajtów (jedną linię obrazu)
	int	80h

	cmp	eax, 0
	jge	.dalej

				; w razie błędu wyłączamy tryb graficzny
	push	dword TEXT
	call	vga_setmode	; ustawiamy tryb tekstowy 80x25
	add	esp, 1*4

	jmp	problem
.dalej:

	push	dword 320	; tyle bajtów na raz wyświetlić
	dec	edi		; teraz EDI = numer linii na ekranie (0-199)
	push	edi		; numer linii, na której wyświetlić dane
	push	dword 0		; numer kolumny
	push	dword kolor	; dane do wyświetlenia
	call	vga_drawscansegment
	add	esp, 4*4

	pop	edi		; przywróć EDI

	dec	edi		; zmniejsz numer wyświetlanej linii
	jnz	obrazek		; jeśli EDI różne od zera, rób kolejne linie

	mov	eax, 6
	int	80h		; zamknij plik

	mov	eax, 3
	xor	ebx, ebx
	mov	ecx, z
	mov	edx, 1
	int	80h		; czekamy na klawisz

	push	dword TEXT
	call	vga_setmode	; ustawiamy tryb tekstowy 80x25
	add	esp, 1*4

	xor	eax, eax	; kod błędu=0 (brak błędu)
koniec:
	mov	ebx, eax
	mov	eax, 1
	int	80h		; wyjście z programu


section ".data" writeable

plik		db	"test.bmp", 0
paleta:		times 768*4 db 0
b		db 0
g		db 0
r		db 0
z		db 0
kolor:		times 320 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 kłopotów.


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