【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图片大小相匹配。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/868534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows电脑桌面便签在哪里找?

在忙碌的工作中&#xff0c;我们经常会有很多事情需要记住。这时&#xff0c;电脑桌面便签就成为了我们的好帮手。那么&#xff0c;在Windows电脑上&#xff0c;我们该如何找到桌面便签呢&#xff1f;下面&#xff0c;就让我来为大家详细介绍一下。 其实&#xff0c;Windows电…

告别盲目跟风!1688竞品数据分析实战指南(图文解析)

不管是哪个行业&#xff0c;想把这个做起来&#xff0c;做下去&#xff0c;第一就要学会模仿&#xff0c;不要自己盲目瞎做&#xff0c;因为别人的数据&#xff0c;都是得到了认可的&#xff0c;先模仿后超越&#xff0c;1688运营里面模仿就是要学会看竞品&#xff0c;店雷达总…

基于AD8232的心电图套件的测试

基于AD8232的心电图套件的测试 1、测试设备2、电源的选择3、 用于测试心电图套件的模拟心电图电路基本4017B的电路基于multisim的电路仿真基于STM32F103RCT6 参考测试数据 1、测试设备 1、AD8232心电模块 2、手持示波器 3、心电信号模拟发生器 4、NI multisim 14.3 5、实物待补…

django后台定制

Django 后台&#xff08;Admin&#xff09;是一个强大的工具&#xff0c;用于管理 Django 项目中的数据模型。然而&#xff0c;默认的 Django Admin 可能无法满足所有项目的需求&#xff0c;因此经常需要进行定制。以下是一些关于 Django 后台定制的推荐、介绍以及技术实现的建…

全卷积网络之FCN图像语义分割

前言 FCN是一种用于图像语义分割的全卷积网络&#xff0c;可以端到端进行像素级预测。 语义分割 图像语义分割是图像处理和机器视觉技术中的重要一环&#xff0c;旨在对图像中的每个像素进行分类。与普通的分类任务不同&#xff0c;语义分割任务输出与输入大小相同的图像&…

鸿蒙语言基础类库:【@ohos.uri (URI字符串解析)】

URI字符串解析 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入…

亿康源精英盛宴暨亿康源启动成功举办

&#xff08;本台记者报&#xff09;2024年7月7日下午&#xff0c;亿康源精英盛宴暨启动仪式在杭州市中维歌德大酒店盛大举行。此次盛会不仅吸引了行业内的专业人才、著名投资界大咖和科技领域的杰出企业家&#xff0c;还汇聚了众多关注大健康产业的各界人士&#xff0c;共同见…

软件设计之Java入门视频(13)

软件设计之Java入门视频(13) 视频教程来自B站尚硅谷&#xff1a; 尚硅谷Java入门视频教程&#xff0c;宋红康java基础视频 相关文件资料&#xff08;百度网盘&#xff09; 提取密码&#xff1a;8op3 idea 下载可以关注 软件管家 公众号 学习内容&#xff1a; 该视频共分为1-7…

【在线词典】项目实现

15_Dictionary 在线词典 搭建客户端-服务器架构 准备必要的资源 整理原始数据 整理英汉双语对照表&#xff0c;将XLSX格式转换成CSV格式&#xff0c;准备好vocabulary_list.csv文件备用 注意&#xff1a;CSV格式的文件必须使用UTF-8的字符集&#xff1b; 建立mydatabase.…

SCI三区|儿童学习优化算法KLO:基于社会进化和认知学习的优化算法

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2024年&#xff0c;ST Javed受到社会环境下家庭儿童的早期社会学习行为启发&#xff0c;提出了儿童学习优化算法&#xff08;Kids Learning Optimizer, KLO&#xff09;。 2.算法原理 2.…

珍藏多年的计算机内核结构大全笔记,掌握计算机工作原理真不难

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

江洲的《家书》,岂止抵万金

题记 今晨6点钟&#xff0c;像往日一样的背上鱼具包&#xff0c;欲驾乘清凉舒适的晨风&#xff0c;前往味江河堤享受钓翁乐趣。孰料开门一看&#xff0c;朦胧的天空竟下着淅淅沥沥的小雨。 今年的天气异常&#xff0c;是笔者寄居“西川第一天”古镇5年来所未见&#xff1a;再…

顺序表实现

size属于结构体的作用域 如果要访问一个结构体的指针用-> 如果要访问一个结构体的变量用. 点操作 #include<stdio.h> #include<stdlib.h> #include<string.h> #include"seqlist.h" //typedef struct seqList{ // SLDataType* _data; //需…

CSS技巧 - 一日一例 (1):会讨好的热情按钮

题外话: 从今天开始,我准备开设一个新的专栏,专门写 使用CSS实现各种酷炫按钮的方法,本专栏目前准备写40篇左右,大概会完成如下按钮效果: 今天,我来介绍第一个按钮的实现方法:会讨好的热情按钮。为什么我给它起这样的名字呢?你看它像不像一个不停摇尾巴的小黄?当你鼠…

29 H3C SecPath F1000 系统(概述)

29 H3C SecPath F1000 系统 系统全局功能&#xff08;高可靠性 日志设置 报表设置 会话设置 升级中心 Lcense配置 高级虚拟化 管理员 维护 诊断中心 配置指导&#xff09; 高可靠性 1 vrrp VRRP将局域网内的可以承担网关功能的一组设备划分在一起&#xff0c;组成一个备份组…

【Spring Boot】Spring AOP动态代理,以及静态代理

目录 Spring AOP代理一. 代理的概念二. 静态代理三. JDK代理3.1 重写 invoke 方法进⾏功能增强3.2 通过Proxy类随机生成代理对象 四. CGLIB代理4.1 自定义类来重写intercept方法4.2 通过Enhancer类的create方法来创建代理类 五. AOP源码剖析 总结(重中之重&#xff0c;精华) Sp…

git使用总结

git介绍 Git是一款免费、开源的分布式版本控制系统 &#xff0c;用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 git安装 下载地址 # 推荐使用国内镜像下载 http://npm.taobao.org/mirro…

解决win10报“无法加载文件……profile.ps1,因为在此系统上禁止运行脚本”的问题

打开命令行报错 解决方法 使用管理员权限打开PowerShell&#xff1a;WinX, 选择“Windows PowerShell&#xff08;管理员&#xff09;” 输入&#xff1a;Set-ExecutionPolicy -ExecutionPolicy RemoteSigned 输入&#xff1a;y确认修改安全策略 &#xff1a;y确认修改安全策略…

前端学习(三)CSS介绍及选择符

##最近在忙期末考试&#xff0c;因此前端笔记的梳理并未及时更新。在学习语言过程中&#xff0c;笔记的梳理对于知识的加深very vital.因此坚持在明天学习新知识前将笔记梳理完整。 主要内容&#xff1a;CSS介绍及选择符 最后更新时间&#xff1a;2024/7/4 目录 内容&#x…

强化学习的数学原理:值函数近似

在上次课介绍了 TD Learning&#xff0c;实际上这次课依然是介绍 TD &#xff0c;但是上次是用的表格形式介绍的&#xff0c;这次课我们将会介绍基于函数的方式。 算法其实不太难&#xff0c;难的是思路和想法&#xff0c;另外这一节将引入神经网络。 另外最经典的 Deep Q-le…