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.Graphics platform 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.