(原創) 如何使用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

  1 /* 
  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     0000,  // file size
 27     00,        // reserved1
 28     00,        // reserved2
 29     54000// RGB data offset
 30     40000// struct BITMAPINFOHEADER size
 31     0000,  // bmp width
 32     0000,  // bmp height
 33     10,        // planes
 34     320,       // bit per pixel
 35     0000,  // compression
 36     0000,  // data size
 37     0000,  // h resolution
 38     0000,  // v resolution 
 39     0000,  // used colors
 40     0000   // 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 }


原圖

capture32.gif


執行結果

capture32_v.gif


34行

320,       // bit per pixel


由於使用了32位元BMP,所以在header檔陣列須改成32。

62行

image_s = (unsigned char *)malloc((size_t)width * height * 4);


68行

image_t = (unsigned char *)malloc((size_t)width * height * 4);


由於每個pixel改用32位元,也就是4 byte,所以malloc要從3改成4。

74行

fread(image_s, sizeof(unsigned char), (size_t)(long)width * height * 4, fp_s);


從檔案將資料讀到一維陣列,也由於32位元的關係,所以改成4。

 79行到91行

for(y = 0; y != height; ++y) {
  
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  
file_size = width * height * 4 + rgb_raw_data_offset;    


計算file size,也因為每個pixel改用4 byte而乘以4。

121行

// write image
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] swwuyamBMP檔案格式
[2] Charles Petzold 1998, Programming Windows, Microsoft Press
瘋小貓的華麗冒險點陣圖(Bitmap)檔案格式
BMP文件格式分析
賴岱佑、劉敏 2007,數位影像處理 技術手冊,文魁資訊
井上誠喜、八木申行、林 正樹、中須英輔、三古公二、奧井誠人 著 2006,吳上立,林宏燉 編譯,C語言數位影像處理,全華出版社

posted on 2008-05-25 17:40  真 OO无双  阅读(8290)  评论(0编辑  收藏  举报

导航