大家好,今天我們要開始講解Screen Saver Maker最關鍵(也是我最頭痛)的圖形特效部分, 圖形特效主要是由Bitblt這個API的加強版StretchBlt來製作的,它與Bitblt唯一的不同點 在於對目標圖像的控制,使用Bitblt的時候,你所能選擇的是要貼上的圖形來源大小及位置和 目標的座標位置,在使用StretchBlt時,你則可以連貼上的大小一起控制,並且利用正負號的轉 換作出翻轉的效果,不過作者的功夫還不到家,尤其是一些數學概念也不好,導至Ver0.9的運算 上有許多無效的結果,不過在Ver0.91已經做了修正,現在我會分別對Ver0.9及Ver0.91做說明, 希望你們能從我的錯誤中得到一些啟示,不要再犯我這種烏龍錯誤了!
首先要從由左至右、由右至左、由上至下、由下至上四種表現方式做起,這四種方式的原理其 實沒有什麼不同,只要推出一種,其它的也就知道怎麼做了,我們就挑由右至左來說明,首先我 要考慮的是貼圖方式,最簡單的方式如下
以For迴圈,將貼上的圖形範圍遞增,像這樣
 

  

   

    

這種方法理論上雖然沒有錯...但是!!當作者使用較大的圖檔測試時卻發生了嚴重的閃爍, 因此,這個方式-不可行!!
作者因此換了一種方式,作者猜想,可能是因為圖檔處理的範圍越來越大,清除重繪的地方也 跟者變大,程式更換畫面的速度又快,程式不及處理才會造成這種情形,所以作者換了下面這種方法
依然使用For迴圈處理,但把圖形分成n等份,每次只貼n分之一的圖
 

  

   

    

如此一來,程式的負擔就應該可以減輕很多了,一試之下,效果還差強人意,至少還在可接受的 範圍內,因此就用這個方法來做了,但是....在作者又多試幾個檔案後發覺,當某些圖檔( 選擇了自動縮放圖形時)載入時,在拼湊圖形時,中間的接縫會有明顯的紋路產生,至於會產生紋 路的原因在於座標值捨去小數點之後的進位問題,這一點,老實講,作者學藝不精,試的出來怎 麼做結果正確,但是原理實在不知道該怎麼解釋才對...為了不誤人子弟,只好讓你們自己來看了 ,(所以說我最討厭這一段了!!每次都要想半天....)總而言之,貼圖的原理如上面所說, 接下來就看你們自己去融會貫通了,接者來看程式
Sub tmrShow_Timer()
Case LEFT_TO_RIGHT         
     If picCut = True Then
        StretchBlt Me.hdc, (DestLeft + (DestWidth / EffectTime) * (loopNo - 1)) - 1, DestTop, (DestWidth / EffectTime) + 1, DestHeight, picPreView.hdc, (SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1)) - 1, SrcTop, (SrcWidth / EffectTime) + 1, SrcHeight, SRCCOPY
     Else
        StretchBlt Me.hdc, DestLeft + (DestWidth / EffectTime) * (loopNo - 1), DestTop, (DestWidth / EffectTime) + 2, DestHeight, picPreView.hdc, SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1), SrcTop, (SrcWidth / EffectTime) + 2, SrcHeight, SRCCOPY
     End If
     DoEvents

     loopNo = loopNo + 1

     If loopNo > (EffectTime + 1) Then
        Me.AutoRedraw = True
        StretchBlt Me.hdc, DestLeft, DestTop, DestWidth, DestHeight, picPreView.hdc, SrcLeft, SrcTop, SrcWidth, SrcHeight, SRCCOPY
        Me.Refresh
        
        overFlag = True
        picPreView.AutoRedraw = False
        Me.AutoRedraw = False
     Else
        tmrShow.Enabled = True
        Exit Sub
     End If
     'Me.Refresh


這裡做的就是貼圖的動作,StretchBlt的用法和BitBlt的用法差不多,只是多了兩個參數指定目標 貼圖的大小,其餘的使用方式沒有不同。由左至右的表現方法是把來源圖型分為(來源圖形寬度/5) +1等份(就是將圖型切成一張張5Pixel大小的圖),得到的就是EffectTime特效需執行的次數,而圖型 貼圖的座標及寬度則在LoadImagePicture時就算出來了,忘記的再回頭看看,因此每次貼圖時的 寬度就可以推出來為(DestWidth / EffectTime),為什麼不是和來源圖形一樣是5Pixel呢?因為 使用者如果點選了圖型自動縮放,那麼貼在目標圖型的寬度就可能要放大或縮小了,因此我們要 再計算一次才會得到正確的值。X座標的位置則是(DestLeft(X座標的原點) + (DestWidth / EffectTime) * (loopNo - 1)(目前貼圖的最後位置)) 其中loopNo就是執行的次數,例如圖型為100*100,假設分為十等份,不考慮原點問題,每次的計算結果如下
Step1.(0)+(100/10)*(1-1) --> 0 貼圖範圍1-10
Step2.(0)+(100/10)*(2-1) --> 10 貼圖範圍11-20
Step3.(0)+(100/10)*(3-1) --> 20 貼圖範圍21-30
Step4.(0)+(100/10)*(4-1) --> 30 貼圖範圍31-40
Step5.(0)+(100/10)*(5-1) --> 40 貼圖範圍41-50
Step6.(0)+(100/10)*(6-1) --> 50 貼圖範圍51-60
Step7.(0)+(100/10)*(7-1) --> 60 貼圖範圍61-70
Step8.(0)+(100/10)*(8-1) --> 70 貼圖範圍71-80
Step9.(0)+(100/10)*(9-1) --> 80 貼圖範圍81-90
Step10.(0)+(100/10)*(10-1) --> 90 貼圖範圍91-100

