tag: 误差扩散,真彩色到高彩色转换
摘要: 在图像的颜色转换过程中,由于颜色值域的不同,转换过程中可能会产生误差;
误差扩散算法通过将误差传递到周围像素而减轻其造成的视觉误差。
正文:
代码使用C++,编译器:VC2005
测试平台:(CPU:AMD64x2 4200+(2.37G); 内存:DDR2 677(双通道); 编译器:VC2005)
A:程序将把一张真彩色图片转换成高彩色图片作为例子,颜色和图片的数据定义:
typedef unsigned short TUInt16;
typedef unsigned long TUInt32;
struct TARGB32 //32 bit color
{
TUInt8 b,g,r,a; // a is alpha
};
struct TPicRegion //一块颜色数据区的描述,便于参数传递
{
TARGB32* pdata; //颜色数据首地址
long byte_width; //一行数据的物理宽度(字节宽度);注意: abs(byte_width)有可能大于等于width*sizeof(TARGB32);
unsigned long width; //像素宽度
unsigned long height; //像素高度
};
//那么访问一个点的函数可以写为:
inline TARGB32& Pixels(const TPicRegion& pic,const long x,const long y)
{
return ( (TARGB32*)((TUInt8*)pic.pdata+pic.byte_width* y) )[x];
}
//高彩色颜色和图片数据定义 (
{
TUInt16 b:5 ;
TUInt16 g:5 ;
TUInt16 r:5 ;
TUInt16 x:1 ;
};
struct TPicRegion_RGB16_555 //一块颜色数据区的描述,便于参数传递
{
TRGB16_555* pdata; //颜色数据首地址
long byte_width; //一行数据的物理宽度(字节宽度)
unsigned long width; //像素宽度
unsigned long height; //像素高度
};
inline TRGB16_555& Pixels(const TPicRegion_RGB16_555& pic,const long x,const long y)
{
return ( (TRGB16_555*)((TUInt8*)pic.pdata+pic.byte_width* y) )[x];
}
例子中使用的16bit高彩色的RGB颜色编码为555; 常见的编码方式还有565和655,某些程序
里面可能还会使用4:4:4:4 (4比特Alpha通道); (提示:利用宏或泛型的方式可以用一个函数
实现同时支持这些格式)
B:真彩色图片直接转换成高彩色图片的简单实现
TRGB16_555 result;
result.r=color.r>>3 ;
result.g=color.g>>3 ;
result.b=color.b>>3 ;
return result;
}
void CvsPic32To16_0(const TPicRegion_RGB16_555& dst,const TPicRegion& src){
for (long y=0;y<src.height;++ y){
for (long x=0;x<src.width;++ x){
Pixels(dst,x,y)= ToColor16(Pixels(src,x,y));
}
}
}
来看一下函数效果
源图片(800x600):
转换后图片:
可以看到,颜色位数的降低,很多区域都产生了失真的色块
速度测试:
//
//CvsPic32To16_0 204.5 FPS
//
C:对直接转换函数的简单速度优化(功能一样)
return ((color.r>>3)<<10)|((color.g>>3)<<5)|(color.b>>3 );
}
void CvsPic32To16_1(const TPicRegion_RGB16_555& dst,const TPicRegion& src){
TUInt16* pDst=(TUInt16* )dst.pdata;
const TARGB32* pSrc= src.pdata;
const long width= src.width;
for (long y=0;y<src.height;++ y){
for (long x=0;x<width;++ x){
pDst[x]= ToColor16_1(pSrc[x]);
}
(TUInt8*&)pDst+= dst.byte_width;
(TUInt8*&)pSrc+= src.byte_width;
}
}
速度测试:
//
//CvsPic32To16_1 507.9 FPS
//
(当然,该函数还可以继续优化的,比如使用MMX、SSE等指令,可以得到更快的速度;)
D:误差扩散的颜色转换函数实现
转换过程中,将产生的转换误差,按一定的系数向右和向下传递(这样写代码比较容易);
我使用的误差传递系数为:
* 2
1 1 0 /4
其他一些常见的误差传递模板(也可以自己设定合适的模板系数系数),可以尝试一下其转换效果
* 3
0 3 2 /8
* 7
3 5 1 /16
* 8 4
2 4 8 4 2
1 2 4 2 1 /42
我使用了一个较为简单的模板,为质量、速度、额外空间占用做了折中;
简单的实现:
float dR;
float dG;
float dB;
};
inline long getBestRGB16_555Color_0(const float wantColor){
float result=wantColor*(31.0/255 );
if (result<=0 )
return 0 ;
else if (result>=31 )
return 31 ;
else
return (long )result;
}
void CvsPic32To16_ErrorDiffuse_Line_0(TUInt16* pDst,const TARGB32* pSrc,long width,TErrorColor_0* PHLineErr){
TErrorColor_0 HErr;
HErr.dR=0; HErr.dG=0; HErr.dB=0 ;
PHLineErr[-1].dB=0; PHLineErr[-1].dG=0; PHLineErr[-1].dR=0 ;
for (long x=0;x<width;++ x)
{
//cB,cG,cR为应该显示的颜色
float cB=(pSrc[x].b+HErr.dB*2+PHLineErr[x].dB+PHLineErr[x-1 ].dB);
float cG=(pSrc[x].g+HErr.dG*2+PHLineErr[x].dG+PHLineErr[x-1 ].dG);
float cR=(pSrc[x].r+HErr.dR*2+PHLineErr[x].dR+PHLineErr[x-1 ].dR);
//rB,rG,rR为转换后的颜色(也就是实际显示颜色)
long rB= getBestRGB16_555Color_0(cB);
long rG= getBestRGB16_555Color_0(cG);
long rR= getBestRGB16_555Color_0(cR);
pDst[x]= rB|(rG<<5)|(rR<<10 );
//计算两个颜色之间的差异的1/4
HErr.dB=(cB-(rB*(255.0/31)))*(1.0/4 );
HErr.dG=(cG-(rG*(255.0/31)))*(1.0/4 );
HErr.dR=(cR-(rR*(255.0/31)))*(1.0/4 );
PHLineErr[x-1].dB+= HErr.dB;
PHLineErr[x-1].dG+= HErr.dG;
PHLineErr[x-1].dR+= HErr.dR;
PHLineErr[x]= HErr;
}
}
void CvsPic32To16_ErrorDiffuse_0(const TPicRegion_RGB16_555& dst,const TPicRegion& src){
TUInt16* pDst=(TUInt16* )dst.pdata;
const TARGB32* pSrc= src.pdata;
const long width= src.width;
TErrorColor_0* _HLineErr=new TErrorColor_0[width+2 ];
for (long x=0;x<width+2;++ x){
_HLineErr[x].dR=0 ;
_HLineErr[x].dG=0 ;
_HLineErr[x].dB=0 ;
}
TErrorColor_0* HLineErr=&_HLineErr[1 ];
for (long y=0;y<src.height;++ y){
CvsPic32To16_ErrorDiffuse_Line_0(pDst,pSrc,width,HLineErr);
(TUInt8*&)pDst+= dst.byte_width;
(TUInt8*&)pSrc+= src.byte_width;
}
delete[]_HLineErr;
}
函数效果:
和上面的直接转换效果对比,色深一样但质量明显好了很多:)
(可以放大该图片来看看,对颜色误差的传递会有一个更好的认识)
速度测试:
//
//CvsPic32To16_ErrorDiffuse_0 33.6 FPS
//