分类: C/C++
2008-04-03 17:32:17
int FClamp0255 (int nValue) {return max (0, min (0xFF, nValue));} // 饱和到0--255 class FCObjImage { public : Invert () ; AdjustRGB (int R, int G, int B) ; } ; void FCObjImage::Invert () { if ((GetHandle() == NULL) || (ColorBits() < 24)) return ; int nSpan = ColorBits() / 8 ; // 每象素字节数3, 4 for (int y=0 ; y < Height() ; y++) { BYTE * pPixel = GetBits (y) ; for (int x=0 ; x < Width() ; x++, pPixel += nSpan) { pPixel[0] = ~pPixel[0] ; pPixel[1] = ~pPixel[1] ; pPixel[2] = ~pPixel[2] ; } } } void FCObjImage::AdjustRGB (int R, int G, int B) { if ((GetHandle() == NULL) || (ColorBits() < 24)) return ; int nSpan = ColorBits() / 8 ; // 每象素字节数3, 4 for (int y=0 ; y < Height() ; y++) { BYTE * pPixel = GetBits (y) ; for (int x=0 ; x < Width() ; x++, pPixel += nSpan) { pPixel[0] = FClamp0255 (pPixel[0] + B) ; pPixel[1] = FClamp0255 (pPixel[1] + G) ; pPixel[2] = FClamp0255 (pPixel[2] + R) ; } } }这里举了两个例子(分别实现反色,调节RGB值功能),现实中会有大量的此类操作:亮度、对比度、饱和度......现在回想一下,你添加这些方法的步骤是什么,Ooooooooo,RCP(我同事的发明,全称:rapid copy paste^-^),第一步一定是从上面复制一块代码下来,然后改掉其中的接口和处理部分。虽然这里的示范代码很短小,不会连同bug一起复制,但,定时炸弹却又多了一个。有天,你的boss告诉你:我不能忍受长时间的等待,请给我加个进度条.....。你也许会加个全局变量,也许会给每个函数加个参数,但不变的是:你必须修改所有这些处理函数的代码,内心的咒骂并不会使你少改其中的任何一个。而此时,bug已经在旁边伺机而动了...然而苦日子远没熬到头,一个月后,你心血来潮的老板会让你在其中加上区域处理的功能,再一个月后......
void Pixel_Invert (BYTE * pPixel) { pPixel[0] = ~pPixel[0] ; pPixel[1] = ~pPixel[1] ; pPixel[2] = ~pPixel[2] ; } void FCObjImage::PixelProcess (void(__cdecl*PixelProc)(BYTE * pPixel)) { if ((GetHandle() == NULL) || (ColorBits() < 24)) return ; int nSpan = ColorBits() / 8 ; // 每象素字节数3, 4 for (int y=0 ; y < Height() ; y++) { BYTE * pPixel = GetBits (y) ; for (int x=0 ; x < Width() ; x++, pPixel += nSpan) { PixelProc (pPixel) ; } } } void FCObjImage::Invert () { PixelProcess (Pixel_Invert) ; }嗯,看样子不错,算法被剥离到一个单一函数中,我们似乎已经解决问题了。处理Invert它完成的非常好,但处理AdjustRGB时遇到了麻烦,RGB那三个调节参数怎么传进去呢?我们的接口参数只有一个,通过添加全局变量/成员变量?这是一个办法,但随着类方法的增加,程序的可读性和维护性会急剧的下降,反而倒不如改之前的效果好。
class FCSinglePixelProcessBase { public : virtual void ProcessPixel (int x, int y, BYTE * pPixel) PURE ; } ; class FCPixelInvert : public FCSinglePixelProcessBase { public : virtual void ProcessPixel (int x, int y, BYTE * pPixel) ; } ; void FCPixelInvert::ProcessPixel (int x, int y, BYTE * pPixel) { pPixel[0] = ~pPixel[0] ; pPixel[1] = ~pPixel[1] ; pPixel[2] = ~pPixel[2] ; } class FCPixelAdjustRGB : public FCSinglePixelProcessBase { public : FCPixelAdjustRGB (int DeltaR, int DeltaG, int DeltaB) ; virtual void ProcessPixel (int x, int y, BYTE * pPixel) ; protected : int m_iDeltaR, m_iDeltaG, m_iDeltaB ; } ; void FCPixelAdjustRGB::ProcessPixel (int x, int y, BYTE * pPixel) { pPixel[0] = FClamp0255 (pPixel[0] + m_iDeltaB) ; pPixel[1] = FClamp0255 (pPixel[1] + m_iDeltaG) ; pPixel[2] = FClamp0255 (pPixel[2] + m_iDeltaR) ; }然后我们修改image类如下:
#include "PixelProcessor.h" class FCObjImage { public : void PixelHandler (FCSinglePixelProcessBase & PixelProcessor, FCObjProgress * progress = NULL) ; } ; void FCObjImage::PixelHandler (FCSinglePixelProcessBase & PixelProcessor, FCObjProgress * progress) { if (GetHandle() == NULL) return ; int nSpan = ColorBits() / 8 ; // 每象素字节数3, 4 for (int y=0 ; y < Height() ; y++) { BYTE * pPixel = GetBits (y) ; for (int x=0 ; x < Width() ; x++, pPixel += nSpan) { PixelProcessor.ProcessPixel (x, y, pPixel) ; } if (progress != NULL) progress->SetProgress (y * 100 / Height()) ; } } void FCObjImage::Invert (FCObjProgress * progress) { PixelHandler (FCPixelInvert(), progress) ; } void FCObjImage::AdjustRGB (int R, int G, int B, FCObjProgress * progress) { PixelHandler (FCPixelAdjustRGB (R,G,B), progress) ; }(以上只是一个基本框架,你可以很轻易的把区域处理的参数添加进去-通过构造时传递一个RECT参数。)
class FCPixelTest : public FCSinglePixelProcessBase { public : virtual void ProcessPixel (int x, int y, BYTE * pPixel) ; } ; void FCPixelTest::ProcessPixel (int x, int y, BYTE * pPixel) { if (y % 2) pPixel[0]=pPixel[1]=pPixel[2] = 0 ; // 奇数行 else pPixel[0]=pPixel[1]=pPixel[2] = 0xFF ; // 偶数行 }然后进行如下调用:
PixelHandler (FCPixelTest(), progress) ;多么的和谐美妙,设计算法的人员只需写出自己的算法,而不用去考虑怎么让它支持进度条和区域这些问题。感觉这就象一把设计优良的AK,你可以不断的往里添加子弹(对象)^-^
#include "PixelProcessor.h"image是图像处理的最底层对象,工程中的所有文件都直接或间接地包含它,因此,任何对image.h本身及它所包含的.h的修改都会引起几乎整个工程的build,这当然是无法忍受的,解决的办法是使用“前置声明”,因为在PixelHandler接口中我们只需要它的引用(也即是说:我(接口)并不需要知道传给我的类的内部结构,给我一个32(64)的内存地址就OK了)。因此我们把
#include "PixelProcessor.h"替换成:
class FCSinglePixelProcessBase ; // external class 前置声明然后在.cpp文件中再包含PixelProcessor.h,这样,对PixelProcessor.h的改变仅仅会导致.cpp文件的重新编译,大大节约了编译时间。