Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SkiaSharp samples #409

Merged
merged 5 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions 8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MandelbrotAnimation", "MandelbrotAnimation\MandelbrotAnimation.csproj", "{AADA2731-0754-4634-944E-1DCC3C1374EB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AADA2731-0754-4634-944E-1DCC3C1374EB}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{AADA2731-0754-4634-944E-1DCC3C1374EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AADA2731-0754-4634-944E-1DCC3C1374EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9E213343-022C-4D68-9714-5D25F8876EC7}
EndGlobalSection
EndGlobal
15 changes: 15 additions & 0 deletions 8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MandelbrotAnimation"
x:Class="MandelbrotAnimation.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

12 changes: 12 additions & 0 deletions 8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace MandelbrotAnimation;

public partial class App : Application
{
public App()
{
InitializeComponent();

MainPage = new AppShell();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="MandelbrotAnimation.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MandelbrotAnimation"
Shell.FlyoutBehavior="Disabled"
Title="MandelbrotAnimation">

<ShellContent
Title="Home"
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />

</Shell>

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace MandelbrotAnimation;

public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace MandelbrotAnimation
{
class BitmapInfo
{
public int PixelWidth { get; private set; }

public int PixelHeight { get; private set; }

public int[] IterationCounts { get; private set; }

public BitmapInfo(int pixelWidth, int pixelHeight, int[] iterationCounts)
{
PixelWidth = pixelWidth;
PixelHeight = pixelHeight;
IterationCounts = iterationCounts;
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Maui.Controls;assembly=SkiaSharp.Views.Maui.Controls"
x:Class="MandelbrotAnimation.MainPage"
Title="Mandelbrot Animation">
<Grid Margin="20"
RowDefinitions="0.1*, 0.1*, 0.8*, 0.1*, 0.1*">
<Label x:Name="statusLabel"
HorizontalTextAlignment="Center" />
<ProgressBar x:Name="progressBar"
Grid.Row="1"/>
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="2"
VerticalOptions="Fill"
PaintSurface="OnCanvasViewPaintSurface" />
<Label x:Name="storageLabel"
Grid.Row="3"
HorizontalTextAlignment="Center" />
<Button x:Name="deleteButton"
Grid.Row="4"
Text="Delete All"
Clicked="OnDeleteButtonClicked" />
</Grid>
</ContentPage>

226 changes: 226 additions & 0 deletions 8.0/SkiaSharp/MandelbrotAnimation/MandelbrotAnimation/MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
using System.Diagnostics;
using System.Numerics;
using SkiaSharp;
using SkiaSharp.Views.Maui;

namespace MandelbrotAnimation;

public partial class MainPage : ContentPage
{
const int COUNT = 10; // The number of bitmaps in the animation. This can go up to 50!
const int BITMAP_SIZE = 1000; // Program uses square bitmaps exclusively

// Uncomment just one of these, or define your own
static readonly Complex center = new Complex(-1.17651152924355, 0.298520986549558);
// static readonly Complex center = new Complex(-0.774693089457127, 0.124226621261617);
// static readonly Complex center = new Complex(-0.556624880053304, 0.634696788141351);

SKBitmap[] bitmaps = new SKBitmap[COUNT]; // array of bitmaps
Stopwatch stopwatch = new Stopwatch(); // for the animation
int bitmapIndex;
double bitmapProgress = 0;

// File path for storing each bitmap in local storage
string FolderPath() => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

string FilePath(int zoomLevel) => Path.Combine(FolderPath(), String.Format("R{0}I{1}Z{2:D2}.png", center.Real, center.Imaginary, zoomLevel));

// Bitmap pixel for Rgba8888 format
uint MakePixel(byte alpha, byte red, byte green, byte blue) => (uint)((alpha << 24) | (blue << 16) | (green << 8) | red);

public MainPage()
{
InitializeComponent();

LoadAndStartAnimation();
}

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();
Dispatcher.StartTimer(TimeSpan.FromMilliseconds(16), OnTimerTick);
}

bool OnTimerTick()
{
int cycle = 6000 * COUNT; // total cycle length in milliseconds

// 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();
}
return true;
}

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;

canvas.Clear();

if (bitmaps[bitmapIndex] != null)
{
// Determine destination rect as square in canvas
int dimension = Math.Min(info.Width, info.Height);
float x = (info.Width - dimension) / 2;
float y = (info.Height - dimension) / 2;
SKRect destRect = new SKRect(x, y, x + dimension, y + dimension);

// Calculate source rectangle based on fraction:
// bitmapProgress == 0: full bitmap
// bitmapProgress == 1: half of length and width of bitmap
float fraction = 0.5f * (1 - (float)Math.Pow(2, -bitmapProgress));
SKBitmap bitmap = bitmaps[bitmapIndex];
int width = bitmap.Width;
int height = bitmap.Height;
SKRect sourceRect = new SKRect(fraction * width, fraction * height,
(1 - fraction) * width, (1 - fraction) * height);

// Display the bitmap
canvas.DrawBitmap(bitmap, sourceRect, destRect);
}
}

void TallyBitmapSizes()
{
long fileSize = 0;

foreach (string filename in Directory.EnumerateFiles(FolderPath()))
{
fileSize += new FileInfo(filename).Length;
}
storageLabel.Text = $"Total storage: {fileSize:N0} bytes";
}

void OnDeleteButtonClicked(object sender, EventArgs args)
{
foreach (string filepath in Directory.EnumerateFiles(FolderPath()))
{
File.Delete(filepath);
}
TallyBitmapSizes();
}
}
Loading
Loading