C++图像处理 -- 图像颜色混合(下)

阅读提示

    《C++图像处理》系列以代码清晰,可读性为主,全部使用C++代码。

    《Delphi图像处理》系列以效率为侧重点,一般代码为PASCAL,核心代码采用BASM。

    尽可能保持二者内容一致,可相互对照。

    本文代码必须包括《C++图像处理 -- 数据类型及公用函数文章中的BmpData.h头文件。

 

    在《C++图像处理 -- 图像颜色混合(上)》和《C++图像处理 -- 图像颜色混合(中)》2篇文章中实现了Photoshop颜色图层混合模式的基本功能,本文拟在此基础上进一步实现带Alpha通道的颜色图层混合模式,这样Photoshop的颜色图层混合模式就可以说被比较完整的实现了。

    要实现带Alpha通道的32位颜色图层混合模式,比24位颜色图层混合模式复杂的多,后者只是前者的一个特例。

    下面是带Alpha通道的32位颜色图层混合模式的全部代码:

//---------------------------------------------------------------------------

typedef FLOAT		BWParams, *PBWParams;

// 黑白调整缺省参数:红,黄,绿,洋红,蓝,青
CONST INT _BWDefault[] = {410, 614, 410, 819, 205, 614};

enum
{
	BWIndexBlue		= 0x40000,
	BWIndexGreen	= 0x20000,
	BWIndexRed		= 0x00000
};

enum
{
	IndexBlue	= 0x00000,
	IndexGreen	= 0x10000,
	IndexRed	= 0x20000
};

typedef union 				// 颜色分量交换结构
{
	INT tmp;				// 交换时用的临时变量
	struct
	{
		SHORT value;		// 颜色分量值
		SHORT index;		// 颜色分量索引
	};
}RGBIndex;
//---------------------------------------------------------------------------

// 交换像素分量
FORCEINLINE
VOID SwapRgb(RGBIndex &a, RGBIndex &b)
{
	a.tmp ^= b.tmp;
	b.tmp ^= a.tmp;
	a.tmp ^= b.tmp;
}
//---------------------------------------------------------------------------

// 获取黑白灰度
FORCEINLINE
INT	GetBWGray(CONST PARGBQuad pixel, CONST PINT bwParams)
{
	RGBIndex max, mid, min;
	min.tmp = pixel->Blue | BWIndexBlue;
	mid.tmp = pixel->Green | BWIndexGreen;
	max.tmp = pixel->Red | BWIndexRed;

	if (max.value < mid.value)
		SwapRgb(max, mid);
	if (max.value < min.value)
		SwapRgb(max, min);
	if (min.value > mid.value)
		SwapRgb(min, mid);

	return (((max.value - mid.value) * bwParams[max.index] +
		(mid.value - min.value) * bwParams[max.index + mid.index - 1] +
		512) >> 10) + min.value;
}
//---------------------------------------------------------------------------

VOID ColorMix(PARGBQuad pd, CONST PARGBQuad ps, INT gray)
{
	// 灰度计算常数:蓝,绿、红
	CONST INT ys[3] = {113, 604, 307};

	RGBIndex max, mid, min;
	min.tmp = ps->Blue | IndexBlue;
	mid.tmp = ps->Green | IndexGreen;
	max.tmp = ps->Red | IndexRed;

	if (max.value < mid.value)
		SwapRgb(max, mid);
	if (max.value < min.value)
		SwapRgb(max, min);
	if (min.value > mid.value)
		SwapRgb(min, mid);

	INT max_min = max.value - min.value;
	// 饱和度为0,返回灰度
	if (max_min == 0)
	{
		pd->Blue = pd->Green = pd->Red = gray;
		return;
	}
	INT mid_min = mid.value - min.value;

	INT newMax, newMid, newMin;
	gray <<= 10;
	newMax = (gray + (max_min - mid_min) * ys[mid.index] + max_min * ys[min.index] + 512) >> 10;
	newMin = newMax - max_min;
	if (newMax > 255)
	{
		INT hueCoef = (mid_min << 10) / max_min;
		INT v0 = (ys[mid.index] * hueCoef) >> 10;
		INT v1 = ys[min.index] + ys[mid.index] - v0;
		newMin = (gray - (ys[max.index] + v0) * 255 + (v1 >> 1)) / v1;
		newMid = newMin + (((255 ^ newMin) * hueCoef + 512) >> 10);
		newMax = 255;

	}
	else if (newMin < 0)
	{
		INT hueCoef = (mid_min << 10) / max_min;
		INT tmp = ys[max.index] + ((ys[mid.index] * hueCoef + 512) >> 10);
		newMax = (gray + (tmp >> 1)) / tmp;
		newMid = (newMax * hueCoef + 512) >> 10;
		newMin = 1;
	}
	else
		newMid = newMin + mid_min;

	((LPBYTE)pd)[max.index] = newMax;
	((LPBYTE)pd)[mid.index] = newMid;
	((LPBYTE)pd)[min.index] = newMin;
}
//---------------------------------------------------------------------------

