2015年1月30日 星期五

C# - PictureBox 繪圖處理閃爍的方法

動畫這一門技術,運用的是多數圖片不斷播放的原理,讓視覺產生一連串畫面不斷閃爍而造成會動的效果。程式,就是要去處理這一連串動作而寫出的方法。


相信有很多人有玩過遊戲,或者卡通電影動畫這類的
(沒有童年的話快去補完你的人生先~XD)

假設要在表單上進行圖片的繪製,但是在實際的測試中發現了問題,那就是重繪的時候會發生閃爍,或者在程式繪製動畫的高頻率刷新的時候,也會產生閃爍,這時要用到的解決辦法,是對動畫進行雙緩衝(Double Buffering)處理。



在要了解雙緩衝這個名詞之前,先來探討下為什麼重繪的時候會發生閃爍:
動畫的原理就是利用了人眼的視覺殘留(Visual staying)現象,當一副畫面進入眼睛產生影像後,並不會立刻消失,而是仍會保留一小段時間,於是當連續的圖像以很高的速度切換的時候,人眼會看到動態的影響,而不是處於切換中的單個圖像。
這個過程可以下圖1:












將這每一張的圖片串成一連串的播放動作,就會產生下面的結果:
(有某有感覺在跑的港覺-(拿刀狀ing),說~~~)























當這三幅圖片以一定頻率直接切換的時候,人們就會看到貌似是在跑步的動態。

想要更詳細的了解動畫可以參考--->>>維基百科



那麼為什麼依據這個原理來程式設計繪製動畫的時候會出現閃爍呢?

假設不加任何處理,就在畫布C上進行繪圖,那麼電腦的處理過程是這樣的:

Step 1: 將C以背景色填充(也就是清除C上現有的內容)
Step 2: 在C上按照要求繪製新的畫面

那麼這樣的過程會對動畫產生怎樣的影響呢?請看下圖2:










能看出和圖1的差別嗎?Step1相當於在原本連續的動畫中嵌入了空白的畫面,這個空白的畫面由於和人眼中原本殘留的圖像反差非常大,所以便會破壞視覺殘留產生的動畫,給人的感覺就是,這個動畫在不停的閃爍。
於是我們知道消除Step 1這個過程帶來的影響,就能夠避免在繪製的時候發生閃爍。如果直接把Step 1略過是個好的方式嗎?如果只是單純的略過Step 1,那麼動畫就會變成這樣:












在視覺上就成了一個拖著殘像尾巴的動畫,像下面的情況:

















(當然這也可以當做一種影像處理的效果,例如想做出像飛影的殘像拳這樣XDDDDD)


上面的圖就能了解雙緩衝是怎樣防止閃爍的。
假如我們希望在螢幕S上展示動畫,首先我們需要在記憶體中建立一個虛擬的畫布C,然後我們所有的繪圖操作都在C上進行,當繪製動畫的一幀完畢後,我們啪唧~把C直接往S上一拍,這樣就既不會出現拖尾巴,也不會出現Refresh時的短暫空白了,如下圖所示,下方的畫面就代表那塊虛擬的畫布:

















實際應用的時候還是會遇到一些問題,這些問題涉及到C#本身表單的繪製機制,當在pictureBox上繪圖的時候,特別是pictureBox還存在背景圖片的時候,就會遇到問題:

// 初始化圖片
Bitmap image = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);

// 初始化影像
Graphics g = Graphics.FromImage(image);

// 給圖區域碼 Begin
// 實作區
// 給圖區域碼 End
pictureBox1.CreateGraphics().DrawImage(image, 0, 0);

圖像g是透明的,所以在g上繪製後,貼在pictureBox上,背景圖片還是會展示出來,但是問題也就來了,由於pictureBox的繪製機制問題,如果我在pictureBox上貼一張透明的圖,將透明的圖片貼上去,那其實和直接略掉剛才所說的Step 1一樣!如果想要將之前的內容去除,就不得不再使用pictureBox1.Refresh()方法,而這樣的話,顯然會導致閃爍,那麼該怎麼辦呢?
一般寫程式時會用下面幾種方式,比如將表單的Double Buffered屬性設置為true,或者通過繼承或者反射機制,將pictureBox的Double Buffered屬性設置為true,但是經過實驗,這些方法都會有些問題。
其實原理就是,這個問題所遇到的障礙就是不能影響pictureBox的背景圖的展示,所以為何不把pictureBox的背景圖片也提取出來,作為底層圖像呢?
下面是原理範例:

