阅读提示:
《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++图像处理 -- 文章索引》。