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