第5章直方图修正和彩色变换这一章,我们主要和调色板打交道。先从最简单的反色讲起。5.1反色反色(invert)就是形成底片效果。例如,图5.2为图5.1反色后的结果。图5.1原图图5.2图5.1反色后的结果反色有时是很有用的,比如,图5.1中黑色区域占绝大多数,这样打印起来很费墨,我们可以先进行反色处理后再打印。反色的实际含义是将R、G、B值反转。若颜色的量化级别是256,则新图的R、G、B值为255减去原图的R、G、B值。这里针对的是所有图,包括真彩图、带调色板的彩色图(又称为伪彩色图)、和灰度图。针对不同种类有不同的处理。先看看真彩图。我们知道真彩图不带调色板,每个象素用3个字节,表示R、G、B三个分量。所以处理很简单,把反转后的R、G、B值写入新图即可。再来看看带调色板的彩色图,我们知道位图中的数据只是对应调色板中的一个索引值,我们只需要将调色板中的颜色反转,形成新调色板,而位图数据不用动,就能够实现反转。灰度图是一种特殊的伪彩色图,只不过调色板中的R、G、B值都是一样的而已。所以反转的处理和上面讲的一样。这里,我想澄清一个概念。过去我们讲二值图时,一直都说成黑白图。二值位图一定是黑白的吗?答案是不一定。我们安装Windows95时看到的那幅setup.bmp是由蓝色和黑色组成的,但它实际上是二值图。原来,它的调色板中的两种颜色是黑与蓝,而不是黑与白。所以说二值图也可以是彩色的,只不过一般情况下是黑白图而已。下面的程序实现了反色,注意其中真彩图和调色板位图处理时的差别。BOOLInvert(HWNDhWnd){DWORDOffBits,BufSize;LPBITMAPINFOHEADERlpImgData;LPSTRlpPtr;HLOCALhTempImgData;LPBITMAPINFOHEADERlpTempImgData;LPSTRlpTempPtr;HDChDc;HFILEhf;LONGx,y;LOGPALETTE*pPal;HPALETTEhPrevPalette=NULL;HLOCALhPal;DWORDi;unsignedcharRed,Green,Blue;OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);BufSize=OffBits+bi.biHeight*LineBytes;//新开缓冲区的大小if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL){MessageBox(hWnd,Errorallocmemory!,ErrorMessage,MB_OK|MB_ICONEXCLAMATION);returnFALSE;}lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData);lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData);//拷贝头信息memcpy(lpTempImgData,lpImgData,BufSize);hDc=GetDC(hWnd);if(NumColors!=0){//NumColors不为0说明是带调色板的lpPtr=(char*)lpImgData+sizeof(BITMAPINFOHEADER);//指向原图数据lpTempPtr=(char*)lpTempImgData+sizeof(BITMAPINFOHEADER);//指向新图数据//为新调色板分配内存hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+NumColors*sizeof(PALETTEENTRY));pPal=(LOGPALETTE*)LocalLock(hPal);pPal-palNumEntries=(WORD)NumColors;pPal-palVersion=0x300;for(i=0;iNumColors;i++){Blue=(unsignedchar)(*lpPtr++);Green=(unsignedchar)(*lpPtr++);Red=(unsignedchar)(*lpPtr++);lpPtr++;//反转调色板中的颜色,存入新的调色板pPal-palPalEntry[i].peRed=(BYTE)(255-Red);pPal-palPalEntry[i].peGreen=(BYTE)(255-Green);pPal-palPalEntry[i].peBlue=(BYTE)(255-Blue);pPal-palPalEntry[i].peFlags=0;*(lpTempPtr++)=(unsignedchar)(255-Blue);*(lpTempPtr++)=(unsignedchar)(255-Green);*(lpTempPtr++)=(unsignedchar)(255-Red);*(lpTempPtr++)=0;}if(hPalette!=NULL)DeleteObject(hPalette);hPalette=CreatePalette(pPal);//产生新的调色板LocalUnlock(hPal);LocalFree(hPal);if(hPalette){hPrevPalette=SelectPalette(hDc,hPalette,FALSE);RealizePalette(hDc);}}else{//不带调色板,说明是真彩色图for(y=0;ybi.biHeight;y++){lpPtr=(char*)lpImgData+(BufSize-LineBytes-y*LineBytes);lpTempPtr=(char*)lpTempImgData+(BufSize-LineBytes-y*LineBytes);for(x=0;xbi.biWidth;x++){Blue=(unsignedchar)(*lpPtr++);Green=(unsignedchar)(*lpPtr++);Red=(unsignedchar)(*lpPtr++);//反转位图数据中的颜色,存入新的位图数据中*(lpTempPtr++)=(unsignedchar)(255-Blue);*(lpTempPtr++)=(unsignedchar)(255-Green);*(lpTempPtr++)=(unsignedchar)(255-Red);}}}if(hBitmap!=NULL)DeleteObject(hBitmap);hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,(LONG)CBM_INIT,(LPSTR)lpTempImgData+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD),(LPBITMAPINFO)lpTempImgData,DIB_RGB_COLORS);if(hPalette&&hPrevPalette){SelectPalette(hDc,hPrevPalette,FALSE);RealizePalette(hDc);}hf=_lcreat(c:\\invert.bmp,0);_lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));_lwrite(hf,(LPSTR)lpTempImgData,BufSize);_lclose(hf);//释放内存和资源ReleaseDC(hWnd,hDc);LocalUnlock(hTempImgData);LocalFree(hTempImgData);GlobalUnlock(hImgData);returnTRUE;}5.2彩色图转灰度图第2章中提到了YUV的颜色表示方法,在这种表示方法中,Y分量的物理含义就是亮度,它含了灰度图(grayscale)的所有信息,只用Y分量就完全能够表示出一幅灰度图来。YUV和RGB之间有着如下的对应关系:我们利用上式,根据R、G、B的值求出Y值后,将R、G、B值都赋值成Y,就能表示出灰度图来,这就是彩色图转灰度图的原理。先看看真彩图。我们知道真彩图不带调色板,每个象素用3个字节,表示R、G、B三个分量。所以处理很简单,根据R、G、B的值求出Y值后,将R、G、B值都赋值成Y,写入新图即可。再来看看带调色板的彩色图,我们知道位图中的数据只是对应调色板中的一个索引值,我们只需要将调色板中的彩色变成灰度,形成新调色板,而位图数据不用动,就可以了。下面的程序实现了彩色图到灰度图的转换,注意其中真彩图和调色板位图处理时的差别。BOOLColortoGrayScale(HWNDhWnd){DWORDSrcOffBits,SrcBufSize,DstBufSize,DstLineBytes;LPBITMAPINFOHEADERlpImgData;LPSTRlpPtr;HLOCALhTempImgData;LPBITMAPINFOHEADERlpTempImgData;LPSTRlpTempPtr;HDChDc;HFILEhf;LONGx,y;BITMAPFILEHEADERDstBf;BITMAPINFOHEADERDstBi;LOGPALETTE*pPal;HPALETTEhPrevPalette;HLOCALhPal;DWORDNewNumColors;WORDNewBitCount;floatY;DWORDi;unsignedcharRed,Green,Blue,Gray;NewNumColors=NumColors;//NewNumColors为新图的颜色数NewBitCount=bi.biBitCount;//NewBitCount为新图的颜色位数if(NumColors==0)//真彩图{NewNumColors=256;NewBitCount=8;}//由于颜色位数有可能发生了改变,所以要重新计算每行占用的字节数以及//新图的缓冲区大小DstLineBytes=(DWORD)WIDTHBYTES(bi.biWidth*NewBitCount);DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+NewNumColors*sizeof(RGBQUAD)+(DWORD)DstLineBytes*bi.biHeight);//DstBf和DstBi为新的BITMAPFILEHEADER和BITMAPINFOHEADER//拷贝原来的头信息memcpy((char*)&DstBf,(char*)&bf,sizeof(BITMAPFILEHEADER));memcpy((char*)&DstBi,(char*)&bi,sizeof(BITMAPINFOHEADER));//做必要的改变DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER);DstBf.bfOffBits=(DWORD)(NewNumColors*sizeof(RGBQUAD)+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER));DstBi.biClrUsed=0;DstBi.biBitCount=NewBitCount;//原图的缓冲区的大小SrcOffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);SrcBufSize=SrcOffBits+bi.biHeight*LineBytes;if((hTempImgData=LocalAlloc(LHND,DstBufSize))==NULL){MessageBox(hWnd,Errorallocmemory!,ErrorMessage,MB_OK|MB_ICONEXCLAMATION);returnFALSE;}