第二十章:异步和文件I/O.(二十)

一个MVVM Mandelbrot
Xamarin.Forms - 以这种方式为像素着色。该程序还允许放大特定位置。这是Mandelbrot集的一个特征,无论你放大多远,图像仍然很有趣。不幸的是,基于doubleprecision浮点数的分辨率,缩放存在实际限制。
该程序使用MVVM原理进行架构,虽然在看到有些奇怪的用户界面以及ViewModel如何处理该用户界面后,您可能会质疑该决策的智慧。
MandelbrotXF的奇怪用户界面源于决定避免任何特定于平台的代码。在最初编写此程序时,Xamarin.Forms不支持拖动和捏合等触摸操作,这些操作可能有助于放大到特定位置。相反,程序的整个用户界面使用两个Slider元素,两个Stepper元素,两个Button元素,一个ProgessBar和使用BoxView实现的视觉效果来实现。
当您第一次运行该程序时,您将看到以下内容:
2018_12_30_205722
白色十字准线 - 没有出现在空白的iOS和Windows 10移动屏幕的白色背景 - 在10秒的时间内淡出,这样它们就不会模糊你很快就会欣赏的漂亮照片, 但你可以通过操纵滑块或步进器来将它们带回来。
但是你要做的第一件事就是按下Go按钮。 该按钮被取消按钮替换,ProgressBar指示进度。 当它完成后,你会看到一个彩色的Mandelbrot集:
2018_12_30_205945
它完成得很快,因为最大迭代次数(由底部步进器标记的循环表示)仅为2到第三次幂,或8.因此,黑色Mandelbrot集的轮廓不像早期程序那样尖锐。 与更高的最大迭代计数相比,更多的点被标记为集合的成员。 您可以将迭代计数增加2的幂。这是一个更清晰的图像,最大迭代次数为64:
2018_12_30_210108
两个滑块视图允许您选择一个新的中心,该中心在滑块正下方显示为复数。 第一个步进元素(标记为缩放)允许您选择放大系数,也是2的幂。当您操纵这三个元素时,您将看到一个带十字准线的方框
由六个薄BoxView元素构成。 该框标记下次按“执行”按钮时将放大的区域:
2018_12_30_210223
现在再次按下Go按钮并等待。 现在,以前装箱的区域填充了位图:
2018_12_30_210332
计算新图像后,十字准线将被重新定位,您可以重新定位中心并再次放大,然后再次放大。
2018_12_30_210429
但是,通常放大的越多,查看所有细节所需的最大迭代次数就越多。 对于每个设备,前面屏幕截图中的图像可以获得四倍于迭代的明显更多细节:
2018_12_30_210534
这是Mandelbrot套装的一个特点,你可以随心所欲地放大,你仍然可以看到同样多的细节。 但是,通常你需要不断增加最大迭代次数,当你达到放大系数2到48次左右时,你已经达到了涉及双精度浮动分辨率的上限 点数。 相邻像素不再与不同的复数相关联,并且图像开始看起来像块状:
2018_12_30_210643
这不是超越的容易障碍。 存在可变精度浮点数的实现,但由于它们不是由计算机的数学协处理器直接处理,涉及这些数字的计算必然比浮点数或双精度类型慢得多,并且您可能不希望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数据绑定无法处理的那些作业。 (绘制十字准线和放大框是该代码隐藏文件的工作。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值