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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

看看这组B端规范,你就会感叹:钱真是万能的。

B端设计规范的作用和价值主要体现在以下几个方面&#xff1a; 统一视觉风格和用户体验&#xff1a;B端设计规范可以规定统一的视觉风格和用户界面&#xff0c;使得不同的产品和服务在外观和交互上保持一致&#xff0c;提升用户的使用体验和满意度。 提高产品开发效率&#xf…

Android Studio下载Gradle特别慢,甚至超时,失败。。。解决方法

使用Android studio下载或更新gradle时超级慢怎么办&#xff1f; 切换服务器&#xff0c;立马解决。打开gradle配置文件 修改服务器路径 distributionUrlhttps\://mirrors.cloud.tencent.com/gradle/gradle-7.3.3-bin.zip 最后&#xff0c;同步&#xff0c;下载&#xff0c;速…

【RAG检索增强生成】MaxKB:构建企业级知识库问答系统(Ollama+Qwen2)

目录 引言1、MaxKB概述1.1 定义与目标1.2 特点与优势 2、MaxKB原理3、MaxKB架构4、基于MaxKBOllamaQwen2搭建本地知识库4.1 环境准备4.2 部署MaxKB4.3 部署Ollama4.4 部署运行qwen24.5 知识库配置4.5.1登录 MaxKB 系统4.5.2上传文档4.5.3设置分段规则 4.6 模型配置4.7 创建应用…

一入“网贷”深似海:来自多名负债人的真实自述!

在温州&#xff0c;有个名叫小琴的25岁女孩&#xff0c;她的故事&#xff0c;是许多年轻人深陷网贷泥潭的一个缩影。小琴&#xff0c;一个普通的大学毕业生&#xff0c;两年的职场生涯并未能让她摆脱大学时期留下的网贷阴影。那时&#xff0c;她每月靠着1000元的生活费勉强维持…

注意!Vue.js 或 Nuxt.js 中请停止使用.value

大家好&#xff0c;我是CodeQi&#xff01; 一位热衷于技术分享的码仔。 当您在代码中使用.value时&#xff0c;必须每次都检查变量是否存在并且是引用。 这可能很麻烦&#xff0c;因为在运行时使用.value可能会导致错误。然而&#xff0c;有一个简单的解决方法&#xff0c;即…

力扣61. 旋转链表(java)

思路&#xff1a;用快慢指针找到最后链表k个需要移动的节点&#xff0c;然后中间断开节点&#xff0c;原尾节点连接原头节点&#xff0c;返回新的节点即可&#xff1b; 但因为k可能比节点数大&#xff0c;所以需要先统计节点个数&#xff0c;再取模&#xff0c;看看k到底需要移…

【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)

目录 概述&#xff1a; 1. 阻塞IO (Blocking IO) 2. 非阻塞IO (Non-blocking IO) 3. IO多路复用 (I/O Multiplexing) 4. 信号驱动IO (Signal-driven IO) 阻塞式IO 非阻塞式IO 信号驱动IO&#xff08;Signal-driven IO&#xff09; 信号IO实例&#xff1a; IO多路复用…

2024企业加密软件丨为什么企业需要防泄密

企业为什么需要防泄密&#xff1f; 企业的数据中包含了许多核心机密&#xff0c;如研发成果、商业计划、客户资料等。这些信息的泄露可能使竞争对手获得不正当的优势&#xff0c;给企业带来严重损失。 数据泄露事件往往会对企业的声誉造成负面影响&#xff0c;降低客户信任度…

【ROS2】Ubuntu 24.04 源码编译安装 Jazzy Jalisco

目录 系统要求 系统设置 设置区域启用所需的存储库安装开发工具 构建 ROS 2 获取 ROS 2 代码使用 rosdep 安装依赖项安装额外的 RMW 实现&#xff08;可选&#xff09;在工作区构建代码 设置环境 尝试一些例子 下一步 备用编译器 Clang保持最新状态 故障排除 卸载 系统要求 当前…

RRStudio 下载及安装(详尽版)

R语言来自S语言&#xff0c;是S语言的一个变种。S语言、C语言、Unix系统都是贝尔实验室的研究成果。R 语言是一种解释型的面向数学理论研究工作者的语言&#xff0c;主要用于统计分析、绘图、数据挖掘。 R 语言自由软件&#xff0c;免费、开放源代码&#xff0c;支持各个主要计…

