16 位實模式 DOS(Turbo C / Borland C++),直接操作硬體:
0xA000:0000 (VGA 模式 13h,320×200×256 色)0x3C6-0x3C9INT 0x10 設置視頻模式open/read/lseek/close (低級 POSIX 風格)// 位圖文件頭 (14 字節)
typedef struct {
uint16_t bfType; // 'BM' (0x4D42)
uint32_t bfSize; // 文件大小
uint16_t bfReserved1; // 0
uint16_t bfReserved2; // 0
uint32_t bfOffBits; // 數據偏移
} BITMAPFILEHEADER;
// 位圖信息頭 (40 字節,BITMAPINFOHEADER)
typedef struct {
uint32_t biSize; // 40
int32_t biWidth; // 寬
int32_t biHeight; // 高 (正數=底部優先)
uint16_t biPlanes; // 1
uint16_t biBitCount; // 色深:1/4/8/24/32
uint32_t biCompression; // 0=BI_RGB 無壓縮
uint32_t biSizeImage; // 數據大小
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed; // 實際調色板顏色數
uint32_t biClrImportant;
} BITMAPINFOHEADER;
// 調色板項 (4 字節,BGR 順序 + 保留)
typedef struct {
uint8_t rgbBlue;
uint8_t rgbGreen;
uint8_t rgbRed;
uint8_t rgbReserved;
} RGBQUAD;
bfType == 'BM'、biBitCount == 8、biCompression == 0。RGBQUAD,順序 BGR。需將 8-bit 分量縮放至 6-bit (VGA DAC):outp(0x3C9, color >> 2)。outp(0x3C6, 0xFF); // 掩碼寄存器
outp(0x3C8, index); // 寫索引端口
outp(0x3C9, r>>2); outp(0x3C9, g>>2); outp(0x3C9, b>>2);
width (8-bit 索引),**無填充**(8-bit 索引天然字節對齊)。video_mem[y * 320 + x] = index。lseek(fp, -(height-y)*width, SEEK_END) 從文件末尾倒序讀取每行,直接寫入視頻內存對應行。#include <stdio.h>, <dos.h>, <conio.h>, <fcntl.h>, <io.h>, <stdlib.h>
#define VGA_256 0x13
#define TEXT_MODE 0x03
#define SCREEN_W 320
#define SCREEN_H 200
unsigned char far *vram = (unsigned char far *)0xA0000000L;
void set_mode(int mode) {
union REGS r; r.h.ah = 0; r.h.al = mode; int86(0x10, &r, &r);
}
void set_palette(int idx, RGBQUAD *c) {
outp(0x3C6, 0xFF);
outp(0x3C8, idx);
outp(0x3C9, c->rgbRed >> 2);
outp(0x3C9, c->rgbGreen >> 2);
outp(0x3C9, c->rgbBlue >> 2);
}
void load_bmp(const char *file) {
int fp = open(file, O_RDONLY | O_BINARY);
BITMAPFILEHEADER fh; read(fp, &fh, sizeof(fh));
BITMAPINFOHEADER ih; read(fp, &ih, sizeof(ih));
if (fh.bfType != 0x4D42 || ih.biBitCount != 8 || ih.biCompression != 0) { close(fp); exit(1); }
RGBQUAD pal[256];
for (int i = 0; i < 256; i++) {
read(fp, &pal[i].rgbBlue, 1);
read(fp, &pal[i].rgbGreen, 1);
read(fp, &pal[i].rgbRed, 1);
read(fp, &pal[i].rgbReserved, 1);
set_palette(i, &pal[i]);
}
unsigned char line[SCREEN_W];
for (int y = SCREEN_H - 1; y >= 0; y--) {
lseek(fp, -(long)(SCREEN_H - y) * SCREEN_W, SEEK_END);
read(fp, line, SCREEN_W);
_fmemcpy(vram + y * SCREEN_W, line, SCREEN_W); // far memcpy
}
close(fp);
}
int main() {
set_mode(VGA_256);
load_bmp("256.bmp");
getch();
set_mode(TEXT_MODE);
return 0;
}
行字節 = ((寬 * 色深位數 + 31) / 32) * 4
// 1-bit: 8像素/字節
// 4-bit: 2像素/字節
// 8-bit: 1像素/字節 (無填充)
// 24-bit: 3字節/像素 (需填充)
bfOffBits > 14 + 40 則存在調色板區塊。vram[y*width+x] = color 即現代 GPU 繪圖管線的片段著色器邏輯雛形。int86/outp 直接操作硬體,今有 DirectX/Vulkan/Metal、framebuffer、DRM/KMS 統一抽象。這段代碼承載了 PC 圖形編程的啟蒙記憶:**模式設置 → 調色板編程 → 線性幀緩衝區寫入 → 文件格式解析**。雖然 DOS 早已退出歷史舞台,但「零拷貝視頻內存寫入、調色板 LUT、掃描行翻轉、二進制格式解析」等核心概念,在現代圖形引擎、視頻解碼器、嵌入式顯示驅動中依然熠熠生輝。