How to Fix .NET MAUI Returning PNG Headers When Exporting BMP Images
When working with cross-platform image processing in .NET MAUI via Microsoft.Maui.Graphics, developers occasionally encounter a puzzling issue: requesting a BMP image via ImageFormat.Bmp outputs byte data containing a standard PNG header (89 50 4E 47) instead of the expected Bitmap file signature (42 4D / BM).
In this guide, we'll look at why this happens and explore modern, reliable approaches to generate authentic BMP files in your .NET MAUI applications.
Why Does .NET MAUI Output PNG Instead of BMP?
Microsoft.Maui.Graphics relies on native platform encoders behind the scenes. On platforms like Android, the native Android.Graphics.Bitmap.CompressFormat API supports only a limited set of compression formats (such as PNG, JPEG, and WEBP).
When you request an unsupported or unimplemented format like ImageFormat.Bmp via image.AsBytes() or ToPlatformImage().AsBytes(), the underlying platform implementation silently falls back to its default lossless format: PNG. Because no exception is thrown, the output byte array ends up with valid PNG magic numbers instead of BMP headers.
Solution 1: Use SixLabors.ImageSharp (Recommended)
The cleanest and most cross-platform way to handle specialized image encoding (like BMP, TIFF, or ICO) in .NET MAUI is to use a pure C# image library such as SixLabors.ImageSharp. It doesn't rely on Android or iOS native encoders, guaranteeing true BMP output.
1. Install the NuGet Package
dotnet add package SixLabors.ImageSharp
2. Load, Resize, and Save as BMP
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Bmp;
public async Task<byte[]> ConvertToBmpAsync(FileResult fileResult)
{
using var inputStream = await fileResult.OpenReadAsync();
using var image = await Image.LoadAsync(inputStream);
// Resize while maintaining aspect ratio or custom dimensions
image.Mutate(x => x.Resize(320, 480));
using var outputStream = new MemoryStream();
// Explicitly encode to BMP with your preferred bits-per-pixel configuration
var encoder = new BmpEncoder
{
BitsPerPixel = BmpBitsPerPixel.Pixel24
};
await image.SaveAsBmpAsync(outputStream, encoder);
return outputStream.ToArray();
}
Solution 2: Use SkiaSharp
If your project already uses SkiaSharp.Views.Maui, you can leverage SkiaSharp to encode pixel buffers or manipulate raw bitmap data.
1. Install SkiaSharp
dotnet add package SkiaSharp
2. Convert and Encode to BMP
using SkiaSharp;
public async Task<byte[]> ConvertWithSkiaSharpAsync(Stream imageStream)
{
using var skBitmap = SKBitmap.Decode(imageStream);
// Create resized bitmap
var info = new SKImageInfo(320, 480);
using var resized = skBitmap.Resize(info, SKFilterQuality.High);
using var skImage = SKImage.FromBitmap(resized);
// Note: SkiaSharp supports SKEncodedImageFormat.Bmp directly on supported runtimes
using var data = skImage.Encode(SKEncodedImageFormat.Bmp, 100);
return data.ToArray();
}
Solution 3: Lightweight Pure C# BMP Header Writer
If you prefer zero external dependencies and have access to raw RGBA pixel arrays from Microsoft.Maui.Graphics, you can prepend standard 54-byte BMP headers manually:
public static byte[] CreateBmp24(byte[] rgbaPixels, int width, int height)
{
int rowPadding = (4 - (width * 3) % 4) % 4;
int imageSize = (width * 3 + rowPadding) * height;
int fileSize = 54 + imageSize;
byte[] bmp = new byte[fileSize];
using var ms = new MemoryStream(bmp);
using var bw = new BinaryWriter(ms);
// BMP Header
bw.Write((ushort)0x4D42); // "BM"
bw.Write(fileSize); // File size in bytes
bw.Write((ushort)0); // Reserved
bw.Write((ushort)0); // Reserved
bw.Write(54); // Pixel data offset
// DIB Header (BITMAPINFOHEADER)
bw.Write(40); // Header size
bw.Write(width); // Image width
bw.Write(height); // Image height (positive = bottom-up)
bw.Write((ushort)1); // Planes
bw.Write((ushort)24); // Bits per pixel (RGB 24-bit)
bw.Write(0); // Compression (0 = None)
bw.Write(imageSize); // Image data size
bw.Write(0); // X pixels per meter
bw.Write(0); // Y pixels per meter
bw.Write(0); // Colors in color table
bw.Write(0); // Important color count
// Write Pixel Data (Bottom-Up, BGR format)
for (int y = height - 1; y >= 0; y--)
{
for (int x = 0; x < width; x++)
{
int index = (y * width + x) * 4;
bw.Write(rgbaPixels[index + 2]); // Blue
bw.Write(rgbaPixels[index + 1]); // Green
bw.Write(rgbaPixels[index + 0]); // Red
}
for (int p = 0; p < rowPadding; p++)
bw.Write((byte)0);
}
return bmp;
}
Summary
- Cause:
Microsoft.Maui.Graphicsplatform implementations silently fall back to PNG when target formats like BMP are unsupported by the host operating system's native encoder. - Fix: Use a cross-platform encoding library like SixLabors.ImageSharp or SkiaSharp to ensure strict format compliance across Android, iOS, and Windows.