The personal website of Scott W Harden
October 16th, 2021

Status Badges with Maui.Graphics

Status badges are popular decorators on GitHub readme pages and project websites. Badgen.net and shields.io are popular HTTP APIs for dynamically generating SVG status badges. In this article we will use the new Microsoft.Maui.Graphics package to generate status badges from a C# console application. This application can be downloaded: BadgeApp.zip

Badge.cs

The Badge class contains all the logic needed to render and save a badge as a PNG file.

This code demonstrates a few advanced topics which are worth considering:

  • image scaling
  • state management (save/restore)
  • clipping
  • rounded rectangles
  • string measurement
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

public class Badge
{
    readonly string Name;
    readonly string Value;
    readonly SizeF NameSize;
    readonly SizeF ValueSize;

    // Customize these to change the style of the button
    public Color BackgroundLeft = Color.FromArgb("#666");
    public Color BackgroundRight = Color.FromArgb("#08C");
    public Color BackgroundLiner = Colors.White.WithAlpha(.15f);
    public Color FontColor = Colors.White;
    public Color FontShadow = Colors.Black.WithAlpha(.5f);
    public Color OverlayTop = Colors.Black.WithAlpha(0);
    public Color OverlayBottom = Colors.Black.WithAlpha(.25f);

    public Badge(string name, string value)
    {
        Name = name;
        Value = value;
        NameSize = MeasureString(name);
        ValueSize = MeasureString(value);
    }

    public void SavePng(string pngFilePath, float scale = 1)
    {
        float totalWidth = NameSize.Width + ValueSize.Width;
        int imageWidth = (int)totalWidth + 22;
        RectangleF imageRect = new(0, 0, imageWidth, 20);

        int scaledWidth = (int)(imageRect.Width * scale);
        int scaledHeight = (int)(imageRect.Height * scale);
        BitmapExportContext bmp = SkiaGraphicsService.Instance.CreateBitmapExportContext(scaledWidth, scaledHeight);
        ICanvas canvas = bmp.Canvas;
        canvas.Scale(scale, scale);

        // left background
        canvas.FillColor = BackgroundLeft;
        canvas.FillRoundedRectangle(imageRect, 5);

        // right background
        float bg2x = 10 + NameSize.Width;
        canvas.SaveState();
        canvas.ClipRectangle(bg2x, 0, bmp.Width, bmp.Height);
        canvas.FillColor = BackgroundRight;
        canvas.FillRoundedRectangle(imageRect, 5);
        canvas.RestoreState();

        // vertical line
        canvas.StrokeColor = BackgroundLiner;
        canvas.DrawLine(bg2x, 0, bg2x, bmp.Height);

        // background overlay shadow
        var pt = new LinearGradientPaint() { StartColor = OverlayTop, EndColor = OverlayBottom };
        canvas.SetFillPaint(pt, new Point(0, 0), new Point(0, bmp.Height));
        canvas.FillRoundedRectangle(imageRect, 5);

        // draw text backgrounds
        canvas.FontSize = 12;
        float offsetY = 14;
        float offsetX1 = 5;
        float offsetX2 = 15;
        float shadowOffset = 1;

        // text shadow
        canvas.FontColor = FontShadow;
        canvas.DrawString(Name, offsetX1 + shadowOffset, offsetY + shadowOffset, HorizontalAlignment.Left);
        canvas.DrawString(Value, offsetX2 + NameSize.Width + shadowOffset, offsetY + shadowOffset, HorizontalAlignment.Left);

        // text foreground
        canvas.FontColor = FontColor;
        canvas.DrawString(Name, offsetX1, offsetY, HorizontalAlignment.Left);
        canvas.DrawString(Value, offsetX2 + NameSize.Width, offsetY, HorizontalAlignment.Left);

        // save the output
        bmp.WriteToFile(pngFilePath);
    }

