BMP是英文Bitmap(位图)的简写,也被称为DIB(与设备无关的位图),是一种独立于显示器的位图数字图像文件格式,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持。
通过本文,我们将学习如何通过编程语言C++对BMP格式存储的图像进行基础的旋转操作。
在上一篇文章:C++:实现BMP图像的读入与复制操作 中,我们介绍了BMP类型图片的存储格式,讲解了如何使用C++对BMP图片进行读入和复制操作,在这篇文章中,我们将进一步深入,学习如何使用C++对BMP图片进行旋转操作。
实际上,通过C++对BMP图像进行旋转操作于对BMP图像进行赋值操作是大同小异的。
首先,我们需要正确读入BMP图像,这里不过多赘述,直接上代码:
#include <iostream>
#include<windows.h>
#include<fstream>
#pragma pack(2)//设置对齐边界为2
using namespace std;
struct RGB_data
{
BYTE blue;
BYTE red;
BYTE green;
};
struct RGB_data2
{
BYTE blue;
BYTE red;
BYTE green;
BYTE rgbReserved;
};
struct Plus//32位图会额外有一段
{
DWORD bV5RedMask;
DWORD bV5GreenMask;
DWORD bV5BlueMask;
DWORD bV5AlphaMask;
DWORD bV5CSType;
CIEXYZTRIPLE bV5Endpoints;
DWORD bV5GammaRed;
DWORD bV5GammaGreen;
DWORD bV5GammaBlue;
DWORD bV5Intent;
DWORD bV5ProfileData;
DWORD bV5ProfileSize;
DWORD bV5Reserved;
};
bool is32_flag = false;
string bmp_in_address;
string bmp_out_address;
BITMAPFILEHEADER former_bmp_FILE_Head;
BITMAPINFOHEADER former_bmp_DIB_Head;
RGB_data* former_rgb_data1;//用于24位BMP图像
RGB_data2* former_rgb_data2;//用于32位BMP图像
Plus former_bmp_Plus;
int former_width;//原图宽度
int former_height;//原图高度
bool bmp_read(string s)
{
//跟随地址以二进制读入bmp文件
ifstream former_bmp_info(s, ios::in | ios::binary);
if (!former_bmp_info)//若无法打开对应文件
{
cout << "Open File Failed!" << endl;
former_bmp_info.close();
return false;
}
else
{
cout << "Open File Successfully!" << endl << endl;
}
former_bmp_info.read((char*)&former_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));//读文件头
//判断是否正确读入bmp文件
if (former_bmp_FILE_Head.bfType != 0x4D42)
{
cout << "该文件非BMP格式!" << endl;
return false;
}
else
{
cout << "该文件为BMP格式!" << endl << endl;
}
former_bmp_info.read((char*)&former_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));//读DIB头
cout << "位图文件头:" << endl;
cout << "DIB头占用字节数:" << "14 bytes" << endl;
cout << "位图文件类型: 0x" << hex << former_bmp_FILE_Head.bfType << endl;//16进制
cout << "位图文件大小: " << dec << former_bmp_FILE_Head.bfSize << " bytes" << endl;
cout << "偏移字节数: " << former_bmp_FILE_Head.bfOffBits << endl;
cout << endl;
cout << "DIB头占用字节数:" << former_bmp_DIB_Head.biSize << " bytes" << endl;
cout << "位图宽度: " << former_bmp_DIB_Head.biWidth << endl;
cout << "位图高度: " << former_bmp_DIB_Head.biHeight << endl;
cout << "位图压缩类型: " << former_bmp_DIB_Head.biCompression << endl;
cout << "位图像素位数: " << former_bmp_DIB_Head.biBitCount << endl;
cout << "位图数据总占用字节数: " << former_bmp_DIB_Head.biSizeImage << " bytes" << endl;
if (former_bmp_DIB_Head.biBitCount == 32)
{
is32_flag = true;
cout << "输入的图片为32位" << endl;
former_bmp_info.read((char*)&former_bmp_Plus, sizeof(Plus));//32位图像会多一个Plus部分
}
former_width = former_bmp_DIB_Head.biWidth;
former_height = former_bmp_DIB_Head.biHeight;
//计算扫描一行所需要的字节数
int former_line_byte = (former_bmp_DIB_Head.biBitCount * former_width / 8 + 3) / 4 * 4;
int need_zeros = former_line_byte - former_bmp_DIB_Head.biBitCount * former_width / 8;
if (is32_flag)
{
former_rgb_data2 = new RGB_data2[former_width * former_height];
for (int i = 0; i < former_height; i++)
{
former_bmp_info.read((char*)former_rgb_data2 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
}
}
else
{
former_rgb_data1 = new RGB_data[former_width * former_height];
for (int i = 0; i < former_height; i++)
{
former_bmp_info.read((char*)former_rgb_data1 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
}
}
former_bmp_info.close();
return true;
}
int main()
{
cout << "请输入你要旋转的BMP图像的地址:" << endl;
cin >> bmp_in_address;
cout << endl;
if (!bmp_read(bmp_in_address))return 0;
cout << endl;
return 0;
}
分别用24位图和32位图进行检验:
经检验,代码正确,均实现了正确的读入,接下来我们开始实现对bmp图像进行旋转的函数。
首先完成旋转后FILE头和DIB头的信息编辑,旋转后与旋转前的BMP图像的信息头区别仅在于——旋转后的图像宽=旋转前图像的高,旋转后图像的高=旋转前图像的宽。
然后要注意若BMP图像为32位,需要在信息头后额外加上一段(我这边定义为PLUS结构体)
因此还要加上一行代码来判断原图像是否为32位BMP图
接下来就需要考虑旋转操作了,我们这里考虑90°顺时针旋转。
BMP格式编码像素区的存储方式为从左往右,从下往上。(旋转坐标对应示例图如下)
所以可以得到旋转后的像素区:
最后把像素区也写入到待编辑的BMP图像即可,注意补零操作
(注:在写入部分我们预先定义了两种像素格式,一种是RGB_data,只有RGB三个通道,是24位BMP图的像素格式,另一种是RGB_data2,除了RGB三个通道之外还存在一个rgbreserved(保留通道),在进行旋转操作的时候,二者的区别仅在于此)
完整的像素区编码操作:
最后我们来验证一下是否能够顺利对24位图和32位图完成旋转操作:
顺利完成顺时针旋转操作。
最后附上完整代码:
#include <iostream>
#include<windows.h>
#include<fstream>
#pragma pack(2)//设置对齐边界为2
using namespace std;
struct RGB_data
{
BYTE blue;
BYTE red;
BYTE green;
};
struct RGB_data2
{
BYTE blue;
BYTE red;
BYTE green;
BYTE rgbReserved;
};
struct Plus//32位图会额外有一段
{
DWORD bV5RedMask;
DWORD bV5GreenMask;
DWORD bV5BlueMask;
DWORD bV5AlphaMask;
DWORD bV5CSType;
CIEXYZTRIPLE bV5Endpoints;
DWORD bV5GammaRed;
DWORD bV5GammaGreen;
DWORD bV5GammaBlue;
DWORD bV5Intent;
DWORD bV5ProfileData;
DWORD bV5ProfileSize;
DWORD bV5Reserved;
};
bool is32_flag = false;
string bmp_in_address;
string bmp_out_address;
BITMAPFILEHEADER former_bmp_FILE_Head;
BITMAPINFOHEADER former_bmp_DIB_Head;
RGB_data* former_rgb_data1;//用于24位BMP图像
RGB_data2* former_rgb_data2;//用于32位BMP图像
Plus former_bmp_Plus;
int former_width;//原图宽度
int former_height;//原图高度
bool bmp_read(string s)
{
//跟随地址以二进制读入bmp文件
ifstream former_bmp_info(s, ios::in | ios::binary);
if (!former_bmp_info)//若无法打开对应文件
{
cout << "Open File Failed!" << endl;
former_bmp_info.close();
return false;
}
else
{
cout << "Open File Successfully!" << endl << endl;
}
former_bmp_info.read((char*)&former_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));//读文件头
//判断是否正确读入bmp文件
if (former_bmp_FILE_Head.bfType != 0x4D42)
{
cout << "该文件非BMP格式!" << endl;
return false;
}
else
{
cout << "该文件为BMP格式!" << endl << endl;
}
former_bmp_info.read((char*)&former_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));//读DIB头
cout << "位图文件头:" << endl;
cout << "DIB头占用字节数:" << "14 bytes" << endl;
cout << "位图文件类型: 0x" << hex << former_bmp_FILE_Head.bfType << endl;//16进制
cout << "位图文件大小: " << dec << former_bmp_FILE_Head.bfSize << " bytes" << endl;
cout << "偏移字节数: " << former_bmp_FILE_Head.bfOffBits << endl;
cout << endl;
cout << "DIB头占用字节数:" << former_bmp_DIB_Head.biSize << " bytes" << endl;
cout << "位图宽度: " << former_bmp_DIB_Head.biWidth << endl;
cout << "位图高度: " << former_bmp_DIB_Head.biHeight << endl;
cout << "位图压缩类型: " << former_bmp_DIB_Head.biCompression << endl;
cout << "位图像素位数: " << former_bmp_DIB_Head.biBitCount << endl;
cout << "位图数据总占用字节数: " << former_bmp_DIB_Head.biSizeImage << " bytes" << endl;
if (former_bmp_DIB_Head.biBitCount == 32)
{
is32_flag = true;
cout << "输入的图片为32位" << endl;
former_bmp_info.read((char*)&former_bmp_Plus, sizeof(Plus));//32位图像会多一个Plus部分
}
former_width = former_bmp_DIB_Head.biWidth;
former_height = former_bmp_DIB_Head.biHeight;
//计算扫描一行所需要的字节数
int former_line_byte = (former_bmp_DIB_Head.biBitCount * former_width / 8 + 3) / 4 * 4;
int need_zeros = former_line_byte - former_bmp_DIB_Head.biBitCount * former_width / 8;
if (is32_flag)
{
former_rgb_data2 = new RGB_data2[former_width * former_height];
for (int i = 0; i < former_height; i++)
{
former_bmp_info.read((char*)former_rgb_data2 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
}
}
else
{
former_rgb_data1 = new RGB_data[former_width * former_height];
for (int i = 0; i < former_height; i++)
{
former_bmp_info.read((char*)former_rgb_data1 + i * former_width * former_bmp_DIB_Head.biBitCount / 8, former_bmp_DIB_Head.biBitCount / 8 * former_width);
former_bmp_info.seekg(need_zeros, ios::cur);//忽略掉补位
}
}
former_bmp_info.close();
return true;
}
bool bmp_rotate(string s)
{
BITMAPFILEHEADER rotate_bmp_FILE_Head = former_bmp_FILE_Head;
BITMAPINFOHEADER rotate_bmp_DIB_Head = former_bmp_DIB_Head;
ofstream bmp_out(s, ios::out | ios::binary);
if (!bmp_out)
{
cout << "Creat File Failed!" << endl;
bmp_out.close();
return false;
}
else
{
cout << "Creat File Successfully!" << endl;
}
rotate_bmp_DIB_Head.biHeight = former_width;
rotate_bmp_DIB_Head.biWidth = former_height;
int rotate_width = former_height;
int rotate_height = former_width;
bmp_out.write((char*)&rotate_bmp_FILE_Head, sizeof(BITMAPFILEHEADER));
bmp_out.write((char*)&rotate_bmp_DIB_Head, sizeof(BITMAPINFOHEADER));
if (is32_flag)bmp_out.write((char*)&former_bmp_Plus, sizeof(Plus));
int rotate_line_byte = (rotate_bmp_DIB_Head.biBitCount * rotate_bmp_DIB_Head.biWidth / 8 + 3) / 4 * 4;
int need_zeros = rotate_line_byte - rotate_bmp_DIB_Head.biBitCount * rotate_bmp_DIB_Head.biWidth / 8;
string temp_zero = "00000";//用0补齐
if (is32_flag)
{
RGB_data2* rotate_rgb_data = new RGB_data2[former_width * former_height];
for (int i = 0; i < rotate_height; i++)//翻转后的高
{
for (int j = 0; j < rotate_width; j++)//翻转后的宽
{
//做九十度顺时针旋转 w,h 原 [i,j] 变为[j,h-i]
//原[i,j]内存 former_rgb_data + i * former_height + j 变为 rotate_rgb_data + j * former_height + h-i
//现[i,j] 对应原来 [h-j,i] rotate_rgb_data + i * former_height + j 对应 former_rgb_data + (h-j) * former_width + i
//warning:BMP格式编码像素区是从左下往右上,下方的反而先读入,所以顺逆会反一个方向
//现[i,j] 对应原来 [j,w-i] rotate_rgb_data + i * former_height + j 对应 former_rgb_data + j * former_width +w-i-1
*(rotate_rgb_data + i * rotate_width + j) = *(former_rgb_data2 + j * former_width + former_width - 1 - i);
}
}
//写入bmp文件
for (int i = 0; i < rotate_height; i++) {
bmp_out.write((char*)rotate_rgb_data + i * rotate_width * rotate_bmp_DIB_Head.biBitCount / 8, rotate_bmp_DIB_Head.biBitCount / 8 * rotate_width);
bmp_out.write((char*)&temp_zero, need_zeros);
}
delete[] rotate_rgb_data;
}
else
{
RGB_data* rotate_rgb_data = new RGB_data[former_width * former_height];
for (int i = 0; i < rotate_height; i++)//翻转后的高
{
for (int j = 0; j < rotate_width; j++)//翻转后的宽
{
*(rotate_rgb_data + i * rotate_width + j) = *(former_rgb_data1 + j * former_width + former_width - 1 - i);
}
}
//写入bmp文件
for (int i = 0; i < rotate_height; i++) {
bmp_out.write((char*)rotate_rgb_data + i * rotate_width * rotate_bmp_DIB_Head.biBitCount / 8, rotate_bmp_DIB_Head.biBitCount / 8 * rotate_width);
bmp_out.write((char*)&temp_zero, need_zeros);
}
delete[] rotate_rgb_data;
}
bmp_out.close();
return true;
}
int main()
{
cout << "请输入你要旋转的BMP图像的地址:" << endl;
cin >> bmp_in_address;
cout << endl;
if (!bmp_read(bmp_in_address))return 0;
cout << endl;
cout << "请输入你要输出的BMP图像的地址:" << endl;
cin >> bmp_out_address;
if (!bmp_rotate(bmp_out_address))cout << "旋转文件失败" << endl;
else cout << "旋转文件成功" << endl;
return 0;
}