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图片

效果

在这里插入图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值