和常见的16、24、32位色BMP格式不同,索引位图是一种使用调色板 + 8位色的位图格式,每个像素只占一个字节,像素值为0~255索引,其颜色保存在调色板对应位置数据
使用Photoshop打开已有的图片,再选择菜单【图像】、【模式】、【索引颜色…】
随后弹出的对话框中可选择对应的算法进行索引颜色整理,将当前图像的颜色归纳的256色之中
确定之后图像就变成索引位图了,再次通过菜单【图像】、【模式】、【颜色表…】,可以修改调色板内容
然后【存储为…】,保存为8位色BMP图片格式
参考msdn文档:https://docs.microsoft.com/zh-cn/windows/win32/gdi/bitmap-storage,可知位图的存储格式为
在头文件Wingdi.h中声明
成员 | 说明 |
---|---|
bfType | 0x4D42,表示BM |
bfSize | 文件总的大小,包括BITMAPFILEHEADER size |
bfReserved1 | 0 |
bfReserved2 | 0 |
bfOffBits | 图像数据(Color-index array)相对于文件头的起始位置 |
在头文件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 |
假设需要将位图读到二维数组boost::multi_array
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));
}
由于之前读取位图的bfh、bih之后,内存中就已经看到数据,所以依样画葫芦,再把数据写回文件,但由于位图宽度数据需要4的倍数,所以建议将原来的数组进行相应扩大
// boost::multi_array 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.com/zh-cn/windows/win32/gdi/bitmap-storage
https://blog.csdn.net/Wintalen/article/details/1014820
https://sunriver2000.blog.csdn.net/article/details/104251831