    SizeF MeasureString(string text, string fontName = "Arial", float fontSize = 12)
    {
        var fontService = new SkiaFontService("", "");
        using SkiaSharp.SKTypeface typeFace = fontService.GetTypeface(fontName);
        using SkiaSharp.SKPaint paint = new() { Typeface = typeFace, TextSize = fontSize };
        float width = paint.MeasureText(text);
        float height = fontSize;
        return new SizeF(width, height);
    }
}

Program.cs

This simple program is all it takes to render and save a badge.

Badge myBadge = new("Maui", "Graphics");
myBadge.SavePng("demo.png");

Customization

You can reach into the Badge class and customize styles as desired.

Badge myBadge = new("Maui", "Graphics")
{
    BackgroundRight = Microsoft.Maui.Graphics.Color.FromArgb("#3cc51d"),
};
myBadge.SavePng("demo1b.png");

Image Scaling

Microsoft.Maui.Graphics natively supports image scaling. This allows you to create large badges without any loss in quality that would come from creating a small badge and resizing the bitmap.

Badge myBadge = new("Maui", "Graphics");
myBadge.SavePng("demo1.png", scale: 1);
myBadge.SavePng("demo2.png", scale: 2);
myBadge.SavePng("demo5.png", scale: 5);

Resources

Markdown source code last modified on October 17th, 2021
---
Title: Status Badges with Maui.Graphics
Description: How to use Microsoft.Maui.Graphics to render status badges
Date: 2021-10-16 8:40PM EST
Tags: csharp, maui
---

# Status Badges with Maui.Graphics

