在編寫有關(guān)圖像顯示的軟件時,有時為了軟件的兼容性和通用性,不得不采用VGA標(biāo)準(zhǔn)的圖形模式,這就涉及到如何在16色圖形模式下顯示256色及全彩色圖像的問題。解決這一問題有兩種方法。一種是采用色彩近似的方法,即根據(jù)需要顯示的全部顏色,經(jīng)過尋優(yōu)來選擇16種代表性的顏色,每一種顏色都用這16種顏色中最接近的一種來代替?!队嬎銠C世界月刊》的《用集群方法進行顏色選擇》一文詳細描述了該方法。但事實上,該方法僅對于某些理想的情況適用,而對于更普遍的情況,該方法無論從運算速度還是從處理效果來講,都不可能令人感到滿意,因而不宜在實際中運用。另一種方法是被眾多商品化軟件所廣泛采用的抖動技術(shù),其原理是利用多種可見顏色的組合來模擬一種不可見的顏色。目前,關(guān)于彩色圖像抖動算法的資料不多。筆者通過對灰度圖像處理算法及Windows環(huán)境下一些圖像處理軟件的剖析,得出了抖動算法的一般原理和實現(xiàn)方法。
一、抖動算法原理
我們知道,在256色及全彩色圖像中,每一種顏色均由R、G、B三個顏色分量組成,而每一個分量又一般由一個字節(jié)表示。這樣,每一個顏色分量可有256級亮度變化。
本算法的關(guān)鍵在于引入亮度矩陣的概念,即采用一個16×16的矩陣來表示每一個顏色分量的亮度值,不同亮度值對應(yīng)著矩陣的不同排列。矩陣全為0時對應(yīng)亮度0,全為255時對應(yīng)亮度255。
當(dāng)亮度值為L時,亮度矩陣中將有[L255×256]個255及[(1-L255)×256]個0,此時,矩陣的平均亮度值為
L'={[L/255×256]×255+[(1-L/255)×256]×0}/256=L
這就是說,矩陣的平均亮度正好為顏色分量的實際亮度。
假設(shè)某一顏色C的R、G、B三個顏色分量的亮度矩陣分別為:
@@01A04600.GIF;公式一@@
其中rmn、gmn、bmn(0≤m, n≤15)取值為0或15。
將上述三個矩陣作疊加運算,得
@@01A04601.GIF;公式二@@
其中的Cmn為表1中由rmn、gmn、bmn所確定的顏色值。表1為VGA16色圖形模式下的標(biāo)準(zhǔn)調(diào)色板(并非設(shè)置模式后的缺省調(diào)色板)。顯然,Cmn只可能為0及9~15之中的一個。由此方法得到的矩陣Mc即可視為顏色C的模擬矩陣。由于顏色C的R、G、B三個顏色分量與亮度矩陣MR、MG、MB有著相等的亮度值,所以矩陣MC從視覺效果上來講能很好地模擬顏色C。但在顯示時,不可能用整個這樣的矩陣來替代一個像素點,那將導(dǎo)致整幅圖像長寬均變成原圖的16倍。實際的做法是:若該像素點距離圖像原點的座標(biāo)為(X,Y),則令:
m=Y mod 16
n=X mod 16 (1)
此時,可用MC中的顏色Cmn來顯示該像素。
@@01A04602.GIF;表1 16色圖形模式標(biāo)準(zhǔn)調(diào)色板@@
二、算法實現(xiàn)
1.亮度矩陣的表示
算法中要用到257個16×16的亮度矩陣,如果對每一個都分別表示的話,將占用很大的內(nèi)存空間(大于64K)。由于亮度矩陣的排列及增長均有一定的規(guī)律性,我們只需要采用一個16×16的矩陣即可。該矩陣中256個元素的取值分別為0~255,按一定規(guī)律排列。令其為:
@@01A04603.GIF;公式三@@
亮度為L時的矩陣可由H變化而來,其中
@@01A04604.GIF;公式四@@
2.顏色查找表算法中只用到了顏色0及9~15,我們可以忽略其他項并將有用部分表示為一個三維數(shù)組形式的顏色查找表,如表2所示。此時,r, g, b值作為數(shù)組下標(biāo),取值為0或1。
與之相應(yīng),我們將(2)式變?yōu)?BR> @@01A04605.GIF;公式三@@
3.每一像素的顯示步驟
①對256色圖像,由顏色索引值查顏色映射表獲取R、G、B值;對全彩色圖像,直接讀取R、G、B值;
②根據(jù)像素座標(biāo)(X,Y),由(1)式求得m, n;
③根據(jù)R、G、B值,由(3)式求得rmn、gmn、bmn;
④由rmn、gmn、bmn查表2得顏色值C;
⑤將像素以顏色C顯示于(X,Y)處。
本文所附程序用于在16色圖形模式下顯示256色及全彩色BitMap圖像。
關(guān)于BitMap圖像的格式及讀取方法,許多資料均有介紹,這里不再贅述。
該程序由Turbo C 2.0及Borland C 3.1編譯,運行通過。運行方法為:
show文件名.BMP
@@01A04606.GIF;公式三表2 顏色查找表@@
事實證明,采用本文所描述的算法,可以得到與許多商品化軟件相似的處理速度和處理效果。
源程序:
#include
#include
#include
#include
#define NoError 0
#define ErrorFileOpen1
#define ErrorFileType 2
#define ErrorImageColor 3
typedef struct tagBITMAPFILEHEADER {
unsigned int bfType;
unsigned longbfSize;
unsigned intbfReserved1; unsigned intbfReserved2;
unsigned longbfoffBits;
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
unsigned longbiSize;
unsigned long biWidth;
unsigned longbiHeight;
unsigned intbiPlanes;
unsigned intbiBitCount;
unsigned long biCompression;
unsigned long biSizeImage;
unsigned long biXPelsPerMeter;
unsigned long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
} BITMAPINFOHEADER;
typedef struct tagRGBQUAD {
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned charrgbRed;
unsigned char rgbReserved;
} RGBQUAD;
void main(int argc,char *argv[]);
int ShowBmp(char *FileName);
int GetColor(unsigned char R,unsigned char G, unsigned char B,int X,int Y);
void SetVideoMode(unsigned char Mode);
void SetPalReg(unsigned char *palReg);
void SetDacReg(unsigned char *DacReg, int Color, int Count);
void PutPixel(int X, int Y,unsigned char Color);
unsigned char PalReg[17]= { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0};
unsigned char StandardPal[48]= {
0, 0, 0, 32, 0, 0, 0,32, 0, 32,32, 0, 0, 0,32, 32, 0,32, 0,32,32, 32,32,
32, 48,48,48, 63, 0, 0, 0,63, 0, 63,63, 0, 0, 0,63, 63, 0,63, 0,63,63, 63,6
3,63,};
unsigned char LightnessMatrix [16][16]= {
{ 0,235,59,219,15,231,55,215,2,232,56,217,12,229,52,213},
{128,64,187,123,143,79,183,119,130,66,184,120,140,76,180,116},
{33,192,16,251,47,207,31,247,34,194,18,248,44,204,28,244},
{161,97,144,80,175,111,159,95,162,98,146,82,172,108,156,92},
{8,225,48,208,5,239,63,223,10,226,50,210,6,236,60,220},
{136,72,176,112,133,69,191,127,138,74,178,114,134,70,188,124},
{41,200,24,240,36,197,20,255,42,202,26,242,38,198,22,252},
{169,105,152,88,164,100,148,84,170,106,154,90,166,102,150,86},
{3,233,57,216,13,228,53,212,1,234,58,218,14,230,54,214},
{131,67,185,121,141,77,181,117,129,65,186,122,142,78,182,118},
{35,195,19,249,45,205,29,245,32,193,17,250,46,206,30,246},
{163,99,147,83,173,109,157,93,160,96,145,81,174,110,158,94},
{11,227,51,211,7,237,61,221,9,224,49,209,4,238,62,222},
{139,75,179,115,135,71,189,125,137,73,177,113,132,68,190,126},
{43,203,27,243,39,199,23,253,40,201,25,241,37,196,21,254},
{171,107,155,91,167,103,151,87,168,104,153,89,165,101,149,85},
};
unsigned char ColorTable[2][2][2]= {
{{0,12},{10,14}},{{9,13},{11,15}}};
unsigned char ColorMap[256][3];
void main (int argc, char *argv[])
{
if(argc!=2) {
printf("Usage:\n\tSHOW Filename.BMP\n");
exit(1);
}
ShowBmp(argv[1]);
}
int ShowBmp(char *FileName)
{
FILE *Fp;
BITMAPFILEHEADER FileHead;
BITMAPINFOHEADER InfoHead;
RGBQUAD RGB;
int N, W,Y,X,C,Color;
unsigned char Buffer[4096];
if (!(Fp=fopen(FileName,"rb")))
return(ErrorFileOpen);
fread(&FileHead,siazeof(BITMAPFILEHEADER),1,Fp);
if(FileHead.bfType!='BM')
return(ErrorFileType);
fread(&InfoHead,sizeof(BITMAPFILEHEADER),1,Fp);
if(InfoHead.biBitcount!=8 && InfoHead.biBitCount!=24) {
fclose(Fp);
return(ErrorImageColor);
}
SetVideoMode(0x12);
SetPalReg(PalReg);
SetDacReg(StandardPa1,0,16);
if(InfoHead.biBitcount==8) {
for (N=0;N<256;N++) {
fread(&RGB, sizeof(RGBQUAD),1,Fp);
ColorMap[N][0]=RGB.rgbRed;
ColorMap[N][1]=RGB.rgbGreen;
ColorMap[N][2]=RGB.rgbBlue;
}
W=(InfoHead.biwidth+3)/4*4;
for(Y=InfoHead.biHeight-1;Y>=480;Y--)
fread(Buffer,sizeof(unsigned char),W,Fp);
for(;Y>0;Y--) {
fread(Buffer, sizeof(unsigned char),w,FP);
for (X=0;X C=Buffer[X];
Color=GetColor(ColorMap[C][0],ColorMap[C][1],ColorMap[C][2],X,Y);
PutPixel (X,Y,color);
}
}
}
else {
W=(infoHead.biWidth*3+3)/4*4;
for(Y=InfoHead.biHeight-1;Y>639;Y--)
fread(Buffer,sizeof(unsigned char),W,Fp);
for(;Y>=0;Y--) {
fread(Buffer,sizeof(unsigned char),W,Fp);
for (X=0;X C=X*3;
Color=GetColor(Buffer[C+2],Buffer[C+1],Buffer[C],X,Y);
PutPixel(X,Y,color);
}
}
}
getch();
fclose(Fp);
SetVideoMode(0x03);
return(NoError);
}
int GetColor(unsigned char R, unsigned char G,unsigned char B, int X,int Y){
unsigned int L=LightnessMatrix[Y & 0x0F][X & 0x0F];
return(colorTable[(
unsigned int)R*256/255>L][(
unsigned int)G*256/255>L][(
unsigned int)B*256/255>L]);}
void SetVideoMode(unsigned char Mode){
-H=0x00;
-AL=Mode;
geninterrupt(0x10);
}
voidSetPalReg(unsigned char *PalReg){
-ES=FP-SEG((unsigned char far*)PalReg);
-DX=FP-OFF((unsigned char far*)PalReg;
-AX=0x1002;
geninterrupt(0x10);
}
void SetDacReg(unsigned char *DacReg,int Color,int Count){
-ES=FP-SEG((unsigned char far*)DacReg);
-DX=FP-OFF((unsigned char far*)DacReg);
-AX=0x1012;
-BX=Color;
-CX=Count;
geninterrupt(0x10);
}
void PutPixel(int X, int Y, unsigned charColor){
-AH=0x0C;
-AL=Color;
-CX=X;
-DX=Y;
geninterrupt(0x10);
}
}
一、抖動算法原理
我們知道,在256色及全彩色圖像中,每一種顏色均由R、G、B三個顏色分量組成,而每一個分量又一般由一個字節(jié)表示。這樣,每一個顏色分量可有256級亮度變化。
本算法的關(guān)鍵在于引入亮度矩陣的概念,即采用一個16×16的矩陣來表示每一個顏色分量的亮度值,不同亮度值對應(yīng)著矩陣的不同排列。矩陣全為0時對應(yīng)亮度0,全為255時對應(yīng)亮度255。
當(dāng)亮度值為L時,亮度矩陣中將有[L255×256]個255及[(1-L255)×256]個0,此時,矩陣的平均亮度值為
L'={[L/255×256]×255+[(1-L/255)×256]×0}/256=L
這就是說,矩陣的平均亮度正好為顏色分量的實際亮度。
假設(shè)某一顏色C的R、G、B三個顏色分量的亮度矩陣分別為:
@@01A04600.GIF;公式一@@
其中rmn、gmn、bmn(0≤m, n≤15)取值為0或15。
將上述三個矩陣作疊加運算,得
@@01A04601.GIF;公式二@@
其中的Cmn為表1中由rmn、gmn、bmn所確定的顏色值。表1為VGA16色圖形模式下的標(biāo)準(zhǔn)調(diào)色板(并非設(shè)置模式后的缺省調(diào)色板)。顯然,Cmn只可能為0及9~15之中的一個。由此方法得到的矩陣Mc即可視為顏色C的模擬矩陣。由于顏色C的R、G、B三個顏色分量與亮度矩陣MR、MG、MB有著相等的亮度值,所以矩陣MC從視覺效果上來講能很好地模擬顏色C。但在顯示時,不可能用整個這樣的矩陣來替代一個像素點,那將導(dǎo)致整幅圖像長寬均變成原圖的16倍。實際的做法是:若該像素點距離圖像原點的座標(biāo)為(X,Y),則令:
m=Y mod 16
n=X mod 16 (1)
此時,可用MC中的顏色Cmn來顯示該像素。
@@01A04602.GIF;表1 16色圖形模式標(biāo)準(zhǔn)調(diào)色板@@
二、算法實現(xiàn)
1.亮度矩陣的表示
算法中要用到257個16×16的亮度矩陣,如果對每一個都分別表示的話,將占用很大的內(nèi)存空間(大于64K)。由于亮度矩陣的排列及增長均有一定的規(guī)律性,我們只需要采用一個16×16的矩陣即可。該矩陣中256個元素的取值分別為0~255,按一定規(guī)律排列。令其為:
@@01A04603.GIF;公式三@@
亮度為L時的矩陣可由H變化而來,其中
@@01A04604.GIF;公式四@@
2.顏色查找表算法中只用到了顏色0及9~15,我們可以忽略其他項并將有用部分表示為一個三維數(shù)組形式的顏色查找表,如表2所示。此時,r, g, b值作為數(shù)組下標(biāo),取值為0或1。
與之相應(yīng),我們將(2)式變?yōu)?BR> @@01A04605.GIF;公式三@@
3.每一像素的顯示步驟
①對256色圖像,由顏色索引值查顏色映射表獲取R、G、B值;對全彩色圖像,直接讀取R、G、B值;
②根據(jù)像素座標(biāo)(X,Y),由(1)式求得m, n;
③根據(jù)R、G、B值,由(3)式求得rmn、gmn、bmn;
④由rmn、gmn、bmn查表2得顏色值C;
⑤將像素以顏色C顯示于(X,Y)處。
本文所附程序用于在16色圖形模式下顯示256色及全彩色BitMap圖像。
關(guān)于BitMap圖像的格式及讀取方法,許多資料均有介紹,這里不再贅述。
該程序由Turbo C 2.0及Borland C 3.1編譯,運行通過。運行方法為:
show文件名.BMP
@@01A04606.GIF;公式三表2 顏色查找表@@
事實證明,采用本文所描述的算法,可以得到與許多商品化軟件相似的處理速度和處理效果。
源程序:
#include
#include
#include
#include
#define NoError 0
#define ErrorFileOpen1
#define ErrorFileType 2
#define ErrorImageColor 3
typedef struct tagBITMAPFILEHEADER {
unsigned int bfType;
unsigned longbfSize;
unsigned intbfReserved1; unsigned intbfReserved2;
unsigned longbfoffBits;
} BITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER {
unsigned longbiSize;
unsigned long biWidth;
unsigned longbiHeight;
unsigned intbiPlanes;
unsigned intbiBitCount;
unsigned long biCompression;
unsigned long biSizeImage;
unsigned long biXPelsPerMeter;
unsigned long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
} BITMAPINFOHEADER;
typedef struct tagRGBQUAD {
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned charrgbRed;
unsigned char rgbReserved;
} RGBQUAD;
void main(int argc,char *argv[]);
int ShowBmp(char *FileName);
int GetColor(unsigned char R,unsigned char G, unsigned char B,int X,int Y);
void SetVideoMode(unsigned char Mode);
void SetPalReg(unsigned char *palReg);
void SetDacReg(unsigned char *DacReg, int Color, int Count);
void PutPixel(int X, int Y,unsigned char Color);
unsigned char PalReg[17]= { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0};
unsigned char StandardPal[48]= {
0, 0, 0, 32, 0, 0, 0,32, 0, 32,32, 0, 0, 0,32, 32, 0,32, 0,32,32, 32,32,
32, 48,48,48, 63, 0, 0, 0,63, 0, 63,63, 0, 0, 0,63, 63, 0,63, 0,63,63, 63,6
3,63,};
unsigned char LightnessMatrix [16][16]= {
{ 0,235,59,219,15,231,55,215,2,232,56,217,12,229,52,213},
{128,64,187,123,143,79,183,119,130,66,184,120,140,76,180,116},
{33,192,16,251,47,207,31,247,34,194,18,248,44,204,28,244},
{161,97,144,80,175,111,159,95,162,98,146,82,172,108,156,92},
{8,225,48,208,5,239,63,223,10,226,50,210,6,236,60,220},
{136,72,176,112,133,69,191,127,138,74,178,114,134,70,188,124},
{41,200,24,240,36,197,20,255,42,202,26,242,38,198,22,252},
{169,105,152,88,164,100,148,84,170,106,154,90,166,102,150,86},
{3,233,57,216,13,228,53,212,1,234,58,218,14,230,54,214},
{131,67,185,121,141,77,181,117,129,65,186,122,142,78,182,118},
{35,195,19,249,45,205,29,245,32,193,17,250,46,206,30,246},
{163,99,147,83,173,109,157,93,160,96,145,81,174,110,158,94},
{11,227,51,211,7,237,61,221,9,224,49,209,4,238,62,222},
{139,75,179,115,135,71,189,125,137,73,177,113,132,68,190,126},
{43,203,27,243,39,199,23,253,40,201,25,241,37,196,21,254},
{171,107,155,91,167,103,151,87,168,104,153,89,165,101,149,85},
};
unsigned char ColorTable[2][2][2]= {
{{0,12},{10,14}},{{9,13},{11,15}}};
unsigned char ColorMap[256][3];
void main (int argc, char *argv[])
{
if(argc!=2) {
printf("Usage:\n\tSHOW Filename.BMP\n");
exit(1);
}
ShowBmp(argv[1]);
}
int ShowBmp(char *FileName)
{
FILE *Fp;
BITMAPFILEHEADER FileHead;
BITMAPINFOHEADER InfoHead;
RGBQUAD RGB;
int N, W,Y,X,C,Color;
unsigned char Buffer[4096];
if (!(Fp=fopen(FileName,"rb")))
return(ErrorFileOpen);
fread(&FileHead,siazeof(BITMAPFILEHEADER),1,Fp);
if(FileHead.bfType!='BM')
return(ErrorFileType);
fread(&InfoHead,sizeof(BITMAPFILEHEADER),1,Fp);
if(InfoHead.biBitcount!=8 && InfoHead.biBitCount!=24) {
fclose(Fp);
return(ErrorImageColor);
}
SetVideoMode(0x12);
SetPalReg(PalReg);
SetDacReg(StandardPa1,0,16);
if(InfoHead.biBitcount==8) {
for (N=0;N<256;N++) {
fread(&RGB, sizeof(RGBQUAD),1,Fp);
ColorMap[N][0]=RGB.rgbRed;
ColorMap[N][1]=RGB.rgbGreen;
ColorMap[N][2]=RGB.rgbBlue;
}
W=(InfoHead.biwidth+3)/4*4;
for(Y=InfoHead.biHeight-1;Y>=480;Y--)
fread(Buffer,sizeof(unsigned char),W,Fp);
for(;Y>0;Y--) {
fread(Buffer, sizeof(unsigned char),w,FP);
for (X=0;X
Color=GetColor(ColorMap[C][0],ColorMap[C][1],ColorMap[C][2],X,Y);
PutPixel (X,Y,color);
}
}
}
else {
W=(infoHead.biWidth*3+3)/4*4;
for(Y=InfoHead.biHeight-1;Y>639;Y--)
fread(Buffer,sizeof(unsigned char),W,Fp);
for(;Y>=0;Y--) {
fread(Buffer,sizeof(unsigned char),W,Fp);
for (X=0;X
Color=GetColor(Buffer[C+2],Buffer[C+1],Buffer[C],X,Y);
PutPixel(X,Y,color);
}
}
}
getch();
fclose(Fp);
SetVideoMode(0x03);
return(NoError);
}
int GetColor(unsigned char R, unsigned char G,unsigned char B, int X,int Y){
unsigned int L=LightnessMatrix[Y & 0x0F][X & 0x0F];
return(colorTable[(
unsigned int)R*256/255>L][(
unsigned int)G*256/255>L][(
unsigned int)B*256/255>L]);}
void SetVideoMode(unsigned char Mode){
-H=0x00;
-AL=Mode;
geninterrupt(0x10);
}
voidSetPalReg(unsigned char *PalReg){
-ES=FP-SEG((unsigned char far*)PalReg);
-DX=FP-OFF((unsigned char far*)PalReg;
-AX=0x1002;
geninterrupt(0x10);
}
void SetDacReg(unsigned char *DacReg,int Color,int Count){
-ES=FP-SEG((unsigned char far*)DacReg);
-DX=FP-OFF((unsigned char far*)DacReg);
-AX=0x1012;
-BX=Color;
-CX=Count;
geninterrupt(0x10);
}
void PutPixel(int X, int Y, unsigned charColor){
-AH=0x0C;
-AL=Color;
-CX=X;
-DX=Y;
geninterrupt(0x10);
}
}

