zoukankan      html  css  js  c++  java
  • 我的第一个MFC小项目(3)之 位图转换

    关于位图的格式,文件头,信息头,颜色表,像素位等等,我在以前在《我的第一个MFC小项目(2)》有过简单的介绍,当时还操VISIO自己画图呢,不过当时真的非常非常的模糊,甚至还没有灰度图和彩图的概念。没有捧着一本书认真研究,纯粹是完成项目过程当中遇到什么不懂的就直接google...欢迎拍砖,欢迎讨论。

    8位位图除了可以索引彩色图像外,也可以是灰阶图像,相信更多的是用于灰度的图像,既然有8位的灰阶图像,也就是说从白到黑分成256种渐变,那16位灰阶图像也是存在的,只不过从白到黑分成2^16种渐变;但这是一种很大的浪费我觉得,因为灰阶图像应用不是非常广,在一些专业领域或许会用到。

    而如今PC下的更多的是24位的,32位的,16位的也有。一开始的时候还不知道什么是RGB,其实简单来说就是Red,Green,Blue分别用一定的位数来存储他们的值。16位以上多见RGB格式的图,16位位图图片还可分为R5G6B5,R5G5B5X1(有1位不携带信息,其实就是最高位),R5G5B5A1,R4G4B4A4 等等。

    其中RGB555,BITMAPINFOHEADER信息头字段biCompression成员的值是BI_RGB,它的存储格式是:

    XRRRRRGG GGGBBBBB,注意它跟24位位图没有颜色表。

    24位位图更简单,它的存储格式是:

    RRRRRRRR GGGGGGGG BBBBBBBB

    而32位的位图,新增了一个透明度,这在XP下很常见了:

    AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB

     

    图形处理中,通常把RGB三种颜色信息称为红通道、绿通道和蓝通道,相应的把透明度称为Alpha通道。由RGB形成的图像均称做真彩色

                                                                                                                                                                                                                                            

    OK,差不多就啰嗦到这里吧。真正在写程序中遇到的问题是位图的转换:将24位真彩位图转换为8位灰阶位图。而没有上面的东西,下面的东西看起来会很吃力(还包括《我的第一个MFC小项目(2)》)。

     

    问题描述的清楚一点吧:原图片是24位位图,而如今项目当中的绝多数操作都是基于8位位图,不可能让客户多一手准备8位的位图,自然而然转换的任务就落在了程序的头上了。

     

    我的解决方法是这样:就用一个方法来实现这个转换的操作,而函数的参数就一个路径(让客户只提供路径,这方便很多了),在转换之后再写为另一个8位位图文件。

     

    或许一开始拿到这样的任务,还是摸不着头脑,不知道从何下手;我画了下面的一张图,

    image

     

    Y是什么,Y就是明度,在灰阶位图当中,只有明度(灰阶值),而没有色度和浓度,它和RGB是有区别的,是两种不同的颜色编码方法,RGB注重的是色彩,而YUV注重的亮度。幸运的是,两者之间可以进行转换,

     

    
