【C++】BMP图片结构深度解析及其在C++中的操作与应用

在这里插入图片描述

引言

BMP(Bitmap Image File)是一种与设备无关的图像文件格式,它采用了一种非常直接的方式来存储图像数据,即按照图像的行和列顺序,逐像素地存储颜色值。由于其简单性和可移植性,BMP文件在图像处理、图像分析以及图形学教学中被广泛使用。本文将详细解析BMP图片的内部结构,探讨在C++中如何复制图片数据、配置图片参数、保存和读取BMP图片,并讨论BMP图片在Base64编码中的应用。
在这里插入图片描述

BMP图片结构解析

BMP文件由文件头(File Header)、信息头(Info Header)和颜色数据(Color Data)三部分组成。

1. 文件头(Bitmap File Header)

文件头是一个14字节的结构,用于标识文件为BMP格式并提供关于文件类型、大小及位置的信息。

typedef struct {  
    UINT16 bfType;         // 文件类型,必须是'BM'  
    UINT32 bfSize;         // 文件大小,以字节为单位  
    UINT16 bfReserved1;    // 保留,必须为0  
    UINT16 bfReserved2;    // 保留,必须为0  
    UINT32 bfOffBits;      // 从文件头到实际位图数据的偏移量  
} BITMAPFILEHEADER;

bfType:必须为’BM’,用于标识这是一个BMP文件。
bfSize:整个文件的大小,包括文件头、信息头和颜色数据。
bfOffBits:从文件头到图像数据的偏移量,通常是文件头和信息头大小之和。

2. 信息头(Bitmap Information Header)

信息头紧随文件头之后,其大小可以是12、28、40、52、56、64或108字节,具体取决于BMP文件的版本和特性。最常用的版本是BITMAPINFOHEADER(40字节)。

typedef struct {  
    UINT32 biSize;         // 本结构的大小,以字节为单位  
    INT32  biWidth;        // 位图的宽度,以像素为单位  
    INT32  biHeight;       // 位图的高度,以像素为单位。如果biHeight为正,则位图图像存储在底向上;如果为负,则图像存储在顶向下  
    UINT16 biPlanes;       // 目标设备的平面数,必须为1  
    UINT16 biBitCount;     // 每个像素的位数,可以是1、4、8、16、24或32  
    UINT32 biCompression;  // 压缩类型,0表示不压缩  
    UINT32 biSizeImage;    // 图像数据的大小,以字节为单位。当biCompression为0时,可以不设置  
    INT32  biXPelsPerMeter; // 水平分辨率,每米像素数  
    INT32  biYPelsPerMeter; // 垂直分辨率,每米像素数  
    UINT32 biClrUsed;      // 位图实际使用的颜色表中的颜色数,如果为0,则使用biBitCount  
    UINT32 biClrImportant; // 位图显示时重要的颜色数,如果为0,则所有颜色都重要  
} BITMAPINFOHEADER;

biWidth和biHeight定义了图像的尺寸。
biBitCount决定了每个像素的颜色深度,直接影响了颜色数据的存储方式。
biCompression为0时表示图像数据未压缩。

3. 颜色数据(Color Data)

颜色数据紧跟在信息头之后,根据biBitCount的不同,颜色数据的存储方式也会有所不同。对于未压缩的24位BMP图像,颜色数据直接按照BGR(蓝、绿、红)的顺序逐行存储每个像素的颜色值。

在C++中操作BMP图片

1. 读取BMP图片

读取BMP图片通常涉及打开文件、读取文件头和信息头,然后根据这些信息读取颜色数据。

#include <fstream>  
#include <vector>  
#include <iostream>  
  