// 初始化畫板
Bitmap image = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);

// 獲取背景層
Bitmap bg = (Bitmap)pictureBox1.BackgroundImage;

// 初始化圖像
Bitmap canvas = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);

// 初始化圖形面板
Graphics g = Graphics.FromImage(image);
Graphics gb = Graphics.FromImage(canvas);

// 繪圖部分 Begin
// ... ...
// 繪圖部分 End

gb.DrawImage(bg, 0, 0); // 先繪製背景層
gb.DrawImage(image, 0, 0); // 再繪製繪畫層

pictureBox1.BackgroundImage = canvas; // 設置為背景層
pictureBox1.Refresh();
pictureBox1.CreateGraphics().DrawImage(canvas, 0, 0);

注意標註的地方,就是添加的部分。
pictureBox的Refresh()方法不會影響其背景層,所以將最後合成的畫布直接貼在背景層上,這樣再Refresh()就不會產生閃爍了,同時,由於系統會自動重繪背景層,所以在視窗最小化或者被遮擋過後,繪製的圖像也不會消失,這樣一來,閃爍問題就被解決了!

在撰寫像是繪圖引擎這種機制,其實都會套用上面較基本的方法,例如Unity(各家繪圖演算法不盡相同,各有長短)。試想如果只是幾張圖,就會產生這麼大的閃爍狀況,那更多的影像特效,沒有處理好,其實非常耗電腦運算的效能,上面只是比較簡單的觀念,最重要的還是要勤練功,找到屬於自己的演算模式。

其實以我曾經開發過遊戲的經驗來說,閃爍其實也是一種寫程式的技巧,也可以運用在寫引擎這件事情上,但有些時候還是取決於電腦的效能,有時候考量的層面很多,俗話說的好,一個技術做到越後面,腦袋就會越來越爆炸,我想說什麼呢?? 就是扯遠了XDDDDD。

-註-
雙緩衝(double buffering):

雙重的圖形輸出記憶體,以動態的方式配合螢幕輸出的速率。當一組記憶體正在讀出的時候,另一組則在寫入,如此輪流讀寫的方式使得螢幕可以即時地更新畫面資料。



-雲遊山水為知已逍遙一生而忘齡- 電腦神手

2015年1月6日 星期二

C# Design Pattern - Visitor Pattern 參觀者模式

如果需要對一個複雜系統架構(Acceptor),針對整個系統做一巡訪
並做不同的動作處理,將把這處理器(Visitor)的部份獨立出來成為物件
也就是將資料處理由資料結構中分離出來,就是Visitor Pattern。

例如,有一個檔案架構系統,希望把整個檔案目錄架構整個列印出來,
需要有一個print_Visitor是負責來做把檔案列印出來的工作,
只要把這print_Visitor丟到一個複雜架構(acceptor)內,就會達到這目的。

如果在這檔案架構內,希望可以把所有圖檔都可以產生一個縮圖
這時候,可以產生另一個thumb_Visitor,用來負責產生縮圖的工作
只要把這thumb_Visitor丟到架構內,就會達到目的。

對於一個複雜架構的系統(Acceptor),丟進去不同功能的Visitor
就可以對整個系統做不同功能的事。

實作Visitor Pattern會用到一個比較複雜的遞迴(Double dispatch)
一般遞迴是自己呼叫自己,在這裡面是Acceptor與Visitor的彼此呼叫遞迴


參與者
1.Acceptor - 內需要定義一個accept method,這method只是都固定用來把自己當作參數,呼叫啟動visitor的巡訪動作:

public Class Acceptor {
        public void accept(Visitor v){
    v.visit(this)
        };
        .....

}

2.Visitor - 程式的運作核心是在Visitor的visit function:
public Class Visitor{
    public void visit(Acceptor entry){
        Iterator it=entry.iterator()
        while(it.hasNext()){
            Acceptor en=it.next();
            en.accept(this);
        }
    }
}

3.Main - 外部程式

Acceptor rootdir=new Acceptor("root");
rootdir.accept(new Visitor());

