图片编辑之对比度调整
09 Jan 2011
对比度,具体的概念解释可以参考 Wiki 或者百度百科 。简单的讲对比度反应了图片上亮区域和暗区域的层次感。而反应到图像编辑上,调整对比度就是在保证平均亮度不变的情况下,扩大或缩小亮的点和暗的点的差异。既然是要保证平均亮度不变,所以对每个点的调整比例必须作用在该值和平均亮度的差值之上,这样才能够保证计算后的平均亮度不变,故有调整公式:
Out = Average + (In – Average) * ( 1 + percent)
其中 In 表示原始像素点亮度,Average 表示整张图片的平均亮度,Out 表示调整后的亮度,而 percent 即调整范围[-1,1]。证明这个公式的正确性相当简单:
设图上有 n 个像素点,各个点亮度为 Ai,平均亮度为 A,变化率为 alpha,则有:
但是实际处理中,并没有太多的必要去计算一张图的平均亮度:一来耗时间,二来在平均亮度上的精确度并不会给图像的处理带来太多的好处。一般就假设一张图的平均亮度为 128,即一半亮度,而一张正常拍照拍出来的图平均亮度应该是在[100,150]。在肉眼看来两者基本没有任何区别(见上一篇人眼对亮度敏感度的解释),而如果真实地去计算平均亮度还会带来很大的计算量。如下:
通过计算平均亮度来调整对比度
void AdjustContrastUsingAverageThreshold ( TiBitmapData & bitmap , double level )
{
TINYIMAGE_ASSERT_VOID ( level >= - 1.0 && level <= 1.0 );
double rThresholdSum = 0 , gThresholdSum = 0 , bThresholdSum = 0 ;
double detal = level + 1 ;
int width = bitmap . GetWidth ();
int height = bitmap . GetHeight ();
int stride = bitmap . GetStride ();
int bpp = bitmap . GetBpp ();
u8 * bmpData = bitmap . GetBmpData ();
int offset = stride - width * bpp ;
long pixels = bitmap . GetTotalPixels ();
for ( int i = 0 ; i < height ; i ++ )
{
for ( int j = 0 ; j < width ; j ++ )
{
rThresholdSum += bmpData [ rIndex ];
gThresholdSum += bmpData [ gIndex ];
bThresholdSum += bmpData [ bIndex ];
bmpData += bpp ;
}
bmpData += offset ;
}
int rThreshold = ( int )( rThresholdSum / pixels );
int gThreshold = ( int )( gThresholdSum / pixels );
int bThreshold = ( int )( bThresholdSum / pixels );
u8 r_lookup [ 256 ], g_lookup [ 256 ], b_lookup [ 256 ];
for ( int i = 0 ; i < 256 ; i ++ )
{
r_lookup [ i ] = ( u8 ) CLAMP0255 ( rThreshold + ( i - rThreshold ) * detal );
g_lookup [ i ] = ( u8 ) CLAMP0255 ( gThreshold + ( i - gThreshold ) * detal );
b_lookup [ i ] = ( u8 ) CLAMP0255 ( bThreshold + ( i - bThreshold ) * detal );
}
AdjustCurve ( bitmap , r_lookup , g_lookup , b_lookup );
}
不计算平均亮度:
void AdjustContrastUsingConstThreshold ( TiBitmapData & bitmap , double level )
{
TINYIMAGE_ASSERT_VOID ( level >= - 1.0 && level <= 1.0 );
u8 lookup [ 256 ];
double delta = 1 + level ;
const int threshold = 0x7F ; //128 可以认为是平均亮度
for ( int i = 0 ; i < 256 ; i ++ )
{
lookup [ i ] = ( u8 ) CLAMP0255 ( threshold + ( i - threshold ) * delta );
}
AdjustCurve ( bitmap , lookup , TINYIMAGE_CHANEL_RGB );
}
在调用算法的时候完全可以通过一个开关来控制到底是调用哪个。个人推荐下一种,虽然不严格符合调整对比度的语义,但效果基本一致。在 Release 下下一种基本是瞬间完成,对于 3K*2K 的图也能保证在 100ms 内完成。
void AdjustContrast ( TiBitmapData & bitmap , double level )
{
#ifdef CONSTTHRESHOLD
AdjustContrastUsingConstThreshold ( bitmap , level );
#else
AdjustContrastUsingAverageThreshold ( bitmap , level );
#endif
}