void ReadBMP(const std::string& filename, BITMAPFILEHEADER& fileHeader, BITMAPINFOHEADER& infoHeader, std::vector<unsigned char>& imageData) {  
    std::ifstream file(filename, std::ios::binary);  
    if (!file.is_open()) {  
        std::cerr << "Failed to open file: " << filename << std::endl;  
        return;  
    }  
  
    file.read(reinterpret_cast<char*>(&fileHeader), sizeof(BITMAPFILEHEADER));  
    file.read(reinterpret_cast<char*>(&infoHeader), infoHeader.biSize); // 注意这里只读取biSize指定的字节数

// 跳转到颜色数据开始的位置  
file.seekg(fileHeader.bfOffBits, std::ios::beg);  
 
// 计算颜色数据的大小  
if (infoHeader.biCompression == 0) { // 未压缩  
    imageData.resize(infoHeader.biSizeImage);  
} else {  
    // 处理压缩数据,这里仅考虑未压缩情况  
    std::cerr << "Compressed BMP images are not supported in this example." << std::endl;  
    return;  
}  
 
// 读取颜色数据  
file.read(reinterpret_cast<char*>(imageData.data()), imageData.size());  
 
file.close();
}

##### 2. 复制图片数据  
  
复制图片数据通常意味着创建一个新的BMP文件,并将原始图片的数据(包括文件头、信息头和颜色数据)复制到新文件中。  
  
