C++实现Photoshop图层颜色混合模式(续)

文章《C++实现Photoshop图层颜色混合模式》发表后,获得了很多朋友的好评,特别是喜爱研究图形编程的朋友。还有几位好友给我发了邮件,有位朋友在来信中说,虽然Photoshop图层混合模式的原理及公式网上都能搜索到,但大都是一些概念性的东西,很少有具体实现代码,特别是准确性比较高的实现代码。还有朋友问不用GDI+,其它图形数据是否也能用文章中代码进行颜色混合(这位可能是初学者)。

可以说,为了尽可能准确地模仿Photoshop的颜色混合模式,我确实费了好几天功夫,这一点从那篇文章开头部分就可看出来,呵呵,可能是年纪大、人太笨的缘故。代码测试好后,自己还有些兴奋,所以很快就发表了。后来自己发现一些需要改进的地方,主要是代码结构不够好。比如相同的代码几个函数中都有,这不利于代码的维护;其次是通用性不强。几个函数只适用于GDI+图像,其它图像类型就需要修改了;还有的地方可以做些优化。

下面是修改后的图像颜色混合代码,还是使用BCB2007写的,不过我尽量了通用的C++编程方式,好像除了try......__finally异常处理块(不知道其它编译器是否支持),所有BCB独有的特性都没有使用。所以,采用其它编译器可能只需微小的改动。

头文件:colormixer.h

//--------------------------------------------------------------------------- #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 typedef BitmapData *PBitmapData; // 按grapParams对位图数据src去色后拷贝到dst,如src=NULL,dst自身去色 VOID ImageGray(PBitmapData dst, PBitmapData src, CONST INT grayParams[]); // 图像数据颜色混合。 // baseData基色图象,color混合颜色,grayBase是否灰度基色图像 VOID ImageColorMixer(PBitmapData baseData, ARGB color, BOOL grayBase); // 图像数据颜色混合。 // baseData基色图象,mixData混合色图像,grayBase是否灰度基色图像。 VOID ImageColorMixer(PBitmapData baseData, CONST PBitmapData mixData, BOOL grayBase); // 用给定的图像数据制造并返回24位位图数据结构。 // 参数;宽度,高度,扫描线宽度,扫描线首地址,返回的位图数据结构指针。 // 注:如果stride=0,自动计算扫描线宽度 FORCEINLINE VOID GetBitmapData24(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 * 24 + 32) & ~32) >> 3; } // Windows 位图颜色混合 FORCEINLINE BOOL GetBitmapData24(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) { GetBitmapData24(pbi->bmiHeader.biWidth, pbi->bmiHeader.biHeight, 0, NULL, data); data->Scan0 = (LPVOID)new BYTE[data->Height * data->Stride]; pbi->bmiHeader.biBitCount = 24; 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 SetBitmapData24(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 BitmapColorMixer(HBITMAP baseBmp, ARGB color, BOOL grayBase) { BitmapData data; BITMAPINFO bi; if (!GetBitmapData24(baseBmp, &bi, &data)) return; try { ImageColorMixer(&data, color, grayBase); SetBitmapData24(baseBmp, &bi, &data); } __finally { delete[] data.Scan0; } } FORCEINLINE VOID BitmapColorMixer(HBITMAP baseBmp, HBITMAP mixBmp, BOOL grayBase) { BitmapData bData, mData; BITMAPINFO bi; if (!GetBitmapData24(baseBmp, &bi, &bData)) return; if (!GetBitmapData24(mixBmp, &bi, &mData)) { delete[] bData.Scan0; return; } try { ImageColorMixer(&bData, &mData, grayBase); SetBitmapData24(baseBmp, &bi, &bData); } __finally { delete[] bData.Scan0; delete[] mData.Scan0; } } #ifdef USE_GDIPLUS // GDI+位图颜色混合。 FORCEINLINE VOID GpBitmapColorMixer(Bitmap *baseBmp, ARGB color, BOOL grayBase) { BitmapData data; Gdiplus::Rect r(0, 0, baseBmp->GetWidth(), baseBmp->GetHeight()); baseBmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat24bppRGB, &data); try { ImageColorMixer(&data, color, grayBase); } __finally { baseBmp->UnlockBits(&data); } } FORCEINLINE VOID GpBitmapColorMixer(Bitmap *baseBmp, Bitmap *mixBmp, BOOL grayBase) { BitmapData bData, mData; Gdiplus::Rect r(0, 0, baseBmp->GetWidth(), baseBmp->GetHeight()); baseBmp->LockBits(&r, ImageLockModeRead | ImageLockModeWrite, PixelFormat24bppRGB, &bData); mixBmp->LockBits(&r, ImageLockModeRead, PixelFormat24bppRGB, &mData); try { ImageColorMixer(&bData, &mData, grayBase); } __finally { mixBmp->UnlockBits(&bData); baseBmp->UnlockBits(&mData); } } #endif // USE_GDIPLUS #ifdef INC_VCL // VCL位图颜色混合 FORCEINLINE VOID GetVCLBitmapData24(::Graphics::TBitmap *bmp, PBitmapData data) { bmp->PixelFormat = pf24bit; GetBitmapData24(bmp->Width, bmp->Height, 0, bmp->ScanLine[bmp->Height - 1], data); // GetBitmapData24(bmp->Width, bmp->Height, // -((((INT)data->Width * 24 + 32) & ~32) >> 3), bmp->ScanLine[0], data); } FORCEINLINE VOID TBitmapColorMixer(::Graphics::TBitmap *baseBmp, ARGB color, BOOL grayBase) { BitmapData data; GetVCLBitmapData24(baseBmp, &data); ImageColorMixer(&data, color, grayBase); } FORCEINLINE VOID TBitmapColorMixer(::Graphics::TBitmap *baseBmp, ::Graphics::TBitmap *mixBmp, BOOL grayBase) { BitmapData bData, mData; GetVCLBitmapData24(baseBmp, &bData); GetVCLBitmapData24(mixBmp, &mData); ImageColorMixer(&bData, &mData, grayBase); } #endif // INC_VCL #endif


