C++實現24位真彩BMP圖平移,映象,旋轉90、180度
一、BMP檔案格式解析
BMP檔案格式,又稱為Bitmap(點陣圖)或是DIB(Device-Independent Device,裝置無關點陣圖),是Window系統中廣泛使用的影象檔案格式。由於它可以不作任何變換地儲存影象畫素域的資料,因此成為我們取得RAW資料的重要來源。Windows的圖形使用者介面(graphical user interfaces)也在它的內建影象子系統GDI中對BMP格式提供了支援。
BMP檔案總體上由4部分組成,分別是點陣圖檔案頭、點陣圖資訊頭、調色盤和影象資料,如表5-1所示。
表5-1 BMP檔案的組成結構
點陣圖檔案頭(bitmap-file header) 大小: 14個位元組
點陣圖資訊頭(bitmap-information header) 大小: 40個位元組
彩色表/調色盤(color table) 大小: 由顏色索引數決定
點陣圖資料(bitmap-data) 大小: 由影象尺寸決定
下面來詳細看一下每個組成部分的細節。
1.點陣圖檔案頭(bitmap-file header)
點陣圖檔案頭(bitmap-file header)包含了影象型別、影象大小、影象資料存放地址和兩個保留未使用的欄位。
開啟WINGDI.h檔案,搜尋”BITMAPFILEHEADER”就可以定位到BMP檔案的點陣圖檔案頭的資料結構定義。
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
表5-2列出了tagBITMAPFILEHEADER中各欄位的含義。
表5-2 tagBITMAPFILEHEADER結構
字 段 名 大小(單位:位元組) 描 述
bfType 2 點陣圖類別,根據不同的操作
系統而不同,在Windows
中,此欄位的值總為‘BM’
bfSize 4 BMP影象檔案的大小
bfReserved1 2 總為0
bfReserved2 2 總為0
bfOffBits 4 BMP影象資料的地址
2.點陣圖資訊頭(bitmap-information header)
點陣圖資訊頭(bitmap-information header)包含了點陣圖資訊頭的大小、影象的寬高、影象的色深、壓縮說明影象資料的大小和其他一些引數。
開啟WINGDI.h檔案,搜尋”tagBITMAPINFOHEADER”就可以定位到BMP檔案的點陣圖資訊頭的資料結構定義。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
表5-3列出了tagBITMAPFILEHEADER中各欄位的含義。
表5-3 tagBITMAPFILEHEADER結構
字 段 名 大小
(單位:
位元組) 描 述
biSize 4 本結構的大小,根據不同的作業系統而不同,在Windows中,此欄位的值總為28h位元組=40位元組
biWidth 4 BMP影象的寬度,單位畫素
biHeight 4 總為0
biPlanes 2 總為0
biBitCount 2 BMP影象的色深,即一個畫素用多少位表示,常見有1、4、8、16、24和32,分別對應單色、16色、256色、16位高彩色、24位真彩色和32位增強型真彩色
biCompression 4 壓縮方式,0表示不壓縮,1表示RLE8壓縮,2表示RLE4壓縮,3表示每個畫素值由指定的掩碼決定
biSizeImage 4 BMP影象資料大小,必須是4的倍數,影象資料大小不是4的倍數時用0填充補足
biXPelsPerMeter 4 水平解析度,單位畫素/m
biYPelsPerMeter 4 垂直解析度,單位畫素/m
biClrUsed 4 BMP影象使用的顏色,0表示使用全部顏色,對於256色點陣圖來說,此值為100h=256
biClrImportant 4 重要的顏色數,此值為0時所有顏色都重要,對於使用調色盤的BMP影象來說,當顯示卡不能夠顯示所有顏色時,此值將輔助驅動程式顯示顏色
3.彩色表/調色盤(color table)
彩色表/調色盤(color table)是單色、16色和256色影象檔案所特有的,相對應的調色盤大小是2、16和256,調色盤以4位元組為單位,每4個位元組存放一個顏色值,影象 的資料是指向調色盤的索引。
可以將調色盤想象成一個數組,每個陣列元素的大小為4位元組,假設有一256色的BMP影象的調色盤資料為:
調色盤[0]=黑、調色盤[1]=白、調色盤[2]=紅、調色盤[3]=藍…調色盤[255]=黃
影象資料01 00 02 FF表示呼叫調色盤[1]、調色盤[0]、調色盤[2]和調色盤[255]中的資料來顯示影象顏色。
在早期的計算機中,顯示卡相對比較落後,不一定能保證顯示所有顏色,所以在調色盤中的顏色資料應儘可能將影象中主要的顏色按順序排列在前面,點陣圖資訊 頭的biClrImportant欄位指出了有多少種顏色是重要的。
每個調色盤的大小為4位元組,按藍、綠、紅儲存一個顏色值。
開啟WINGDI.h檔案,搜尋”tagRGBTRIPLE”就可以定位到BMP檔案的調色盤的資料結構定義。
typedef struct tagRGBQUAD {
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;
表5-4列出了tagRGBTRIPLE中各欄位的含義。
表5-4 tagRGBTRIPLE結構
字 段 名 大小(單位:位元組) 描 述
rgbBlue 1 藍色值
rgbGreen 1 綠色值
rgbRed 1 紅色值
rgbReserved 1 保留,總為0
4.點陣圖資料(bitmap-data)
如果影象是單色、16色和256色,則緊跟著調色盤的是點陣圖資料,點陣圖資料是指向調色盤的索引序號。
如果點陣圖是16位、24位和32位色,則影象檔案中不保留調色盤,即不存在調色盤,影象的顏色直接在點陣圖資料中給出。
16點陣圖像使用2位元組儲存顏色值,常見有兩種格式:5位紅5位綠5位藍和5位紅6位綠5位藍,即555格式和565格式。555格式只使用了15 位,最後一位保留,設為0。
24點陣圖像使用3位元組儲存顏色值,每一個位元組代表一種顏色,按紅、綠、藍排列。
32點陣圖像使用4位元組儲存顏色值,每一個位元組代表一種顏色,除了原來的紅、綠、藍,還有Alpha通道,即透明色。
如果影象帶有調色盤,則點陣圖資料可以根據需要選擇壓縮與不壓縮,如果選擇壓縮,則根據BMP影象是16色或256色,採用RLE4或RLE8壓縮算 法壓縮。
RLE4是壓縮16色影象資料的,RLE4採用表5-5所示方式壓縮資料。
二、C++實現
1.在CodeBlocks中新建專案
新建Bitmap類和標頭檔案
標頭檔案中的宣告
#ifndef BMPFILE_H
#define BMPFILE_H
#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
using namespace std;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
//點陣圖檔案頭定義
typedef struct tagBITMAPFILEHEADER {
// WORD bfType;//單獨讀取,結構體中就不定義了
DWORD bfSize;//檔案大小
WORD bfReserved1;//保留字
WORD bfReserved2;//保留字
DWORD bfOffBits;//從檔案頭到實際點陣圖資料的偏移位元組數
} BITMAPFILEHEADER;
//點陣圖資訊頭定義
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;//資訊頭大小
DWORD biWidth;//影象寬度
DWORD biHeight;//影象高度
WORD biPlanes;//位平面數
WORD biBitCount;//每畫素位數
DWORD biCompression;//壓縮型別
DWORD biSizeImage;//壓縮影象大小位元組數
DWORD biXPelsPerMeter;//水平解析度
DWORD biYPelsPerMeter;//垂直解析度
DWORD biClrUsed;//點陣圖實際用到的色彩數
DWORD biClrImportant;//本點陣圖中重要的色彩數
} BITMAPINFOHEADER;
//畫素資訊
typedef struct tagIMAGEDATA
{
BYTE blue;
BYTE green;
BYTE red;
} DATA;
class BmpFile
{
public:
BmpFile();
virtual ~BmpFile();
void translation(int x, int y);
void mirror(char axis);
void clone(char* filePath);
void rotate90();
void rotate180();
void readfile(char* filePath);
int getPSize();
protected:
private:
BITMAPFILEHEADER strHead;
BITMAPINFOHEADER strInfo;
int h, w, pSize;
int mW, mSize;
WORD bfType;
DATA* src;
};
#endif // BMPFILE_H
對BMP點陣圖映象,平移,旋轉等操作,實際是就是對點陣圖資料那部分的畫素矩陣進行操作,可以使用兩個二維陣列,儲存變換前後的狀態,觀察總的畫素值是否變化,24位真彩圖的畫素數 = 總的位元組數/3 , 因為每個畫素需要用24位二進位制來表示,也就是3個位元組。計算變換後的寬和高,以及畫素的變化規律。
BmpFile.cpp中方法的實現
#include "BmpFile.h"
int BmpFile::getPSize(){
return pSize;
}
//從指定路徑讀取BMP點陣圖
void BmpFile::readfile(char* filePath){
FILE *fpi;
fpi = fopen(filePath, "rb");
if (fpi != NULL) {
//先讀取檔案型別
fread(&bfType, 1, sizeof(WORD), fpi);
if (0x4d42 != bfType) {
cout << "Error:The file is not a bmp image" << endl;
}
//讀取bmp檔案的檔案頭和資訊頭
fread(&strHead, 1, sizeof(tagBITMAPFILEHEADER), fpi);
fread(&strInfo, 1, sizeof(tagBITMAPINFOHEADER), fpi);
//獲取影象的寬度,以畫素為單位
h = strInfo.biHeight;
//獲取影象的高度,以畫素為單位
w = strInfo.biWidth;
//每一行畫素的位元組數必須是4的整數倍,不足用0補齊
if (w % 4 == 0) {
mW = w;
}
else {
mW = (w / 4 + 1) * 4;
}
//判斷圖片是否損壞
if (h*mW * 3 != strInfo.biSizeImage) {
cout<<"Error: image broken!"<<endl;
}
//獲得圖片的畫素數
pSize = strInfo.biSizeImage / 3;
//初始化原始圖片的畫素陣列
src = new DATA[pSize];
//讀取bmp資料資訊
fread(src, 1, sizeof(DATA)*pSize, fpi);
fclose(fpi);
}
}
//平移
void BmpFile::translation(int x, int y){
int moveX, moveY;
if (x % 4 == 0)
moveX = x;
else
moveX = x / 4 * 4;
moveY = y;
int newH = h + moveY;
int newW = mW + moveX;
int newSize = newH * newW;
DATA* target = new DATA[newSize];
memset(target, 0, sizeof(DATA)*newSize);
for (int i = 0; i < newH; i++) {
for (int j = 0; j < newW; j++) {
if (i < moveY) {
target[i*newW + j].red = 255;
target[i*newW + j].blue = 255;
target[i*newW + j].green = 255;
continue;
}
if (j < moveX) {
target[i*newW + j].red = 255;
target[i*newW + j].blue = 255;
target[i*newW + j].green = 255;
}
else {
target[i*newW + j] = src[(i - moveY)*mW + (j - moveX)];
}
}
}
FILE *fpo1;
fpo1 = fopen("C:\\Users\\Administrator\\Desktop\\translation.bmp", "wb");
BITMAPFILEHEADER newHead = strHead;
BITMAPINFOHEADER newInfo = strInfo;
newHead.bfSize = (DWORD)(newHead.bfSize - pSize * 3 + newSize * 3);
newInfo.biHeight = (DWORD)newH;
newInfo.biWidth = (DWORD)newW;
newInfo.biSizeImage = (DWORD)(newSize * 3);
fwrite(&bfType, 1, sizeof(WORD), fpo1);
fwrite(&newHead, 1, sizeof(tagBITMAPFILEHEADER), fpo1);
fwrite(&newInfo, 1, sizeof(tagBITMAPINFOHEADER), fpo1);
fwrite(target, 1, sizeof(DATA)*(newSize), fpo1);
fclose(fpo1);
delete[] target;
}
//映象
void BmpFile::mirror(char axis){
DATA* target = new DATA[pSize];
memset(target, 0, sizeof(DATA) * pSize);
if (axis == 'y') {
for (int i = 0; i < h; i++) {
for (int j = 0; j < mW; j++) {
target[i*mW + j] = src[i*mW + mW - 1 - j];
}
}
}
else {
for (int i = 0; i < h; i++) {
for (int j = 0; j < mW; j++) {
target[i*mW + j] = src[(h - 1 - i)*mW + j];
}
}
}
FILE *fpo1;
fpo1 = fopen("C:\\Users\\Administrator\\Desktop\\mirror.bmp", "wb");
fwrite(&bfType, 1, sizeof(WORD), fpo1);
fwrite(&strHead, 1, sizeof(tagBITMAPFILEHEADER), fpo1);
fwrite(&strInfo, 1, sizeof(tagBITMAPINFOHEADER), fpo1);
fwrite(target, 1, sizeof(DATA)*(pSize), fpo1);
fclose(fpo1);
delete[] target;
}
//複製
void BmpFile::clone(char* filePath){
DATA* target = new DATA[pSize];
memset(target, 0, sizeof(DATA)*pSize);
for (int i = 0; i < h; i++) {
for (int j = 0; j < mW; j++) {
target[i*mW + j] = src[i*mW + j];
}
}
FILE *fpo1;
fpo1 = fopen(filePath, "wb");
BITMAPFILEHEADER newHead = strHead;
BITMAPINFOHEADER newInfo = strInfo;
newHead.bfSize = (DWORD)(newHead.bfSize);
newInfo.biHeight = (DWORD)h;
newInfo.biWidth = (DWORD)mW;
newInfo.biSizeImage = (DWORD)(pSize * 3);
fwrite(&bfType, 1, sizeof(WORD), fpo1);
fwrite(&newHead, 1, sizeof(tagBITMAPFILEHEADER), fpo1);
fwrite(&newInfo, 1, sizeof(tagBITMAPINFOHEADER), fpo1);
fwrite(target, 1, sizeof(DATA)*(pSize), fpo1);
fclose(fpo1);
cout<<" success"<<endl;
delete[] target;
}
//旋轉90度
void BmpFile::rotate90(){
DATA* target = new DATA[pSize];
memset(target, 0, sizeof(DATA)*pSize);
for (int i = 0; i < h; i++) {
for (int j = 0; j < mW; j++) {
target[j*h + (h-1-i)] = src[i*mW + j];
}
}
FILE *fpo1;
fpo1 = fopen("C:\\Users\\Administrator\\Desktop\\rotate90.bmp", "wb");
BITMAPFILEHEADER newHead = strHead;
BITMAPINFOHEADER newInfo = strInfo;
newHead.bfSize = (DWORD)(newHead.bfSize);
newInfo.biHeight = (DWORD)mW;
newInfo.biWidth = (DWORD)h;
newInfo.biSizeImage = (DWORD)(pSize * 3);
fwrite(&bfType, 1, sizeof(WORD), fpo1);
fwrite(&newHead, 1, sizeof(tagBITMAPFILEHEADER), fpo1);
fwrite(&newInfo, 1, sizeof(tagBITMAPINFOHEADER), fpo1);
fwrite(target, 1, sizeof(DATA)*(pSize), fpo1);
fclose(fpo1);
cout<<"success"<<endl;
delete[] target;
};
//旋轉180度
void BmpFile::rotate180(){
DATA* target = new DATA[pSize];
memset(target, 0, sizeof(DATA)*pSize);
for (int i = 0; i < h; i++) {
for (int j = 0; j < mW; j++) {
target[(h-1-i)*mW + (mW-j-1)] = src[i*mW + j];
}
}
FILE *fpo1;
fpo1 = fopen("C:\\Users\\Administrator\\Desktop\\rotate180.bmp", "wb");
BITMAPFILEHEADER newHead = strHead;
BITMAPINFOHEADER newInfo = strInfo;
newHead.bfSize = (DWORD)(newHead.bfSize);
newInfo.biHeight = (DWORD)h;
newInfo.biWidth = (DWORD)mW;
newInfo.biSizeImage = (DWORD)(pSize * 3);
fwrite(&bfType, 1, sizeof(WORD), fpo1);
fwrite(&newHead, 1, sizeof(tagBITMAPFILEHEADER), fpo1);
fwrite(&newInfo, 1, sizeof(tagBITMAPINFOHEADER), fpo1);
fwrite(target, 1, sizeof(DATA)*(pSize), fpo1);
fclose(fpo1);
cout<<"success"<<endl;
delete[] target;
};
main.cpp中測試
#include"BmpFile.h"
using namespace std;
int main() {
//初始化BMP檔案類
BmpFile *bmp = new BmpFile();
//指定圖片路徑
char filePath[] = "C:\\Users\\Administrator\\Desktop\\input.bmp";
//從指定路徑讀取圖片
bmp->readfile(filePath);
cout << "clone the bmp" << endl;
//複製一張圖片至指定路徑
bmp->clone(filePath);
cout<<"rotate90"<<endl;
//旋轉90度
bmp->rotate90();
cout<<"rotate180"<<endl;
//旋轉180度
bmp->rotate180();
//向右上方平移
int x, y;
cout << "Translation, input the X(0<X<400): ";
cin >> x;
cout << "Translation, input the Y(0<Y<400): ";
cin >> y;
bmp->translation(x, y);
//映象
char axis;
cout << "Mirror, according to X axis or Y axis? (x or y): ";
cin >> axis;
bmp->mirror(axis);
return 0;
}
如果縮放的話,可以藉助雙線性插值方法計算縮放後非整數位置部分畫素的值,這裡就不多說了。