\begin{array}{rll}
Y &= 0.299 * R + 0.587 * G + 0.114 * B \\
U &= 0.436 * (B - Y) / (1 - 0.114) \\
V &= 0.615 * (R - Y) / (1 - 0.299)
\end{array}

     

    对,上面的第一个公式正是我们想要的,熟悉DIB格式的童鞋都应该有思路了吧,像素位里面就存着RGB,它与上面的公式能够帮助我们将24位位图转换为8位灰阶位图。

     

    其中有个问题,就是涉及到位图的每行所占字节数的问题,在《我的第一个MFC小项目(2)》中也涉及了一点点,所以再次重申一次,Windows规定一个扫描行所占的字节数必须是4的倍数(即以long为单位),不足的以0填充,每行所占字节数的计算公式:((((width*32)+31)/32)*4)。

     

    举一个简单的例子:假如一个8位灰阶位图,它的宽是31,则每一行需要31个字节存储,因为字节数必须是4的整倍数,所以应该是32字节,此时BITMAPINFOHEADER字段当中,biWidth=31,biBitCount=8,但要清楚,每行所占字节是32字节。因此在做每行转换为8位位图的时候,我们需要每个扫描行+32,而不是每个扫描行+31。

     

    所以下面所展示的程序,看到宽度转换可以回到这里找答案。上代码吧,

     

    void Convertto8Bit()
    {
    	HANDLE hFile;					//文件句柄
    	DWORD dwWritten;			//记录以写入的字节数
    	hFile   =   CreateFile(L"F:\\1.bmp",GENERIC_READ,
    	FILE_SHARE_READ,
    	NULL,
    	OPEN_EXISTING,
    	FILE_FLAG_SEQUENTIAL_SCAN,
    	NULL);
    
    	BITMAPFILEHEADER  bmfh;					//文件头
    	ReadFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
    
    	BITMAPINFOHEADER   bmif;					//信息头
    	ReadFile(hFile,&bmif,40,&dwWritten,NULL);  
    
    	DWORD   dwSizeImage;						//源文件像素位大小
    	dwSizeImage   =									//计算 像素位 的大小
    		bmfh.bfSize - 
    		sizeof(BITMAPFILEHEADER) - 
    		sizeof(BITMAPINFOHEADER);
    
    	BYTE * pBits   =   new BYTE[dwSizeImage];   
    	ReadFile(hFile,pBits,dwSizeImage,&dwWritten,NULL);
    
    	::CloseHandle(hFile);
    
    	long  lSrcWidth = bmif.biWidth;					//原图长与宽
    	long lSrcHeight = bmif.biHeight;
    
    	long lLineBytes;							//原图每行总字节数
    	long lScanWidth;						//转换为8位图之后的宽度,必须是大于原图且为4的倍数
    
    	lLineBytes = ((lSrcWidth*3)/4)*4;			//为了与8位位图数据对齐,原图每行总字节数也必须为4的倍数,
    	if(lLineBytes<lSrcWidth*3)					//在这里转换需要比原图每行总字节数大
    		lLineBytes += 4;
    
    	lScanWidth = (lSrcWidth/4)*4;			//8位位图的宽度必须为4的倍数,在这里转换需要比原图宽度大
    	if(lScanWidth<lSrcWidth)
    		lSrcWidth += 4;
    
    	DWORD dwSizeNewImage = lSrcWidth * lSrcHeight + 2;			//为什么要预留两位呢
    	BYTE * bits = new BYTE[dwSizeNewImage];
    
    	for(int i=0; i<lSrcHeight; i++)
    	{
    		for(int j=0; j<lSrcWidth; j++)
    		{
    			BYTE color[3];						//对应RGB的红绿蓝值
    			DWORD dwColorTemp;			//Y值,RGB转换为Y之后的值Y=0.299*R+0.587*G+0.114*B
    			for(int s=0;s<3;s++)			//一个RGB对应一个Y值
    				color[s]=pBits[i*lLineBytes+j*3+s];
    
    			dwColorTemp=unsigned int(color[2]*0.299+color[1]*0.587+color[0]*0.114);     
    
    			if(dwColorTemp>255)
    				dwColorTemp = 255;
    
    			if(dwColorTemp<0)
    				dwColorTemp = 0;
    			bits[i*lScanWidth+j]=(unsigned char)dwColorTemp;
    		}
    	}
    	bits[dwSizeNewImage-1] = 0;
    	bits[dwSizeNewImage-2] = 0;
    
    	RGBQUAD   *rgbQuad   =   new   RGBQUAD[256];   //颜色表
    	for(int i=0;i<256;i++)   
    	{   
    		rgbQuad[i].rgbBlue   =   i;     
    		rgbQuad[i].rgbGreen   =   i;     
    		rgbQuad[i].rgbRed   =   i;     
    		rgbQuad[i].rgbReserved   =   0;     
    	}   
    
    	//完善8位位图的文件头和信息头字段
    
    	bmfh.bfOffBits = sizeof(BITMAPFILEHEADER) +
    		sizeof(BITMAPINFOHEADER) + 
    		256*sizeof(RGBQUAD);				//颜色表有256个
    
    	bmfh.bfReserved1 = 0;
    	bmfh.bfReserved2 = 0;
    	
    	bmfh.bfSize = bmfh.bfOffBits + dwSizeNewImage;			//size in byte of the file
    
    	bmif.biBitCount = 8;															//信息头中bitcounts改为8
    	bmif.biSizeImage =  dwSizeNewImage;
    
    	//写入转换后得到的8位位图
    	hFile=CreateFile(L"F://2.bmp",GENERIC_WRITE,
    		FILE_SHARE_WRITE,NULL,
    		CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
    	::WriteFile(hFile,&bmfh,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
    	::WriteFile(hFile,&bmif,sizeof(BITMAPINFOHEADER),&dwWritten,NULL);
    	::WriteFile(hFile,rgbQuad,256*sizeof(RGBQUAD),&dwWritten,NULL);
    	::WriteFile(hFile,bits,dwSizeNewImage,&dwWritten,NULL);
    	::CloseHandle(hFile);
    }

    童鞋们,在这里缺了很多的检测,文件大小的检测,如果位图超过4M,就不被允许了,所以一开始应该结合getfilesize来检测,还有读入数据的时候是否已经读入正确的字节数,特别是文件头和信息头;BITMAPFILEHEADER中bftype字段应该为‘BM’;文件句柄的检测等等,因为程序长,我忽略了。一个优秀程序员都不应该忘记这些,很明显菜鸟一个啊....

    传送门:

    我的第一个MFC小项目(2)之 初涉位图
    我的第一个MFC小项目(4)之 位图转换(续)

    欢迎讨论,欢迎拍砖...

    捣乱小子 2011-12-08


    更多请访问:http://daoluan.net
  • 相关阅读:
    L3-015. 球队“食物链”【DFS + 剪枝】
    L3-002. 堆栈【主席树 or 线段树 or 分块】
    PTA L1-006 连续因子【暴力模拟】
    【路由和交换之H3C自导自演】
    【ospf-stub区域配置】
    【ospf-链路验证】
    【ospf-vlink虚拟连接】
    【c学习-14】
    【c学习-13】
    【php学习-5】
  • 原文地址:https://www.cnblogs.com/daoluanxiaozi/p/2280453.html
Copyright © 2011-2022 走看看