async void LoadAndStartAnimation() { // Show total bitmap storage TallyBitmapSizes(); // Create progressReporter for async operation Progress <double> progressReporter = new Progress <double>((double progress) => progressBar.Progress = progress); // Create (unused) CancellationTokenSource for async operation CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); // Loop through all the zoom levels for (int zoomLevel = 0; zoomLevel < COUNT; zoomLevel++) { // If the file exists, load it if (File.Exists(FilePath(zoomLevel))) { statusLabel.Text = $"Loading bitmap for zoom level {zoomLevel}"; using (Stream stream = File.OpenRead(FilePath(zoomLevel))) { bitmaps[zoomLevel] = SKBitmap.Decode(stream); } } // Otherwise, create a new bitmap else { statusLabel.Text = $"Creating bitmap for zoom level {zoomLevel}"; CancellationToken cancelToken = cancelTokenSource.Token; // Do the (generally lengthy) Mandelbrot calculation BitmapInfo bitmapInfo = await Mandelbrot.CalculateAsync(center, 4 / Math.Pow(2, zoomLevel), 4 / Math.Pow(2, zoomLevel), BITMAP_SIZE, BITMAP_SIZE, (int)Math.Pow(2, 10), progressReporter, cancelToken); // Create bitmap & get pointer to the pixel bits SKBitmap bitmap = new SKBitmap(BITMAP_SIZE, BITMAP_SIZE, SKColorType.Rgba8888, SKAlphaType.Opaque); IntPtr basePtr = bitmap.GetPixels(); // Set pixel bits to color based on iteration count for (int row = 0; row < bitmap.Width; row++) { for (int col = 0; col < bitmap.Height; col++) { int iterationCount = bitmapInfo.IterationCounts[row * bitmap.Width + col]; uint pixel = 0xFF000000; // black if (iterationCount != -1) { double proportion = (iterationCount / 32.0) % 1; byte red = 0, green = 0, blue = 0; if (proportion < 0.5) { red = (byte)(255 * (1 - 2 * proportion)); blue = (byte)(255 * 2 * proportion); } else { proportion = 2 * (proportion - 0.5); green = (byte)(255 * proportion); blue = (byte)(255 * (1 - proportion)); } pixel = MakePixel(0xFF, red, green, blue); } // Calculate pointer to pixel IntPtr pixelPtr = basePtr + 4 * (row * bitmap.Width + col); unsafe // requires compiling with unsafe flag { *(uint *)pixelPtr.ToPointer() = pixel; } } } // Save as PNG file SKData data = SKImage.FromBitmap(bitmap).Encode(); try { File.WriteAllBytes(FilePath(zoomLevel), data.ToArray()); } catch { // Probably out of space, but just ignore } // Store in array bitmaps[zoomLevel] = bitmap; // Show new bitmap sizes TallyBitmapSizes(); } // Display the bitmap bitmapIndex = zoomLevel; canvasView.InvalidateSurface(); } // Now do the animation stopwatch.Start(); int cycle = 6000 * COUNT; // total cycle length in milliseconds while (true) { // Time in milliseconds from 0 to cycle int time = (int)(stopwatch.ElapsedMilliseconds % cycle); // Make it sinusoidal, including bitmap index and gradation between bitmaps double progress = COUNT * 0.5 * (1 + Math.Sin(2 * Math.PI * time / cycle - Math.PI / 2)); // These are the field values that the PaintSurface handler uses bitmapIndex = (int)progress; bitmapProgress = progress - bitmapIndex; // It doesn't often happen that we get up to COUNT, but an exception would be raised if (bitmapIndex < COUNT) { // Show progress in UI statusLabel.Text = $"Displaying bitmap for zoom level {bitmapIndex}"; progressBar.Progress = bitmapProgress; // Update the canvas canvasView.InvalidateSurface(); } // Wait 16 milliseconds await Task.Delay(16); } }
async void LoadAndStartAnimation() { // Show total bitmap storage TallyBitmapSizes(); // Create progressReporter for async operation Progress <double> progressReporter = new Progress <double>((double progress) => progressBar.Progress = progress); // Create (unused) CancellationTokenSource for async operation CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); // Loop through all the zoom levels for (int zoomLevel = 0; zoomLevel < COUNT; zoomLevel++) { // If the file exists, load it if (File.Exists(FilePath(zoomLevel))) { statusLabel.Text = $"Loading bitmap for zoom level {zoomLevel}"; using (Stream stream = File.OpenRead(FilePath(zoomLevel))) { bitmaps[zoomLevel] = SKBitmap.Decode(stream); } } // Otherwise, create a new bitmap else { statusLabel.Text = $"Creating bitmap for zoom level {zoomLevel}"; CancellationToken cancelToken = cancelTokenSource.Token; // Do the (generally lengthy) Mandelbrot calculation BitmapInfo bitmapInfo = await Mandelbrot.CalculateAsync(center, 4 / Math.Pow(2, zoomLevel), 4 / Math.Pow(2, zoomLevel), BITMAP_SIZE, BITMAP_SIZE, (int)Math.Pow(2, 10), progressReporter, cancelToken); // Create bitmap & get pointer to the pixel bits SKBitmap bitmap = new SKBitmap(BITMAP_SIZE, BITMAP_SIZE, SKColorType.Rgba8888, SKAlphaType.Opaque); IntPtr basePtr = bitmap.GetPixels(); // Set pixel bits to color based on iteration count for (int row = 0; row < bitmap.Width; row++) { for (int col = 0; col < bitmap.Height; col++) { int iterationCount = bitmapInfo.IterationCounts[row * bitmap.Width + col]; uint pixel = 0xFF000000; // black if (iterationCount != -1) { double proportion = (iterationCount / 32.0) % 1; byte red = 0, green = 0, blue = 0; if (proportion < 0.5) { red = (byte)(255 * (1 - 2 * proportion)); blue = (byte)(255 * 2 * proportion); } else { proportion = 2 * (proportion - 0.5); green = (byte)(255 * proportion); blue = (byte)(255 * (1 - proportion)); } pixel = MakePixel(0xFF, red, green, blue); } // Calculate pointer to pixel IntPtr pixelPtr = basePtr + 4 * (row * bitmap.Width + col); unsafe // requires compiling with unsafe flag { *(uint *)pixelPtr.ToPointer() = pixel; } } } // Save as PNG file SKData data = SKImage.FromBitmap(bitmap).Encode(); try { File.WriteAllBytes(FilePath(zoomLevel), data.ToArray()); } catch { // Probably out of space, but just ignore } // Store in array bitmaps[zoomLevel] = bitmap; // Show new bitmap sizes TallyBitmapSizes(); } // Display the bitmap bitmapIndex = zoomLevel; canvasView.InvalidateSurface(); } // Now start the animation stopwatch.Start(); Device.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick); }