python实现windows非白名单exe监控并杀死

目录 一、限定死白名单 二、增加自定义白名单文件 需求&#xff1a;孩子在家用电脑上网课&#xff0c;总是悄悄打开游戏或视频软件 方案&#xff1a;指定白名单exe&#xff0c;打开非白名单的就自动被杀死&#xff0c;并记录日志供查看 一、限定死白名单 import psutil imp…

【C语言】continue 关键字

当在C语言中使用continue关键字时&#xff0c;它用于控制循环语句的执行流程。与break不同&#xff0c;continue不会终止整个循环&#xff0c;而是终止当前迭代&#xff0c;并立即开始下一次迭代。这种行为使得可以在循环内部根据特定条件跳过某些代码块&#xff0c;从而控制程…

中国国家标准介绍

一、介绍 中国国家标准信息公共服务平台&#xff0c;这是由中国国家市场监督管理总局和中国国家标准化管理委员会共同运营的官方网站 https://openstd.samr.gov.cn/ 标准分为三类&#xff1a; GB&#xff1a;强制性国家标准GB/T&#xff1a;推荐行国家标准GB/Z&#xff1a;指导…

LT86101UXE 国产原装 HDMI2.0 / DVI中继器方案 分辨率 4Kx2K 用于多显示器 DVI/HDMI电缆扩展模块

1. 描述 Lontium LT86101UXE HDMI2.0 / DVI中继器特性高速中继器符合HDMI2.0/1.4规范,最大6 gbps高速数据率、自适应均衡RX输入和pre-emphasized TX输出支持长电缆应用程序,没有晶体在船上保存BOM成本,内部灵活的PCB TX巷交换路由。 LT86101UXE HDMI2.0/DVI中继器自动检测线缆损…

傅里叶变换

傅里叶定理指出&#xff1a; 任何信号都可以表示成&#xff08;或者无限逼近&#xff09;一系列正弦信号的叠加。在一维领域&#xff0c;信号是一维正弦波的叠加&#xff0c;那么想象一下&#xff0c;在二维领域&#xff0c;实际上是无数二维平面波的叠加&#xff0c;$(x&…

【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(九)-git(1)

Git是一个版本管理控制系统&#xff08;缩写VCS&#xff09;&#xff0c;它可以在任何时间点&#xff0c;将文档的状态作为更新记录保存起来&#xff0c;也可以在任何时间点&#xff0c;将更新记录恢复回来。 文章目录 前言 一、git是什么 二、git基本概念 三、git基本命令 总结…

Vue3中为Ant Design Vue中Modal.confirm自定义内容

在一次业务开发时代码时&#xff0c;碰到了一种既想要Modal.confirm样式&#xff0c;又想要定制其content内容的情况。 大部分情况下&#xff0c;使用Modal.method()这种方式时&#xff0c;可能content内容固定都是字符串&#xff0c;那如果想要做更高级的交互怎么办&#xff…

将QT移植到IMX6ULL开发板

文章目录 前言一、编译系统1.设置交叉编译工具链2.编译系统3.烧写 二、Linux中下载QT1.安装 Qtcreator2.创建第一个程序3.配置 QtCreator 开发环境&#xff08;1&#xff09;打开选项界面&#xff08;2&#xff09;选择编译器&#xff08;3&#xff09;设置编译器&#xff08;4…

SoftCLT: 时间序列的软对比学习《Soft Contrastive Learning for Time Series》(时间序列、时序分类任务、软...

2024年6月25日&#xff0c;10:11&#xff0c;好几天没看论文了&#xff0c;一直在摸鱼写代码(虽然也没学会多少)&#xff0c;今天看一篇师兄推荐的。 论文&#xff1a; Soft Contrastive Learning for Time Series 或者是&#xff1a; Soft Contrastive Learning for Time Seri…

AutoX.js从某音分享链接解析出视频ID

背景 从某音分享的链接中解析出数字的videoID&#xff0c;用来做评论Intent跳转 思路 基本所有的短链接都是302跳转或者js跳转&#xff0c;熟悉http协议都知道&#xff0c;当状态码为302&#xff0c;从headers中提取Location即刻获得视频的原链接 链接中就带有videoId 要注意…