4.在Visitor實作迴圈上
private String currentdir=""
public void visit(Directory dir){
    String savedir=currentdir+"/"+dir.getName()
    Iterator it=dir.iterator();
    while(it.hasNext()){
        Entry entry=(Entry)it.next();
        entry.accept(this)
    }
    currentdir=savedir;
}

下面操作表示可以在物體結構的元素進行。訪客可以讓你定義一個新的操作,而不改變其運作上的元素的類。




進階的運用,和角色類和參與這個模式的對象可以更細分如下:

Visitor  (Visitor)
聲明在對象結構中的每個類ConcreteElement的訪問操作。操作的名稱和簽名識別發送訪問請求,訪問者的類。讓參觀者確定具體類的元素被訪問。那麼Visitor可以通過其特定的界面直接訪問的元素.。

ConcreteVisitor(IncomeVisitor,VacationVisitor)
實現由觀眾宣布每個操作。每個操作實現為在結構中相應的類或對象中定義的算法的一個片段。 ConcreteVisitor提供了上下文的算法和存儲其本地狀態。這種狀態經常結構的遍歷期間累積的結果。

Element  (Element)
定義一個接受的操作,需要一個Visitor作為一個參數。

ConcreteElement  (Employee)
實現了一個接受的操作,需要一個Visitor作為一個參數.

ObjectStructure  (Employees)
可以枚舉它的元素
可提供一個高層次的接口,以允許訪問者訪問其元素
可以是一個複合物(圖案)或集合,例如一個清單或一組

這種結構的代碼演示在其中一個對象遍歷對象結構,在這種結構中的每個節點上執行相同的操作的訪問者模式。不同的Visitor對象定義不同的操作。

using System;
using System.Collections.Generic;

namespace DoFactory.GangOfFour.Visitor.Structural
{
    /// <summary>
    /// MainApp startup class for Structural
    /// Visitor Design Pattern.
    /// </summary>
    class MainApp
    {
        static void Main()
        {
            // Setup structure
            ObjectStructure o = new ObjectStructure();
            o.Attach(new ConcreteElementA());
            o.Attach(new ConcreteElementB());

            // Create visitor objects
            ConcreteVisitor1 v1 = new ConcreteVisitor1();
            ConcreteVisitor2 v2 = new ConcreteVisitor2();

            // Structure accepting visitors
            o.Accept(v1);
            o.Accept(v2);

            // Wait for user
            Console.ReadKey();
        }
    }

    /// <summary>
    /// The 'Visitor' abstract class
    /// </summary>
    abstract class Visitor
    {
        public abstract void VisitConcreteElementA(
          ConcreteElementA concreteElementA);
        public abstract void VisitConcreteElementB(
          ConcreteElementB concreteElementB);
    }

    /// <summary>
    /// A 'ConcreteVisitor' class
    /// </summary>
    class ConcreteVisitor1 : Visitor
    {
        public override void VisitConcreteElementA(
          ConcreteElementA concreteElementA)
        {
            Console.WriteLine("{0} visited by {1}",
              concreteElementA.GetType().Name, this.GetType().Name);
        }

        public override void VisitConcreteElementB(
          ConcreteElementB concreteElementB)
        {
            Console.WriteLine("{0} visited by {1}",
              concreteElementB.GetType().Name, this.GetType().Name);
        }
    }

    /// <summary>
    /// A 'ConcreteVisitor' class
    /// </summary>
    class ConcreteVisitor2 : Visitor
    {
        public override void VisitConcreteElementA(
          ConcreteElementA concreteElementA)
        {
            Console.WriteLine("{0} visited by {1}",
              concreteElementA.GetType().Name, this.GetType().Name);
        }

        public override void VisitConcreteElementB(
          ConcreteElementB concreteElementB)
        {
            Console.WriteLine("{0} visited by {1}",
              concreteElementB.GetType().Name, this.GetType().Name);
        }
    }

    /// <summary>
    /// The 'Element' abstract class
    /// </summary>
    abstract class Element
    {
        public abstract void Accept(Visitor visitor);
    }

    /// <summary>
    /// A 'ConcreteElement' class
    /// </summary>
    class ConcreteElementA : Element
    {
        public override void Accept(Visitor visitor)
        {
            visitor.VisitConcreteElementA(this);
        }

        public void OperationA()
        {
        }
    }

    /// <summary>
    /// A 'ConcreteElement' class
    /// </summary>
    class ConcreteElementB : Element
    {
        public override void Accept(Visitor visitor)
        {
            visitor.VisitConcreteElementB(this);
        }

