(原創) 如何使用ANSI C讀寫32位元的BMP圖檔? (C/C++) (C) (Image Processing)
Abstract
本文介紹如何使用ANSI C讀寫32位元的BMP圖檔做簡單的影像處理,並解析BMP格式。
Introduction
在(原創) 如何使用ANSI C讀寫24位元的BMP圖檔? (C/C++) (C) (Image Processing),我們討論了使用C語言讀取BMP圖檔,不過有網友發現這個範例對於某些BMP並不適用,由於BMP格式版本眾多,我起初也不以為意,認為大概是版本的問題,直到最近要將(原創) 如何將CMOS所擷取的影像傳到PC端? (IC Design) (DE2)所轉出的圖片處理時,發現竟然無法處理,深入研究之後,才發現問題出在32位元的BMP格式。
32位元BMP格式與24位元BMP格式的差異
嚴格來說,32位元BMP格式和24位元BMP格式的差異不大,雖然只是小小的差異,但是若以讀寫24位元BMP的程式去讀寫32位元BMP格式,結果將完全不對。
簡單的說,32位元的BMP,每個pixel使用32位元來儲存,雖然如此,RGB每個顏色還是使用8 bit而已,並沒有因為32位元後,使用更多的bit來儲存RGB,那剩下沒用的8 bit呢?就沒用到,不過在讀取或寫入時,必須略過這沒用的8 bit。
除此之外,在header部分的bit per pixel,儲存的是32,而非原來的24,計算file size部分,由於每個pixel為32位元,也就是4 byte,計算的方式也會不太一樣。有code有真相,我們就直接來看code吧。
C語言 / Bmp32ReadWrite.c
2 (C) OOMusou 2007 http://oomusou.cnblogs.com
3
4 Filename : Bmp32ReadWrite.c
5 Compiler : Visual C++ 8.0 / ANSI C
6 Description : Demo the how to read and write bmp by standard library
7 Release : 05/25/2008 1.0
8 */
9 #include <stdio.h>
10 #include <stdlib.h>
11
12 int upside_down(const char *fname_s, const char *fname_t) {
13 FILE *fp_s = NULL; // source file handler
14 FILE *fp_t = NULL; // target file handler
15 unsigned int x,y; // for loop counter
16 unsigned int width, height; // image width, image height
17 unsigned char *image_s = NULL; // source image array
18 unsigned char *image_t = NULL; // target image array
19 unsigned char R, G, B; // color of R, G, B
20 unsigned int y_avg; // average of y axle
21 unsigned int y_t; // target of y axle
22
23 unsigned char header[54] = {
24 0x42, // identity : B
25 0x4d, // identity : M
26 0, 0, 0, 0, // file size
27 0, 0, // reserved1
28 0, 0, // reserved2
29 54, 0, 0, 0, // RGB data offset
30 40, 0, 0, 0, // struct BITMAPINFOHEADER size
31 0, 0, 0, 0, // bmp width
32 0, 0, 0, 0, // bmp height
33 1, 0, // planes
34 32, 0, // bit per pixel
35 0, 0, 0, 0, // compression
36 0, 0, 0, 0, // data size
37 0, 0, 0, 0, // h resolution
38 0, 0, 0, 0, // v resolution
39 0, 0, 0, 0, // used colors
40 0, 0, 0, 0 // important colors
41 };
42
43 unsigned int file_size; // file size
44 unsigned int rgb_raw_data_offset; // RGB raw data offset
45
46 fp_s = fopen(fname_s, "rb");
47 if (fp_s == NULL) {
48 printf("fopen fp_s error\n");
49 return -1;
50 }
51
52 // move offset to 10 to find rgb raw data offset
53 fseek(fp_s, 10, SEEK_SET);
54 fread(&rgb_raw_data_offset, sizeof(unsigned int), 1, fp_s);
55 // move offset to 18 to get width & height;
56 fseek(fp_s, 18, SEEK_SET);
57 fread(&width, sizeof(unsigned int), 1, fp_s);
58 fread(&height, sizeof(unsigned int), 1, fp_s);
59 // move offset to rgb_raw_data_offset to get RGB raw data
60 fseek(fp_s, rgb_raw_data_offset, SEEK_SET);
61
62 image_s = (unsigned char *)malloc((size_t)width * height * 4);
63 if (image_s == NULL) {
64 printf("malloc images_s error\n");
65 return -1;
66 }
67
68 image_t = (unsigned char *)malloc((size_t)width * height * 4);
69 if (image_t == NULL) {
70 printf("malloc image_t error\n");
71 return -1;
72 }
73
74 fread(image_s, sizeof(unsigned char), (size_t)(long)width * height * 4, fp_s);
75
76 // vertical inverse algorithm
77 y_avg = 0 + (height-1);
78
79 for(y = 0; y != height; ++y) {
80 for(x = 0; x != width; ++x) {
81 R = *(image_s + 4 * (width * y + x) + 2);
82 G = *(image_s + 4 * (width * y + x) + 1);
83 B = *(image_s + 4 * (width * y + x) + 0);
84
85 y_t = y_avg - y;
86
87 *(image_t + 4 * (width * y_t + x) + 2) = R;
88 *(image_t + 4 * (width * y_t + x) + 1) = G;
89 *(image_t + 4 * (width * y_t + x) + 0) = B;
90 }
91 }
92
93 // write to new bmp
94 fp_t = fopen(fname_t, "wb");
95 if (fp_t == NULL) {
96 printf("fopen fname_t error\n");
97 return -1;
98 }
99
100 // file size
101 file_size = width * height * 4 + rgb_raw_data_offset;
102 header[2] = (unsigned char)(file_size & 0x000000ff);
103 header[3] = (file_size >> 8) & 0x000000ff;
104 header[4] = (file_size >> 16) & 0x000000ff;
105 header[5] = (file_size >> 24) & 0x000000ff;
106
107 // width
108 header[18] = width & 0x000000ff;
109 header[19] = (width >> 8) & 0x000000ff;
110 header[20] = (width >> 16) & 0x000000ff;
111 header[21] = (width >> 24) & 0x000000ff;
112
113 // height
114 header[22] = height &0x000000ff;
115 header[23] = (height >> 8) & 0x000000ff;
116 header[24] = (height >> 16) & 0x000000ff;
117 header[25] = (height >> 24) & 0x000000ff;
118
119 // write header
120 fwrite(header, sizeof(unsigned char), rgb_raw_data_offset, fp_t);
121 // write image
122 fwrite(image_t, sizeof(unsigned char), (size_t)(long)width * height * 4, fp_t);
123
124 fclose(fp_s);
125 fclose(fp_t);
126
127 return 0;
128 }
129
130 int main() {
131 upside_down("capture32.bmp", "capture32_v.bmp");
132 }
原圖
執行結果
34行
由於使用了32位元BMP,所以在header檔陣列須改成32。
62行
68行
由於每個pixel改用32位元,也就是4 byte,所以malloc要從3改成4。
74行
從檔案將資料讀到一維陣列,也由於32位元的關係,所以改成4。
79行到91行
for(x = 0; x != width; ++x) {
R = *(image_s + 4 * (width * y + x) + 2);
G = *(image_s + 4 * (width * y + x) + 1);
B = *(image_s + 4 * (width * y + x) + 0);
y_t = y_avg - y;
*(image_t + 4 * (width * y_t + x) + 2) = R;
*(image_t + 4 * (width * y_t + x) + 1) = G;
*(image_t + 4 * (width * y_t + x) + 0) = B;
}
}
同理,也不需全部改成4,至於沒用到的8 bit可以不用理他,因為沒有影響大局。
100行
file_size = width * height * 4 + rgb_raw_data_offset;
計算file size,也因為每個pixel改用4 byte而乘以4。
121行
fwrite(image_t, sizeof(unsigned char), (size_t)(long)width * height * 4, fp_t);
同理,也要乘以4。
其他的部分,皆與(原創) 如何使用ANSI C讀寫24位元的BMP圖檔? (C/C++) (C) (Image Processing)原理相同,我就不在贅言,請自行參考。
Conclusion
還是那句老話,在Charles Petzold的Programming Windows[2] Ch.15中對BMP格式有非常詳盡的敘述,若要徹底了解BMP各種版本的差異,這一本書是終極指南。
網路上能找到的,幾乎都是24位元BMP的code,但隨著硬體的進步,32位元的BMP則越來越普及,本文希望對32位元BMP有需求的開發人員有所幫助。
See Also
(原創) 如何使用ANSI C讀寫24位元的BMP圖檔? (C/C++) (C) (Image Processing)
(原創) 如何使用ANSI C讀寫24/32位元的BMP圖檔? (C/C++) (C) (Image Processing)
(原創) 如何將CMOS所擷取的影像傳到PC端? (IC Design) (DE2)
(原創) 如何使用ISO C++讀寫bmp圖檔? (C/C++) (Image Processing)
(原創) 如何使用C++/CLI读/写jpg檔? (C++/CLI)
(原創) 如何用程序的方式载入jpg图形文件? (C#/ASP.NET)
Reference
[1] swwuyam的BMP檔案格式
[2] Charles Petzold 1998, Programming Windows, Microsoft Press
瘋小貓的華麗冒險的點陣圖(Bitmap)檔案格式
BMP文件格式分析
賴岱佑、劉敏 2007,數位影像處理 技術手冊,文魁資訊
井上誠喜、八木申行、林 正樹、中須英輔、三古公二、奧井誠人 著 2006,吳上立,林宏燉 編譯,C語言數位影像處理,全華出版社