// 图像黑白调整。
// 调整参数bwParams为元素数等于6的数组指针,分别为红,黄,绿,青,蓝,洋红
VOID ImageBlackWhite(BitmapData *data, CONST PBWParams bwParams = NULL)
{
	// 拷贝像素灰度参数,并交换青色和洋红色
	INT params[6], *pparams;
	if (bwParams)
	{
		for (INT i = 0; i < 6; i ++)
			params[i] = (INT)(bwParams[i] * 1024 + 0.5);
		params[3] ^= params[5];
		params[5] ^= params[3];
		params[3] ^= params[5];
		pparams = params;
	}
	else
		pparams = (INT*)_BWDefault;

	PARGBQuad p = (PARGBQuad)data->Scan0;
	INT dataOffset = (data->Stride >> 2) - (INT)data->Width;

	for (UINT y = 0; y < data->Height; y ++, p += dataOffset)
	{
		for (UINT x = 0; x < data->Width; x ++, p ++)
		{
			INT gray = GetBWGray(p, pparams);
			p->Blue = p->Green = p->Red =
				(gray & ~0xff) == 0? gray : gray > 255? 255 : 0;
		}
	}
}
//---------------------------------------------------------------------------

// 灰度图像染色。
VOID ImageTint(BitmapData *grayData, ARGB color)
{
	ARGBQuad colorTable[256];
	PARGBQuad p = colorTable;
	ARGB alpha = color >> 24;

	if (alpha == 0) return;

	for (INT i = 0; i < 256; i ++, p ++)
	{
		ColorMix(p, (PARGBQuad)&color, i);
		p->Alpha = 0;
	}

	if (alpha < 255)
	{
		p = colorTable;
		for (INT i = 0; i < 256; i ++, p ++)
		{
			p->Blue = i + ((p->Blue - i) * alpha + 127) / 255;
			p->Green = i + ((p->Green - i) * alpha + 127) / 255;
			p->Red = i + ((p->Red - i) * alpha + 127) / 255;
		}
	}

	p = (PARGBQuad)grayData->Scan0;
	INT dataOffset = (grayData->Stride >> 2) - (INT)grayData->Width;

	for (UINT y = 0; y < grayData->Height; y ++, p += dataOffset)
	{
		for (UINT x = 0; x < grayData->Width; x ++, p ++)
		{
			p->Color = (p->Color & 0xff000000) | colorTable[p->Blue].Color;
		}
	}
}
//---------------------------------------------------------------------------


VOID RgbColorMixer(BitmapData *dest, CONST BitmapData *source)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
		{
			ColorMix(pd, ps, GetBWGray(pd, (PINT)_BWDefault));
		}
	}
}
//---------------------------------------------------------------------------

VOID ArgbColorMixer(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
		{
			INT a = (ps->Alpha * alpha + 127) / 255;
			if (a)
			{
				ARGBQuad c;
				ColorMix(&c, ps, GetBWGray(pd, (PINT)_BWDefault));
				pd->Red = pd->Red + (((c.Red - pd->Red) * a + 127) / 255);
				pd->Green = pd->Green + (((c.Green - pd->Green) * a + 127) / 255);
				pd->Blue = pd->Blue + (((c.Blue - pd->Blue) * a + 127) / 255);
			}
		}
	}
}
//---------------------------------------------------------------------------

