C++实现Photoshop图层颜色混合模式(完整版)

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

要实现带Alpha通道的32位颜色图层混合模式,比24位颜色图层混合模式复杂的多,后者只是前者的一个特例。因此本文代码作了全面修改,部分代码进行了优化,主要是将原代码在像素合成过程中的一些浮点运算全部改为整数运算,将某些整数除法改为移位运算等,这样可以使代码效率提高一些,但同时代码的可读性也有所下降。只是熊掌与鱼不可兼得,特别是图像像素级的处理,随便一个小小的改进会使得效率成倍提高(其实,本文像素处理代码中还存在的一些整数除法,也是可以全部优化掉的),另外,原处理代码要求2张图像尺寸一样大,现在可处理2张不同大小图像的颜色混合(最终合成大小为底层目标大小)。

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

//--------------------------------------------------------------------------- #ifndef ColorMixerH #define ColorMixerH //--------------------------------------------------------------------------- #include <windows.h> #ifdef USE_GDIPLUS #include <algorithm> using std::min; using std::max; #include <gdiplus.h> using namespace Gdiplus; #endif #ifdef INC_VCL #include <Graphics.hpp> #endif //--------------------------------------------------------------------------- #ifndef USE_GDIPLUS typedef DWORD ARGB; struct BitmapData { UINT Width; UINT Height; INT Stride; UINT PixelFormat; LPVOID Scan0; UINT Reserved; }; #endif #define PixelAlphaFlag 0x10000 typedef BitmapData *PBitmapData; // 按grapParams对位图数据src去色后拷贝到dst,如src=NULL,dst自身去色 VOID ImageGray(PBitmapData dst, PBitmapData src, CONST FLOAT *BWParams); // 灰度图像数据染色。 // grayData灰度图象,color颜色 VOID ImageColorTint(PBitmapData grayData, ARGB color); // 图像数据颜色混合。 // dest目标图象,source源图像,alpha不透明度。 VOID ImageColorMixer(PBitmapData dest, CONST PBitmapData source, FLOAT alpha); // 用给定的图像数据制造并返回32位位图数据结构。 // 参数;宽度,高度,扫描线宽度,扫描线首地址,返回的位图数据结构指针。 // 注:如果stride=0,自动计算扫描线宽度 FORCEINLINE VOID GetBitmapData32(INT width, INT height, INT stride, LPVOID scan0, PBitmapData data) { data->Width = width; data->Height = height; data->Scan0 = scan0; if (stride) data->Stride = stride; else data->Stride = (INT)(data->Width << 2); (UINT)data->Reserved = 0; } // Windows 位图颜色混合 FORCEINLINE BOOL GetBitmapData32(HBITMAP bitmap, BITMAPINFO *pbi, PBitmapData data) { HDC hDC = GetDC(0); pbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); pbi->bmiHeader.biBitCount = 0; BOOL res = GetDIBits(hDC, bitmap, 0, 0, NULL, pbi, DIB_RGB_COLORS) != 0; if (res) { GetBitmapData32(pbi->bmiHeader.biWidth, pbi->bmiHeader.biHeight, 0, NULL, data); data->Scan0 = (LPVOID)new BYTE[data->Height * data->Stride]; pbi->bmiHeader.biBitCount = 32; pbi->bmiHeader.biCompression = BI_RGB; GetDIBits(hDC, bitmap, 0, data->Height, data->Scan0, pbi, DIB_RGB_COLORS); res = TRUE; } ReleaseDC(0, hDC); return res; } FORCEINLINE VOID SetBitmapData(HBITMAP bitmap, BITMAPINFO *pbi, CONST PBitmapData data) { HDC hDC = GetDC(0); SetDIBits(hDC, bitmap, 0, data->Height, data->Scan0, pbi, DIB_RGB_COLORS); ReleaseDC(0, hDC); } FORCEINLINE VOID BitmapColorTint(HBITMAP bitmap, ARGB color, BOOL isGray) { BitmapData data; BITMAPINFO bi; if (!GetBitmapData32(bitmap, &bi, &data)) return; try { if (!isGray) ImageGray(&data, &data, NULL); ImageColorTint(&data, color); SetBitmapData(bitmap, &bi, &data); } __finally { delete[] data.Scan0; } } FORCEINLINE VOID BitmapColorMixer(HBITMAP baseBmp, HBITMAP mixBmp, FLOAT alpha) { BitmapData bData, mData; BITMAPINFO bi; if (!GetBitmapData32(baseBmp, &bi, &bData)) return; if (!GetBitmapData32(mixBmp, &bi, &mData)) { delete[] bData.Scan0; return; } try { ImageColorMixer(&bData, &mData, alpha); SetBitmapData(baseBmp, &bi, &bData); } __finally { delete[] bData.Scan0; delete[] mData.Scan0; } } #ifdef USE_GDIPLUS // GDI+位图颜色混合。 FORCEINLINE VOID GpBitmapColorTint(Bitmap *bitmap, ARGB color, BOOL isGray) { BOOL hasAlpha = bitmap->GetPixelFormat() & PixelFormatAlpha; BitmapData data; Gdiplus::Rect r(0, 0, bitmap->GetWidth(), bitmap->GetHeight()); bitmap->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &data); try { if (hasAlpha) (UINT)data.Reserved |= PixelAlphaFlag; if (!isGray) ImageGray(&data, &data, NULL); ImageColorTint(&data, color); } __finally { (UINT)data.Reserved &= 0xff; bitmap->UnlockBits(&data); } } FORCEINLINE VOID GpBitmapColorMixer(Bitmap *baseBmp, Bitmap *mixBmp, FLOAT alpha) { BOOL bAlpha = baseBmp->GetPixelFormat() & PixelFormatAlpha; BOOL mAlpha = mixBmp->GetPixelFormat() & PixelFormatAlpha; BitmapData bData, mData; Gdiplus::Rect br(0, 0, baseBmp->GetWidth(), baseBmp->GetHeight()); Gdiplus::Rect mr(0, 0, mixBmp->GetWidth(), mixBmp->GetHeight()); baseBmp->LockBits(&br, ImageLockModeRead | ImageLockModeWrite, PixelFormat32bppARGB, &bData); mixBmp->LockBits(&mr, ImageLockModeRead, PixelFormat32bppARGB, &mData); try { if (bAlpha) (UINT)bData.Reserved |= PixelAlphaFlag; if (mAlpha) (UINT)mData.Reserved |= PixelAlphaFlag; ImageColorMixer(&bData, &mData, alpha); } __finally { (UINT)bData.Reserved &= 0xff; (UINT)mData.Reserved &= 0xff; mixBmp->UnlockBits(&bData); baseBmp->UnlockBits(&mData); } } #endif // USE_GDIPLUS #ifdef INC_VCL // VCL位图颜色混合 FORCEINLINE VOID GetVCLBitmapData32(::Graphics::TBitmap *bmp, PBitmapData data) { BOOL hasAlpha = bmp->PixelFormat == pf32bit; bmp->PixelFormat = pf32bit; GetBitmapData32(bmp->Width, bmp->Height, 0, bmp->ScanLine[bmp->Height - 1], data); if (hasAlpha) (UINT)data->Reserved |= PixelAlphaFlag; // GetBitmapData32(bmp->Width, bmp->Height, // -((((INT)data->Width * 32 + 32) & ~32) >> 3), bmp->ScanLine[0], data); } FORCEINLINE VOID TBitmapColorTint(::Graphics::TBitmap *bmp, ARGB color, BOOL isGray) { BitmapData data; GetVCLBitmapData32(bmp, &data); if (!isGray) ImageGray(&data, &data, NULL); ImageColorTint(&data, color); } FORCEINLINE VOID TBitmapColorMixer(::Graphics::TBitmap *baseBmp, ::Graphics::TBitmap *mixBmp, FLOAT alpha) { BitmapData bData, mData; GetVCLBitmapData32(baseBmp, &bData); GetVCLBitmapData32(mixBmp, &mData); ImageColorMixer(&bData, &mData, alpha); } #endif // INC_VCL #endif//--------------------------------------------------------------------------- #pragma hdrstop #define USE_GDIPLUS #include "ColorMixer.h" //--------------------------------------------------------------------------- #pragma package(smart_init) // 红,黄,绿,洋红,蓝,青 CONST INT DefGrayParams[] = {410, 614, 410, 819, 205, 614}; enum { OptionBlue = 0x40000, OptionGreen = 0x20000, OptionRed = 0x00000 }; enum { IndexBlue = 0x00000, IndexGreen = 0x10000, IndexRed = 0x20000 }; union ARGBQuad { ARGB color; BYTE Elements[4]; struct { BYTE Blue; BYTE Green; BYTE Red; BYTE Alpha; }; }; typedef ARGBQuad *PARGBQuad; union RGBIndex // 颜色分量交换结构 { INT tmp; // 交换时用的临时变量 struct { SHORT value; // 颜色分量值 SHORT index; // 颜色分量索引 }; }; // 交换像素分量 FORCEINLINE VOID SwapRgb(RGBIndex &a, RGBIndex &b) { a.tmp += b.tmp; b.tmp = a.tmp - b.tmp; a.tmp -= b.tmp; } VOID ImageGray(PBitmapData dst, PBitmapData src, CONST FLOAT *BWParams) { INT params[6]; CONST INT *PParams; RGBIndex max, mid, min; if (BWParams) { // 拷贝像素灰度参数,并交换青色和洋红色:红,黄,绿,洋红,蓝,青 for (INT i = 0; i < 5; i ++) params[i] = (INT)(BWParams[i] * 1024 + 0.5); params[5] += params[3]; params[3] = params[5] - params[3]; params[5] -= params[3]; PParams = params; } else PParams = DefGrayParams; PARGBQuad pd = (PARGBQuad)dst->Scan0; PARGBQuad ps = src? (PARGBQuad)src->Scan0 : pd; INT offset = dst->Stride - dst->Width * sizeof(ARGBQuad); for (UINT y = 0; y < dst->Height; y ++, (BYTE*)pd += offset, (BYTE*)ps += offset) { for (UINT x = 0; x < dst->Width; x ++, pd ++, ps ++) { max.tmp = ps->Red | OptionRed; mid.tmp = ps->Green | OptionGreen; min.tmp = ps->Blue | OptionBlue; 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 gray = (((max.value - mid.value) * PParams[max.index] + (mid.value - min.value) * PParams[max.index + mid.index - 1] + 512) >> 10) + min.value; pd->Red = pd->Green = pd->Blue = (gray & ~0xff) == 0? gray : gray > 255? 255 : 0; } } } // 获取黑白灰度 FORCEINLINE INT GetBWGray(PARGBQuad p) { RGBIndex max, mid, min; max.tmp = p->Red | OptionRed; mid.tmp = p->Green | OptionGreen; min.tmp = p->Blue | OptionBlue; 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) * DefGrayParams[max.index] + (mid.value - min.value) * DefGrayParams[max.index + mid.index - 1] + 512) >> 10) + min.value; } // 像素ps与灰度gray混合 VOID GrayMixer(PARGBQuad pd, PARGBQuad ps, INT gray) { CONST INT ys[3] = {113, 604, 307}; RGBIndex max, mid, min; INT max_min, mid_min; INT newMax, newMid, newMin; max.tmp = ps->Red | IndexRed; mid.tmp = ps->Green | IndexGreen; min.tmp = ps->Blue | IndexBlue; if (max.value < mid.value) SwapRgb(max, mid); if (max.value < min.value) SwapRgb(max, min); if (min.value > mid.value) SwapRgb(min, mid); max_min = max.value - min.value; if (max_min == 0) { pd->Red = pd->Green = pd->Blue = gray; return; } mid_min = mid.value - min.value; newMid = ((gray << 10) - (max_min - mid_min) * ys[max.index] + mid_min * ys[min.index] + 512) >> 10; newMin = newMid - mid_min; if (newMid < 0 || newMin < 0) { INT hueCoef = (mid_min << 10) / max_min; INT tmp = ys[max.index] + ((ys[mid.index] * hueCoef + 512) >> 10); newMax = ((gray << 10) + (tmp >> 1)) / tmp; newMid = (newMax * hueCoef + 512) >> 10; newMin = 1; } else { newMax = newMin + 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 << 10) - (ys[max.index] + v0) * 255 + (v1 >> 1)) / v1; newMid = newMin + (((255 ^ newMin) * hueCoef + 512) >> 10); newMax = 255; } } pd->Elements[max.index] = newMax; pd->Elements[mid.index] = newMid; pd->Elements[min.index] = newMin; } VOID DataRgbMixer(PBitmapData dest, CONST PBitmapData source) { PARGBQuad pd = (PARGBQuad)dest->Scan0; PARGBQuad ps = (PARGBQuad)source->Scan0; INT width = dest->Width < source->Width? dest->Width : source->Width; INT height = dest->Height < source->Height? dest->Height : source->Height; INT dOffset = dest->Stride - width * sizeof(ARGBQuad); INT sOffset = source->Stride - width * sizeof(ARGBQuad); for (INT y = 0; y < height; y ++, (BYTE*)pd += dOffset, (BYTE*)ps += sOffset) { for (INT x = 0; x < width; x ++, pd ++, ps ++) GrayMixer(pd, ps, GetBWGray(pd)); } } VOID DataArgbMixer(PBitmapData dest, CONST PBitmapData source, INT alpha) { PARGBQuad pd = (PARGBQuad)dest->Scan0; PARGBQuad ps = (PARGBQuad)source->Scan0; INT width = dest->Width < source->Width? dest->Width : source->Width; INT height = dest->Height < source->Height? dest->Height : source->Height; INT dOffset = dest->Stride - width * sizeof(ARGBQuad); INT sOffset = source->Stride - width * sizeof(ARGBQuad); for (INT y = 0; y < height; y ++, (BYTE*)pd += dOffset, (BYTE*)ps += sOffset) { for (INT x = 0; x < width; x ++, pd ++, ps ++) { INT a = (ps->Alpha * alpha) >> 8; if (a) { ARGBQuad c; c.color = pd->color; GrayMixer(&c, ps, GetBWGray(pd)); pd->Red = pd->Red + (((c.Red - pd->Red) * a + 128) >> 8); pd->Green = pd->Green + (((c.Green - pd->Green) * a + 128) >> 8); pd->Blue = pd->Blue + (((c.Blue - pd->Blue) * a + 128) >> 8); } } } } FORCEINLINE VOID PArgbMixer(PARGBQuad pd, PARGBQuad ps, INT alpha) { ps->Red = (ps->Red * alpha + 127) / 255; ps->Green = (ps->Green * alpha + 127) / 255; ps->Blue = (ps->Blue * alpha + 127) / 255; pd->Red = (pd->Red * pd->Alpha + 127) / 255; pd->Green = (pd->Green * pd->Alpha + 127) / 255; pd->Blue = (pd->Blue * pd->Alpha + 127) / 255; pd->Red = pd->Red + ps->Red - (pd->Red * alpha + 127) / 255; pd->Green = pd->Green + ps->Green - (pd->Green * alpha + 127) / 255; pd->Blue = pd->Blue + ps->Blue - (pd->Blue * alpha + 127) / 255; pd->Alpha = pd->Alpha + alpha - (pd->Alpha * alpha + 127) / 255; pd->Red = pd->Red * 255 / pd->Alpha; pd->Green = pd->Green * 255 / pd->Alpha; pd->Blue = pd->Blue * 255 / pd->Alpha; } VOID DataPArgbMixer(PBitmapData dest, CONST PBitmapData source, INT alpha) { PARGBQuad pd = (PARGBQuad)dest->Scan0; PARGBQuad ps = (PARGBQuad)source->Scan0; INT width = dest->Width < source->Width? dest->Width : source->Width; INT height = dest->Height < source->Height? dest->Height : source->Height; INT dOffset = dest->Stride - width * sizeof(ARGBQuad); INT sOffset = source->Stride - width * sizeof(ARGBQuad); for (INT y = 0; y < height; y ++, (BYTE*)pd += dOffset, (BYTE*)ps += sOffset) { for (INT x = 0; x < width; x ++, pd ++, ps ++) { INT a = (ps->Alpha * alpha) >> 8; if (a) { if (pd->Alpha == 0) pd->color = ps->color; else { ARGBQuad c, d; c.color = d.color = pd->color; pd->color = ps->color; // 源像素与目标像素灰度混合到c GrayMixer(&c, ps, GetBWGray(&d)); // 将c用不透明度a与目标像素混合 PArgbMixer(&d, &c, a); // 将合成的目标像素与源像素混合后,为最终结果 PArgbMixer(pd, &d, c.Alpha); } } } } } VOID ImageColorMixer(PBitmapData dest, CONST PBitmapData source, FLOAT alpha) { INT a = (INT)(alpha * 256); if (a < 0) a = 0; else if (a > 256) a = 256; if (a == 256 && ((UINT)dest->Reserved & PixelAlphaFlag) == 0 && ((UINT)source->Reserved & PixelAlphaFlag) == 0) DataRgbMixer(dest, source); else if (((UINT)dest->Reserved & PixelAlphaFlag) == 0) DataArgbMixer(dest, source, a); else DataPArgbMixer(dest, source, a); } VOID ImageColorTint(PBitmapData grayData, ARGB color) { ARGBQuad mixTable[256]; for (INT i = 0; i < 256; i ++) { mixTable[i].color = color; GrayMixer(&mixTable[i], &mixTable[i], i); } if (color < 0xff000000) (UINT)grayData->Reserved |= PixelAlphaFlag; PARGBQuad p = (PARGBQuad)grayData->Scan0; INT offset = grayData->Stride - grayData->Width * sizeof(ARGBQuad); for (UINT y = 0; y < grayData->Height; y ++, (BYTE*)p += offset) { for (UINT x = 0; x < grayData->Width; x ++, p ++) { INT index = p->Blue; p->Alpha = p->Alpha * mixTable[index].Alpha / 255; p->Red = mixTable[index].Red; p->Green = mixTable[index].Green; p->Blue = mixTable[index].Blue; } } }

下面给出几张png图片测试效果图:

C++实现Photoshop图层颜色混合模式(完整版)C++实现Photoshop图层颜色混合模式(完整版)

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

C++实现Photoshop图层颜色混合模式(完整版)

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

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

C++实现Photoshop图层颜色混合模式(完整版)

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

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

由于本人水平有限,虽经改进,但错误在所难免,欢迎提出宝贵意见,邮箱地址:[email protected]