阅读提示:
《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的图层混合选项中,除了不透明度外,还有个填充数选项,二者的区别是:不透明度选项指的是图层本身而言,填充数选项是混合时本图层像素的填充比例。但实际上二者在本质上是一回事,也就是是说二者的乘积就是最终的填充不透明度。
由于本人水平有限,虽经改进,但错误在所难免,欢迎提出宝贵意见,邮箱地址:[email protected]
因水平有限,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]
这里可访问《C++图像处理 -- 文章索引》。