在图像处理软件中生成8bit和24bit深度的BMP文件,编写程序实现不同像素深度文件的相互转换。重点掌握函数定义、缓存区分配、倒序读写、结构体操作。
8bitBMP图像文件包括4部分:
位图文件头fileheader
typedef struct tagBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; } BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
位图信息头infoheader
typedef struct tagBITMAPINFOHEADER{ DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;
调色板
typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD;
以及位图数据,结合调色板使用颜色索引值。
24bit图像不含调色板,位图数据为取值范围【0,255】的B,G,R数据,即BGR分别对应8bit。
编程思路:
24bit转8bit相对困难,因为要设计图像的调色板
几种思路:(1)看看画图软件的调色板是怎么设计的
调色板中间部分很有顺序,但最开始和最后的部分没有看懂为什么这样设计,觉得可能是为了方便向其他深度如4bit转换。所以没有选择此方法。
(2)在【0,255】上等间隔取值
8bit对应256种颜色,256不是立方数,因此可以选择RGB取3+3+2=8bit。但是似乎哪个颜色分量取2bit都对整体画面会产生较大影响,所以放弃这个方法。
如果要使三个彩色分量平均取值,666=216最接近256色,但是剩下的40个值怎么取也很麻烦。
(3) 根据图像的颜色频次选择
这是我在网上找到的方法思路:统计图像中颜色出现的频次,然后根据频次最高的256种颜色设置为调色板。这种方法的好处显而易见,图像只有较少的低频次颜色区域会出现失真,图像大部分区域保留了真实的色彩。但问题是24bit对应16777216种情况,如果全部计算需要的资源太多。因此可以选择只计算高四位,即4+4+4=12bit,共4096种情况,再取频次最高的256个。
编程思路:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<Windows.h> using namespace std; BITMAPFILEHEADER File_header,new_header; BITMAPINFOHEADER info_header,new_info; tagRGBQUAD t; static int Bpalette[256], Gpalette[256], Rpalette[256]; int main(int *argc,char *argv[]) { char* fpath1 = argv[1];//读取图像文件路径 char* fpath2 = argv[2];//写入图像文件路径 FILE* f24 = fopen(fpath1, "rb"); FILE* f8 = fopen(fpath2, "wb"); if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, f8) != 1) { cout << "read File_header error"; exit(0); } if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, f8) != 1) { cout << "read info_header error"; exit(0); } unsigned char* b1 = new unsigned char[info_header.biSizeImage]; unsigned char* b2 = new unsigned char[3 * info_header.biSizeImage]; unsigned char* start = b2; for (int i = 0; 4*i<File_header.bfOffBits-sizeof(BITMAPFILEHEADER)- sizeof(BITMAPINFOHEADER);i++) { fread(&t, sizeof(tagRGBQUAD), 1, f8); Bpalette[i] = t.rgbBlue; Gpalette[i] = t.rgbGreen; Rpalette[i] = t.rgbRed; } fread(b1, 1, info_header.biSizeImage, f8); for(int i=0;i< info_header.biSizeImage;i++) { *b2++ = Bpalette[b1[i]]; *b2++ = Gpalette[b1[i]]; *b2++ = Rpalette[b1[i]]; } new_header.bfType = 0x4D42; new_header.bfSize = (14 + 40 + 3 * info_header.biSizeImage); new_header.bfReserved1 = 0; new_header.bfReserved2 = 0; new_header.bfOffBits = 14 + 40; new_info.biSize = 40; new_info.biWidth = info_header.biWidth; new_info.biHeight = info_header.biHeight; new_info.biBitCount = 24; new_info.biPlanes = 1; new_info.biCompression = info_header.biCompression; new_info.biSizeImage = 3 * info_header.biSizeImage; new_info.biXPelsPerMeter = info_header.biXPelsPerMeter; new_info.biYPelsPerMeter = info_header.biYPelsPerMeter; new_info.biClrUsed = 0; new_info.biClrImportant = 0; fwrite(&new_header,sizeof(new_header),1,f24); fwrite(&new_info, sizeof(new_info), 1, f24); fwrite(start, 1, new_info.biSizeImage*3, f24); delete[] b1; delete[] start; fclose(f8); fclose(f24); return 0; }
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<Windows.h> #include<algorithm> using namespace std; BITMAPFILEHEADER File_header, new_header; BITMAPINFOHEADER info_header, new_info; tagRGBQUAD t; int main(int* argc, char* argv[]) { char* fpath1 = argv[1];//读取图像文件路径 char* fpath2 = argv[2];//写入图像文件路径 FILE* f24 = fopen(fpath1, "rb"); FILE* f8 = fopen(fpath2, "wb"); if (fread(&File_header, sizeof(BITMAPFILEHEADER), 1, f24) != 1) { cout << "read File_header error"; exit(0); } if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, f24) != 1) { cout << "read info_header error"; exit(0); } unsigned char* b24 = new unsigned char[info_header.biSizeImage]; unsigned char* b8 = new unsigned char[info_header.biSizeImage / 3]; unsigned int freq[4096] = {0}; unsigned int *color = new unsigned int[4096]; int pcolor = 0; fread(b24, sizeof(unsigned char), info_header.biSizeImage, f24); //找频率最高的256个颜色 for (int i = 0; i < info_header.biSizeImage/3; i++) { int a= (b24[i*3] & 0xf0)*1000000 + (b24[i*3+1] & 0xf0)*1000 + (b24[i*3+2] & 0xf0); unsigned int *b=find(color, color + sizeof(color), a); if (b != color + sizeof(color)) { freq[b - color]++; } else { color[pcolor] = a; freq[pcolor]++; pcolor++; } } int temp; for (int i = 0; i<4096; i++) { for (int j = i; j < 4096; j++) { if (freq[j] > freq[i]) { temp = color[i]; color[i] = color[i+1]; color[j] = temp; } } } //创建调色板 tagRGBQUAD t[256]; for (int i = 0; i < 256; i++) { t[i].rgbBlue = color[i] /1000000; t[i].rgbRed = color[i] %1000; t[i].rgbGreen = (color[i] - t[i].rgbBlue*1000000- t[i].rgbRed)/1000; t[i].rgbReserved = 0; } //找到每个像素对应距离最小的调色板编号 for (int i = 0; i < info_header.biSizeImage / 3; i++) { long mindist = 3*pow(256,3); int minindex = 0; for (int j = 0; j < 256; j++) { long dist = (int)pow((t[j].rgbBlue - b24[i * 3]), 2) + (int)pow((t[j].rgbGreen - b24[i * 3 + 1]),2) + (int)pow((t[j].rgbRed - b24[i * 3 + 2]),2); if (dist < mindist) { mindist = dist; minindex = j; } } b8[i] = minindex; } new_header.bfType = 0x4D42; new_header.bfSize = (14 + 40 + 4*256 + info_header.biSizeImage/3); new_header.bfReserved1 = 0; new_header.bfReserved2 = 0; new_header.bfOffBits = 14 + 40+4*256; new_info.biSize = 40; new_info.biWidth = info_header.biWidth; new_info.biHeight = info_header.biHeight; new_info.biBitCount = 8; new_info.biPlanes = 1; new_info.biCompression = 0; new_info.biSizeImage = info_header.biSizeImage/3; new_info.biXPelsPerMeter = info_header.biXPelsPerMeter; new_info.biYPelsPerMeter = info_header.biYPelsPerMeter; new_info.biClrUsed = 0; new_info.biClrImportant = 0; fwrite(&new_header, sizeof(new_header), 1, f8); fwrite(&new_info, sizeof(new_info), 1, f8); fwrite(&t, sizeof(tagRGBQUAD), 256, f8); fwrite(b8, 1, new_info.biSizeImage, f8); delete[] b24; delete[] b8; fclose(f8); fclose(f24); return 0; }
用画图软件导出一个8ibt
可以看到导出后图像出现了色彩失真
转24bit后的图像:
还是刚才那幅图像
转8bit后:
可以看到采用这种方法的产生的失真非常小。