您现在的位置是:首页 >技术教程 >C++ 将RGB像素数据封装为BMP图像网站首页技术教程
C++ 将RGB像素数据封装为BMP图像
前言
最近在从零学习音视频,在看雷神的教学帖,雷神通过c去将RGB像素数据封装为BMP图像,在这里通过c++进行一个简单的复现。也记录一下自己的音视频之路
RGB24
首先,先了解一下RGB,RGB是一种颜色空间,它由三个颜色通道组成,即红色(R)、绿色(G)和蓝色(B)。每个通道的值可以在0到255之间取值,表示了该颜色通道在该像素中的亮度值。通过组合这三个通道的值,可以得到所有可能的颜色。至于储存格式是用的packed,也就是RGBRGBRGB,想更详细的了解可以参考这个博主的文章:RGB与YUV转换以及存储格式
RGB24格式是指一种图像存储格式,每个像素用24个比特表示,分别用8个比特表示红、绿、蓝三个颜色通道的数值。RGB24格式通常用于视频流或图像序列的传输和处理。除了RGB24格式外,还有其他的RGB格式,如RGB32、RGB565、RGB555等。这些格式的区别在于每个像素用多少比特表示,以及每个颜色通道的分辨率不同。例如,RGB32格式每个像素用32个比特表示,其中8个比特表示透明度,另外8个比特表示红、绿、蓝三个颜色通道的数值。RGB565格式每个像素用16个比特表示,其中5个比特表示红色通道,6个比特表示绿色通道,5个比特表示蓝色通道。RGB555格式也是用16个比特表示,其中5个比特表示红、绿、蓝三个颜色通道的数值,还有1个比特表示透明度。
BMP图像
BMP(Bitmap)是一种常见的图像文件格式,它是Windows操作系统中最早使用的原生位图文件格式之一。BMP图像文件以二进制形式存储,可以存储单色、16色、256色、24位色等不同颜色深度的图像,其中24位色即每个像素用24个比特表示,分别表示红、绿、蓝三个颜色通道的数值。BMP图像文件的结构比较简单,由文件头、位图信息头、调色板和像素数据组成。其中文件头包含文件类型、文件大小、保留字段等信息,位图信息头包含图像宽度、高度、位深度等信息,调色板用于存储颜色信息,像素数据则是实际的图像数据。由于BMP图像文件存储的是原始图像数据,因此文件大小相对较大,但读取速度较快,适合用于图像处理和编辑。BMP图像文件格式已被广泛应用于Windows操作系统中的各种应用程序中,如画图、Word、PowerPoint等。
代码实现
我们需要定义两个结构体一个是文件头,一个是信息头,可看这篇博客,BMP格式详解
我们首先定义BMP文件头的结构体:
// BMP文件头结构体
struct BMPFileHeader {
uint16_t type; // 文件类型,必须为"BM"
uint32_t size; // 文件大小,单位为字节
uint16_t reserved1; // 保留字段,必须为0
uint16_t reserved2; // 保留字段,必须为0
uint32_t offset; // 像素数据起始位置,单位为字节
};
BMP 文件头是 BMP 图像文件的重要组成部分,必须包含以下字段:
文件类型(type):必须为 “BM”,即 0x42,0x4D。
文件大小(size):整个 BMP 文件的大小,包括 BMP 文件头、位图信息头、调色板和像素数据等所有数据,单位为字节。
保留字段(reserved1 和 reserved2):必须为 0。
像素数据起始位置(offset):表示 BMP 文件头和位图信息头的长度,即位图数据在文件中的偏移量,单位为字节。
然后定义信息头
// BMP位图信息头结构体
struct BMPInfoHeader {
uint32_t size; // 信息头大小,必须为40
int32_t width; // 图像宽度,单位为像素
int32_t height; // 图像高度,单位为像素
uint16_t planes; // 颜色平面数,必须为1
uint16_t bit_count; // 每个像素的位数,必须为24
uint32_t compression; // 压缩方式,必须为0
uint32_t size_image; // 像素数据大小,单位为字节
int32_t x_pels_per_meter; // X方向像素数/米
int32_t y_pels_per_meter; // Y方向像素数/米
uint32_t clr_used; // 使用的颜色数,必须为0
uint32_t clr_important; // 重要的颜色数,必须为0
};
BMP 信息头一般包含以下字段:
信息头大小(size):必须为40,表示这个结构体的大小。
图像宽度(width):单位为像素。
图像高度(height):单位为像素。
颜色平面数(planes):必须为1,表示颜色平面数为1。
每个像素的位数(bit_count):必须为24,表示每个像素用24个比特存储,分别表示红、绿、蓝三个颜色通道的数值。
压缩方式(compression):必须为0,表示不压缩。
像素数据大小(size_image):单位为字节,表示实际图像数据的大小,包括填充字节。
X方向像素数/米(x_pels_per_meter):表示图像在X方向上的分辨率,单位为像素/米。
Y方向像素数/米(y_pels_per_meter):表示图像在Y方向上的分辨率,单位为像素/米。
使用的颜色数(clr_used):必须为0,表示使用的颜色数量为默认值。
重要的颜色数(clr_important):必须为0,表示所有颜色都是重要的。
接下来插入文件和信息头
// BMP文件头
file_header.type = 0x4D42; // BM
file_header.size = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + width * height * 3;
file_header.offset = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
ofs.write(reinterpret_cast<char*>(&file_header), sizeof(file_header));
// BMP位图信息头
info_header.size = sizeof(BMPInfoHeader);
info_header.width = width;
info_header.height = height;
info_header.planes = 1;
info_header.bit_count = 24;
info_header.size_image = width * height * 3;
ofs.write(reinterpret_cast<char*>(&info_header), sizeof(info_header));
需要说明的是bit_count字段为每个像素的位数,我们这里是将RGB24来进行封装,所以我们这里应为24,size字段表示整个文件的大小,等于文件头和位图信息头的大小加上图像数据的大小;offset字段表示图像数据相对于文件头的偏移量
完整代码如下:
#include <iostream>
#include <fstream>
#pragma pack(push, 1) // 1字节对齐
// BMP文件头结构体
struct BMPFileHeader {
uint16_t type; // 文件类型,必须为"BM"
uint32_t size; // 文件大小,单位为字节
uint16_t reserved1; // 保留字段,必须为0
uint16_t reserved2; // 保留字段,必须为0
uint32_t offset; // 像素数据起始位置,单位为字节
};
// BMP位图信息头结构体
struct BMPInfoHeader {
uint32_t size; // 信息头大小,必须为40
int32_t width; // 图像宽度,单位为像素
int32_t height; // 图像高度,单位为像素
uint16_t planes; // 颜色平面数,必须为1
uint16_t bit_count; // 每个像素的位数,必须为24
uint32_t compression; // 压缩方式,必须为0
uint32_t size_image; // 像素数据大小,单位为字节
int32_t x_pels_per_meter; // X方向像素数/米
int32_t y_pels_per_meter; // Y方向像素数/米
uint32_t clr_used; // 使用的颜色数,必须为0
uint32_t clr_important; // 重要的颜色数,必须为0
};
#pragma pack(pop)
// 将RGB24格式像素数据封装为BMP图像
bool write_bmp(const char* filename, uint8_t* data, int32_t width, int32_t height) {
BMPFileHeader file_header = { 0 };
BMPInfoHeader info_header = { 0 };
std::ofstream ofs(filename, std::ios::binary);
if (!ofs) {
std::cerr << "Failed to create file: " << filename << std::endl;
return false;
}
// BMP文件头
file_header.type = 0x4D42; // BM
file_header.size = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader) + width * height * 3;
file_header.offset = sizeof(BMPFileHeader) + sizeof(BMPInfoHeader);
ofs.write(reinterpret_cast<char*>(&file_header), sizeof(file_header));
// BMP位图信息头
info_header.size = sizeof(BMPInfoHeader);
info_header.width = width;
info_header.height = height;
info_header.planes = 1;
info_header.bit_count = 24;
info_header.size_image = width * height * 3;
ofs.write(reinterpret_cast<char*>(&info_header), sizeof(info_header));
// 像素数据
int32_t row_size = ((width * 3 + 3) / 4) * 4; // 行字节数,必须为4的倍数
uint8_t* row_data = new uint8_t[row_size];
for (int32_t y = height - 1; y >= 0; --y) { // BMP图像的行是从下往上存储的
for (int32_t x = 0; x < width; ++x) {
row_data[x * 3 + 2] = data[(y * width + x) * 3 + 0]; // B
row_data[x * 3 + 1] = data[(y * width + x) * 3 + 1]; // G
row_data[x * 3 + 0] = data[(y * width + x) * 3 + 2]; // R
}
ofs.write(reinterpret_cast<char*>(row_data), row_size);
}
delete[] row_data;
ofs.close();
return true;
}
int main() {
uint8_t* data = new uint8_t[640 * 480 * 3]; // RGB24格式像素数据
// ...填充像素数据...
for (int32_t y = 0; y < 480; ++y) {
for (int32_t x = 0; x < 640; ++x) {
int32_t index = (y * 640 + x) * 3;
data[index + 0] = x % 256; // R
data[index + 1] = y % 256; // G
data[index + 2] = (x + y) % 256; // B
}
}
write_bmp("test.bmp", data, 640, 480);
delete[] data;
return 0;
}
我们按照位置填充像素数据,得到了一张渐变的BMP图片