Merhaba. Aslında bu yazıya VGA Paleti serisinin ikinci yazısı olarak başlamıştım ama ikinci yazı için geliştirdiğim bitmap (.bmp) dosya görüntüleyici ve buna bağlı olarak bitmap dosyaları bu yazıda ayrı ele almaya karar verdim. Böylece konuyu dağıtmadan sonraki yazıda tekrar mod 13h'teki VGA palet işlemlerine döneceğim.
BMP Dosya Formatı
Önceki yazıda C ile mod 13h için küçük bir bmp dosya göstericisi geliştirdiğimden bahsetmiştim. Bitmap (bmp) dosyalar yapı olarak oldukça basitler. Dolayısıyla konunun tamamını bir yazıda ele alabilirim.
Teknik olarak BMP dosyalar RLE (run length encoding) ile sıkıştırılabilse de, sıkıştırılmış bir bmp dosya bulmak pek mümkün değil. BMP dosya formatı Microsoft ve IBM tarafından geliştirilmiş olmasına rağmen, hatırladığım kadarıyla MS Paint(brush) bile sıkıştırılmış BMP kaydedemiyordu. Bu nedenle RLE sıkıştırmasına hiç değinmeyeceğim.
Bu bölümde kodun ayrıntısına girmek istemesem de, dosya formatına ait yapıları kodun header dosyasındaki ( VGA13.H ) veri yapılarından açıklamam daha kolay olacak. Dosyanın en başında 'BM' dosya imzası var. bitmapfileheader yapısında tek önemli alan pixel verilerini gösteren OffsPixelArray göstericisi (pointer), yukarıdaki resimde 36h, 04h değerlerinin olduğu alan. Bundan sonra görselle ilgili verileri tutan bitmapimageheader geliyor. Ben verilerin dosya offsetlerini de bu dosyada belirttim.
Wikipedia'daki BMP dosya formatı maddesine göre, bitmapimageheader yapısının yedi farklı sürümü var. Benim de temel aldığım 40-byte uzunluktaki BITMAPINFOHEADER sürümü en yaygın sürüm. Ben kendi kodumu, GIMP de dahil, farklı görüntü işleme yazılımlarıyla kaydettiğim ve internetten indirdiğim toplam 23 dosyayla test ettim. Bu dosyalardan birini ekleyemiyorum, ancak 22 dosyalı test setini de linkledim. Bu arada GIMP, en son sürüm BITMAPV5HEADER'ı geliştirmiş olmasına rağmen kaydederken default bunu kullanmıyor.
Bitmap dosyalar dört farklı renk derinliğini destekliyor. Bunlar BitsPerPixel alanında kayıtlı. BitsPerPixel;
- 1 ise görsel siyah ve beyaz olmak üzere iki renklidir. Her byte 8 piksel içerir.
- 4 ise görsel onaltı renklidir ve bu onaltı renk için bir palet alanı da bulunur. Her byte iki piksel içerir.
- 8 ise görsel 256 renklidir. Dosya palet içerir. Pixel dizisinde her byte bir renk içerir.
- 24 ise görselin bir pikseli, her biti 8 bitten oluşan R, G, B üçlülerinden oluşur. Dosya palet içermez.
Ben sadece 8 bit per pixel görsellere odaklandım, çünkü hem mod 13h'le daha fazlasını zaten gösteremem, hem de palet yalnız 4 ve 8 bit renk derinliğinde var. Yazıdaki görselleri Wikipedia'da VGA maddesindeki, telin üzerinde oturan üç papağan görselini 320x200'e boyutlandırarak ürettim.
![]() |
| Doğrudan Link |
8-bitlik görseller için bir parantez açmam gerek. Mantıken gri tonlamalı palet, i indis olmak üzere i: (i, i, i) şeklinde. Haliyle paleti dosyaya kaydetmeye gerek yok. Ben, dosya 8-bit renk derinliğinde ve palet yoksa gri tonlamalı ele alınıyor, ama bazı programlar uyumluluk için paleti yine de dosya başlığına kaydediyor biliyordum. GIMP'le kaydettiğim gri tonlamalı görsel paletli kaydedildi ve paleti elle silip dört farklı programla denediğim halde dosyayı açamadım. Üstelik Wikipedia'da renk derinliği 8 veya daha küçük dosyalarda palet zorunlu diyor. Öte yandan test setindeki bmp_08.bmp dosyası bu tür paletsiz 8-bit dosyaya bir örnek. Kısacası, bu üzerinde anlaşmaya varılamamış bir konu. Kodlama açısından benim işime geldi, paletsiz durum için ekstra birşey yapmama gerek kalmadı. Benim kodum adı geçen dosyayı açıyor, ama gri tonlamalı paleti yüklemesi gerektiğini bilmediğinden default mod 13h paletiyle açıyor.
Palet verisi, bitmapimageheader'ı takip ediyor. bitmapfileheader'ın büyüklüğü sabit 14-byte. bitmapimageheader'ın büyüklüğüyse HeaderSize alanında veriliyor. Yani paletin offseti bu değere 14 eklenerek bulunabilir. Bu toplamın sonucu bitmapfileheader.OffsPixelArray değerine eşitse, paletin olmadığı anlaşılır (örn. 24-byte renk derinliği için). Her palet girdisi sırasıyla R,G,B,A'yı temsil eden 4 byte'dan oluşur. Ancak alfa kanalı ve transparanlık .bmp dosyalarda standart değildir. Bu nedenle A değeri her zaman sıfırdır. Toplamda 2BitsPerPixel kadar palet girdisi bulunur.
Paleti piksel verileri takip eder. Palet bitmapfileheader.OffsPixelArray ofsetinde bitmelidir. Burada pikseller soldan sağa ve aşağıdan yukarı kaydedilmiştir. Yani ilk veri sol alttaki piksele aittir. Eğer görselin genişliği dördün katı değilse, satırlar dördün katı olacak şekilde sıfırla tamamlanarak saklanır. Örn. 133 piksel genişlikteki bir görselde, veriyi 136'ya tamamlamak için her satırın sonunda üç tane 0 bulunur.
![]() |
| DOSBox Ekran görüntüsü |
BMP Gösterici
Yukarıda, programın ekran çıktısını gösteren bir örnek var.
Ben kodu 174. satırdaki main(...) fonksiyonundan başlayıp inceleyeceğim. Burada ilkin her iki header için bellekte statik olarak yer ayrılıyor (satır 177-178). Eğer bir bmp dosya programa parametre olarak verilmemişse, kullanıcıya dosyanın adını soruyor (satır 188) ve bu dosya okunmak üzere açılıyor (satır 193).
readFirstHeader(...) fonksiyonu dosyayı doğrudan bir veri yapısına okuyor ve dosya imzası 'BM'ye eşit değilse (satır 43) sıfırdan farklı bir hata değeriyle çıkıyor. Bu programı da sonlandırıyor (satır 199).
199. satırdaki readSecondHeader(...) fonksiyonu da benzer şekilde dosyadan ikinci başlığı veri yapısına okuyor. Burada kolaylık olsun diye showBMPinfo fonksiyonuyla (satır 22) dosyanın başlığını ekrana yazdırıyorum. Bu fonksiyon çağrısı istenirse kapatılabilir veya bir debug parametresine bağlanabilir. Eğer açılan dosya bizim gösterebileceğimiz özellikte değilse, örn. renk derinliği sekizden farklıysa veya sıkıştırma varsa, fonksiyon ve program bir hata değeriyle sonlanıyor. 69. satırda, dosyanın büyüklüğü 320x200'den büyükse ekrana bir uyarı yazılıyor ancak dosya açılıyor.
mode13() ve textmode() fonksiyonlarını kolaylık olması için assembly ile yazdım (satır 78 ve 86).
Yukarıda bitmapimageheader için aslında birden fazla sürüm olduğunu yazmıştım. Bu sürümlerin birçoğunun ortak noktası, genişlik, yükseklik ve renk derinliği alanları. Eğer başlık tesadüfen BITMAPINFOHEADER değilse (farklı uzunlukta bir başlıksa) readSecondHeader(...) fonksiyonu başlığı tamamen okuyamamış olacak. Bu nedenle 207. satırda olası okunmayan byte'lar atlanıp, dosya göstericisi paletin başına çekilerek setPalette(...) ile palet işlemlerine geçiliyor. Bu fonksiyonda palet, ColorCount adedinde 4-byte'lık parçalarla okunuyor. 105. satırda, ilk yazıda da anlattığım gibi renk indisi 0x3C8 portuna ve ardından rengin R,G,B değerleri 0x3C9 portuna yazılıyor. VGA 18-bit olduğu için 8-bit'lik değerler iki sağa kaydırılarak 6-bit'e dönüştürülüyor.
210. satırdaki fseek(...) yine BITMAPINFOHEADER'dan farklı header sürümlerinde doğru yeri okumak için var, aksi takdirde zaten dosya göstericisinde o anda firstHeader.OffsPixelArray değeri olmalı. setImage(...), piksel dizisini okuyup ekrana basmakla görevli. Bu fonksiyonu iki kere yazdım. 134. satırdaki ilk sürümde dosya byte-byte okunduğu için verimsizdi. İkinci sürümde görsel satır satır okunuyor. Bunun için genişliği, ceilTo4 ile dördün katı olacak şekilde yukarı yuvarlıyorum, ve bu büyüklükte bir bellek alanını ayırıyorum (satır 160). Sonra byte'ları dosyadan okuyup for döngüsü içerisinde (satır 167) ekrana yazdırıyorum. Burada PutPixel fonksiyonu yerine şöyle birşey yapılabilirdi:
push A000
pop es
mov ax, i
mov di,ax
shr ax,8
shr di,6
add di,ax
mov cx, c4
rep movsb
Bu kod parçası yalnızca bir taslak. linebuffer göstericisi DS:SI'ye yükleniyor. i, 320 ile çarpılıp (shr ile) DI'ye yükleniyor. Satır genişliği de CX'e yüklenip, rep movsb ile linebuffer blok blok VGA ekran belleğine yazılabilirdi. Ancak bu durumda MIN makrosunu da assembly'e uyarlamak gerekiyor. Bu şekilde, daha öncekinden bile hızlı çalışması gerekir ama bunu uygulamadım, çünkü C içinden assembly ve far pointer'larla uğraşmak istemedim, yoksa debugging süreleri inanılmaz uzun sürebilirdi.


