Generating a dynamic background on .NET MAUI
Introduction
I’m going to show a way to generate a random background for .NET MAUI ContentPage. It uses Skia to handle the heavy lifting and should work with any version of .NET MAUI. I used .NET 10. There is a sample repo with the awe inspiring name, DynamicBackground.
I have an app that has a bunch of controls on the page and I wanted to have something different as the background. I was bored with gradients, so I decided to do some drawing. The actual drawing code was generated by AI, but I did some tweaking here and there.
What we need to do
I used Skia for the drawing. With Skia, I generate a PNG bitmap image using Skia and assign that bitmap to the BackgroundImageSource of the ContentPage. That puts the image at the back of the screen and it’s not tied to any layout control.
We’ll start with the default MAUI template. Create a new MAUI app through the IDE of your choice or with dotnet new maui -n "MyMauiApp" On top of that, I’ll add the drawing code.
Skia
We need to install a Skia NuGet package. I used SkiaSharp.Extended.UI.Maui, that brought enough of Skia to allow the code to work.
install-package SkiaSharp.Extended.UI.Maui
If you are not familair with Skia, it’s an open source 2D graphics library that is written in C++. SkiaSharp is a set of .NET wrappers for the C++ code. It’s platform independent, so this will work on Android, iOS, and Windows.
MAUI stuff
We’ll add the following method. It gets passed a canvas to draw on and struct with the size of the drawing area.
private void DrawAbstractShapes(SKCanvas canvas, SKImageInfo info)
{
// Determine how many layers of blur to add
int numShapes = _random.Next(3, 7); // More shapes = more complexity
for (int i = 0; i < numShapes; i++)
{
// -- 1. Create a random Color for the shape --
// We focus on bright blues, greens, or cyans to stand out from dark background.
// We keep alpha (transparency) low so shapes overlay softly.
byte red = (byte)_random.Next(0, 50); // Keep red low for a blue vibe
byte green = (byte)_random.Next(100, 255);
byte blue = (byte)_random.Next(150, 255);
byte alpha = (byte)_random.Next(30, 80); // 30-80 out of 255 is quite translucent
SKColor shapeColor = new SKColor(red, green, blue, alpha);
// -- 2. Create the blur effect --
// The blur sigma controls how hazy the edge is. High values give that "misty" look.
// We scale it based on the screen size so it looks right on all devices.
float baseBlurSigma = Math.Max(info.Width, info.Height) / 10f; // Scale factor
float blurSigma = (float)_random.NextDouble() * baseBlurSigma + (baseBlurSigma * 0.5f);
// Use an Outer blur to fade the edge of the shape completely
using (SKPaint blurPaint = new SKPaint())
{
blurPaint.IsAntialias = true;
blurPaint.Color = shapeColor;
blurPaint.Style = SKPaintStyle.Fill;
// This is the magic line that creates the softness
blurPaint.MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, blurSigma);
// -- 3. Define the Shape Geometry --
// We draw large, soft, overlapping circles/ovals.
float maxWidth = info.Width * 1.5f; // Can go off screen
float maxHeight = info.Height * 1.5f;
float radiusX = (float)_random.NextDouble() * (maxWidth * 0.6f) + (maxWidth * 0.2f);
float radiusY = (float)_random.NextDouble() * (maxHeight * 0.6f) + (maxHeight * 0.2f);
// Random position, often near the center or edges to overlay
float centerX = (float)_random.NextDouble() * info.Width;
float centerY = (float)_random.NextDouble() * info.Height;
SKRect ovalRect = new SKRect(
centerX - radiusX,
centerY - radiusY,
centerX + radiusX,
centerY + radiusY
);
// -- 4. Draw the blurred shape onto the canvas --
canvas.DrawOval(ovalRect, blurPaint);
}
}
}
The code is pretty well commented. We generate a random number of shapes, with some random colors, and draw them on a canvas with some blurring.
The canvas and image dimensions come from a method named GenerateBackgroundImage(). And it does exactly what it says on the tin.
private ImageSource GenerateBackgroundImage()
{
// 1. Get dimensions (defaulting to 1080x2400 if the page isn't layouted yet)
int width = (int)(this.Width > 0 ? this.Width : 1080);
int height = (int)(this.Height > 0 ? this.Height : 2400);
// 2. Create the info struct (No 'using' here!)
SKImageInfo info = new SKImageInfo(width, height);
// 3. Create an off-screen surface (This DOES need 'using')
using (SKSurface surface = SKSurface.Create(info))
{
SKCanvas canvas = surface.Canvas;
// --- DRAWING LOGIC ---
canvas.Clear(SKColors.Black);
DrawAbstractShapes(canvas, info); // Use the logic from our previous step
// ---------------------
// 4. Snapshot the surface into an image and encode to PNG
using (SKImage image = surface.Snapshot())
using (SKData data = image.Encode(SKEncodedImageFormat.Png, 100))
{
// 5. Convert to a stream for MAUI
var ms = new MemoryStream();
data.SaveTo(ms);
ms.Seek(0, SeekOrigin.Begin);
return ImageSource.FromStream(() => ms);
}
}
}
We create a SKSurface instance to contain our drawing canvas. We create a SKImageInfo object to define the size of the canvas. We call DrawAbstractShapes() to draw a randomized bunch of shapes.
Then we get an SKImage from the surface, encode it as a PNG image in a memorystream, and finally return it as an ImageSource.
To execute it at runtime, we use the OnSizeAllocated() method of the page. That will fire any time the page size changes or the orientation changes.
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
// This does the magic
this.BackgroundImageSource = GenerateBackgroundImage();
}
Seeing it in action.
I ran the app as Windows app and took a couple of screenshots

To change the image, all I did was resize the image. You don’t see a dramatic difference between the image renderings. That’s totally, it’s just supposed to be the background.
Comments