在 DOS 時代,直接操作 VGA 顯示記憶體(0xA0000)是圖形程式設計的核心技能。本文提供完整的 C 語言實作,展示如何在 320×200、256 色模式(Mode 0x13)下載入並顯示未壓縮的 8 位 BMP 檔案,並深度解析 BMP 檔案格式結構。
#define VGA256 0x13 // 320x200 256色模式
#define TEXT_MODE 0x03 // 80x25 文字模式
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
// VGA 調色板埠
#define PALETTE_MASK 0x3C6
#define PALETTE_REGISTER_RD 0x3C7
#define PALETTE_REGISTER_WR 0x3C8
#define PALETTE_DATA 0x3C9
// 顯示緩衝區基址
unsigned char far *video_buffer = (char far *)0xA0000000L;
// BMP 檔案頭 (14 bytes)
typedef struct BMP_file {
unsigned int bfType; // 固定 0x4D42 ('BM')
unsigned long bfSize; // 檔案大小
unsigned int Reserved1; // 保留 0
unsigned int Reserved2; // 保留 0
unsigned long bfOffset; // 資料區偏移
} bitmapfile;
// BMP 資訊頭 (40 bytes)
typedef struct BMP_info {
unsigned long biSize; // 資訊頭大小 (40)
unsigned long biWidth; // 圖寬
unsigned long biHeight; // 圖高
unsigned int biPlanes; // 色板數 (1)
unsigned int biBitCount; // 色深 (1/4/8/24/32)
unsigned long biCompression; // 壓縮類型 (0=無壓縮)
unsigned long biSizeImage; // 影像資料大小
unsigned long biXPelsPerMeter; // 水平解析度
unsigned long biYPelsPerMeter; // 垂直解析度
unsigned long biClrUsed; // 實際使用顏色數
unsigned long biClrImportant; // 重要顏色數
} bitmapinfo;
// 調色板項目 (4 bytes)
typedef struct RGB_BMP_typ {
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char reserved;
} RGB_BMP, *RGB_BMP_ptr;
// 完整 BMP 非資料區結構
typedef struct bmp_picture_typ {
bitmapfile file;
bitmapinfo info;
RGB_BMP palette[256];
} bmp_picture, *bmp_picture_ptr;
// 設定單一調色板暫存器
void Set_BMP_Palette_Register(int index, RGB_BMP_ptr color) {
outp(PALETTE_MASK, 0xFF);
outp(PALETTE_REGISTER_WR, index);
outp(PALETTE_DATA, color->red >> 2); // 8-bit → 6-bit (VGA DAC)
outp(PALETTE_DATA, color->green >> 2);
outp(PALETTE_DATA, color->blue >> 2);
}
// 驗證 BMP 格式
void Check_Bmp(bmp_picture_ptr bmp_ptr) {
if (bmp_ptr->file.bfType != 0x4D42) { printf("Not a BMP file!\n"); exit(1); }
if (bmp_ptr->info.biCompression != 0) { printf("Can not display compressed BMP!\n"); exit(1); }
if (bmp_ptr->info.biBitCount != 8) { printf("Not a 256 color BMP file!\n"); exit(1); }
}
void BMP_Load_Screen(char *bmp) {
int fp, i, j, n_bytes;
bmp_picture bmp256;
unsigned char line_buf[SCREEN_WIDTH];
unsigned char far *line_ptr;
if ((fp = open(bmp, O_RDONLY)) == -1) { printf("Can not open file:%s", bmp); exit(1); }
read(fp, &bmp256.file, sizeof(bitmapfile));
read(fp, &bmp256.info, sizeof(bitmapinfo));
Check_Bmp(&bmp256);
// 讀取調色板 (256 × 4 bytes)
for (i = 0; i < 256; i++) {
read(fp, &bmp256.palette[i].blue, 1);
read(fp, &bmp256.palette[i].green, 1);
read(fp, &bmp256.palette[i].red, 1);
read(fp, &bmp256.palette[i].reserved, 1);
}
for (i = 0; i < 256; i++)
Set_BMP_Palette_Register(i, &bmp256.palette[i]);
// 顯示位圖:BMP 掃描行由下而上,VGA 由上而下
for (i = SCREEN_HEIGHT - 1; i >= 0; i--) {
lseek(fp, -(long)(SCREEN_HEIGHT - i) * SCREEN_WIDTH, SEEK_END);
n_bytes = read(fp, line_buf, SCREEN_WIDTH);
line_ptr = video_buffer + (SCREEN_HEIGHT - i - 1) * SCREEN_WIDTH;
for (j = 0; j < n_bytes; j++)
line_ptr[j] = line_buf[j];
}
close(fp);
}
void Set_Video_Mode(int mode) {
union REGS inregs, outregs;
inregs.h.ah = 0;
inregs.h.al = (unsigned char)mode;
int86(0x10, &inregs, &outregs); // BIOS 影片中斷
}
void main() {
Set_Video_Mode(VGA256);
BMP_Load_Screen("256.bmp");
getch(); // 等待按鍵
Set_Video_Mode(TEXT_MODE); // 恢復文字模式
}
| 欄位 | 大小 | 說明 |
|---|---|---|
| bfType | WORD (2) | 固定 'BM' (0x4D42),小端序存儲為 42 4D |
| bfSize | DWORD (4) | 整個檔案大小 (bytes) |
| bfReserved1/2 | WORD (2+2) | 保留,必為 0 |
| bfOffBits | DWORD (4) | 像素資料相對檔案開頭的偏移量 |
| 欄位 | 關鍵值 | 說明 |
|---|---|---|
| biSize | 40 | 資訊頭大小 |
| biWidth/biHeight | 像素 | 圖片寬高,高度為正表示底部在前 (BMP 特有) |
| biPlanes | 1 | 固定為 1 |
| biBitCount | 1/4/8/24/32 | 色深:8=256色,24=真彩色 |
| biCompression | 0/1/2 | 0=無壓縮,1=RLE8,2=RLE4 |
| biClrUsed | 0/2^n | 0 表示使用完整調色板 (2^biBitCount) |
DataSizePerLine = ((biWidth * biBitCount + 31) / 32) * 4DataSize = DataSizePerLine * biHeight每像素 1 byte,直接作為調色板索引。讀取索引 4 → 調色板第 5 項 → RGB 值。
此程式展示了 DOS 時代「直擊硬體」的程式設計風格:直接操作記憶體映射 I/O、BIOS 中斷、連接埠 I/O。雖然現代作業系統禁止使用者程式直接存取硬體,但理解這些底層機制有助於:
BMP 格式雖老舊,但其無壓縮、結構簡單的特性,使其成為教學、嵌入式圖形、簡易圖片處理的首選格式。