通過我這些天用C++讀寫bmp映像的經曆,摸索再摸索,終於對bmp檔案的結構、操作有了一定的瞭解,下面就大概介紹bmp圖片純C++的讀取、旋轉和儲存的實現過程。
要用C++讀取bmp圖片檔案,首先要弄清楚bmp格式圖片檔案的結構。可以參考這篇文章:http://blog.csdn.net/xiajun07061225/article/details/5813726
有幾點需要注意的是:
在讀取bmp圖片的時候,一定要注意記憶體對齊的問題,譬如檔案頭,否則無法讀取出正確結果。
關於圖片的像素資料,每一行的像素的位元組數必須是4的整數倍。如果不是,則需要補齊。一般來說,bmp影像檔的資料是從下到上,從左至右的。即從檔案中最先讀到的是映像最下面一行的左邊第一個像素,然後是座標第二個.....接下來是倒數第二行的第一個像素。
採用的編譯環境是VS2008。
關於映像旋轉,並不難。只需要搞清楚像素座標變換公式就行。我以映像的中心點為座標原點。先把像素在靶心圖表像中的位置變化為座標系中的位置,做旋轉變換求出變換之前的在座標系中的座標,再變換為在圖片中的位置。
公式:(x1,y1)是變換之前的座標系中的座標,(x2,y2)是變換之後的座標系中的座標。angle為逆時針旋轉的角度數。
x1 = cos(angle)*x2-sin(angle)*y2;
y1 = sin(angle)*x2-cos(angle)*y2;
My Code分為兩個版本:灰階圖的和彩色圖的。
灰階圖:
灰階圖是只含亮度資訊,不含彩色資訊的映像。bmp格式檔案中並沒有灰階圖這個概念,但是我們很容易地用bmp檔案來表示灰階圖。方法是用256色的調色盤,只不過這個調色盤有點特殊,每一項的RGB值都是相同的,從(0,0,0),(1,1,1),...,一直到(255,255,255)。這樣,灰階圖就可以用256色圖來表示了。其映像資料就是調色盤索引值,也就是實際的RGB的亮度值。另外因為是256色的調色盤,所以映像資料中的一個位元組代表一個像素。如果是彩色的256色圖,影像處理後可能會產生不屬於這256色的顏色,所以,影像處理一般採用灰階圖。這也可以更好地將重點放在演算法上。
下面是灰階圖旋轉代碼,能處理任意尺寸的bmp灰階圖,以及旋轉任意角度(逆時針)。
程式碼封裝括兩個檔案:BmpRot.h和BmpRot.cpp
BmpRot.h:
typedef unsigned char BYTE;typedef unsigned short WORD;typedef unsigned int DWORD;typedef long LONG;//位元影像檔案頭定義;//其中不包含檔案類型資訊(由於結構體的記憶體結構決定,//要是加了的話將不能正確讀取檔案資訊)typedef struct tagBITMAPFILEHEADER{//WORD bfType;//檔案類型,必須是0x424D,即字元“BM”DWORD bfSize;//檔案大小WORD bfReserved1;//保留字WORD bfReserved2;//保留字DWORD bfOffBits;//從檔案頭到實際位元影像資料的位移位元組數}BITMAPFILEHEADER;typedef struct tagBITMAPINFOHEADER{DWORD biSize;//資訊頭大小LONG biWidth;//映像寬度LONG biHeight;//映像高度WORD biPlanes;//位平面數,必須為1WORD biBitCount;//每一像素位元數DWORD biCompression; //壓縮類型DWORD biSizeImage; //壓縮映像大小位元組數LONG biXPelsPerMeter; //水平解析度LONG biYPelsPerMeter; //垂直解析度DWORD biClrUsed; //位元影像實際用到的色彩數DWORD biClrImportant; //本位元影像中重要的色彩數}BITMAPINFOHEADER; //位元影像資訊頭定義typedef struct tagRGBQUAD{BYTE rgbBlue; //該顏色的藍色分量BYTE rgbGreen; //該顏色的綠色分量BYTE rgbRed; //該顏色的紅色分量BYTE rgbReserved; //保留值}RGBQUAD;//調色盤定義//像素資訊typedef struct tagIMAGEDATA{BYTE blue;//BYTE green;//BYTE red;}IMAGEDATA;
BmpRot.cpp:
#include <stdio.h>#include "BmpRot.h"#include "stdlib.h"#include "math.h"#include <iostream>#define PI 3.14159//圓周率宏定義#define LENGTH_NAME_BMP 30//bmp圖片檔案名稱的最大長度using namespace std;//變數定義BITMAPFILEHEADER strHead;RGBQUAD strPla[256];//256色調色盤BITMAPINFOHEADER strInfo;//顯示位元影像檔案頭資訊void showBmpHead(BITMAPFILEHEADER pBmpHead){cout<<"位元影像檔案頭:"<<endl;cout<<"檔案大小:"<<pBmpHead.bfSize<<endl;cout<<"保留字_1:"<<pBmpHead.bfReserved1<<endl;cout<<"保留字_2:"<<pBmpHead.bfReserved2<<endl;cout<<"實際位元影像資料的位移位元組數:"<<pBmpHead.bfOffBits<<endl<<endl;}void showBmpInforHead(tagBITMAPINFOHEADER pBmpInforHead){cout<<"位元影像資訊頭:"<<endl;cout<<"結構體的長度:"<<pBmpInforHead.biSize<<endl;cout<<"位元影像寬:"<<pBmpInforHead.biWidth<<endl;cout<<"位元影像高:"<<pBmpInforHead.biHeight<<endl;cout<<"biPlanes平面數:"<<pBmpInforHead.biPlanes<<endl;cout<<"biBitCount採用顏色位元:"<<pBmpInforHead.biBitCount<<endl;cout<<"壓縮方式:"<<pBmpInforHead.biCompression<<endl;cout<<"biSizeImage實際位元影像資料佔用的位元組數:"<<pBmpInforHead.biSizeImage<<endl;cout<<"X方向解析度:"<<pBmpInforHead.biXPelsPerMeter<<endl;cout<<"Y方向解析度:"<<pBmpInforHead.biYPelsPerMeter<<endl;cout<<"使用的顏色數:"<<pBmpInforHead.biClrUsed<<endl;cout<<"重要顏色數:"<<pBmpInforHead.biClrImportant<<endl;}int main(){char strFile[LENGTH_NAME_BMP];//bmp檔案名稱IMAGEDATA *imagedata = NULL;//動態分配儲存原圖片的像素資訊的二維數組IMAGEDATA *imagedataRot = NULL;//動態分配儲存旋轉後的圖片的像素資訊的二維數組int width,height;//圖片的寬度和高度cout<<"請輸入所要讀取的檔案名稱:"<<endl;cin>>strFile;FILE *fpi,*fpw;fpi=fopen(strFile,"rb");if(fpi != NULL){//先讀取檔案類型WORD bfType;fread(&bfType,1,sizeof(WORD),fpi);if(0x4d42!=bfType){cout<<"the file is not a bmp file!"<<endl;return NULL;}//讀取bmp檔案的檔案頭和資訊頭fread(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpi);//showBmpHead(strHead);//顯示檔案頭fread(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpi);//showBmpInforHead(strInfo);//顯示檔案資訊頭//讀取調色盤for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++){fread((char *)&(strPla[nCounti].rgbBlue),1,sizeof(BYTE),fpi);fread((char *)&(strPla[nCounti].rgbGreen),1,sizeof(BYTE),fpi);fread((char *)&(strPla[nCounti].rgbRed),1,sizeof(BYTE),fpi);fread((char *)&(strPla[nCounti].rgbReserved),1,sizeof(BYTE),fpi);}width = strInfo.biWidth;height = strInfo.biHeight;//映像每一行的位元組數必須是4的整數倍width = (width * sizeof(IMAGEDATA) + 3) / 4 * 4;//imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));imagedata = (IMAGEDATA*)malloc(width * height);imagedataRot = (IMAGEDATA*)malloc(2 * width * 2 * height * sizeof(IMAGEDATA));//初始化原始圖片的像素數組for(int i = 0;i < height;++i){for(int j = 0;j < width;++j){(*(imagedata + i * width + j)).blue = 0;//(*(imagedata + i * width + j)).green = 0;//(*(imagedata + i * width + j)).red = 0;}}//初始化旋轉後圖片的像素數組for(int i = 0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){(*(imagedataRot + i * 2 * width + j)).blue = 0;//(*(imagedataRot + i * 2 * width + j)).green = 0;//(*(imagedataRot + i * 2 * width + j)).red = 0;}}//fseek(fpi,54,SEEK_SET);//讀出圖片的像素資料fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);fclose(fpi);}else{cout<<"file open error!"<<endl;return NULL;}//圖片旋轉處理int RotateAngle;//要旋轉的角度數double angle;//要旋轉的弧度數int midX_pre,midY_pre,midX_aft,midY_aft;//旋轉所圍繞的中心點的座標midX_pre = width / 2;midY_pre = height / 2;midX_aft = width;midY_aft = height;int pre_i,pre_j,after_i,after_j;//旋轉前後對應的像素點座標cout<<"輸入要旋轉的角度(0度到360度,逆時針旋轉):"<<endl;cin>>RotateAngle;angle = 1.0 * RotateAngle * PI / 180;for(int i = 0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){after_i = i - midX_aft;//座標變換after_j = j - midY_aft;pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midX_pre;pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midY_pre;if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原圖範圍內*(imagedataRot + i * 2 * width + j) = *(imagedata + pre_i * width + pre_j);}}//儲存bmp圖片if((fpw=fopen("b.bmp","wb"))==NULL){cout<<"create the bmp file error!"<<endl;return NULL;}WORD bfType_w=0x4d42;fwrite(&bfType_w,1,sizeof(WORD),fpw);//fpw +=2;fwrite(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpw);strInfo.biWidth = 2 * width;strInfo.biHeight = 2 * height;fwrite(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpw);//儲存調色盤資料for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++){fwrite(&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpw);fwrite(&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpw);fwrite(&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpw);fwrite(&strPla[nCounti].rgbReserved,1,sizeof(BYTE),fpw);}//儲存像素資料for(int i =0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){fwrite( &((*(imagedataRot + i * 2 * width + j)).blue),1,sizeof(BYTE),fpw);//fwrite( &((*(imagedataRot + i * 2 * width + j)).green),1,sizeof(BYTE),fpw);//fwrite( &((*(imagedataRot + i * 2 * width + j)).red),1,sizeof(BYTE),fpw);}}fclose(fpw);//釋放記憶體delete[] imagedata;delete[] imagedataRot;}
資料測試:
旋轉前和旋轉後的對比(45度):
彩色圖:
彩色圖的處理和灰階圖略有不一樣。主要是像素資料不同。由於每行資料的位元組數必須是4的整數倍,這個地方處理起來要比灰階圖麻煩很多,多以暫時還 沒做好。本程式的局限性就是只能處理尺寸是4的整數倍的圖片,可以旋轉任意角度(逆時針)。
參考代碼:分兩個檔案:BmpRot.h和BmpRot.cpp
BmpRot.h:
typedef unsigned char BYTE;typedef unsigned short WORD;typedef unsigned int DWORD;typedef long LONG;//位元影像檔案頭定義;//其中不包含檔案類型資訊(由於結構體的記憶體結構決定,//要是加了的話將不能正確讀取檔案資訊)typedef struct tagBITMAPFILEHEADER{//WORD bfType;//檔案類型,必須是0x424D,即字元“BM”DWORD bfSize;//檔案大小WORD bfReserved1;//保留字WORD bfReserved2;//保留字DWORD bfOffBits;//從檔案頭到實際位元影像資料的位移位元組數}BITMAPFILEHEADER;typedef struct tagBITMAPINFOHEADER{DWORD biSize;//資訊頭大小LONG biWidth;//映像寬度LONG biHeight;//映像高度WORD biPlanes;//位平面數,必須為1WORD biBitCount;//每一像素位元數DWORD biCompression; //壓縮類型DWORD biSizeImage; //壓縮映像大小位元組數LONG biXPelsPerMeter; //水平解析度LONG biYPelsPerMeter; //垂直解析度DWORD biClrUsed; //位元影像實際用到的色彩數DWORD biClrImportant; //本位元影像中重要的色彩數}BITMAPINFOHEADER; //位元影像資訊頭定義typedef struct tagRGBQUAD{BYTE rgbBlue; //該顏色的藍色分量BYTE rgbGreen; //該顏色的綠色分量BYTE rgbRed; //該顏色的紅色分量BYTE rgbReserved; //保留值}RGBQUAD;//調色盤定義//像素資訊typedef struct tagIMAGEDATA{BYTE red;BYTE green;BYTE blue;}IMAGEDATA;
BmpRot.cpp:
#include <stdio.h>#include "BmpRot.h"#include "stdlib.h"#include "math.h"#include <iostream>#define PI 3.14159//圓周率宏定義#define LENGTH_NAME_BMP 30//bmp圖片檔案名稱的最大長度using namespace std;//變數定義BITMAPFILEHEADER strHead;RGBQUAD strPla[256];//256色調色盤BITMAPINFOHEADER strInfo;//顯示位元影像檔案頭資訊void showBmpHead(BITMAPFILEHEADER pBmpHead){cout<<"位元影像檔案頭:"<<endl;cout<<"檔案大小:"<<pBmpHead.bfSize<<endl;cout<<"保留字_1:"<<pBmpHead.bfReserved1<<endl;cout<<"保留字_2:"<<pBmpHead.bfReserved2<<endl;cout<<"實際位元影像資料的位移位元組數:"<<pBmpHead.bfOffBits<<endl<<endl;}void showBmpInforHead(tagBITMAPINFOHEADER pBmpInforHead){cout<<"位元影像資訊頭:"<<endl;cout<<"結構體的長度:"<<pBmpInforHead.biSize<<endl;cout<<"位元影像寬:"<<pBmpInforHead.biWidth<<endl;cout<<"位元影像高:"<<pBmpInforHead.biHeight<<endl;cout<<"biPlanes平面數:"<<pBmpInforHead.biPlanes<<endl;cout<<"biBitCount採用顏色位元:"<<pBmpInforHead.biBitCount<<endl;cout<<"壓縮方式:"<<pBmpInforHead.biCompression<<endl;cout<<"biSizeImage實際位元影像資料佔用的位元組數:"<<pBmpInforHead.biSizeImage<<endl;cout<<"X方向解析度:"<<pBmpInforHead.biXPelsPerMeter<<endl;cout<<"Y方向解析度:"<<pBmpInforHead.biYPelsPerMeter<<endl;cout<<"使用的顏色數:"<<pBmpInforHead.biClrUsed<<endl;cout<<"重要顏色數:"<<pBmpInforHead.biClrImportant<<endl;}int main(){char strFile[LENGTH_NAME_BMP];//bmp檔案名稱IMAGEDATA *imagedata = NULL;//動態分配儲存原圖片的像素資訊的二維數組IMAGEDATA *imagedataRot = NULL;//動態分配儲存旋轉後的圖片的像素資訊的二維數組int width,height;//圖片的寬度和高度cout<<"請輸入所要讀取的檔案名稱:"<<endl;cin>>strFile;FILE *fpi,*fpw;fpi=fopen(strFile,"rb");if(fpi != NULL){//先讀取檔案類型WORD bfType;fread(&bfType,1,sizeof(WORD),fpi);if(0x4d42!=bfType){cout<<"the file is not a bmp file!"<<endl;return NULL;}//讀取bmp檔案的檔案頭和資訊頭fread(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpi);//showBmpHead(strHead);//顯示檔案頭fread(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpi);//showBmpInforHead(strInfo);//顯示檔案資訊頭//讀取調色盤for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++){//儲存的時候,一般去掉保留字rgbReservedfread((char *)&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpi);fread((char *)&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpi);fread((char *)&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpi);cout<<"strPla[nCounti].rgbBlue"<<strPla[nCounti].rgbBlue<<endl;cout<<"strPla[nCounti].rgbGreen"<<strPla[nCounti].rgbGreen<<endl;cout<<"strPla[nCounti].rgbRed"<<strPla[nCounti].rgbRed<<endl;}width = strInfo.biWidth;height = strInfo.biHeight;imagedata = (IMAGEDATA*)malloc(width * height * sizeof(IMAGEDATA));imagedataRot = (IMAGEDATA*)malloc(2 * width * 2 * height * sizeof(IMAGEDATA));//初始化原始圖片的像素數組for(int i = 0;i < height;++i){for(int j = 0;j < width;++j){(*(imagedata + i * width + j)).blue = 0;(*(imagedata + i * width + j)).green = 0;(*(imagedata + i * width + j)).red = 0;}}//初始化旋轉後圖片的像素數組for(int i = 0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){(*(imagedataRot + i * 2 * width + j)).blue = 0;(*(imagedataRot + i * 2 * width + j)).green = 0;(*(imagedataRot + i * 2 * width + j)).red = 0;}}//fseek(fpi,54,SEEK_SET);//讀出圖片的像素資料fread(imagedata,sizeof(struct tagIMAGEDATA) * width,height,fpi);/*for(int i = 0;i < height;++i){fread(imagedata + i * width * sizeof(IMAGEDATA),sizeof(struct tagIMAGEDATA) * width,height,fpi);}*/fclose(fpi);}else{cout<<"file open error!"<<endl;return NULL;}//圖片旋轉處理int RotateAngle;//要旋轉的角度數double angle;//要旋轉的弧度數int midX_pre,midY_pre,midX_aft,midY_aft;//旋轉所圍繞的中心點的座標midX_pre = width / 2;midY_pre = height / 2;midX_aft = width;midY_aft = height;int pre_i,pre_j,after_i,after_j;//旋轉前後對應的像素點座標cout<<"輸入要旋轉的角度(0度到360度,逆時針旋轉):"<<endl;cin>>RotateAngle;angle = 1.0 * RotateAngle * PI / 180;for(int i = 0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){after_i = i - midY_aft;//座標變換after_j = j - midX_aft;pre_i = (int)(cos((double)angle) * after_i - sin((double)angle) * after_j) + midY_pre;pre_j = (int)(sin((double)angle) * after_i + cos((double)angle) * after_j) + midX_pre;if(pre_i >= 0 && pre_i < height && pre_j >= 0 && pre_j < width)//在原圖範圍內*(imagedataRot + i * 2 * width + j) = *(imagedata + pre_i * width + pre_j);}}//儲存bmp圖片if((fpw=fopen("b.bmp","wb"))==NULL){cout<<"create the bmp file error!"<<endl;return NULL;}WORD bfType_w=0x4d42;fwrite(&bfType_w,1,sizeof(WORD),fpw);//fpw +=2;fwrite(&strHead,1,sizeof(tagBITMAPFILEHEADER),fpw);strInfo.biWidth = 2 * width;strInfo.biHeight = 2 * height;fwrite(&strInfo,1,sizeof(tagBITMAPINFOHEADER),fpw);//儲存調色盤資料for(unsigned int nCounti=0;nCounti<strInfo.biClrUsed;nCounti++){fwrite(&strPla[nCounti].rgbBlue,1,sizeof(BYTE),fpw);fwrite(&strPla[nCounti].rgbGreen,1,sizeof(BYTE),fpw);fwrite(&strPla[nCounti].rgbRed,1,sizeof(BYTE),fpw);}//儲存像素資料for(int i =0;i < 2 * height;++i){for(int j = 0;j < 2 * width;++j){fwrite( &(*(imagedataRot + i * 2 * width + j)).red,1,sizeof(BYTE),fpw);//注意三條語句的順序:否則顏色會發生變化fwrite( &(*(imagedataRot + i * 2 * width + j)).green,1,sizeof(BYTE),fpw);fwrite( &(*(imagedataRot + i * 2 * width + j)).blue,1,sizeof(BYTE),fpw);}}fclose(fpw);//釋放記憶體delete[] imagedata;delete[] imagedataRot;}
資料測試:(旋轉10°)
注意:顏色問題已解決。請看代碼中注釋部分。