```cpp  
void CopyBMP(const std::string& sourceFilename, const std::string& destinationFilename) {  
    BITMAPFILEHEADER fileHeader;  
    BITMAPINFOHEADER infoHeader;  
    std::vector<unsigned char> imageData;  
  
    ReadBMP(sourceFilename, fileHeader, infoHeader, imageData);  
  
    std::ofstream file(destinationFilename, std::ios::binary);  
    if (!file.is_open()) {  
        std::cerr << "Failed to open file for writing: " << destinationFilename << std::endl;  
        return;  
    }  
  
    file.write(reinterpret_cast<const char*>(&fileHeader), sizeof(BITMAPFILEHEADER));  
    file.write(reinterpret_cast<const char*>(&infoHeader), infoHeader.biSize);  
    file.write(reinterpret_cast<const char*>(imageData.data()), imageData.size());  
  
    file.close();  
}

3. 配置图片参数

配置图片参数通常意味着修改BITMAPINFOHEADER结构中的某些字段,如宽度、高度、颜色深度等。

void ConfigureBMP(BITMAPINFOHEADER& infoHeader, int newWidth, int newHeight, int newBitCount) {  
    infoHeader.biWidth = newWidth;  
    infoHeader.biHeight = newHeight;  
    infoHeader.biBitCount = newBitCount;  
  
    // 注意:修改biBitCount后可能需要重新计算biSizeImage和其他相关字段  
    // 这里仅作为示例,未进行完整计算  
}

4. 保存BMP图片

保存BMP图片与复制图片数据类似,但通常是在修改图片数据或参数后进行。

void SaveBMP(const std::string& filename, const BITMAPFILEHEADER& fileHeader, const BITMAPINFOHEADER& infoHeader, const std::vector<unsigned char>& imageData) {  
    std::ofstream file(filename, std::ios::binary);  
    if (!file.is_open()) {  
        std::cerr << "Failed to open file for writing: " << filename << std::endl;  
        return;  
    }  
  
    file.write(reinterpret_cast<const char*>(&fileHeader), sizeof(BITMAPFILEHEADER));  
    file.write(reinterpret_cast<const char*>(&infoHeader), infoHeader.biSize);  
    file.write(reinterpret_cast<const char*>(imageData.data()), imageData.size());  
  
    file.close();  
}

BMP图片在Base64结构中的应用

Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于BMP图片是二进制文件,因此可以将其转换为Base64字符串以便于在文本环境中传输或存储。

在C++中,可以使用第三方库(如OpenSSL、Boost.Asio等)来执行Base64编码和解码。以下是一个简化的示例,说明如何将BMP图片数据转换为Base64字符串(注意:这里不直接提供完整的Base64编码实现,因为实现细节可能因库而异)。

// 假设有函数encodeBase64可以将二进制数据转换为Base64字符串  
std::string EncodeBMPToBase64(const std::vector<unsigned char>& imageData) {  
    // 这里使用伪代码表示Base64编码过程  
    // return encodeBase64(imageData);  
    return "这里是Base64编码后的字符串"; // 示例返回  
}  
  
// 假设有函数decodeBase64可以将Base64字符串转换回二进制数据  
std::vector<unsigned char> DecodeBase64ToBMP(const std::string& base64String) {  
    // 注意:这里并没有直接给出Base64解码的实现,因为这通常依赖于外部库。  
    // 但我们可以想象有一个这样的函数,它接受一个Base64编码的字符串,  
    // 并返回一个包含解码后二进制数据的std::vector<unsigned char>。  
  
    // 伪代码示例  
    // std::vector<unsigned char> decodedData = decodeBase64(base64String);  
  
    // 由于我们没有实际的解码函数,这里只是返回一个模拟的解码后数据。  
    // 在实际应用中,你需要使用如OpenSSL、Boost.Asio或任何其他支持Base64的库来填充这个实现。  
    std::vector<unsigned char> mockDecodedData = { /* 假设的数据 */ };  
  
    // 返回一个空的vector作为占位符,代表你应该在这里填充实际的解码逻辑。  
    return mockDecodedData;  
}  
  
// 实际应用中,你可能需要结合读取BMP文件、Base64编码/解码以及保存文件的功能。  
// 下面是一个简化的例子,说明如何将这些步骤组合起来:  
  
// 假设你已经有了一个Base64编码的BMP图片字符串  
std::string base64EncodedBMP = "这里是你的Base64编码后的BMP图片字符串";  
  
// 首先,将Base64编码的字符串解码回BMP图片的二进制数据  
std::vector<unsigned char> decodedBMPData = DecodeBase64ToBMP(base64EncodedBMP);  
  
// 注意:在实际应用中,你还需要从解码后的数据中提取或重新构造BITMAPFILEHEADER和BITMAPINFOHEADER,  
// 因为这些头部信息在Base64编码过程中被当作普通二进制数据一起编码了。  
// 但为了简化,我们这里假设你已经有了或可以重新生成这些头部信息。  
  
// 假设我们已经有了一个有效的BITMAPFILEHEADER和BITMAPINFOHEADER  
BITMAPFILEHEADER fileHeader = { /* ... */ };  
BITMAPINFOHEADER infoHeader = { /* ... 假设这些是从解码数据或其他来源获取的 */ };  

现在,你可以使用SaveBMP函数将解码后的BMP数据保存到文件中(如果你已经重新构造了头部信息)
注意:这里的imageData应该是解码后的完整BMP数据,包括头部和颜色数据。 但由于我们假设只有颜色数据被Base64编码了,我们需要将头部信息和颜色数据组合起来。
在这个简化的例子中,我们跳过这个步骤,因为通常需要额外的逻辑来正确地重新组合它们。
正确的做法应该是先解析Base64数据(可能包括头部和颜色数据), 然后根据BMP格式重新构造这些部分,最后使用SaveBMP保存。

由于这个例子的限制,我们不会在这里实现完整的逻辑。
但你可以想象,在拥有完整BMP数据(包括头部和颜色数据)后, 你只需要调用SaveBMP函数,就像我们在之前的例子中做的那样,来保存文件。

请注意,上述代码中的DecodeBase64ToBMP函数是一个占位符,你需要使用实际的Base64解码库来填充它。同样,重新构造BMP文件的头部信息通常需要根据BMP的具体格式和编码的数据来进行,这可能需要额外的解析和逻辑处理。

在实际应用中,处理BMP文件和Base64编码/解码时,请确保你了解BMP文件的格式规范,并正确使用外部库来处理Base64编码和解码。此外,注意处理错误和异常情况,以确保程序的健壮性和可靠性。

当然,我们可以继续讨论如何在C++中处理BMP文件和Base64编码/解码的集成,以及如何处理从Base64解码后可能只包含BMP图片颜色数据(而不包含文件头和信息头)的情况。

首先,我们需要明确一点:通常,将整个BMP文件(包括文件头、信息头和颜色数据)编码为Base64字符串会更简单,因为这样可以避免在解码后重新构造头部信息的复杂性。但是,如果出于某种原因你只有颜色数据的Base64编码,那么你需要有额外的信息或逻辑来重新创建头部。

处理只有颜色数据被Base64编码的情况

解码Base64字符串:首先,使用Base64解码库将Base64字符串解码回原始的二进制颜色数据。
获取或创建头部信息:你需要知道或计算出BMP图片的宽度、高度、位深度等参数,以便创建BITMAPINFOHEADER。如果你没有这些信息,你可能无法正确地重新构造整个BMP文件。
创建或填充文件头:BITMAPFILEHEADER通常包含文件类型、大小、保留字节和偏移到像素数据的指针。你可以根据BITMAPINFOHEADER和颜色数据的大小来填充这个文件头。
保存BMP文件:使用上述的头部信息和解码后的颜色数据,你可以使用SaveBMP函数(或类似的函数)将BMP图片保存到文件中。
示例代码框架
以下是一个简化的代码框架,说明如何结合这些步骤:

#include <iostream>  
#include <vector>  
#include <fstream>  
  
// 假设的Base64解码函数  
std::vector<unsigned char> DecodeBase64(const std::string& base64String) {  
    // 这里应该是实际的Base64解码实现  
    // 返回解码后的二进制数据  
    std::vector<unsigned char> decodedData; // 假设这里填充了解码后的数据  
    return decodedData;  
}  
  
// 假设的创建BMP头部信息的函数  
void CreateBMPHeaders(int width, int height, int bitCount, BITMAPFILEHEADER& fileHeader, BITMAPINFOHEADER& infoHeader) {  
    // 初始化fileHeader和infoHeader  
    // ...  
    // 注意:这里只是示例,你需要根据BMP规范来设置这些值  
}  
  
// 保存BMP文件的函数(之前已经定义过,但这里再次给出以供参考)  
void SaveBMP(const std::string& filename, const BITMAPFILEHEADER& fileHeader, const BITMAPINFOHEADER& infoHeader, const std::vector<unsigned char>& imageData) {  
    // ...(与之前相同)  
}  
  
int main() {  
    std::string base64EncodedColorData = "这里是你的Base64编码后的颜色数据字符串";  
  
    // 解码Base64字符串  
    std::vector<unsigned char> decodedColorData = DecodeBase64(base64EncodedColorData);  
  
    // 假设你知道或可以计算出BMP的宽度、高度和位深度  
    int width = 640;  
    int height = 480;  
    int bitCount = 24; // 例如,24位真彩色  
  
    // 创建BMP头部信息  
    BITMAPFILEHEADER fileHeader;  
    BITMAPINFOHEADER infoHeader;  
    CreateBMPHeaders(width, height, bitCount, fileHeader, infoHeader);  
  
    // 注意:这里我们假设decodedColorData已经包含了足够的数据来填充整个BMP图片的颜色部分  
    // 如果不是这样,你可能需要调整infoHeader中的biSizeImage字段来反映实际的数据大小  
  
    // 保存BMP文件  
    SaveBMP("output.bmp", fileHeader, infoHeader, decodedColorData);  
  
    return 0;  
}

请注意,上面的代码中的DecodeBase64和CreateBMPHeaders函数都是假设的,你需要自己实现它们。DecodeBase64函数应该使用你选择的Base64解码库来实现,而CreateBMPHeaders函数则需要你根据BMP文件的规范来正确设置头部信息。

此外,如果解码后的颜色数据大小与根据宽度、高度和位深度计算出的预期大小不匹配,你需要相应地调整BITMAPINFOHEADER中的biSizeImage字段,并可能需要处理数据填充或截断的情况。但是,在这个简化的例子中,我们假设解码后的数据是完整的,并且与预期的BMP图片大小相匹配。

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值