【數字影象處理】C++讀取、旋轉和儲存bmp影象檔案程式設計實現
通過我這些天用C++讀寫bmp影象的經歷,摸索再摸索,終於對bmp檔案的結構、操作有了一定的瞭解,下面就大概介紹bmp圖片純C++的讀取、旋轉和儲存的實現過程。
有幾點需要注意的是:
在讀取bmp圖片的時候,一定要注意記憶體對齊的問題,譬如檔案頭,否則無法讀取出正確結果。
關於圖片的畫素資料,每一行的畫素的位元組數必須是4的整數倍。如果不是,則需要補齊。一般來說,bmp影象檔案的資料是從下到上,從左到右的。即從檔案中最先讀到的是影象最下面一行的左邊第一個畫素,然後是座標第二個.....接下來是倒數第二行的第一個畫素。
採用的編譯環境是VS2008。
關於影象旋轉,並不難。只需要搞清楚畫素座標變換公式就行。我以影象的中心點為座標原點。先把畫素在目標影象中的位置變化為座標系中的位置,做旋轉變換求出變換之前的在座標系中的座標,再變換為在圖片中的位置。
公式:(x1,y1)是變換之前的座標系中的座標,(x2,y2)是變換之後的座標系中的座標。angle為逆時針旋轉的角度數。
x1 = cos(angle)*x2-sin(angle)*y2;
y1 = sin(angle)*x2-cos(angle)*y2;
我的程式碼分為兩個版本:灰度圖的和彩色圖的。
灰度圖:
灰度圖是隻含亮度資訊,不含彩色資訊的影象。bmp格式檔案中並沒有灰度圖這個概念,但是我們很容易地用bmp檔案來表示灰度圖。方法是用256色的調色盤,只不過這個調色盤有點特殊,每一項的RGB值都是相同的,從(0,0,0),(1,1,1),...,一直到(255,255,255)。這樣,灰度圖就可以用256色圖來表示了。其影象資料就是調色盤索引值,也就是實際的RGB的亮度值。
下面是灰度圖旋轉程式碼,能處理任意尺寸的bmp灰度圖,以及旋轉任意角度(逆時針)。
程式碼包括兩個檔案:BmpRot.h和BmpRot.cpp
BmpRot.h:
BmpRot.cpp: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;//位平面數,必須為1 WORD 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;
#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;//位平面數,必須為1
WORD 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++)
{
//儲存的時候,一般去掉保留字rgbReserved
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);
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°)
注意:顏色問題已解決。請看程式碼中註釋部分。