FORCEINLINE
VOID PArgbMixer(PARGBQuad pd, CONST PARGBQuad ps, INT alpha)
{
	pd->Blue = (pd->Blue * pd->Alpha + 127) / 255;
	pd->Green = (pd->Green * pd->Alpha + 127) / 255;
	pd->Red = (pd->Red * pd->Alpha + 127) / 255;

	pd->Blue += (((ps->Blue - pd->Blue) * alpha + 127) / 255);
	pd->Green += (((ps->Green - pd->Green) * alpha + 127) / 255);
	pd->Red += (((ps->Red - pd->Red) * alpha + 127) / 255);
	pd->Alpha += (alpha - (pd->Alpha * alpha + 127) / 255);

	pd->Blue = pd->Blue * 255 / pd->Alpha;
	pd->Green = pd->Green * 255 / pd->Alpha;
	pd->Red = pd->Red * 255 / pd->Alpha;
}
//---------------------------------------------------------------------------

VOID PArgbColorMixer(BitmapData *dest, CONST BitmapData *source, INT alpha)
{
	PARGBQuad pd, ps;
	UINT width, height;
	INT dstOffset, srcOffset;
	GetDataCopyParams(dest, source, width, height, pd, ps, dstOffset, srcOffset);

	for (UINT y = 0; y < height; y ++, pd += dstOffset, ps += srcOffset)
	{
		for (UINT x = 0; x < width; x ++, pd ++, ps ++)
		{
			INT a = (ps->Alpha * alpha + 127) / 255;
			if (a)
			{
				if (pd->Alpha == 0) *pd = *ps;
				else
				{
					ARGBQuad c, d;
					c.Color = d.Color = pd->Color;
					pd->Color = ps->Color;
					// 源像素与目标像素灰度混合到c
					ColorMix(&c, ps, GetBWGray(&d, (PINT)_BWDefault));
					// 将c用不透明度a与目标像素混合
					PArgbMixer(&d, &c, a);
					// 将合成的目标像素与源像素混合后,为最终结果
					PArgbMixer(pd, &d, c.Alpha);
				}
			}
		}
	}
}
//---------------------------------------------------------------------------

// 图像颜色模式混合
VOID ImageColorMixer(BitmapData *dest, CONST BitmapData *source, FLOAT alpha = 1)
{
	INT a = (INT)(alpha * 255);
	if (a <= 0) return;
	if (a > 255) a = 255;

	if (a == 255 && !HasAlphaFlag(dest) && !HasAlphaFlag(source))
		RgbColorMixer(dest, source);
	else if (!HasAlphaFlag(dest))
		ArgbColorMixer(dest, source, a);
	else
		PArgbColorMixer(dest, source, a);
}
//---------------------------------------------------------------------------

    比较一下《C++图像处理 -- 图像颜色混合(中)》的代码,可以发现颜色混合的基本功能代码并没有改变,只是修改了灰度图象染色函数ImageTint和颜色混合函数ImageColorMixer,原来的ImageColorMixer函数更名为RgbColorMixer,成为了新的ImageColorMixer函数的一部分。另外,新的ImageColorMixer函数还增添了一个alpha参数,用以改变上层图像的不透明度,取值范围为0 - 1。

    本文不准备再贴例子代码,只给出下面几张png图片颜色合成和染色的测试效果图:

                           源图一(苹果)                                                  源图二(雪花)

    上面一排是雪花图为底层,苹果图为上层实现的颜色混合,从左到右,Alpha分别为100%、80%和50%。

    下面一排是苹果图为底层,雪花图为上层实现的颜色混合,从左到右,Alpha分别为100%、80%和50%。

    从左到右:雪花图100%Alpha红色染色,雪花图50%Alpha红色染色,苹果图100%Alpha蓝色染色。

    另外,需要说明的是,在Photoshop的图层混合选项中,除了不透明度外,还有个填充数选项,二者的区别是:不透明度选项指的是图层本身而言,填充数选项是混合时本图层像素的填充比例。但实际上二者在本质上是一回事,也就是是说二者的乘积就是最终的填充不透明度。

    由于本人水平有限,虽经改进,但错误在所难免,欢迎提出宝贵意见,邮箱地址:maozefa@hotmail.com

 

    因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:maozefa@hotmail.com

    这里可访问《C++图像处理 -- 文章索引

  • 8
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值