代码文件:colormixer.cpp

//--------------------------------------------------------------------------- #pragma hdrstop #define USE_GDIPLUS #include "ColorMixer.h" //--------------------------------------------------------------------------- #pragma package(smart_init) // 红,黄,绿,青,蓝,洋红 CONST INT DefGrayParams[] = {40, 60, 40, 60, 20, 80}; enum { OptionBlue = 0x40000, OptionGreen = 0x20000, OptionRed = 0x00000 }; enum { IndexBlue = 0x00000, IndexGreen = 0x10000, IndexRed = 0x20000 }; #include <pshpack1.h> union RGBThree { BYTE Elements[3]; struct { BYTE Blue; BYTE Green; BYTE Red; }; }; #include <poppack.h> typedef RGBThree *PRGBThree; 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; } // 获取像素R、G、B的最大、中间及最小值。并保留原分量的序号(索引) FORCEINLINE VOID GetRgbOrder(PRGBThree p, RGBIndex &max, RGBIndex &mid, RGBIndex &min) { max.tmp = p->Red | IndexRed; mid.tmp = p->Green | IndexGreen; min.tmp = p->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); } VOID ImageGray(PBitmapData dst, PBitmapData src, CONST INT grayParams[]) { RGBIndex max, mid, min; INT params[6]; // 拷贝像素灰度参数,并交换青色和洋红色:红,黄,绿,洋红,蓝,青 for (INT i = 0; i < 5; i ++) params[i] = grayParams[i]; params[5] = grayParams[3]; params[3] = grayParams[5]; PRGBThree pd = (PRGBThree)dst->Scan0; PRGBThree ps = src? (PRGBThree)src->Scan0 : pd; INT offset = dst->Stride - dst->Width * sizeof(RGBThree); 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) * params[max.index] + (mid.value - min.value) * params[max.index + mid.index - 1] + 50) / 100 + min.value; pd->Red = pd->Green = pd->Blue = (gray & ~0xff) == 0? gray : gray > 255? 255 : 0; } } } VOID ImageColorMixer(PBitmapData baseData, CONST PBitmapData mixData, BOOL grayBase) { CONST DOUBLE ys[] = {0.11, 0.59, 0.3}; RGBIndex max, mid, min; INT newMax, newMid, newMin; INT max_min, mid_min; DOUBLE hueCoef; if (!grayBase) ImageGray(baseData, NULL, DefGrayParams); PRGBThree pb = (PRGBThree)baseData->Scan0; PRGBThree pm = (PRGBThree)mixData->Scan0; INT offset = baseData->Stride - baseData->Width * sizeof(RGBThree); for (UINT y = 0; y < baseData->Height; y ++, (BYTE*)pb += offset, (BYTE*)pm += offset) { for (UINT x = 0; x < baseData->Width; x ++, pb ++, pm ++) { GetRgbOrder(pm, max, mid, min); max_min = max.value - min.value; if (max_min == 0) continue; // 饱和度为0,不着色 mid_min = mid.value - min.value; hueCoef = (double)mid_min / (double)max_min; newMid = (int)(pb->Blue - (max_min - mid_min) * ys[max.index] + mid_min * ys[min.index] + 0.5); newMin = newMid - mid_min; if (newMin < 0 || newMid <= 0) { newMax = (int)(pb->Blue / (ys[max.index] + ys[mid.index] * hueCoef) + 0.5); newMid = (int)(newMax * hueCoef + 0.5); newMin = 0; } else { newMax = newMin + max_min; if (newMax > 255) { newMin = (int)((pb->Blue - (ys[max.index] + ys[mid.index] * hueCoef) * 255) / (ys[min.index] - ys[mid.index] * (hueCoef - 1)) + 0.5); newMid = (int)(newMin + (255 - newMin) * hueCoef + 0.5); newMax = 255; } } pb->Elements[max.index] = newMax; pb->Elements[mid.index] = newMid; pb->Elements[min.index] = newMin; } } } // 制造并返回一个灰度色阶与color混合的表。 VOID MakeMixTable(ARGB color, PRGBThree pMixTable) { RGBThree mixs[256]; for (INT i = 0; i < 256; i ++) { mixs[i].Red = ((PRGBThree)&color)->Red; mixs[i].Green = ((PRGBThree)&color)->Green; mixs[i].Blue = ((PRGBThree)&color)->Blue; pMixTable[i].Blue = i; } BitmapData d1, d2; GetBitmapData24(256, 1, 0, pMixTable, &d1); d2.Scan0 = (LPVOID)mixs; ImageColorMixer(&d1, &d2, TRUE); } VOID ImageColorMixer(PBitmapData baseData, ARGB color, BOOL grayBase) { RGBIndex max, mid, min; GetRgbOrder((PRGBThree)&color, max, mid, min); if ((max.value - min.value) == 0) return; // 饱和度为0,不着色返回 RGBThree mixTable[256]; MakeMixTable(color, mixTable); if (!grayBase) ImageGray(baseData, NULL, DefGrayParams); PRGBThree p = (PRGBThree)baseData->Scan0; INT offset = baseData->Stride - baseData->Width * sizeof(RGBThree); for (UINT y = 0; y < baseData->Height; y ++, (BYTE*)p += offset) { for (UINT x = 0; x < baseData->Width; x ++, p ++) { p->Red = mixTable[p->Blue].Red; p->Green = mixTable[p->Blue].Green; p->Blue = mixTable[p->Blue].Blue; } } }


