CTF做了图片的隐写题,还没有形成系统的认识,先来总结一下BMP图的组成,并通过将彩色图转为二值图的例子加深下理解。
只写了位图二进制文件的格式和代码实现,至于诸如RGB色彩和调色板是什么的一些概念就不啰嗦了。
BMP位图文件格式
BMP文件由文件头、位图信息头、调色板和图形数据四部分组成,真彩色图是没有调色板的。每部分的具体结构在代码中具体列出并解释。
结构体的对齐
定义文件头部各结构体时要注意对齐的问题,至于什么是结构体对齐,请看这篇博文,写的很详细http://www.cnblogs.com/motadou/archive/2009/01/17/1558438.html
位图的四字节对齐
这个对齐是位图每行像素的对齐,不要和上面的结构体对齐混淆,每行像素所占字节数必须是4字节的整数倍,不足的要填充,这时因为内存分配单位是32位的,即4字节,读入的每行像素是连续的,不能和其他行共占一个内存单位
彩色图转为灰度图
RGB共有256种灰色分量,就是R=G=B时的色彩是灰色的,所以可以用一个字节来表示,将彩色图转化为灰度图就是让真彩色图的三个颜色分量等于一个相同的数值,具体等于多少可以与多种方法,比如求三个分量的平均值,取三个分量的最大值或者使其中两个分量等于另一个分量的值,还有一种常用的方法是取加权平均值,根据这个很著名的心理学公式Gray = R*0.299 + G*0.587 + B*0.114
灰度图转化为二值图
二值图只有两个颜色,黑和白,而灰度有256种颜色,将灰度转化为二值是选取一个阈值,将灰度值大于这个阈值的置成白色,反之为黑色,关于这个阈值如何选取和作用范围也有多种方法,不再赘述,为了简单我在全局范围内选用一个固定的阈值190(针对我这张测试图片,手敲了个190,转化的效果还可以)二值图只有两个索引,可以用1bit表示,但是我写的程序是用1个字节表示的,至于如何压缩,看你喽。。。
说了这么多,还是看代码吧,这样更容易理解,额,好像几乎给每行都写了注释╮(╯▽╰)╭,不要嫌我墨迹
1 /* 2 Author:蔚蓝行 3 Blog:http://www.cnblogs.com/duanv/ 4 */ 5 #include <stdio.h> 6 #include <stdlib.h> 7 8 /*位图文件头*/ 9 #pragma pack(1)//单字节对齐 10 typedef struct tagBITMAPFILEHEADER 11 { 12 unsigned char bfType[2];//文件格式 13 unsigned int bfSize;//文件大小 14 unsigned short bfReserved1;//保留 15 unsigned short bfReserved2;//保留 16 unsigned int bfOffBits;//数据偏移量 17 }fileHeader; 18 #pragma pack() 19 20 /*位图信息头*/ 21 #pragma pack(1) 22 typedef struct tagBITMAPINFOHEADER 23 { 24 unsigned int biSize;//BITMAPINFOHEADER结构所需要的字数 25 int biWidth;//图像宽度,像素为单位 26 int biHeight;//图像高度,像素为单位,为正数,图像是倒序的,为负数,图像是正序的 27 unsigned short biPlanes;//为目标设备说明颜色平面数,总被置为1 28 unsigned short biBitCount;//说明比特数/像素 29 unsigned int biCompression;//说明数据压缩类型 30 unsigned int biSizeImage;//说明图像大小,字节单位 31 int biXPixPerMeter;//水平分辨率,像素/米 32 int biYPixPerMeter;//垂直分辨率 33 unsigned int biClrUsed;//颜色索引数 34 unsigned int biClrImportant;//重要颜色索引数,为0表示都重要 35 }fileInfo; 36 #pragma pack() 37 38 /*调色板结构*/ 39 #pragma pack(1) 40 typedef struct tagRGBQUAD 41 { 42 unsigned char rgbBlue;//蓝色分亮度 43 unsigned char rgbGreen;//绿色分亮度 44 unsigned char rgbRed;//红色分亮度 45 unsigned char rgbReserved; 46 }rgbq; 47 #pragma pack() 48 49 int main() 50 { 51 /*变量声明*/ 52 FILE *fpBMP,*fpTwoValue;//源文件fpBMP,目标文件fpTwoValue 53 54 fileHeader *fh;//位图文件头 55 fileInfo *fi;//位图信息头 56 rgbq *rg;//调色板 57 58 int i,j,k=0; 59 unsigned char *a;//存储源图每行像素值 60 unsigned char b;//存储每个像素的灰度值或二值 61 unsigned char *c;//存储每行像素的二值 62 63 /********************************************************************/ 64 65 /*打开源文件,创建输出文件*/ 66 if((fpBMP=fopen("/Users/SPY/Desktop/1.bmp","rb"))==NULL){ 67 printf("file open failed"); 68 exit(0); 69 } 70 71 if((fpTwoValue=fopen("/Users/SPY/Desktop/2.bmp","wb"))==NULL){ 72 printf("file creat failed"); 73 exit(0); 74 } 75 76 /********************************************************************/ 77 78 /*创建位图文件头,信息头,调色板*/ 79 fh=(fileHeader *)malloc(sizeof(fileHeader)); 80 fi=(fileInfo *)malloc(sizeof(fileInfo)); 81 rg=(rgbq *)malloc(2*sizeof(rgbq)); 82 83 /*读入源位图文件头和信息头*/ 84 fread(fh,sizeof(fileHeader),1,fpBMP); 85 fread(fi,sizeof(fileInfo),1,fpBMP); 86 87 /*修改文件头,信息头信息*/ 88 fi->biBitCount=8;//转换成二值图后,颜色深度由24位变为8位 89 fi->biSizeImage=((fi->biWidth+3)/4)*4*fi->biHeight;//每个像素由三字节变为单字节,同时每行像素要四字节对齐 90 fi->biClrUsed=2;//颜色索引表数量,二值图为2 91 fi->biClrImportant=0;//重要颜色索引为0,表示都重要 92 fh->bfOffBits=sizeof(fileHeader)+sizeof(fileInfo)+2*sizeof(rgbq);//数据区偏移量,等于文件头,信息头,索引表的大小之和 93 fh->bfSize=fh->bfOffBits+fi->biSizeImage;//文件大小,等于偏移量加上数据区大小 94 rg[0].rgbBlue=rg[0].rgbGreen=rg[0].rgbRed=rg[0].rgbReserved=0;//调色板颜色为黑色对应的索引为0 95 rg[1].rgbBlue=rg[1].rgbGreen=rg[1].rgbRed=255;//白色对应的索引为1 96 rg[1].rgbReserved=0; 97 98 /********************************************************************/ 99 100 /*将位图文件头,信息头和调色板写入文件*/ 101 fwrite(fh,sizeof(fileHeader),1,fpTwoValue); 102 fwrite(fi,sizeof(fileInfo),1,fpTwoValue); 103 fwrite(rg,2*sizeof(rgbq),1,fpTwoValue); 104 105 /*将彩色图转为二值图*/ 106 a=(unsigned char *)malloc((fi->biWidth*3+3)/4*4);//给变量a申请源图每行像素所占大小的空间,考虑四字节对齐问题 107 c=(unsigned char *)malloc((fi->biWidth+3)/4*4);//给变量c申请目标图每行像素所占大小的空间,同样四字节对齐 108 109 for(i=0;i<fi->biHeight;i++){//遍历图像每行的循环 110 for(j=0;j<((fi->biWidth*3+3)/4*4);j++){//遍历每行中每个字节的循环 111 fread(a+j,1,1,fpBMP);//将源图每行的每一个字节读入变量a所指向的内存空间 112 //printf("%d ",a[j]); 113 } 114 for(j=0;j<fi->biWidth;j++){//循环像素宽度次,就不会计算读入四字节填充位 115 b=(int)(0.114*(float)a[k]+0.587*(float)a[k+1]+0.299*(float)a[k+2]);//a中每三个字节分别代表BGR分量,乘上不同权值转化为灰度值 116 //printf("%d",b); 117 if(190<=(int)b) b=1;//将灰度值转化为二值,这里选取的阈值为190 118 else b=0; 119 c[j]=b;//存储每行的二值 120 k+=3; 121 } 122 fwrite(c,(fi->biWidth+3)/4*4,1,fpTwoValue);//将二值像素四字节填充写入文件,填充位没有初始化,为随机值 123 k=0; 124 } 125 126 /********************************************************************/ 127 128 /*释放内存空间,关闭文件*/ 129 free(fh); 130 free(fi); 131 free(rg); 132 free(a); 133 free(c); 134 fclose(fpBMP); 135 fclose(fpTwoValue); 136 printf("success! "); 137 return 0; 138 }
运行的结果如下(不支持上传位图,只能看下效果了):