图片编辑之对比度调整

对比度,具体的概念解释可以参考 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
}