Hi @mc ,
Thanks for reaching out.
I understand that you want to apply a slider-controlled black-and-white effect to your images in a .NET MAUI app, while also being able to switch between multiple images at runtime. Below is a complete working solution using SkiaSharp and embedded resources. This approach allows your images to be displayed in the app with a slider controlling the effect from full color → grayscale → hard black-and-white.
MainPage.xaml
<?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="MauiBWApp.MainPage">
<VerticalStackLayout Padding="20" Spacing="15">
<!-- SKCanvas -->
<skia:SKCanvasView x:Name="CanvasView"
HeightRequest="400"
PaintSurface="CanvasView_PaintSurface" />
<!-- Slider -->
<Slider x:Name="ThresholdSlider"
Minimum="0"
Maximum="1"
Value="0.0"
ValueChanged="ThresholdSlider_ValueChanged" />
<!-- Slider Label -->
<Label x:Name="ThresholdLabel"
HorizontalOptions="Center" />
<!-- Image name label -->
<Label x:Name="ImageNameLabel"
HorizontalOptions="Center"
FontAttributes="Bold"
FontSize="Medium" />
<!-- Buttons -->
<HorizontalStackLayout Spacing="10" HorizontalOptions="Center">
<Button Text="Previous" Clicked="PreviousImageButton_Clicked" />
<Button Text="Next" Clicked="NextImageButton_Clicked" />
</HorizontalStackLayout>
</VerticalStackLayout>
</ContentPage>
MainPage.xaml.cs
using Microsoft.Maui.Controls;
using SkiaSharp;
using SkiaSharp.Views.Maui;
using SkiaSharp.Views.Maui.Controls;
using System.Reflection;
namespace MauiBWApp;
public partial class MainPage : ContentPage
{
private SKBitmap? _originalBitmap;
private float _sliderValue = 0.0f;
private string[] _imageResources = Array.Empty<string>();
private int _currentImageIndex = 0;
public MainPage()
{
InitializeComponent();
ThresholdLabel.Text = $"Slider: {_sliderValue:F2}";
// Detect all embedded PNG images in Resources/Images
var assembly = GetType().Assembly;
_imageResources = assembly.GetManifestResourceNames()
.Where(r => r.EndsWith(".png", StringComparison.OrdinalIgnoreCase) &&
r.Contains("Resources.Images"))
.ToArray();
if (_imageResources.Length > 0)
{
LoadCurrentImage();
}
else
{
System.Diagnostics.Debug.WriteLine("No embedded images found in Resources/Images!");
}
}
private Task LoadBitmapFromResource(string resourceName)
{
try
{
var assembly = GetType().Assembly;
using Stream? stream = assembly.GetManifestResourceStream(resourceName);
if (stream == null)
{
System.Diagnostics.Debug.WriteLine($"STREAM IS NULL - check resource path: {resourceName}");
return Task.CompletedTask;
}
_originalBitmap = SKBitmap.Decode(stream);
CanvasView.InvalidateSurface();
// Show image name
var parts = resourceName.Split('.');
string shortName = parts[parts.Length - 2];
ImageNameLabel.Text = shortName;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error loading image: {ex.Message}");
}
return Task.CompletedTask;
}
private void LoadCurrentImage()
{
if (_imageResources.Length == 0) return;
_ = LoadBitmapFromResource(_imageResources[_currentImageIndex]);
}
private void ThresholdSlider_ValueChanged(object sender, ValueChangedEventArgs e)
{
_sliderValue = (float)e.NewValue;
ThresholdLabel.Text = $"Slider: {_sliderValue:F2}";
CanvasView.InvalidateSurface();
}
private void NextImageButton_Clicked(object sender, EventArgs e)
{
if (_imageResources.Length == 0) return;
_currentImageIndex = (_currentImageIndex + 1) % _imageResources.Length;
LoadCurrentImage();
}
private void PreviousImageButton_Clicked(object sender, EventArgs e)
{
if (_imageResources.Length == 0) return;
_currentImageIndex = (_currentImageIndex - 1 + _imageResources.Length) % _imageResources.Length;
LoadCurrentImage();
}
private void CanvasView_PaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
var canvas = e.Surface.Canvas;
canvas.Clear(SKColors.White);
if (_originalBitmap == null)
return;
using var paint = new SKPaint();
float t = _sliderValue;
float[] matrix;
// Slider effect: 0 → full color, 0.99 → grayscale, 1 → hard black-and-white
if (t <= 0.99f)
{
float alpha = t / 0.99f;
matrix = new float[]
{
1 - alpha + alpha * 0.2126f, alpha * 0.7152f, alpha * 0.0722f, 0, 0,
alpha * 0.2126f, 1 - alpha + alpha * 0.7152f, alpha * 0.0722f, 0, 0,
alpha * 0.2126f, alpha * 0.7152f, 1 - alpha + alpha * 0.0722f, 0, 0,
0,0,0,1,0
};
}
else
{
float alpha = (t - 0.99f) / 0.01f;
float bias = -128 * alpha;
matrix = new float[]
{
0.2126f, 0.7152f, 0.0722f, 0, bias,
0.2126f, 0.7152f, 0.0722f, 0, bias,
0.2126f, 0.7152f, 0.0722f, 0, bias,
0,0,0,1,0
};
}
paint.ColorFilter = SKColorFilter.CreateColorMatrix(matrix);
// Preserve aspect ratio
float canvasWidth = e.Info.Width;
float canvasHeight = e.Info.Height;
float imgWidth = _originalBitmap.Width;
float imgHeight = _originalBitmap.Height;
float scale = Math.Min(canvasWidth / imgWidth, canvasHeight / imgHeight);
float width = imgWidth * scale;
float height = imgHeight * scale;
float x = (canvasWidth - width) / 2;
float y = (canvasHeight - height) / 2;
var destRect = new SKRect(x, y, x + width, y + height);
canvas.DrawBitmap(_originalBitmap, destRect, paint);
}
}
MauiProgram.cs
using Microsoft.Extensions.Logging;
using SkiaSharp.Views.Maui.Controls.Hosting;
namespace MauiBWApp;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseSkiaSharp() // registers SkiaSharp handlers
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
#if DEBUG
builder.Logging.AddDebug();
#endif
return builder.Build();
}
}
Key Notes
- Images as Embedded Resources for SkiaSharp:
- By default, images in
Resources/Imageshave Build Action: MauiImage, which works for standard MAUIImagecontrols but cannot be loaded via SkiaSharp. - To use SkiaSharp, you need to embed them in the assembly. You can do this in your
.csproj:
<ItemGroup>
<EmbeddedResource Include="Resources\Images\**\*.png" />
</ItemGroup>
- If you previously had any
<EmbeddedResource Remove="…"/>entries for these images, comment them out or delete them, otherwise SkiaSharp won’t see the images. - Alternatively, you can place SkiaSharp images in a separate folder (e.g.,
Assets/ImagesForSkia) to avoid touching theResources/Imagesfolder and keep it for normal MAUI images.
- Slider behavior:
-
0→ original full color -
0.99→ grayscale -
1→ hard black-and-white (threshold)
-
- Dynamic image switching:
- Next/Previous buttons cycle through all embedded PNG images.
- SkiaSharp handles rendering:
- The color matrix automatically adjusts the image based on the slider value, giving a smooth transition from color → grayscale → black-and-white.
Hope this helps! If my answer was helpful - kindly follow the instructions here so others with the same problem can benefit as well.