高度和Y位置則可以不用計算了,因為它是固定貼在DestTop及取DestHeight的高度, 不會有變化的,相信有人看到這裡一定會有疑惑,為什麼上面是這樣計算,但是程式裡 實際的算式卻有+2或-1等額外的計算呢?這就是為了解決上面所說的紋路的問題,在程 式上所做的計算補償,如此一來,顯示出來的圖型才不會有我所說的紋路產生, 但是問題不是這樣就解決了,依圖形的寬度,你可能會發生最後一次貼圖的範圍超出螢幕的 計算結果(例如螢幕寬度為800,X座標為798,貼圖範圍為799-808),但是StretchBlt不接受 這一種參數,會使得最後一次貼圖無效,在畫面上留下一片空白的地方,於是在最後,我會 將整張圖型重新貼上,這樣才會貼滿整個該顯示的畫面,程式才算完成,在這裡可能會有人 注意到,我把Form的AutoRedraw設為True才貼圖上去,這一點則是為了文字顯示時的透空顯示 ,Label是把物件蓋上去後再重會底層的圖形,如果沒有將AutoRedraw設為True,那麼它就會 取原來的黑色背景做為底層的圖案,就會變成黑黑的一塊很難看囉!

-_- 好吧!好吧!你們手上都拿到Ver0.91的版本了吧?這種方法的確還是有許多缺點, 首先就是最後一次貼圖可能發生失敗的問題,雖然我使用了原圖重貼的方式解決了,但是你 應該會注意到,有些圖型會發現明顯的閃動,這是因為補償計算所引發的問題,......我的天 啊!我到底該怎麼解釋..........總之呢!在Ver0.91版我改變了計算的方式,讓最後一次執行 仍然可以順利的貼圖成功,這樣我就沒有必要在將原圖重新貼上去,圖形也就不會發生奇怪的 閃爍了!另外,基於這個原因,我把Form的AutoRedraw直接設為True了,這樣顯示文字才不會 出錯,想更進一步了解的可以把Ver0.91 Print寫檔動作的註解拿掉,宣告一個檔名,將貼圖的 變化記錄下來,Ver0.9也做相同的動作,兩個值比較一下,應該可以比較容易了解,Ver0.91的 程式碼如下,你們可以參考一下,只要弄懂了這一部份,其它三個由左至右、上至下、下至上, 也就可以輕鬆帶過了,這一次的講解就到此為止囉,下一次為你解釋其它三種特效的原理.... 頭好痛啊!!!

Case LEFT_TO_RIGHT
          
     If picCut = True Then
        StretchBlt Me.hdc, (DestLeft + (DestWidth / EffectTime) * (loopNo - 1)) - 1, DestTop, (DestWidth / EffectTime) + 1, DestHeight, picPreView.hdc, (SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1)) - 1, SrcTop, (SrcWidth / EffectTime) + 1, SrcHeight, SRCCOPY
     Else
        If loopNo = EffectTime Then
           StretchBlt Me.hdc, DestLeft + (DestWidth / EffectTime) * (loopNo - 1), DestTop, DestWidth - (DestLeft + (DestWidth / EffectTime) * (loopNo - 1)), DestHeight, picPreView.hdc, SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1), SrcTop, SrcWidth - (SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1)), SrcHeight, SRCCOPY
           'Print #fNum, Format$(DestLeft + (DestWidth / EffectTime) * (loopNo - 1), "000") & Space(4) & Format$(DestTop, "000") & Space(4) & Format$(DestWidth - (DestLeft + (DestWidth / EffectTime) * (loopNo - 1)), "000") & Space(4) & Format$(DestHeight, "000")
        Else
           StretchBlt Me.hdc, DestLeft + (DestWidth / EffectTime) * (loopNo - 1), DestTop, (DestWidth / EffectTime) + 2, DestHeight, picPreView.hdc, SrcLeft + (SrcWidth / EffectTime) * (loopNo - 1), SrcTop, (SrcWidth / EffectTime) + 2, SrcHeight, SRCCOPY
           'Print #fNum, Format$(DestLeft + (DestWidth / EffectTime) * (loopNo - 1), "000") & Space(4) & Format$(DestTop, "000") & Space(4) & Format$((DestWidth / EffectTime) + 2, "000") & Space(4) & Format$(DestHeight, "000")
        End If
        
     End If
     DoEvents
          
     Me.Refresh
     loopNo = loopNo + 1
     If loopNo > (EffectTime) Then
        'Print #fNum, "================================================================"
        overFlag = True
        picPreView.AutoRedraw = False
     Else
        tmrShow.Enabled = True
        Exit Sub
     End If

後記:
我的天!解說的我頭都痛起來了,這一部份實在是很難"講明白,說清楚^_^",如果還是有疑 問的話....嗯....EMail給我,我儘量給他"講明白,說清楚"吧....?

回到VB教學教室