本文以一個在 Borland C++ Builder 6.0 編譯通過的完整範例 DrawBmp.cpp 為基礎,詳細解析 256 色 BMP 檔案結構,並示範如何手動解析檔案標頭、調色盤與像素資料,直接繪製至視窗畫布。
| 位移 | 大小 | 內容 |
|---|---|---|
| 0 | 14 Bytes | 檔案標頭 |
| 14 | 40 Bytes | 資訊標頭 |
| 54 | 256 × 4 Bytes | 調色盤 (RGB + 保留) |
| 1078 | Width × Height Bytes | 像素索引資料 (每 Byte 一像素) |
#include
#pragma hdrstop
#include "DrawBitmap.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
typedef enum {
OK, FORMATERROR, FILENOTFOUND, OVERFLOW, READERROR
} RESULTCODE;
RESULTCODE DrawBmp(int start_x, int start_y, char filename[]);
AnsiString filen;
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {
filen = InputBox("256 位圖顯示程式", "輸入 256 位圖檔名...", filen);
}
void __fastcall TForm1::FormPaint(TObject *Sender) {
RESULTCODE res = DrawBmp(0, 0, filen.c_str());
}
//------------------------------------------------------------
#include
unsigned char DAC[256][3]; // 調色盤 RGB
RESULTCODE DrawBmp(int start_x, int start_y, char filename[]) {
int i, j, k, n, r, g, b, width, height;
int xcnt = 0, ycnt = 0;
FILE *fp;
if ((fp = fopen(filename, "rb")) == NULL) {
ShowMessage("檔案開啟失敗");
return FILENOTFOUND;
}
// 檢查色深
fseek(fp, 28, SEEK_SET);
fread(&i, 2, 1, fp);
if (i != 8) { fclose(fp); return FORMATERROR; }
// 讀取寬高
fseek(fp, 18, SEEK_SET);
fread(&width, 4, 1, fp);
fread(&height, 4, 1, fp);
// 讀取調色盤 (54 位移之後)
fseek(fp, 54, SEEK_SET);
for (i = 0; i < 256; i++) {
DAC[i][0] = fgetc(fp); // B
DAC[i][1] = fgetc(fp); // G
DAC[i][2] = fgetc(fp); // R
fgetc(fp); // 保留位
}
// 調整視窗大小置中 (假設 1024x768)
Form1->Height = height + 30;
Form1->Width = width;
Form1->Left = (1024 - Form1->Width) / 2;
Form1->Top = (768 - Form1->Height) / 2;
ycnt = start_y + height;
unsigned char *buffer = (unsigned char*)malloc(width);
if (!buffer) return OVERFLOW;
// BMP 為自下而上儲存,需反轉 Y 軸
while (ycnt > 0) {
xcnt = 0;
if (fread(buffer, width, 1, fp) != 1) { free(buffer); fclose(fp); return READERROR; }
while (xcnt < width) {
unsigned char data = buffer[xcnt];
// 注意 DAC 存 BGR,Canvas->Pixels 需 RGB
Form1->Canvas->Pixels[xcnt][ycnt] = RGB(DAC[data][2], DAC[data][1], DAC[data][0]);
xcnt++;
}
ycnt--;
}
free(buffer);
fclose(fp);
return OK;
}
BMP 檔案中每筆調色盤記錄為 Blue, Green, Red, Reserved,而 VCL RGB() 宏需 R, G, B,故存取時必須交換:RGB(DAC[i][2], DAC[i][1], DAC[i][0])。
BMP 像素資料由左下角開始向上掃描,螢幕座標原點在左上角。程式以 ycnt = start_y + height 起始,每行繪製後 ycnt-- 實現垂直翻轉。
配置一行寬度的緩衝區 malloc(width),逐行讀取、逐像素轉色繪製,記憶體佔用極小,適合大圖。
使用 RESULTCODE 明確回傳狀態,便於上層處理。
ScanLine 直接寫入位圖記憶體,避免 Pixels[][] 的高呼叫開銷。TBitmap 或 Graphics::TBitmap 快取。StretchDIBits 或 SetDIBitsToDevice 直接繪製 DIB,完全跳過 VCL 封裝層。理解 BMP 檔案結構、調色盤序列、座標系差異,即可不依賴高層封裝直接繪製 256 色位圖。此技術對嵌入式圖形、舊系統遷移、特殊格式處理仍具參考價值。