在网上查资料的时候,无意中连接到了“zyl910”的专栏,
看了里面的一篇文章,感觉颇有收获,就在此抛砖引玉一番。
原文见:http://blog.csdn.net/zyl910/archive/2006/10/11/1330614.aspx
在用VB写图像处理程序的时候,经常会遇到运算结果超出范围的情况。
比如在处理32位位图的时候,由于每一种颜色分量都是8位,它们的值就不能超出0-255的范围。
否则,就会溢出到另一个颜色的分量里去,或者是产生一个“负”的颜色值,前者破坏图像显示效果,
后者将可能导致DIB崩溃。
其实只要在运算之后,输出之前,加上两句判断就可以消除这种错误:
此处假定处理绿色分量:
if Green>255 then Green=255
if Green<0 then Green=0
当然,实际处理的时候,肯定不止一个点,因此这里的Green也就会是一个数组元素,外面套着N重循环了。
单单看这两句,似乎也没什么不好。
可是这只是一个颜色分量而已,还有两个颜色也得这么处理:
if Red>255 then Red=255
if Red<0 then Red=0
if Blue>255 then Blue=255
if Blue<0 then Blue=0
而一个图片少说也有好几万的象素吧,每一点象素处理完都得来这么6句判断。
N万次的比较做下来,效率上的拖累可想而知。
而理想状态的代码应该是型如 Out = Limitation(In) 的自洽方式,而不是丑陋的IF...THEN...
即用一个表达式来自然而然的得到“规范的,合乎要求”的数据。
首先,我这里指的不是函数,用函数的话,我宁可用比较,因为函数更慢。
举两个例子:
例子一:
假设要写一个很简单的程序,界面上有一个CheckBox控件。
当用户点选这个CheckBox的时候窗体背景变成黑色,反选这个CheckBox的时候窗体变成红色。
读者们,请先想一下,让你写这个程序,你会怎么写呢。
是否这样写呢:
Sub Check1_Click()
If Check1.value=1 then
Me.backColor=0
Else
Me.BackColor=VBred
endif
End Sub
或者是简单点写成一句:
If Check1.value=1 then Me.backColor=0 Else Me.BackColor=VBred
其实这两句都是一样的IF判断语句,本质上没有区别。
你有没有想过这样写呢:
Sub Check1_Click()
Me.BackColor=Not(-Check1.Value) and VBRed
End Sub
这样的写法要比前面的优美多了。
首先,可能很多朋友不明白这个语句,其实在VB里面,逻辑变量也是一个LONG型变量。
True=-1
False=0
你可以在写True的地方写-1也可以在该写False的地方用0代替。
因为CheckBox控件它点和不点的值是1和0,只要加个负号,它就成了一个逻辑变量了。
Not(-Check1.Value):
如果你点选了CheckBox,Not(-Check1.Value)=>Not(-1)=>Not True=> False
如果你反选了CheckBox,Not(-Check1.Value)=>Not(0)=>Not False=>True
最后就是一个逻辑“与”运算了:True And Anything = Anything
而:False And Anything = False =0
于是,一个判断语句就变成了一个简单的赋值语句。
例子二:
很多程序需要用到全局逻辑变量来标记某个状态。
比如,在做文档处理,数据库操作或者是其他可以由用户保存修改的程序(也包括图像处理程序)
如果用户对数据进行了修改,这时退出程序的话,应该给用户一个提示“是否要先保存一下呀..."
否则,你的程序100%会被用户强烈投诉,即使你其他功能做得再好也没用。
恩,读者们再动一下“脑白金”吧,如果要你实现这个功能,该如何写?
想好了么?
我来给你个建议吧:
建立一个全局变量:
Dim Saved As Boolean
然后在窗体启动中设置它为True
Sub Form_Load()
...
Saved=True
...
End Sub
为什么?窗体还刚刚载入,当然不可能是“未保存”咯。
然后在用户更改内容的事件中,将这个变量设为:Saved=False
比如:
Sub Text1_Change()
Saved=False
End Sub
还有其他所有用户修改数据的地方都加上这句。
然后在保存数据的模块中将这个变量设为True
Sub SaveContent()
...
...
Saved=True '保存完了,当然就是“已保存”了咯
End Sub
最后呢,只要在窗体被卸载的事件中判断是“未保存”还是“已保存”就可以了。
是这么写么?
Sub Form_Unload(Cancel As Integer)
Dim I as Long
If Saved = False then
I = Msgbox("...要保存么...",,VBYESNOCANCEL) '随手写的,真有这个参数么?
Select Case I
Case VBYes:SaveContent
Case VBNo:End
Case VBCancel:Cancel=1
End Select
endif
End sub
第二个例子绕了那么大一个圈子,其实,只是想说:
If Saved = False then 为什么就不能写成 If Not Saved Then 呢?
这样写不但VB语句优美,而且几乎连英语语法都要通顺了。
这个例子我想说明的其实是逻辑表达式的值并非一定要用=,<,>这些等式符号才能表达出来的。
好了,最后来说说我们开头的那个话题吧。
什么?你已经忘记了?那太好了,请从头再看一遍。
由于我对逻辑运算不大感冒,因此虽然觉得那两个丑陋If...Then...
“或许”可以用一个“优美的”逻辑运算表达式来一次完成,但是一直都没有仔细的研究和尝试。
直到我看到“zyl910”的那篇文章,才重新提起了这个念头来:“哦,原来真的是可以的哦”
仔细看了半天,似乎有点理解了,但是闭上眼睛又忘记了,逻辑运算真是挺搞得哦。
所以就索性抛开文章,直接拿张白纸来推导了。
其实,最基本的原理已经在我举的第一个例子中说明了,即:
True=-1
False=0
既可以参加数学运算又可以参加逻辑运算的超级运动员!
由于我的逻辑运算不熟练,我采用的是反推的方法来得到表达式:
先从if Green<0 then Green=0开始。
如果小于0则等于0
大家知道逻辑运算的结果要么是True要么是False(-1和0)
并且True And Anything = Anything '结论1 (And 运算)
;False And Anything = False =0
这里Anything则是可以做文章的地方了,因为我们最后要得到的是0,N,255这3个分段值。
上面这两个运算就必定是关键,因为当0<N<255时我们需要得到的是N本身的值,而结论1
可以得到这样的结果,我们一定要牢记。
判断N<0 这个表达式,当N小于0的时候,N<0=True=-1
当N大于等于0的时候,N<0=False=0
代入结论1,
(N<0) And N
当N小于0的时候
(N<0) And N => True And N => N
当N大于0的时候
(N<0) And N => False And N => 0
咦,怎么反过来了?
那也简单,把条件改一下吧,反过来判断N>0
当N小于等于0的时候,N>0=False=0
当N大于0的时候, N>0=True=-1
再代入结论1:
(N>0) And N
当N小于等于0的时候
(N>0) And N => False And N => 0
当N大于0的时候
(N>0) And N => True And N => N
正确.完全合乎我们的要求!
等等,别急着开香槟啊,才做了一小半啊,还有一大半没做呢。
接下来我们来分析第二个判断语句
if Green>255 then Green=255
唉,看到255这个数字,实在是想亲上一口。倒不是我有什么特殊爱好,而是因为255正好是十六进制里的FF
而十六进制的FF,就是二进制里的11111111,这是个很奇妙的数字。
任何一个0-255范围内的整数和它进行“与”运算,都会得到原来的数,也就是等于没有运算。
什么?你说我在忽悠你?什么叫做“等于没有运算”?
别忘记我们要做的事,是把一个分支判断语句放到一个表达式里面,
在这里“等于没有运算”=“得到原来的值”
首先我想到的是在何种情况下才能用到这个“等于没有运算”呢?
那就是 Something And 255
当N大于255的时候,Something=True
当N小于等于255的时候,Something=N
这时我又想到了另一个逻辑运算:
True Or Anything = True '结论2 (Or 运算)
False Or Anything = Anthing
这可真是个好东东哦,想什么来什么,上面的那个Something可有着落了。
Something = AnotherThing Or N 就可以了。
对这个AnotherThing的要求是:
当N大于255的时候 AnotherThing=True
当N小于等于255的时候 AnotherThing=False
太简单啦,直接AnotherThing=(N>255)就满足啦
把前面的表达式都列出来看看:
N = Something And 255 '(1)
当N大于255的时候, Something=True
当N小于等于255的时候, Something=N
Something = AnotherThing Or N '(2)
当N大于255的时候 AnotherThing=True
当N小于等于255的时候 AnotherThing=False
AnotherThing=(N>255) '(3)
当N大于255的时候 N>255 = True
当N小于255的时候 N>255 = False
最后反过来一步一步代入前面的表达式:
(3)代入(2)再代入(1)
得到:
(N>255) Or N And 255
验算一下:
当N大于255的时候:
(N>255) Or N And 255 => True Or N And 255 => True And 255 => 255
当N小于等于255的时候:
(N>255) Or N And 255 => False Or N And 255 => N And 255 => N
Yeah!!!
==
等等,别开香槟啊,还有事要做呀。
我们只是把两个条件判断语句变成了两个逻辑表达式,还没有达到最终目的呢。
那就是把两个表达式整合成一个表达式。
先来看一下目前的成果:
if N > 255 then N=255
=》N = (N>255) Or N And 255 ‘(1)
if N < 0 then N=0
=》N = (N>0) And N ‘(2)
似乎有点难度哦,我首先想到的方法是把整个(2)代入(1)中,
N=(((n>0) And N) > 255) Or ((N>0) And N) And 255
整个表达式就变得奇长无比,估计即使成功,运行速度也不会比原来快。
你说化简?恩,我也很想啊,但是连不等式运算都已经忘记,更不说逻辑表达式了。
还是继续分析吧。
表达式(1)只考虑两种情况: a: N>255 b: N<=255
而表达式(2)只考虑 a: N>0 b: N<=0
因此无论N是否大于0,都是被包含在表达式1的范围之内的.
这样的话,只需要将表达式(2)代入一半到表达式(1)中就可以了,
表达式1的括号内的不等式中的那个N不需要代入.
整个表达式变成: (N>255) Or ((N>0) And N) And 255
Or的两边可以交换顺序,变成这样:
((N>0) And N) Or (N>255) And 255
去括号: (N>0) And N Or (N>255) And 255
测试一下:
N=-1:
(-1>0) And –1 Or (-1>255) And 255
=False And –1 Or False And 255
=False Or False And 255
=False And 255
=False
=0
N=10:
(10>0) And 10 Or (10>255) And 255
=True And 10 Or False And 255
=10 Or False And 255
=10 And 255
=10
N=266
(266>0) And 266 Or (266>255) And 255
=True And 266 Or True And 255
=266 Or True And 255
=True And 255
=255
哈哈,终于成功了,回头一看,原来和”zyl910”推导出来的还是同一个东西.
香槟香槟拿出来!
你说我推导出了一个别人早就弄好的东西为什么还这么开心??
废话,要是自己不这么来上一遍,那永远是别人的,你最多只能把它背出来,它终究不在你心里.
最后,再花几分钟写了一个测试程序,来验证一下.
再回过头去看,之前这个东东就是为了要放在图像处理中使用的,因此验证的时候也应该用一个大数组来做.
最后那个” CombWithVar”按钮的事件其实也验证了另外一个想法:用变量代替数组元素运算确实可以提高速度,前提是在非常大的运算次数下才能显现出这个差异来.
测试代码:
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Dim A(49999999) As Integer
Dim B(49999999) As Integer
Dim T As Long
Private Sub Form_Unload(Cancel As Integer)
End Sub
Private Sub Traditional_Click() ‘最原始的方法
Dim I As Long
T = timeGetTime
For I = LBound(A) To UBound(A)
If A(I) > 255 Then B(I) = 255
If A(I) < 0 Then A(I) = 0
Next
I = timeGetTime - T
Me.Print "Traditional Compare:" & I
Text1.Text = Text1.Text & Chr(13) & "Traditional Compare:" & I
End Sub
Private Sub BooleanExp_Click() ‘分成2个逻辑运算
Dim I As Long
T = timeGetTime
For I = LBound(A) To UBound(A)
B(I) = (A(I) > 255) Or A(I) And 255
B(I) = A(I) And (A(I) > 0)
Next
I = timeGetTime - T
Me.Print "Boolean Expression:" & I
Text1.Text = Text1.Text & Chr(13) & "Boolean Expression:" & I
End Sub
Private Sub Combination_Click() ‘合成一个逻辑运算
Dim I As Long
T = timeGetTime
For I = LBound(A) To UBound(A)
B(I) = (A(I) > 0 And A(I) Or (A(I) > 255)) And 255
Next
I = timeGetTime - T
Me.Print "Combination Bool-Exp:" & I
Text1.Text = Text1.Text & Chr(13) & "Combination Bool-Exp:" & I
End Sub
Private Sub CombWithVar_Click() ‘使用变量代替数组元素
Dim I As Long
Dim L As Long
T = timeGetTime
For I = LBound(A) To UBound(A)
L = A(I)
B(I) = (L > 0 And L Or (L > 255)) And 255
Next
I = timeGetTime - T
Me.Print "Com_Bool_Exp_Var:" & I
Text1.Text = Text1.Text & Chr(13) & "Com_Bool_Exp_Var:" & I
End Sub
Private Sub Form_Load() ‘初始化数组
Dim I As Long
For I = 0 To 1000
A(I) = Rnd * I / 2
Next
Me.Print "运算五千万次计时"
Text1.Text = "运算五千万次计时"
End Sub
运算五千万次计时
Traditional Compare:577
Traditional Compare:579
Traditional Compare:536
Traditional Compare:578
Traditional Compare:534
Traditional Compare:553
Traditional Compare:577
Traditional Compare:530
Traditional Compare:530
Traditional Compare:530
Boolean Expression:926
Boolean Expression:839
Boolean Expression:907
Boolean Expression:929
Boolean Expression:809
Boolean Expression:913
Boolean Expression:927
Boolean Expression:877
Boolean Expression:912
Boolean Expression:786
Combination Bool-Exp:593
Combination Bool-Exp:420
Combination Bool-Exp:426
Combination Bool-Exp:433
Combination Bool-Exp:467
Combination Bool-Exp:421
Combination Bool-Exp:415
Combination Bool-Exp:459
Combination Bool-Exp:422
Combination Bool-Exp:428
Com_Bool_Exp_Var:518
Com_Bool_Exp_Var:326
Com_Bool_Exp_Var:320
Com_Bool_Exp_Var:353
Com_Bool_Exp_Var:320
Com_Bool_Exp_Var:343
Com_Bool_Exp_Var:346
Com_Bool_Exp_Var:376
Com_Bool_Exp_Var:351
Com_Bool_Exp_Var:334