天天看點

第二十章:異步和檔案I/O.(二十)

一個MVVM Mandelbrot

Xamarin.Forms - 以這種方式為像素着色。該程式還允許放大特定位置。這是Mandelbrot集的一個特征,無論你放大多遠,圖像仍然很有趣。不幸的是,基于doubleprecision浮點數的分辨率,縮放存在實際限制。

該程式使用MVVM原理進行架構,雖然在看到有些奇怪的使用者界面以及ViewModel如何處理該使用者界面後,您可能會質疑該決策的智慧。

MandelbrotXF的奇怪使用者界面源于決定避免任何特定于平台的代碼。在最初編寫此程式時,Xamarin.Forms不支援拖動和捏合等觸摸操作,這些操作可能有助于放大到特定位置。相反,程式的整個使用者界面使用兩個Slider元素,兩個Stepper元素,兩個Button元素,一個ProgessBar和使用BoxView實作的視覺效果來實作。

當您第一次運作該程式時,您将看到以下内容:

第二十章:異步和檔案I/O.(二十)

白色十字準線 - 沒有出現在空白的iOS和Windows 10移動螢幕的白色背景 - 在10秒的時間内淡出,這樣它們就不會模糊你很快就會欣賞的漂亮照片, 但你可以通過操縱滑塊或步進器來将它們帶回來。

但是你要做的第一件事就是按下Go按鈕。 該按鈕被取消按鈕替換,ProgressBar訓示進度。 當它完成後,你會看到一個彩色的Mandelbrot集:

第二十章:異步和檔案I/O.(二十)
它完成得很快,因為最大疊代次數(由底部步進器标記的循環表示)僅為2到第三次幂,或8.是以,黑色Mandelbrot集的輪廓不像早期程式那樣尖銳。 與更高的最大疊代計數相比,更多的點被标記為集合的成員。 您可以将疊代計數增加2的幂。這是一個更清晰的圖像,最大疊代次數為64:
第二十章:異步和檔案I/O.(二十)

兩個滑塊視圖允許您選擇一個新的中心,該中心在滑塊正下方顯示為複數。 第一個步進元素(标記為縮放)允許您選擇放大系數,也是2的幂。當您操縱這三個元素時,您将看到一個帶十字準線的方框

由六個薄BoxView元素構成。 該框标記下次按“執行”按鈕時将放大的區域:

第二十章:異步和檔案I/O.(二十)
現在再次按下Go按鈕并等待。 現在,以前裝箱的區域填充了位圖:
第二十章:異步和檔案I/O.(二十)
計算新圖像後,十字準線将被重新定位,您可以重新定位中心并再次放大,然後再次放大。
第二十章:異步和檔案I/O.(二十)
但是,通常放大的越多,檢視所有細節所需的最大疊代次數就越多。 對于每個裝置,前面螢幕截圖中的圖像可以獲得四倍于疊代的明顯更多細節:
第二十章:異步和檔案I/O.(二十)
這是Mandelbrot套裝的一個特點,你可以随心所欲地放大,你仍然可以看到同樣多的細節。 但是,通常你需要不斷增加最大疊代次數,當你達到放大系數2到48次左右時,你已經達到了涉及雙精度浮動分辨率的上限 點數。 相鄰像素不再與不同的複數相關聯,并且圖像開始看起來像塊狀:
第二十章:異步和檔案I/O.(二十)

這不是超越的容易障礙。 存在可變精度浮點數的實作,但由于它們不是由計算機的數學協處理器直接處理,涉及這些數字的計算必然比浮點數或雙精度類型慢得多,并且您可能不希望Mandelbrot計算慢一點。

MandelbrotXF程式同時具有ViewModel和底層模型。 模型執行實際數字運算并傳回BitmapInfo類型的對象,該對象訓示像素寬度和高度以及整數數組。 整數數組的大小是像素寬度和高度的乘積,數組的元素是疊代計數。 值-1表示Mandelbrot集的成員:

namespace MandelbrotXF
{
    class BitmapInfo
    {
        public BitmapInfo(int pixelWidth, int pixelHeight, int[] iterationCounts)
        {
            PixelWidth = pixelWidth;
            PixelHeight = pixelHeight;
            IterationCounts = iterationCounts;
        }
        public int PixelWidth { private set; get; }
        public int PixelHeight { private set; get; }
        public int[] IterationCounts { private set; get; }
    }
}           

MandelbrotModel類包含單個異步方法。 除了IProgress對象之外,所有參數都是值類型,是以在計算進行過程中不存在任何參數更改的危險:

namespace MandelbrotXF
{
    class MandelbrotModel
    {
        public Task<BitmapInfo> CalculateAsync(Complex Center, 
                                               double width, double height,
                                               int pixelWidth, int pixelHeight, 
                                               int iterations,
                                               IProgress<double> progress, 
                                               CancellationToken cancelToken)
        {
            return Task.Run(() =>
            {
                int[] iterationCounts = new int[pixelWidth * pixelHeight];
                int index = 0;
                for (int row = 0; row < pixelHeight; row++)
                {
                    progress.Report((double)row / pixelHeight);
                    cancelToken.ThrowIfCancellationRequested();
                    double y = Center.Imaginary - height / 2 + row * height / pixelHeight;
                    for (int col = 0; col < pixelWidth; col++)
                    {
                        double x = Center.Real - width / 2 + col * width / pixelWidth;
                        Complex c = new Complex(x, y);
                        if ((c - new Complex(-1, 0)).MagnitudeSquared < 1.0 / 16)
                        {
                            iterationCounts[index++] = -1;
                        }
                       // http://www.reenigne.org/blog/algorithm-for-mandelbrot-cardioid/
                        else if (c.MagnitudeSquared * (8 * c.MagnitudeSquared - 3) < 
                                                                         3.0 / 32 - c.Real)
                        {
                            iterationCounts[index++] = -1;
                        }
                        else
                        {
                            Complex z = 0;
                            int iteration = 0;
                            do
                            {
                                z = z * z + c;
                                iteration++;
                            }
                            while (iteration < iterations && z.MagnitudeSquared < 4);
                            if (iteration == iterations)
                            {
                                iterationCounts[index++] = -1;
                            }
                            else
                            {
                                iterationCounts[index++] = iteration;
                            }
                        }
                    }
                }
                return new BitmapInfo(pixelWidth, pixelHeight, iterationCounts);
            }, cancelToken);
        }
    }
}           

此CalculateAsync方法僅從ViewModel調用。 ViewModel還旨在為XAML檔案提供資料綁定源,并幫助代碼隐藏檔案執行XAML資料綁定無法處理的那些作業。 (繪制十字準線和放大框是該代碼隐藏檔案的工作。)

繼續閱讀