BMP(Bitmap)是 Windows 標準圖像格式,結構簡單、無壓縮,適合教學與嵌入式裸機顯示。
typedef struct {
uint16_t bfType; // 0x4D42 ('BM')
uint32_t bfSize; // 文件總大小
uint16_t bfReserved1; // 0
uint16_t bfReserved2; // 0
uint32_t bfOffBits; // 像素數據偏移量(通常 54)
} __attribute__((packed)) BMPFileHeader;
typedef struct {
uint32_t biSize; // 40
int32_t biWidth; // 寬度(像素),負值表示自下而上
int32_t biHeight; // 高度,正值自下而上,負值自上而下
uint16_t biPlanes; // 1
uint16_t biBitCount; // 色深:1/4/8/16/24/32
uint32_t biCompression; // 0=BI_RGB 無壓縮
uint32_t biSizeImage; // 像素數據大小(可 0)
int32_t biXPelsPerMeter; // 水平分辨率
int32_t biYPelsPerMeter; // 垂直分辨率
uint32_t biClrUsed; // 調色板顏色數
uint32_t biClrImportant; // 重要顏色數
} __attribute__((packed)) BMPInfoHeader;
// 每行字節數(含填充)
int row_stride = ((width * bit_count + 31) / 32) * 4;
// 像素數據總大小
int image_size = row_stride * abs(height);
// 訪問像素 (x, y) —— y=0 為頂行(邏輯座標)
// 文件中實際行索引 = (height > 0) ? (height - 1 - y) : y
uint8_t *pixel_ptr = data + row_index * row_stride + x * (bit_count / 8);
#include
#include
#include
#include
#include
#include
#pragma pack(push, 1)
typedef struct {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1, bfReserved2;
uint32_t bfOffBits;
} BMPFileHeader;
typedef struct {
uint32_t biSize;
int32_t biWidth, biHeight;
uint16_t biPlanes, biBitCount;
uint32_t biCompression, biSizeImage;
int32_t biXPelsPerMeter, biYPelsPerMeter;
uint32_t biClrUsed, biClrImportant;
} BMPInfoHeader;
#pragma pack(pop)
// 讀取整個 BMP 到內存
int read_bmp(const char *path, BMPFileHeader *fh, BMPInfoHeader *ih, uint8_t **pixels) {
int fd = open(path, O_RDONLY);
if (fd < 0) { perror("open"); return -1; }
if (read(fd, fh, sizeof(*fh)) != sizeof(*fh)) { close(fd); return -2; }
if (fh->bfType != 0x4D42) { close(fd); return -3; } // Not BM
if (read(fd, ih, sizeof(*ih)) != sizeof(*ih)) { close(fd); return -4; }
if (ih->biBitCount != 24) { close(fd); return -5; } // Only 24-bit
int row_stride = ((ih->biWidth * 24 + 31) / 32) * 4;
int img_size = row_stride * abs(ih->biHeight);
*pixels = malloc(img_size);
if (!*pixels) { close(fd); return -6; }
lseek(fd, fh->bfOffBits, SEEK_SET);
if (read(fd, *pixels, img_size) != img_size) { free(*pixels); close(fd); return -7; }
close(fd);
return 0;
}
// 寫入 BMP
int write_bmp(const char *path, BMPFileHeader *fh, BMPInfoHeader *ih, uint8_t *pixels) {
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { perror("open"); return -1; }
int row_stride = ((ih->biWidth * 24 + 31) / 32) * 4;
int img_size = row_stride * abs(ih->biHeight);
fh->bfSize = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + img_size;
fh->bfOffBits = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
write(fd, fh, sizeof(*fh));
write(fd, ih, sizeof(*ih));
write(fd, pixels, img_size);
close(fd);
return 0;
}
// 順時針 90° 旋轉:新寬=原高,新高=原寬
uint8_t *rotate_90_cw(BMPInfoHeader *ih, uint8_t *src) {
int w = ih->biWidth, h = abs(ih->biHeight);
int src_stride = ((w * 24 + 31) / 32) * 4;
int dst_w = h, dst_h = w;
int dst_stride = ((dst_w * 24 + 31) / 32) * 4;
uint8_t *dst = calloc(dst_stride * dst_h, 1); // 零初始化含填充
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
// 源像素指針(邏輯座標 y 從頂部開始)
int src_row = (ih->biHeight > 0) ? (h - 1 - y) : y;
uint8_t *sp = src + src_row * src_stride + x * 3;
// 目標像素:旋轉後 (x, y) → (y, w-1-x)
int dst_x = y;
int dst_y = w - 1 - x;
int dst_row = (ih->biHeight > 0) ? (dst_h - 1 - dst_y) : dst_y; // 保持存儲順序
uint8_t *dp = dst + dst_row * dst_stride + dst_x * 3;
// 複製 BGR
dp[0] = sp[0]; dp[1] = sp[1]; dp[2] = sp[2];
}
}
// 更新信息頭
ih->biWidth = dst_w;
ih->biHeight = (ih->biHeight > 0) ? dst_h : -dst_h; // 保持符號
ih->biSizeImage = dst_stride * dst_h;
return dst;
}
int main(int argc, char **argv) {
if (argc != 3) {
fprintf(stderr, "Usage: %s input.bmp output.bmp\n", argv[0]);
return 1;
}
BMPFileHeader fh;
BMPInfoHeader ih;
uint8_t *pixels = NULL;
if (read_bmp(argv[1], &fh, &ih, &pixels) != 0) {
fprintf(stderr, "Read BMP failed\n");
return 2;
}
printf("Original: %dx%d, %d-bit, stride=%d\n",
ih.biWidth, ih.biHeight, ih.biBitCount,
((ih.biWidth * 24 + 31) / 32) * 4);
uint8_t *rotated = rotate_90_cw(&ih, pixels);
free(pixels);
if (write_bmp(argv[2], &fh, &ih, rotated) != 0) {
fprintf(stderr, "Write BMP failed\n");
free(rotated);
return 3;
}
free(rotated);
printf("Rotated saved to %s\n", argv[2]);
return 0;
}
gcc bmp_rotate.c -o bmp_rotate -std=c99
./bmp_rotate input.bmp output_rotated.bmp
| 功能 | 關鍵點 |
|---|---|
| 任意角度旋轉 | 仿射變換 + 雙線性插值,需浮點計算目標像素反向映射源座標 |
| 縮放 | 最近鄰/雙線性/雙三次插值,獨立處理 X/Y 縮放比 |
| 格式轉換 | 24↔32 位、BGR↔RGB、灰度化(Y=0.299R+0.587G+0.114B) |
| 調色板處理 | 8 位及以下讀取調色板(bfOffBits 後),索引轉 RGB |
| RLE 壓縮 | 解碼 BI_RLE8/4,編碼輸出 |
| 大圖流式處理 | 分塊讀寫,避免全圖載入內存,mmap 加速 |
BMP 格式簡單透明,是學習二進制文件解析、像素級圖像處理、內存佈局控制的最佳切入點。掌握文件頭/信息頭結構、行對齊、座標系轉換、BGR 色序,即可實現讀寫、旋轉、鏡像、裁剪、濾波等基礎操作。進階可對接 libpng/libjpeg/stb_image 處理壓縮格式,或用 OpenCV 做計算機視覺。