1、第 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 值。这里针对的是所有图,包括真彩图、带调色板的彩色图(又称为伪彩色图)、和灰度图。针对不同种类有不同的处理。先看看真彩图。
2、我们知道真彩图不带调色板,每个象素用 3 个字节,表示 R、G、B 三个分量。所以处理很简单,把反转后的 R、G、B 值写入新图即可。再来看看带调色板的彩色图,我们知道位图中的数据只是对应调色板中的一个索引值,我们只需要将调色板中的颜色反转,形成新调色板,而位图数据不用动,就能够实现反转。灰度图是一种特殊的伪彩色图,只不过调色板中的 R、G、B 值 都是一样的而已。所以反转的处理和上面讲的一样。这里,我想澄清一个概念。过去我们讲二值图时,一直都说成黑白图。二值位图一定是黑白的吗?答案是不一定。我们安装 Windows95 时看到的那幅 setup.bmp 是由蓝色和黑色组成的,但它实际上是二值
3、图。原来,它的调色板中的两种颜色是黑与蓝,而不是黑与白。所以说二值图也可以是彩色的,只不过一般情况下是黑白图而已。下面的程序实现了反色,注意其中真彩图和调色板位图处理时的差别。BOOL Invert(HWND hWnd)DWORD OffBits,BufSize;LPBITMAPINFOHEADER lpImgData;LPSTR lpPtr;HLOCAL hTempImgData;LPBITMAPINFOHEADER lpTempImgData;LPSTR lpTempPtr;HDC hDc;HFILE hf;LONG x,y;LOGPALETTE *pPal;HPALETTE hPrevP
4、alette=NULL; HLOCAL hPal;DWORD i;unsigned char Red,Green,Blue;OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER);BufSize=OffBits+bi.biHeight*LineBytes; /新开缓冲区的大小if(hTempImgData=LocalAlloc(LHND,BufSize)=NULL)MessageBox(hWnd,“Error alloc memory!“,“Error Message“,MB_OK|MB_ICONEXCLAMATION);return FALSE;lpImg
5、Data=(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+siz
6、eof(BITMAPINFOHEADER); /指向新图数据/为新调色板分配内存hPal=LocalAlloc(LHND,sizeof(LOGPALETTE)+NumColors*sizeof(PALETTEENTRY);pPal =(LOGPALETTE *)LocalLock(hPal);pPal-palNumEntries =(WORD) NumColors;pPal-palVersion = 0x300;for (i = 0; i palPalEntryi.peRed=(BYTE)(255-Red);pPal-palPalEntryi.peGreen=(BYTE)(255-Green)
7、;pPal-palPalEntryi.peBlue=(BYTE)(255-Blue);pPal-palPalEntryi.peFlags=0;*(lpTempPtr+)=(unsigned char)(255-Blue);*(lpTempPtr+)=(unsigned char)(255-Green);*(lpTempPtr+)=(unsigned char)(255-Red);*(lpTempPtr+)=0;if(hPalette!=NULL) DeleteObject(hPalette);hPalette=CreatePalette(pPal); /产生新的调色板LocalUnlock(h
8、Pal);LocalFree(hPal);if(hPalette)hPrevPalette=SelectPalette(hDc,hPalette,FALSE);RealizePalette(hDc);else /不带调色板,说明是真彩色图for(y=0;ypalNumEntries =(WORD) NewNumColors;pPal-palVersion = 0x300;if(NumColors=0) /真彩色for (i = 0; i palPalEntryi.peRed=(BYTE)i;pPal-palPalEntryi.peGreen=(BYTE)i;pPal-palPalEntryi.
9、peBlue=(BYTE)i;pPal-palPalEntryi.peFlags=(BYTE)0;*(lpTempPtr+)=(unsigned char)i;*(lpTempPtr+)=(unsigned char)i;*(lpTempPtr+)=(unsigned char)i;*(lpTempPtr+)=0;else for (i = 0; i palPalEntryi.peRed=Gray;pPal-palPalEntryi.peGreen=Gray;pPal-palPalEntryi.peBlue=Gray;pPal-palPalEntryi.peFlags=0;*(lpTempPt
10、r+)=(unsigned char)Gray;*(lpTempPtr+)=(unsigned char)Gray;*(lpTempPtr+)=(unsigned char)Gray;*(lpTempPtr+)=0;if(hPalette!=NULL) DeleteObject(hPalette);/生成新的逻辑调色板hPalette=CreatePalette(pPal);LocalUnlock(hPal);LocalFree(hPal);hDc=GetDC(hWnd);if(hPalette)hPrevPalette=SelectPalette(hDc,hPalette,FALSE);Re
11、alizePalette(hDc);if(NumColors=0) /真彩色图才需要处理位图数据for(y=0;ybi.biHeight;y+)lpPtr=(char *)lpImgData+(SrcBufSize-LineBytes-y*LineBytes);lpTempPtr=(char*)lpTempImgData+(DstBufSize-DstLineBytes-y*DstLineBytes);for(x=0;xbi.biWidth;x+)Blue=(unsigned char )(*lpPtr+);Green=(unsigned char )(*lpPtr+);Red=(unsign
12、ed char )(*lpPtr+);Y=(float)(Red*0.299+Green*0.587+Blue*0.114);/从位图数据计算得到 Y 值,写入新图中Gray=(BYTE)Y; *(lpTempPtr+)=(unsigned char)Gray;if(hBitmap!=NULL)DeleteObject(hBitmap);/产生新的位图hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData,(LONG)CBM_INIT,(LPSTR)lpTempImgData+sizeof(BITMAPINFOHEADER)+N
13、ewNumColors*sizeof(RGBQUAD),(LPBITMAPINFO)lpTempImgData,DIB_RGB_COLORS);if(hPalette RealizePalette(hDc);hf=_lcreat(“c:gray.bmp“,0);_lwrite(hf,(LPSTR) _lwrite(hf,(LPSTR)lpTempImgData,DstBufSize);_lclose(hf);/释放内存和资源ReleaseDC(hWnd,hDc);LocalUnlock(hTempImgData);LocalFree(hTempImgData);GlobalUnlock(hIm
14、gData);return TRUE;5.3 真彩图转 256 色图 我们知道,真彩图中包含最多达 224 种颜色,怎样从中选出 256 种颜色,又要使颜色的失真比较小,这是一个比较复杂的问题。一种简单的做法是将 R:G:B 以 3:3:2 表示,即取 R,G 的高 3 位,B 的高两位,组成一个字节,这样就可以表示 256 种颜色了,但不难想象,这种方法的失真肯定很严重。我们下面介绍的算法能够比较好地实现真彩图到 256 色图的转换。它的思想是:准备一个长度为 4096 的数组,代表 4096 种颜色。对图中的每一个象素,取 R、G、B 的最高四位,拼成一个 12 位的整数,对应的数组元素加
15、 1。全部统计完后,就得到了这 4096 种颜色的使用频率。其中,可能有一些颜色一次也没用到,即对应的数组元素为零(假设不为零的数组元素共有 PalCounts 个) 。将这些为零的数组元素清除出去,使得前 PalCounts 个元素都不为零。将这 PalCounts 个数按从大到小的顺序排列 (这里我们使用起泡排序 )。这样,前256 种颜色就是用的最多的颜色,它们将作为调色板上的 256 种颜色。对于剩下的PalCounts-256 种颜色并不是简单地丢弃,而是用前 256 种颜色中的一种来代替,代替的原则是找有最小平方误差的那个。再次对图中的每一个象素,取 R、G、B 的最高四位,拼成一
16、个 12 位的整数,如果对应值在前 256 种颜色中,则直接将该索引值填入位图数据中,如果是在后 PalCounts-256 种颜色中,则用代替色的索引值填入位图数据中。下面的两幅图中,图 5.3 是原真彩图,图.54 是用上面的算法转换成的 256 色图,可以看出,效果还不错。图 5.3 原真彩图 图 5.4 转换后的 256 色图下面是上述算法的源程序。BOOL Trueto256(HWND hWnd)DWORD SrcBufSize,OffBits,DstBufSize,DstLineBytes;LPBITMAPINFOHEADER lpImgData;LPSTR lpPtr;HLOCA
17、L hTempImgData;LPBITMAPINFOHEADER lpTempImgData;LPSTR lpTempPtr;HDC hDc;HFILE hf;LONG x,y;BITMAPFILEHEADER DstBf;BITMAPINFOHEADER DstBi;LOGPALETTE *pPal;HPALETTE hPrevPalette; HLOCAL hPal;WORD i,j;int Red,Green,Blue,ClrIndex;DWORD ColorHits4096;WORD ColorIndex4096;DWORD PalCounts,temp;long ColorErro
18、r1,ColorError2;if(NumColors!=0) /NumColors 不为零,所以不是真彩图MessageBox(hWnd,“Must be a true color bitmap!“,“Error Message“,MB_OK|MB_ICONEXCLAMATION);return FALSE;/由于颜色位数有可能发生了改变,所以要重新计算每行占用的字节数以及/新图的缓冲区大小DstLineBytes=(DWORD)WIDTHBYTES(bi.biWidth*8);DstBufSize=(DWORD)(sizeof(BITMAPINFOHEADER)+256*sizeof(RGBQUAD)+(DWORD)DstLineBytes*bi.biHeight);/DstBf 和 DstBi 为新的 BITMAPFILEHEADER 和 BITMAPINFOHEADER/拷贝原来的头信息memcpy(char *)memcpy(char *)/做必要的改变DstBf.bfSize=DstBufSize+sizeof(BITMAPFILEHEADER);DstBf.bfOffBits=(DWORD)(256*sizeof(RGBQUAD)+sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);