Panel For Example Panel For Example Panel For Example
Grayscale Analysis Algorithm in .NET MAUI

Grayscale Analysis Algorithm in .NET MAUI

Author : Adrian September 02, 2025

This article describes an algorithm for analyzing the grayscale values of stripes on a test strip. While grayscale analysis is a standard task in image processing, often handled with libraries like OpenCV and Python, this project required a custom implementation for a mobile application.

The solution uses .NET MAUI and SkiaSharp to perform binarization, grayscaling, connected-component analysis, and grayscale value calculation.

 

1. Project Environment

The project is built on the .NET 8.0 framework using Visual Studio 2022, with code compatible across iOS, Android, and PC platforms. Testing was performed on Android (minimum version 5.0) and PC.

 

2. UI Interface

The UI is defined in a XAML file and includes two Skia canvases: one to display the original image and another for the grayscale value trend graph. A button triggers the image processing, and a label is used to display debugging information. This UI is a demonstration version and can be further optimized.

 

3. Basic Image Processing

The core image processing includes steps like Gaussian/median filtering, grayscaling, and binarization. These algorithms were implemented manually.

internal static class ImageProcess { public static SKBitmap Binarize(SKBitmap source, byte threshold = 128) { if (source == null) throw new ArgumentNullException(nameof(source)); int width = source.Width; int height = source.Height; var result = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul); var srcPixmap = source.PeekPixels(); var dstPixmap = result.PeekPixels(); IntPtr srcPtr = srcPixmap.GetPixels(); IntPtr dstPtr = dstPixmap.GetPixels(); int bytesPerPixel = 4; int stride = srcPixmap.RowBytes; unsafe { byte* src = (byte*)srcPtr; byte* dst = (byte*)dstPtr; for (int y = 0; y < height; y++) { byte* srcRow = src + y * stride; byte* dstRow = dst + y * stride; for (int x = 0; x < width; x++) { byte b = srcRow[x * 4 + 0]; byte g = srcRow[x * 4 + 1]; byte r = srcRow[x * 4 + 2]; // Grayscale calculation byte gray = (byte)(0.299 * r + 0.587 * g + 0.114 * b); byte bw = (gray >= threshold) ? (byte)255 : (byte)0; dstRow[x * 4 + 0] = bw; // B dstRow[x * 4 + 1] = bw; // G dstRow[x * 4 + 2] = bw; // R dstRow[x * 4 + 3] = 255; // A } } } return result; } }

The grayscaling algorithm converts each pixel's RGBA channels into a single grayscale value using a weighted average. The image is then binarized by comparing this value against a set threshold. C# allows for direct pointer manipulation, which significantly improves processing efficiency.

Since the target is a single large region, a breadth-first search algorithm is used to identify the largest connected component and mark its position and dimensions.

public static SKRectI FindLargestWhiteRegion(SKBitmap binary) { int width = binary.Width; int height = binary.Height; bool[,] visited = new bool[width, height]; List<(int x, int y)> largestRegion = null; int largestSize = 0; SKPixmap pixmap = binary.PeekPixels(); IntPtr ptr = pixmap.GetPixels(); int stride = pixmap.RowBytes; unsafe { byte* basePtr = (byte*)ptr; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (visited[x, y]) continue; byte* pixel = basePtr + y * stride + x * 4; byte r = pixel[2]; if (r < 128) continue; var region = new List<(int x, int y)>(); Queue<(int x, int y)> queue = new(); queue.Enqueue((x, y)); visited[x, y] = true; while (queue.Count > 0) { var (cx, cy) = queue.Dequeue(); region.Add((cx, cy)); foreach (var (nx, ny) in Neighbors(cx, cy, width, height)) { if (!visited[nx, ny]) { byte* p = basePtr + ny * stride + nx * 4; byte nr = p[2]; if (nr >= 128) { visited[nx, ny] = true; queue.Enqueue((nx, ny)); } } } } if (region.Count > largestSize) { largestRegion = region; largestSize = region.Count; } } } } if (largestRegion == null || largestRegion.Count == 0) return SKRectI.Empty; int minX = largestRegion.Min(p => p.x); int maxX = largestRegion.Max(p => p.x); int minY = largestRegion.Min(p => p.y); int maxY = largestRegion.Max(p => p.y); return new SKRectI(minX, minY, maxX + 1, maxY + 1); }

 

4. Grayscale Waveform

Once the target region is identified, the average grayscale value of each row or column within that region is analyzed. This process generates a waveform plot that represents the grayscale trend.