Files
code-snippets/C++/bmp_to_framebuffer.cpp

289 lines
10 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 将 BMP 图片写入到 FrameBuffer
// 示例sudo bmp_to_framebuffer ./image.bmp /dev/fb0
#include <iostream>
#include <fstream>
#include <vector>
#include <cstdint>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/fb.h>
#include <unistd.h>
#include <cstring>
#pragma pack(push, 1)
struct BMPFileHeader {
uint16_t type; // 文件类型 "BM" (0x4D42)
uint32_t size; // 文件大小
uint16_t reserved1; // 保留
uint16_t reserved2; // 保留
uint32_t offset; // 像素数据偏移量
};
struct BMPInfoHeader {
uint32_t size; // 信息头大小
int32_t width; // 宽度
int32_t height; // 高度 (正数表示倒序,负数表示正序)
uint16_t planes; // 色彩平面数
uint16_t bitCount; // 位深度 (1, 4, 8, 16, 24, 32)
uint32_t compression; // 压缩类型 (0:BI_RGB, 1:BI_RLE8, 2:BI_RLE4, 3:BI_BITFIELDS)
uint32_t sizeImage; // 像素数据大小
int32_t xPelsPerMeter; // 水平分辨率
int32_t yPelsPerMeter; // 垂直分辨率
uint32_t clrUsed; // 使用的颜色数
uint32_t clrImportant; // 重要颜色数
};
#pragma pack(pop)
// 辅助结构:用于解析 BI_BITFIELDS 掩码
struct BitFieldInfo {
uint32_t mask;
int shift;
int width;
};
// 计算掩码的移位和宽度
BitFieldInfo getBitFieldInfo(uint32_t mask) {
BitFieldInfo info{};
info.mask = mask;
if (mask == 0) return info;
// 计算右移位数
info.shift = 0;
uint32_t temp = mask;
while ((temp & 1) == 0) {
temp >>= 1;
info.shift++;
}
// 计算位宽
info.width = 0;
while ((temp & 1) == 1) {
temp >>= 1;
info.width++;
}
return info;
}
// RLE8 解压函数
std::vector<uint8_t> decodeRLE8(const uint8_t* data, size_t dataSize, int width, int height) {
std::vector<uint8_t> output(width * height, 0);
int x = 0, y = 0;
size_t pos = 0;
while (pos < dataSize) {
uint8_t count = data[pos++];
if (count == 0) {
uint8_t escape = data[pos++];
if (escape == 0) { // 行结束
x = 0;
y++;
} else if (escape == 1) { // 图像结束
break;
} else if (escape == 2) { // Delta 偏移
x += data[pos++];
y += data[pos++];
} else { // 绝对模式
for (int i = 0; i < escape; i++) {
if (x < width && y < height) {
output[y * width + x] = data[pos];
x++;
}
pos++;
}
if (escape % 2 != 0) pos++; // 2字节对齐
}
} else { // 编码模式
uint8_t color = data[pos++];
for (int i = 0; i < count; i++) {
if (x < width && y < height) {
output[y * width + x] = color;
x++;
}
}
}
}
return output;
}
// RLE4 解压函数
std::vector<uint8_t> decodeRLE4(const uint8_t* data, size_t dataSize, int width, int height) {
std::vector<uint8_t> output(width * height, 0);
int x = 0, y = 0;
size_t pos = 0;
while (pos < dataSize) {
uint8_t count = data[pos++];
if (count == 0) {
uint8_t escape = data[pos++];
if (escape == 0) { // 行结束
x = 0;
y++;
} else if (escape == 1) { // 图像结束
break;
} else if (escape == 2) { // Delta 偏移
x += data[pos++];
y += data[pos++];
} else { // 绝对模式
bool highNibble = true;
uint8_t byte = 0;
for (int i = 0; i < escape; i++) {
if (highNibble) byte = data[pos++];
uint8_t idx = highNibble ? (byte >> 4) : (byte & 0x0F);
if (x < width && y < height) {
output[y * width + x] = idx;
x++;
}
highNibble = !highNibble;
}
// 对齐检查:如果读取的字节数是奇数,跳过填充
if (((escape + 1) / 2) % 2 != 0) pos++;
}
} else { // 编码模式
uint8_t byte = data[pos++];
bool highNibble = true;
for (int i = 0; i < count; i++) {
uint8_t idx = highNibble ? (byte >> 4) : (byte & 0x0F);
if (x < width && y < height) {
output[y * width + x] = idx;
x++;
}
highNibble = !highNibble;
}
}
}
return output;
}
int main(int argc, char* argv[]) {
const char* filename = "image.bmp";
const char* fbname = "/dev/fb0";
if (argc > 1) filename = argv[1];
if (argc > 2) fbname = argv[2];
// 1. 打开 Framebuffer
int fbfd = open(fbname, O_RDWR);
if (fbfd == -1) { perror("打开 fb 失败"); return 1; }
// 2. 获取屏幕信息
struct fb_var_screeninfo vinfo;
struct fb_fix_screeninfo finfo;
ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo);
ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo);
uint8_t* fbp = (uint8_t*)mmap(nullptr, finfo.smem_len, PROT_READ | PROT_WRITE, MAP_SHARED, fbfd, 0);
if (fbp == MAP_FAILED) { perror("内存映射失败"); close(fbfd); return 1; }
// 3. 读取 BMP 文件
std::ifstream file(filename, std::ios::binary);
if (!file) { std::cerr << "无法打开文件" << std::endl; munmap(fbp, finfo.smem_len); close(fbfd); return 1; }
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
file.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
file.read(reinterpret_cast<char*>(&infoHeader), sizeof(infoHeader));
if (fileHeader.type != 0x4D42) { std::cerr << "非 BMP 文件" << std::endl; return 1; }
int imgWidth = infoHeader.width;
int imgHeight = abs(infoHeader.height);
bool isTopDown = (infoHeader.height < 0);
// 4. 读取颜色表 (如果是索引色)
std::vector<uint32_t> colorTable;
if (infoHeader.bitCount <= 8) {
int colorCount = infoHeader.clrUsed == 0 ? (1 << infoHeader.bitCount) : infoHeader.clrUsed;
colorTable.resize(colorCount);
file.read(reinterpret_cast<char*>(colorTable.data()), colorCount * 4);
}
// 5. 读取并解压像素数据
std::vector<uint8_t> pixelData; // 存储解压后的索引或原始数据
bool isIndexed = (infoHeader.bitCount <= 8);
file.seekg(fileHeader.offset, std::ios::beg);
std::vector<uint8_t> rawData(infoHeader.sizeImage);
file.read(reinterpret_cast<char*>(rawData.data()), rawData.size());
if (infoHeader.compression == 0) { // BI_RGB (无压缩)
int rowSize = ((imgWidth * infoHeader.bitCount + 31) / 32) * 4;
pixelData.resize(rowSize * imgHeight);
file.seekg(fileHeader.offset, std::ios::beg);
file.read(reinterpret_cast<char*>(pixelData.data()), pixelData.size());
}
else if (infoHeader.compression == 1) { // BI_RLE8
pixelData = decodeRLE8(rawData.data(), rawData.size(), imgWidth, imgHeight);
}
else if (infoHeader.compression == 2) { // BI_RLE4
pixelData = decodeRLE4(rawData.data(), rawData.size(), imgWidth, imgHeight);
}
else {
std::cerr << "不支持的压缩格式: " << infoHeader.compression << std::endl;
return 1;
}
// 6. 绘制到 Framebuffer
int drawW = std::min(imgWidth, (int)vinfo.xres);
int drawH = std::min(imgHeight, (int)vinfo.yres);
for (int y = 0; y < drawH; ++y) {
// 处理 BMP 上下翻转
int srcY = isTopDown ? y : (imgHeight - 1 - y);
for (int x = 0; x < drawW; ++x) {
uint8_t r, g, b, a = 255;
if (isIndexed) {
uint8_t idx = 0;
// 从解压数据或无压缩数据中获取索引
if (infoHeader.compression == 1 || infoHeader.compression == 2) {
idx = pixelData[srcY * imgWidth + x];
} else {
int rowSize = ((imgWidth * infoHeader.bitCount + 31) / 32) * 4;
uint8_t* srcRow = pixelData.data() + srcY * rowSize;
if (infoHeader.bitCount == 8) {
idx = srcRow[x];
} else if (infoHeader.bitCount == 4) {
idx = (x % 2 == 0) ? (srcRow[x/2] >> 4) : (srcRow[x/2] & 0x0F);
}
}
// 从颜色表获取 RGB (BMP 颜色表格式为 BGRX)
uint32_t color = colorTable[idx];
b = color & 0xFF;
g = (color >> 8) & 0xFF;
r = (color >> 16) & 0xFF;
} else {
// 直接读取 RGB (24/32位)
int rowSize = ((imgWidth * infoHeader.bitCount + 31) / 32) * 4;
uint8_t* srcRow = pixelData.data() + srcY * rowSize;
if (infoHeader.bitCount == 24) {
b = srcRow[x*3+0]; g = srcRow[x*3+1]; r = srcRow[x*3+2];
} else if (infoHeader.bitCount == 32) {
b = srcRow[x*4+0]; g = srcRow[x*4+1]; r = srcRow[x*4+2]; a = srcRow[x*4+3];
}
}
// 写入 Framebuffer (假设是 32位 BGRA 模式,这是最常见的)
long fbIdx = (x + vinfo.xoffset) * (vinfo.bits_per_pixel/8) +
(y + vinfo.yoffset) * finfo.line_length;
if (vinfo.bits_per_pixel == 32) {
fbp[fbIdx+0] = b;
fbp[fbIdx+1] = g;
fbp[fbIdx+2] = r;
fbp[fbIdx+3] = a;
} else if (vinfo.bits_per_pixel == 16) { // RGB565
uint16_t c = ((r>>3)<<11) | ((g>>2)<<5) | (b>>3);
*(uint16_t*)(&fbp[fbIdx]) = c;
}
}
}
// 清理
munmap(fbp, finfo.smem_len);
close(fbfd);
std::cout << "显示完成" << std::endl;
return 0;
}