        public void OperationB()
        {
        }
    }

    /// <summary>
    /// The 'ObjectStructure' class
    /// </summary>
    class ObjectStructure
    {
        private List<Element> _elements = new List<Element>();

        public void Attach(Element element)
        {
            _elements.Add(element);
        }

        public void Detach(Element element)
        {
            _elements.Remove(element);
        }

        public void Accept(Visitor visitor)
        {
            foreach (Element element in _elements)
            {
                element.Accept(visitor);
            }
        }
    }

}

下例範例以員工假日和收入之間的關係,用Visitor來呈現其之間的關係操作。
這個代碼演示Visitor模式中,橫越兩個物體員工的名單和每個員工執行相同的操作。這兩個Visitor對象定義不同的操作 -1調整休假日和其他收入。

using System;
using System.Collections.Generic;

namespace DoFactory.GangOfFour.Visitor.RealWorld
{
    /// <summary>
    /// Visitor Design Pattern.
    /// </summary>
    class MainApp
    {
        /// <summary>
        /// 程式執行的啟動初始
        /// </summary>
        static void Main()
        {
            // 設定 employee collection
            Employees e = new Employees();
            e.Attach(new Clerk());
            e.Attach(new Director());
            e.Attach(new President());

            // Employees are 'visited'
            e.Accept(new IncomeVisitor());
            e.Accept(new VacationVisitor());

            // Wait for user
            Console.ReadKey();
        }
    }

    /// <summary>
    /// 'Visitor' 的 interface
    /// </summary>
    interface IVisitor
    {
        void Visit(Element element);
    }

    /// <summary>
    /// 'ConcreteVisitor' 的 class
    /// </summary>
    class IncomeVisitor : IVisitor
    {
        public void Visit(Element element)
        {
            Employee employee = element as Employee;

            // Provide 10% pay raise
            employee.Income *= 1.10;
            Console.WriteLine("{0} {1}'s new income: {2:C}",
              employee.GetType().Name, employee.Name,
              employee.Income);
        }
    }

    /// <summary>
    /// 'ConcreteVisitor' 的 class
    /// </summary>
    class VacationVisitor : IVisitor
    {
        public void Visit(Element element)
        {
            Employee employee = element as Employee;

            // 提供 3 個 extra vacation days
            Console.WriteLine("{0} {1}'s new vacation days: {2}",
              employee.GetType().Name, employee.Name,
              employee.VacationDays);
        }
    }

    /// <summary>
    /// 'Element' abstract 的 class
    /// </summary>
    abstract class Element
    {
        public abstract void Accept(IVisitor visitor);
    }

    /// <summary>
    /// 'ConcreteElement' 的 class
    /// </summary>
    class Employee : Element
    {
        private string _name;
        private double _income;
        private int _vacationDays;

        // 建構函數
        public Employee(string name, double income,
          int vacationDays)
        {
            this._name = name;
            this._income = income;
            this._vacationDays = vacationDays;
        }

        // name的存取
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        // income的存取
        public double Income
        {
            get { return _income; }
            set { _income = value; }
        }

        // number of vacation days的存取
        public int VacationDays
        {
            get { return _vacationDays; }
            set { _vacationDays = value; }
        }

        public override void Accept(IVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    /// <summary>
    /// 'ObjectStructure' 的 class
    /// </summary>
    class Employees
    {
        private List<Employee> _employees = new List<Employee>();

        public void Attach(Employee employee)
        {
            _employees.Add(employee);
        }

        public void Detach(Employee employee)
        {
            _employees.Remove(employee);
        }

        public void Accept(IVisitor visitor)
        {
            foreach (Employee e in _employees)
            {
                e.Accept(visitor);
            }
            Console.WriteLine();
        }
    }

    // 3個 employee types

    class Clerk : Employee
    {
        // Constructor
        public Clerk()
            : base("Hank", 25000.0, 14)
        {
        }
    }

    class Director : Employee
    {
        // Constructor
        public Director()
            : base("Elly", 35000.0, 16)
        {
        }
    }

    class President : Employee
    {
        // Constructor
        public President()
            : base("Dick", 45000.0, 21)
        {
        }
    }
}

-雲遊山水為知已逍遙一生而忘齡- 電腦神手