文章《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
从头文件中可以看出,改进后的代码有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