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!\n"); 137 return 0; 138 }
運行的結果如下(不支持上傳位圖,只能看下效果了):