**Status badges are popular decorators on GitHub readme pages and project websites.** [Badgen.net](https://badgen.net) and [shields.io](https://shields.io) are popular HTTP APIs for dynamically generating SVG status badges. In this article we will use the new `Microsoft.Maui.Graphics` package to generate status badges from a C# console application. This application can be downloaded: [**BadgeApp.zip**](BadgeApp.zip)

<div class="text-center">

![](images/demo1.png)
![](images/demo1b.png)

</div>

## Badge.cs

The `Badge` class contains all the logic needed to render and save a badge as a PNG file. 

This code demonstrates a few advanced topics which are worth considering:
* image scaling
* state management (save/restore)
* clipping
* rounded rectangles
* string measurement

```cs
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

public class Badge
{
    readonly string Name;
    readonly string Value;
    readonly SizeF NameSize;
    readonly SizeF ValueSize;

    // Customize these to change the style of the button
    public Color BackgroundLeft = Color.FromArgb("#666");
    public Color BackgroundRight = Color.FromArgb("#08C");
    public Color BackgroundLiner = Colors.White.WithAlpha(.15f);
    public Color FontColor = Colors.White;
    public Color FontShadow = Colors.Black.WithAlpha(.5f);
    public Color OverlayTop = Colors.Black.WithAlpha(0);
    public Color OverlayBottom = Colors.Black.WithAlpha(.25f);

    public Badge(string name, string value)
    {
        Name = name;
        Value = value;
        NameSize = MeasureString(name);
        ValueSize = MeasureString(value);
    }

    public void SavePng(string pngFilePath, float scale = 1)
    {
        float totalWidth = NameSize.Width + ValueSize.Width;
        int imageWidth = (int)totalWidth + 22;
        RectangleF imageRect = new(0, 0, imageWidth, 20);

        int scaledWidth = (int)(imageRect.Width * scale);
        int scaledHeight = (int)(imageRect.Height * scale);
        BitmapExportContext bmp = SkiaGraphicsService.Instance.CreateBitmapExportContext(scaledWidth, scaledHeight);
        ICanvas canvas = bmp.Canvas;
        canvas.Scale(scale, scale);

        // left background
        canvas.FillColor = BackgroundLeft;
        canvas.FillRoundedRectangle(imageRect, 5);

        // right background
        float bg2x = 10 + NameSize.Width;
        canvas.SaveState();
        canvas.ClipRectangle(bg2x, 0, bmp.Width, bmp.Height);
        canvas.FillColor = BackgroundRight;
        canvas.FillRoundedRectangle(imageRect, 5);
        canvas.RestoreState();

        // vertical line
        canvas.StrokeColor = BackgroundLiner;
        canvas.DrawLine(bg2x, 0, bg2x, bmp.Height);

        // background overlay shadow
        var pt = new LinearGradientPaint() { StartColor = OverlayTop, EndColor = OverlayBottom };
        canvas.SetFillPaint(pt, new Point(0, 0), new Point(0, bmp.Height));
        canvas.FillRoundedRectangle(imageRect, 5);

        // draw text backgrounds
        canvas.FontSize = 12;
        float offsetY = 14;
        float offsetX1 = 5;
        float offsetX2 = 15;
        float shadowOffset = 1;

        // text shadow
        canvas.FontColor = FontShadow;
        canvas.DrawString(Name, offsetX1 + shadowOffset, offsetY + shadowOffset, HorizontalAlignment.Left);
        canvas.DrawString(Value, offsetX2 + NameSize.Width + shadowOffset, offsetY + shadowOffset, HorizontalAlignment.Left);

        // text foreground
        canvas.FontColor = FontColor;
        canvas.DrawString(Name, offsetX1, offsetY, HorizontalAlignment.Left);
        canvas.DrawString(Value, offsetX2 + NameSize.Width, offsetY, HorizontalAlignment.Left);

        // save the output
        bmp.WriteToFile(pngFilePath);
    }

    SizeF MeasureString(string text, string fontName = "Arial", float fontSize = 12)
    {
        var fontService = new SkiaFontService("", "");
        using SkiaSharp.SKTypeface typeFace = fontService.GetTypeface(fontName);
        using SkiaSharp.SKPaint paint = new() { Typeface = typeFace, TextSize = fontSize };
        float width = paint.MeasureText(text);
        float height = fontSize;
        return new SizeF(width, height);
    }
}
```

## Program.cs

This simple program is all it takes to render and save a badge.

```cs
Badge myBadge = new("Maui", "Graphics");
myBadge.SavePng("demo.png");
```

<div class="text-center">

![](images/demo1.png)

</div>

### Customization

You can reach into the `Badge` class and customize styles as desired.

```cs
Badge myBadge = new("Maui", "Graphics")
{
    BackgroundRight = Microsoft.Maui.Graphics.Color.FromArgb("#3cc51d"),
};
myBadge.SavePng("demo1b.png");
```

<div class="text-center">

![](images/demo1b.png)

</div>

### Image Scaling

`Microsoft.Maui.Graphics` natively supports image scaling. This allows you to create large badges without any loss in quality that would come from creating a small badge and resizing the bitmap.

```cs
Badge myBadge = new("Maui", "Graphics");
myBadge.SavePng("demo1.png", scale: 1);
myBadge.SavePng("demo2.png", scale: 2);
myBadge.SavePng("demo5.png", scale: 5);
```

<div class="text-center">

![](images/demo1.png)
![](images/demo2.png)
![](images/demo5.png)

</div>

## Resources

* Download this application: [**BadgeApp.zip**](BadgeApp.zip)

* [How to `MeasureString()` with Maui.Graphics](https://swharden.com/blog/2021-10-16-maui-graphics-measurestring/)

* [Maui.Graphics on GitHub](https://github.com/dotnet/Microsoft.Maui.Graphics)

* [Maui.Graphics Issue #103 - SVG support](https://github.com/dotnet/Microsoft.Maui.Graphics/issues/103)

* [Maui.Graphics on NuGet](https://www.nuget.org/packages?q=Maui.Graphics)

* [Maui.Graphics WPF Quickstart](https://maui.graphics/quickstart/wpf/)

* [Maui.Graphics WinForms Quickstart](https://maui.graphics/quickstart/winforms/)