和常见的16、24、32位色BMP格式不同,索引位图是一种使用调色板 + 8位色的位图格式,每个像素只占一个字节,像素值为0~255索引,其颜色保存在调色板对应位置数据
Photoshop制作索引位图
使用Photoshop打开已有的图片,再选择菜单【图像】、【模式】、【索引颜色…】
随后弹出的对话框中可选择对应的算法进行索引颜色整理,将当前图像的颜色归纳的256色之中
确定之后图像就变成索引位图了,再次通过菜单【图像】、【模式】、【颜色表…】,可以修改调色板内容
然后【存储为…】,保存为8位色BMP图片格式
GIMP制作索引位图
通过菜单【图像】、【模式】、【索引…】
弹出对话框将图像转换为索引颜色
再通过菜单【窗口】、【可停靠对话框】、【颜色表】
打开颜色表面板,就能在后续操作中指定颜色、及索引值
编辑完成后,需要通过菜单【文件】、【导出…】,选择Windows BMP图像,就能生成8位索引位图
Imagine查看索引位图及颜色表
Imagine是一个轻量级免费图像浏览器,可方便查看各种图片格式,官网:https://www.nyam.pe.kr/dev/imagine/
当打开索引位图时,可通过工具栏上的【编辑调色板】按扭就能打开调色板窗口
索引位图文件格式
参考msdn文档:https://docs.microsoft/zh-cn/windows/win32/gdi/bitmap-storage,可知位图的存储格式为
BITMAPFILEHEADER格式
在头文件Wingdi.h中声明
成员 | 说明 |
---|---|
bfType | 0x4D42,表示BM |
bfSize | 文件总的大小,包括BITMAPFILEHEADER size |
bfReserved1 | 0 |
bfReserved2 | 0 |
bfOffBits | 图像数据(Color-index array)相对于文件头的起始位置 |
BITMAPINFOHEADER格式
在头文件Wingdi.h中声明
成员 | 说明 |
---|---|
biSize | 结构体BITMAPINFOHEADER的大小40 |
biWidth | 图像宽度 |
biHeight | 图像高度 |
biPlanes | 1 |
biBitCount | 索引位图为8 |
biCompression | BI_RGB表示未压缩 |
biSizeImage | 图像数据大小,通常是pitch x biHeight, pitch是:biWidth * sizeof(pixel)的4字节对齐版本 |
biXPelsPerMeter | pixels-per-meter |
biYPelsPerMeter | pixels-per-meter |
biClrUsed | 索引位图为0,表示调色板有256色 |
biClrImportant | 0 |
C++中读取索引位图
假设需要将位图读到二维数组boost::multi_array<byte, 2> img中
std::ifstream ifs("aaa.bmp", std::ios::binary);
//- bfh {bfType=19778 bfSize=787512 bfReserved1=0 ...} tagBITMAPFILEHEADER
// bfType 19778 unsigned short
// bfSize 787512 unsigned long
// bfReserved1 0 unsigned short
// bfReserved2 0 unsigned short
// bfOffBits 1078 unsigned long
BITMAPFILEHEADER bfh;
ifs.read((char*)&bfh, sizeof(bfh));
if (bfh.bfType != 0x4D42)
return;
//- bih {biSize=40 biWidth=1024 biHeight=768 ...} tagBITMAPINFOHEADER
// biSize 40 unsigned long
// biWidth 1024 long
// biHeight 768 long
// biPlanes 1 unsigned short
// biBitCount 8 unsigned short
// biCompression 0 unsigned long
// biSizeImage 786434 unsigned long
// biXPelsPerMeter 11808 long
// biYPelsPerMeter 11808 long
// biClrUsed 0 unsigned long
// biClrImportant 0 unsigned long
BITMAPINFOHEADER bih;
ifs.read((char*)&bih, sizeof(bih));
if (bih.biCompression != BI_RGB || bih.biBitCount != 8)
return;
// 位图每一行的数据字节长度是4的倍数
int pitch = (bih.biWidth * bih.biBitCount + 31) / 32 * 4;
// 逐行将数据读入二维数组
boost::multi_array<unsigned char, 2> img(boost::extents[bih.biHeight][bih.biWidth]);
for (int i = 0; i < bih.biHeight; i++)
{
// Windows位图数据从下往上存储,所以需要反着读取
long offset = bfh.bfOffBits + (bih.biHeight - i - 1) * pitch;
ifs.seekg(offset, SEEK_SET);
ifs.read((char*)&img[i][0], bih.biWidth * sizeof(unsigned char));
}
C++中写入索引位图
由于之前读取位图的bfh、bih之后,内存中就已经看到数据,所以依样画葫芦,再把数据写回文件,但由于位图宽度数据需要4的倍数,所以建议将原来的数组进行相应扩大
// boost::multi_array<unsigned char, 2> img(...);
// 需要将二维数组img的宽度扩大到4的倍数
img.resize(boost::extents[img.shape()[0]][boost::alignment::align_up(img.shape()[1], 4)]);
std::ofstream ofs(path, std::ios::binary, _SH_DENYRW);
BITMAPFILEHEADER bfh = { 0 };
BITMAPINFOHEADER bih = { 0 };
bfh.bfType = 0x4D42;
bfh.bfOffBits = sizeof(bfh) + sizeof(bih) + sizeof(RGBQUAD) * 256;
bfh.bfSize = bfh.bfOffBits + img.num_elements() * sizeof(unsigned char);
bih.biSize = sizeof(bih);
bih.biWidth = img.shape()[1];
bih.biHeight = img.shape()[0];
bih.biPlanes = 1;
bih.biBitCount = 8;
bih.biCompression = BI_RGB;
bih.biSizeImage = img.num_elements() * sizeof(unsigned char);
bih.biXPelsPerMeter = 11808;
bih.biYPelsPerMeter = 11808;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
ofs.write((char*)&bfh, sizeof(bfh));
ofs.write((char*)&bih, sizeof(bih));
// 这里写入灰度数据到调色板数据,可在Photoshop面板中更改
for (int p = 0; p < 256; p++)
{
RGBQUAD q = { p, p, p, 0 };
ofs.write((char*)&q, sizeof(q));
}
for (int i = 0; i < img.shape()[0]; i++)
{
ofs.write((char*)&img[img.shape()[0] - i - 1][0], img.shape()[1] * sizeof(unsigned char));
}
参考:
https://docs.microsoft/zh-cn/windows/win32/gdi/bitmap-storage
https://blog.csdn/Wintalen/article/details/1014820
https://sunriver2000.blog.csdn/article/details/104251831
更多推荐
索引位图制作、C++读写流程
发布评论