从头文件中可以看出,改进后的代码有4个通用的处理函数:

ImageGray用来处理彩色图像去色,这个函数在图像颜色混合处理中是不需要用到的,但可以单独用来实现Photoshop特色的图像灰度化。

2个重载函数 ImageColorMixer分别用着色和图像混合。

GetBitmapData24函数用给定的图像数据制造并返回一个24位像素的BitmapData数据结构,这就使得上面的3个函数可以适应于任何24位图象的处理。BitmapData本是GDI+提供的一个位图数据结构,为了保证通用性,在不使用GDI+时,头文件提供了一个兼容的BitmapData结构,这主要是通过USE_GDIPLUS宏来确定的,去掉colormixer.cpp文件中的宏就可以了。

通用函数后面,提供了3种常用位图的颜色混合处理函数,即Windows位图类型HBITMAP,GDI+位图类型Bitmap和BCB的VCL位图,其中后面的2种根据编译条件决定。

在函数实现方面,具体的颜色混合代码只保留在了一个函数中,有利于改进和维护;着色函数进行了优化,使用了一个256个元素的24位灰度数组作为混合颜色表,处理图像时,直接以图像灰度值为索引取出表中数据即可。

下面是对3种位图类型处理函数的例子代码:

// VCL位图例子void __fastcall TForm1::Button2Click(TObject *Sender) { ::Graphics::TBitmap *bmp = new ::Graphics::TBitmap(); bmp->LoadFromFile(TEXT("d:\\Source.bmp")); TBitmapColorMixer(bmp, 0x314ead, FALSE); Canvas->Draw(0, 0, bmp); delete bmp; } //--------------------------------------------------------------------------- // GDI+位图例子 void __fastcall TForm1::Button3Click(TObject *Sender) { Bitmap *bmp1 = new Bitmap(WideString("d:\\GraySource.bmp"));// 背景图 Bitmap *bmp2 = new Bitmap(WideString("d:\\Test1.bmp")); // 前景图 GpBitmapColorMixer(bmp1, bmp2, TRUE); Gdiplus::Graphics *g = new Gdiplus::Graphics(Canvas->Handle); g->DrawImage(bmp1, 0, 0); delete g; delete bmp2; delete bmp1; } //--------------------------------------------------------------------------- // Windows位图例子 void __fastcall TForm1::Button4Click(TObject *Sender) { HBITMAP bmp = (HBITMAP)LoadImage(NULL, TEXT("d:\\Source.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); BitmapColorMixer(bmp, 0xa9d695, FALSE); BITMAPINFO bi; bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bi.bmiHeader.biBitCount = 0; GetDIBits(Canvas->Handle, bmp, 0, 0, NULL, &bi, DIB_RGB_COLORS); HDC memDC = CreateCompatibleDC(Canvas->Handle); HBITMAP saveBmp = (HBITMAP)SelectObject(memDC, bmp); BitBlt(Canvas->Handle, 0, 0, bi.bmiHeader.biWidth, bi.bmiHeader.biHeight, memDC, 0, 0, SRCCOPY); SelectObject(memDC, saveBmp); DeleteDC(memDC); DeleteObject(bmp); } //---------------------------------------------------------------------------


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值