The personal website of Scott W Harden
May 25th, 2022

Use Maui.Graphics to Draw 2D Graphics in Any .NET Application

This week Microsoft officially released .NET Maui and the new Microsoft.Maui.Graphics library which can draw 2D graphics in any .NET application (not just Maui apps). This page offers a quick look at how to use this new library to draw graphics using SkiaSharp in a .NET 6 console application. The C# Data Visualization site has additional examples for drawing and animating graphics using Microsoft.Maui.Graphics in Windows Forms and WPF applications.

The code below is a full .NET 6 console application demonstrating common graphics tasks (setting colors, drawing shapes, rendering text, etc.) and was used to generate the image above.

// These packages are available on NuGet
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

// Create a bitmap in memory and draw on its Canvas
SkiaBitmapExportContext bmp = new(600, 400, 1.0f);
ICanvas canvas = bmp.Canvas;

// Draw a big blue rectangle with a dark border
Rect backgroundRectangle = new(0, 0, bmp.Width, bmp.Height);
canvas.FillColor = Color.FromArgb("#003366");
canvas.FillRectangle(backgroundRectangle);
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 20;
canvas.DrawRectangle(backgroundRectangle);

// Draw circles randomly around the image
for (int i = 0; i < 100; i++)
{
    float x = Random.Shared.Next(bmp.Width);
    float y = Random.Shared.Next(bmp.Height);
    float r = Random.Shared.Next(5, 50);

    Color randomColor = Color.FromRgb(
        red: Random.Shared.Next(255),
        green: Random.Shared.Next(255),
        blue: Random.Shared.Next(255));

    canvas.StrokeSize = r / 3;
    canvas.StrokeColor = randomColor.WithAlpha(.3f);
    canvas.DrawCircle(x, y, r);
}

// Measure a string
string myText = "Hello, Maui.Graphics!";
Font myFont = new Font("Impact");
float myFontSize = 48;
canvas.Font = myFont;
SizeF textSize = canvas.GetStringSize(myText, myFont, myFontSize);

// Draw a rectangle to hold the string
Point point = new(
    x: (bmp.Width - textSize.Width) / 2,
    y: (bmp.Height - textSize.Height) / 2);
Rect myTextRectangle = new(point, textSize);
canvas.FillColor = Colors.Black.WithAlpha(.5f);
canvas.FillRectangle(myTextRectangle);
canvas.StrokeSize = 2;
canvas.StrokeColor = Colors.Yellow;
canvas.DrawRectangle(myTextRectangle);

// Daw the string itself
canvas.FontSize = myFontSize * .9f; // smaller than the rectangle
canvas.FontColor = Colors.White;
canvas.DrawString(myText, myTextRectangle, 
    HorizontalAlignment.Center, VerticalAlignment.Center, TextFlow.OverflowBounds);

// Save the image as a PNG file
bmp.WriteToFile("console2.png");

Multi-Platform Graphics Abstraction

The Microsoft.Maui.Graphics namespace a small collection of interfaces which can be implemented by many different rendering technologies (SkiaSharp, SharpDX, GDI, etc.), making it possible to create drawing routines that are totally abstracted from the underlying graphics rendering system.

I really like that I can now create a .NET Standard 2.0 project that exclusively uses interfaces from Microsoft.Maui.Graphics to write code that draws complex graphics, then reference that code from other projects that use platform-specific graphics libraries to render the images.

When I write scientific simulations or data visualization code I frequently regard my graphics drawing routines as business logic, and drawing with Maui.Graphics lets me write this code to an abstraction that keeps rendering technology dependencies out of my business logic - a big win!

Rough Edges

After working with this library while it was being developed over the last few months, these are the things I find most limiting in my personal projects which made it through the initial release this week. Some of them are open issues so they may get fixed soon, and depending on how the project continues to evolve many of these rough edges may improve with time. I'm listing them here now so I can keep track of them, and I intend to update this list if/as these topics improve:

  • Strings cannot be accurately measured: The size returned by GetStringSize() is inaccurate and does not respect font. There's an issue tracking this (#279), but it's been open for more than three months and the library was released this week in its broken state.

    • EDIT: I concede multi-platform font support is a very hard problem, but this exactly the type of problem that .NET Maui was created to solve.

  • Missing XML documentation: Intellisense can really help people who are new to a library. The roll-out of a whole new application framework is a good example of a time when a lot of people will be exploring a new library. Let's take the Color class for example (which 100% of people will interact with) and consider misunderstandings that could be prevented by XML documentation and intellisense: If new Color() accepts 3 floats, should they be 0-255 or 0-1? I need to make a color from the RGB web value #003366, why does Color.FromHex() tell me to use FromArgb? Web colors are RGBA, should I use FromRrgba()? But wait, that string is RGB, not ARGB or RGBA, so will it throw an exception? What does Color.Parse() do?

    • Edit 1: Some of these answers are documented in source code, but they are not XML docs, so this information is not available to library users.

    • Edit 2: Is it on the open-source community to contribute XML documentation? If so, fair enough, but it is a very extensive effort (to write and to review), so a call should be put out for this job to ensure someone doesn't go through all the effort then have their open PR sit unmerged for months while it falls out of sync with the main branch.

  • The library has signs of being incomplete: There remain a good number of NotImplementedException and // todo in sections of the code base that indicate additional work is still required.

Again, I'm pointing these things out the very first week .NET Maui was released, so there's plenty of time and opportunity for improvements in the coming weeks and months.

I'm optimistic this library will continue to improve, and I am very excited to watch it progress! I'm not aware of the internal pressures and constraints that led to the library being released like it was this week, but I want to end by complimenting the team on their great job so far and encourage everyone (at Microsoft and in the open-source community at large) to keep moving this library forward. The .NET Maui team undertook an ambitious challenge by setting-out to implement cross-platform graphics support, but achieving this goal elegantly will be a huge accomplishment for the .NET community!

Resources

Markdown source code last modified on May 26th, 2022
---
title: Use Maui.Graphics to Draw 2D Graphics in Any .NET Application
description: How to use Microsoft.Maui.Graphics to draw graphics in a .NET console application and save the output as an image file using SkiaSharp
date: 2022-05-25 22:13:00
tags: csharp, graphics, maui
---

# Use Maui.Graphics to Draw 2D Graphics in Any .NET Application

**This week Microsoft officially released .NET Maui and the new `Microsoft.Maui.Graphics` library which can draw 2D graphics in any .NET application (not just Maui apps).** This page offers a quick look at how to use this new library to draw graphics using SkiaSharp in a .NET 6 console application. The [C# Data Visualization](https://swharden.com/csdv/) site has additional examples for drawing and animating graphics using `Microsoft.Maui.Graphics` in Windows Forms and WPF applications.

<img src="maui-graphics-quickstart.png" class="mx-auto my-5 d-block shadow">

The code below is a full .NET 6 console application demonstrating common graphics tasks (setting colors, drawing shapes, rendering text, etc.) and was used to generate the image above.

```cs
// These packages are available on NuGet
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

// Create a bitmap in memory and draw on its Canvas
SkiaBitmapExportContext bmp = new(600, 400, 1.0f);
ICanvas canvas = bmp.Canvas;

// Draw a big blue rectangle with a dark border
Rect backgroundRectangle = new(0, 0, bmp.Width, bmp.Height);
canvas.FillColor = Color.FromArgb("#003366");
canvas.FillRectangle(backgroundRectangle);
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 20;
canvas.DrawRectangle(backgroundRectangle);

// Draw circles randomly around the image
for (int i = 0; i < 100; i++)
{
    float x = Random.Shared.Next(bmp.Width);
    float y = Random.Shared.Next(bmp.Height);
    float r = Random.Shared.Next(5, 50);

    Color randomColor = Color.FromRgb(
        red: Random.Shared.Next(255),
        green: Random.Shared.Next(255),
        blue: Random.Shared.Next(255));

    canvas.StrokeSize = r / 3;
    canvas.StrokeColor = randomColor.WithAlpha(.3f);
    canvas.DrawCircle(x, y, r);
}

// Measure a string
string myText = "Hello, Maui.Graphics!";
Font myFont = new Font("Impact");
float myFontSize = 48;
canvas.Font = myFont;
SizeF textSize = canvas.GetStringSize(myText, myFont, myFontSize);

// Draw a rectangle to hold the string
Point point = new(
    x: (bmp.Width - textSize.Width) / 2,
    y: (bmp.Height - textSize.Height) / 2);
Rect myTextRectangle = new(point, textSize);
canvas.FillColor = Colors.Black.WithAlpha(.5f);
canvas.FillRectangle(myTextRectangle);
canvas.StrokeSize = 2;
canvas.StrokeColor = Colors.Yellow;
canvas.DrawRectangle(myTextRectangle);

// Daw the string itself
canvas.FontSize = myFontSize * .9f; // smaller than the rectangle
canvas.FontColor = Colors.White;
canvas.DrawString(myText, myTextRectangle, 
    HorizontalAlignment.Center, VerticalAlignment.Center, TextFlow.OverflowBounds);

// Save the image as a PNG file
bmp.WriteToFile("console2.png");
```

## Multi-Platform Graphics Abstraction

**The `Microsoft.Maui.Graphics` namespace a small collection of interfaces which can be implemented by many different rendering technologies** (SkiaSharp, SharpDX, GDI, etc.), making it possible to create drawing routines that are totally abstracted from the underlying graphics rendering system.

I really like that I can now create a .NET Standard 2.0 project that exclusively uses interfaces from `Microsoft.Maui.Graphics` to write code that draws complex graphics, then reference that code from other projects that use platform-specific graphics libraries to render the images.

When I write scientific simulations or data visualization code I frequently regard my graphics drawing routines as business logic, and drawing with Maui.Graphics lets me write this code to an abstraction that keeps rendering technology dependencies out of my business logic - a big win!

## Rough Edges

After working with this library while it was being developed over the last few months, these are the things I find most limiting in my personal projects which made it through the initial release this week. Some of them are [open issues](https://github.com/dotnet/Microsoft.Maui.Graphics/issues) so they may get fixed soon, and depending on how the project continues to evolve many of these rough edges may improve with time. I'm listing them here now so I can keep track of them, and I intend to update this list if/as these topics improve:

<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
  </symbol>
  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
  </symbol>
  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
  </symbol>
</svg>

<div class="alert alert-primary d-flex align-items-center" role="alert">
  <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
  <div>
    <strong>Note:</strong> This section was last reviewed on April 25, 2022 and improvements may have been made since this text was written.
  </div>
</div>

* **Strings cannot be accurately measured:** The size returned by `GetStringSize()` is inaccurate and does not respect font. There's an issue tracking this ([#279](https://github.com/dotnet/Microsoft.Maui.Graphics/issues/279)), but it's been open for more than three months and the library was released this week in its broken state.

  * EDIT: I concede multi-platform font support is a very hard problem, but this exactly the type of problem that .NET Maui was created to solve.<br><br>

* **Missing XML documentation:** Intellisense can really help people who are new to a library. The roll-out of a whole new application framework is a good example of a time when a lot of people will be exploring a new library. Let's take the [`Color` class](https://github.com/dotnet/Microsoft.Maui.Graphics/blob/main/src/Microsoft.Maui.Graphics/Color.cs) for example (which 100% of people will interact with) and consider misunderstandings that could be prevented by XML documentation and intellisense: If `new Color()` accepts 3 floats, should they be 0-255 or 0-1? I need to make a color from the RGB web value `#003366`, why does `Color.FromHex()` tell me to use `FromArgb`? Web colors are RGBA, should I use `FromRrgba()`? But wait, that string is RGB, not ARGB or RGBA, so will it throw an exception? What does `Color.Parse()` do?

  * Edit 1: Some of these answers are [documented in source code](https://github.com/dotnet/Microsoft.Maui.Graphics/blob/e15f2d552d851c28771e7fe092895e908395f8a4/src/Microsoft.Maui.Graphics/Color.cs#L574-L590), but they are not XML docs, so this information is not available to library users.

  * Edit 2: Is it on the open-source community to contribute XML documentation? If so, fair enough, but it is a very extensive effort (to write _and_ to review), so a call should be put out for this job to ensure someone doesn't go through all the effort then have their open PR sit unmerged for months while it falls out of sync with the main branch.

* **The library has signs of being incomplete:** There remain a good number of [NotImplementedException](https://github.com/dotnet/Microsoft.Maui.Graphics/search?q=NotImplementedException) and [// todo](https://github.com/dotnet/Microsoft.Maui.Graphics/search?q=todo) in sections of the code base that indicate additional work is still required.

Again, I'm pointing these things out the very first week .NET Maui was released, so there's plenty of time and opportunity for improvements in the coming weeks and months.

**I'm optimistic this library will continue to improve, and I am very excited to watch it progress!** I'm not aware of the internal pressures and constraints that led to the library being released like it was this week, but I want to end by complimenting the team on their great job so far and encourage everyone (at Microsoft and in the open-source community at large) to keep moving this library forward. The .NET Maui team undertook an ambitious challenge by setting-out to implement cross-platform graphics support, but achieving this goal elegantly will be a huge accomplishment for the .NET community!

## Resources
* [Source code for this project](https://github.com/swharden/Csharp-Data-Visualization/tree/main/projects/maui-graphics)
* [Maui.Graphics WinForms Quickstart](https://swharden.com/csdv/maui.graphics/quickstart-winforms/)
* [Maui.Graphics WPF Quickstart](https://swharden.com/csdv/maui.graphics/quickstart-wpf/)
* [Maui.Graphics Console Quickstart](https://swharden.com/csdv/maui.graphics/quickstart-console/)
* [Maui.Graphics .NET Maui Quickstart](https://swharden.com/csdv/maui.graphics/quickstart-maui/)
* [https://maui.graphics](https://maui.graphics)
October 17th, 2021

NDepend Status Badges

Many project websites and readmes have status badges that display build status, project details, or code metrics. badgen.net and shields.io are popular services for dynamically generating status badges as SVG files using HTTP requests. This article demonstrates how I use C# and Microsoft.Maui.Graphics to build status badges from NDepend static analysis reports. Source code for this project is available on GitHub.

NDepend Trend Data XML

NDepend can analyze a code base at different points in time and display code metric trends. See NDepend: Trend Monitoring for a full description. These metrics are stored in an XML file available in the HTML build folder.

Metric Index

The XML file contains many Root/MetricIndex/Metric elements that describe each metric and its units. This can be parsed to obtain the Name and Unit for each metric.

<Root>
  <MetricIndex>
    <Metric Name="# New Issues since Baseline" Unit="issues" />
    <Metric Name="# Issues Fixed since Baseline" Unit="issues" />
    <Metric Name="# Issues Worsened since Baseline" Unit="issues" />
    <Metric Name="# Issues with severity Blocker" Unit="issues" />
    <Metric Name="# Issues with severity Critical" Unit="issues" />
    <Metric Name="# Issues with severity High" Unit="issues" />
    <Metric Name="# Issues with severity Medium" Unit="issues" />
    ...
  </MetricIndex>
</Root>

Metrics by DateTime

The XML file contains multiple Root/M/R elements that contain the value of each metric at a distinct time point. Numerical metrics have been converted to strings separated by the | character. Metric values for each time point are in the same order as the metric index.

<Root>
  <M>
    <R D="10/16/2021 11:58:04 AM" V="0|0|0|0|1|598|2177|...|19|133" />
    <R D="10/03/2021 04:15:24 PM" V="0|0|0|0|1|593|2160|...|19|132" />
    ...
  </M>
</Root>

Read NDepend Trend XML with C#

To read timestamped metrics from the NDepend XML I started by creating a C# record to hold an individual timestamped metric:

public record Metric
{
    public DateTime DateTime { get; init; }
    public string Name { get; init; }
    public string Unit { get; init; }
    public string Value { get; init; }
}

I then reached for using System.Xml.Linq and using System.Xml.XPath to extract a big list of timestamped metrics from the NDepend XML file:

Metric[] GetMetricsFromXML(string xmlFilePath)
{
    XDocument doc = XDocument.Load(xmlFilePath);
    List<Metric> baseMetrics = new();
    foreach (var el in doc.XPathSelectElement("/Root/MetricIndex").Elements())
    {
        string name = el.Attribute("Name").Value;
        string unit = el.Attribute("Unit").Value;
        baseMetrics.Add(new Metric() { Name = name, Unit = unit });
    }

    List<Metric> allMetrics = new();
    foreach (var runElement in doc.XPathSelectElement("/Root/M").Elements())
    {
        DateTime runDateTime = DateTime.Parse(runElement.Attribute("D").Value);
        string[] values = runElement.Attribute("V").Value.Split("|");

        List<Metric> runMetrics = new();
        for (int i = 0; i < baseMetrics.Count; i++)
            runMetrics.Add(baseMetrics[i] with { DateTime = runDateTime, Value = values[i] });

        allMetrics.AddRange(runMetrics);
    }

    return allMetrics.ToArray();
}

I found it convenient to make a helper function to get only the latest metrics:

Metric[] GetLatestMetrics(Metric[] metrics)
{
    DateTime latestDateTime = metrics.Select(x => x.DateTime).Distinct().OrderBy(x => x).Last();
    return metrics.Where(x => x.DateTime == latestDateTime).ToArray();
}

Generate NDepend Status Badges

I've already written how to make status badges with C# and Maui.Graphics, but that strategy only generates PNG files. For this project I also chose to generate SVG files. Rather than discuss that in detail, I'll just show to the source code for an example SVG file.

It is important to note that in order to know the image width I must measure the string width. In HTML environments this could be done with vanilla Javascript, but in a C# environment I reached for Microsoft.Maui.Graphics (see how to MeasureString() with Maui.Graphics).

<svg xmlns='http://www.w3.org/2000/svg'
    xmlns:xlink='http://www.w3.org/1999/xlink' width='237' height='20' role='img' aria-label='languages: 5'>
    <title>Average # Lines of Code for Types: 25.96</title>
    <linearGradient id='s' x2='0' y2='100%'>
        <!-- linear gradient to use for the background shadow -->
        <stop offset='0' stop-color='#bbb' stop-opacity='.1'/>
        <stop offset='1' stop-opacity='.1'/>
    </linearGradient>
    <clipPath id='r'>
        <!-- clip to a rectangle with rounded edges -->
        <rect width='237' height='20' rx='3' fill='#fff'/>
    </clipPath>
    <g clip-path='url(#r)'>
        <!-- left background -->
        <rect width='195' height='20' fill='#555'/>
        <!-- right background -->
        <rect x='195' width='42' height='20' fill='#007ec6'/>
        <!-- background shadow -->
        <rect width='237' height='20' fill='url(#s)'/>
    </g>
    <g fill='#FFF' text-anchor='center' font-family='Verdana,Geneva,DejaVu Sans,sans-serif' text-rendering='geometricPrecision' font-size='110'>
        <!-- left text semitransparent shadow then white text -->
        <text aria-hidden='true' x='40' y='150' fill='#010101' fill-opacity='.3' transform='scale(.1)' textLength='1854'>Average # Lines of Code for Types</text>
        <text x='40' y='140' transform='scale(.1)' fill='#FFF' textLength='1854'>Average # Lines of Code for Types</text>
        <!-- right text semitransparent shadow then white text -->
        <text aria-hidden='true' x='1994' y='150' fill='#010101' fill-opacity='.3' transform='scale(.1)' textLength='300'>25.96</text>
        <text x='1994' y='140' transform='scale(.1)' fill='#FFF' textLength='300'>25.96</text>
    </g>
</svg>

One day Maui.Graphics may offer SVG export support (issue #103) but for now generating these files discretely isn't too bad.

Badges

After putting it all together these are the badges generated by analyzing the current ScottPlot code base:

SVG

PNG

Resources

Markdown source code last modified on October 17th, 2021
---
Title: NDepend Status Badges
Description: How I used C# and Maui.Graphics to generate status badges for NDepend static analysis metrics
Date: 2021-10-17 1:20PM EST
Tags: csharp, maui
---

# NDepend Status Badges

**Many project websites and readmes have status badges** that display build status, project details, or code metrics. [badgen.net](https://badgen.net/) and [shields.io](https://shields.io/) are popular services for dynamically generating status badges as SVG files using HTTP requests. This article demonstrates how I use C# and `Microsoft.Maui.Graphics` to build status badges from [**NDepend**](https://www.ndepend.com/) static analysis reports. Source code for this project is [available on GitHub](https://github.com/swharden/NDepend-Badges).

<div class='text-center'>

<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/classes.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-types.svg' />

</div>

## NDepend Trend Data XML

NDepend can analyze a code base at different points in time and display code metric trends. See [NDepend: Trend Monitoring](https://www.ndepend.com/features/trend-monitoring#Trend) for a full description. These metrics are stored in an XML file available in the HTML build folder.

### Metric Index

The XML file contains many `Root/MetricIndex/Metric` elements that describe each metric and its units. This can be parsed to obtain the `Name` and `Unit` for each metric.

```xml
<Root>
  <MetricIndex>
    <Metric Name="# New Issues since Baseline" Unit="issues" />
    <Metric Name="# Issues Fixed since Baseline" Unit="issues" />
    <Metric Name="# Issues Worsened since Baseline" Unit="issues" />
    <Metric Name="# Issues with severity Blocker" Unit="issues" />
    <Metric Name="# Issues with severity Critical" Unit="issues" />
    <Metric Name="# Issues with severity High" Unit="issues" />
    <Metric Name="# Issues with severity Medium" Unit="issues" />
    ...
  </MetricIndex>
</Root>
```

### Metrics by DateTime

The XML file contains multiple `Root/M/R` elements that contain the value of each metric at a distinct time point. Numerical metrics have been converted to strings separated by the `|` character. Metric values for each time point are in the same order as the metric index.

```xml
<Root>
  <M>
    <R D="10/16/2021 11:58:04 AM" V="0|0|0|0|1|598|2177|...|19|133" />
    <R D="10/03/2021 04:15:24 PM" V="0|0|0|0|1|593|2160|...|19|132" />
	...
  </M>
</Root>
```

## Read NDepend Trend XML with C# 

To read timestamped metrics from the NDepend XML I started by creating a C# record to hold an individual timestamped metric:

```cs
public record Metric
{
    public DateTime DateTime { get; init; }
    public string Name { get; init; }
    public string Unit { get; init; }
    public string Value { get; init; }
}
```

I then reached for `using System.Xml.Linq` and `using System.Xml.XPath` to extract a big list of timestamped metrics from the NDepend XML file:

```cs
Metric[] GetMetricsFromXML(string xmlFilePath)
{
    XDocument doc = XDocument.Load(xmlFilePath);
    List<Metric> baseMetrics = new();
    foreach (var el in doc.XPathSelectElement("/Root/MetricIndex").Elements())
    {
        string name = el.Attribute("Name").Value;
        string unit = el.Attribute("Unit").Value;
        baseMetrics.Add(new Metric() { Name = name, Unit = unit });
    }

    List<Metric> allMetrics = new();
    foreach (var runElement in doc.XPathSelectElement("/Root/M").Elements())
    {
        DateTime runDateTime = DateTime.Parse(runElement.Attribute("D").Value);
        string[] values = runElement.Attribute("V").Value.Split("|");

        List<Metric> runMetrics = new();
        for (int i = 0; i < baseMetrics.Count; i++)
            runMetrics.Add(baseMetrics[i] with { DateTime = runDateTime, Value = values[i] });

        allMetrics.AddRange(runMetrics);
    }

    return allMetrics.ToArray();
}
```

I found it convenient to make a helper function to get only the latest metrics:

```cs
Metric[] GetLatestMetrics(Metric[] metrics)
{
    DateTime latestDateTime = metrics.Select(x => x.DateTime).Distinct().OrderBy(x => x).Last();
    return metrics.Where(x => x.DateTime == latestDateTime).ToArray();
}
```

## Generate NDepend Status Badges

I've already written [how to make status badges with C# and Maui.Graphics](https://swharden.com/blog/2021-11-16-maui-graphics-badges/), but that strategy only generates PNG files. For this project I also chose to generate SVG files. Rather than discuss that in detail, I'll just show to the source code for an example SVG file. 

It is important to note that in order to know the image width I must measure the string width. In HTML environments this could be done with vanilla Javascript, but in a C# environment I reached for `Microsoft.Maui.Graphics` (see [how to MeasureString() with Maui.Graphics](https://swharden.com/blog/2021-10-16-maui-graphics-measurestring)).

<div class='text-center'>

<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-types.svg' />

</div>

```xml
<svg xmlns='http://www.w3.org/2000/svg'
    xmlns:xlink='http://www.w3.org/1999/xlink' width='237' height='20' role='img' aria-label='languages: 5'>
    <title>Average # Lines of Code for Types: 25.96</title>
    <linearGradient id='s' x2='0' y2='100%'>
        <!-- linear gradient to use for the background shadow -->
        <stop offset='0' stop-color='#bbb' stop-opacity='.1'/>
        <stop offset='1' stop-opacity='.1'/>
    </linearGradient>
    <clipPath id='r'>
        <!-- clip to a rectangle with rounded edges -->
        <rect width='237' height='20' rx='3' fill='#fff'/>
    </clipPath>
    <g clip-path='url(#r)'>
        <!-- left background -->
        <rect width='195' height='20' fill='#555'/>
        <!-- right background -->
        <rect x='195' width='42' height='20' fill='#007ec6'/>
        <!-- background shadow -->
        <rect width='237' height='20' fill='url(#s)'/>
    </g>
    <g fill='#FFF' text-anchor='center' font-family='Verdana,Geneva,DejaVu Sans,sans-serif' text-rendering='geometricPrecision' font-size='110'>
        <!-- left text semitransparent shadow then white text -->
        <text aria-hidden='true' x='40' y='150' fill='#010101' fill-opacity='.3' transform='scale(.1)' textLength='1854'>Average # Lines of Code for Types</text>
        <text x='40' y='140' transform='scale(.1)' fill='#FFF' textLength='1854'>Average # Lines of Code for Types</text>
        <!-- right text semitransparent shadow then white text -->
        <text aria-hidden='true' x='1994' y='150' fill='#010101' fill-opacity='.3' transform='scale(.1)' textLength='300'>25.96</text>
        <text x='1994' y='140' transform='scale(.1)' fill='#FFF' textLength='300'>25.96</text>
    </g>
</svg>
```

One day Maui.Graphics may offer SVG export support ([issue #103](https://github.com/dotnet/Microsoft.Maui.Graphics/issues/103)) but for now generating these files discretely isn't too bad.

## Badges

After putting it all together these are the badges generated by analyzing the current [ScottPlot](https://scottplot.net) code base:

### SVG

<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/new-issues-since-baseline.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-fixed-since-baseline.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-worsened-since-baseline.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-blocker.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-critical.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-high.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-medium.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-low.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/blocker-critical-high-issues.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/suppressed-issues.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules-violated.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules-violated.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates-warn.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates-fail.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/percentage-debt-metric.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/debt-metric.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/annual-interest-metric.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/breaking-point.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/breaking-point-of-blocker-critical-high-issues.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-justmycode.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-notmycode.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/source-files.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/il-instructions.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/il-instructions-notmycode.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/assemblies.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/namespaces.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/types.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/public-types.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/classes.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/abstract-classes.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/interfaces.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/structures.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/abstract-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/concrete-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/fields.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-lines-of-code-for-methods-justmycode.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-methods-with-at-least-3-lines-of-code.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-lines-of-code-for-types-justmycode.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-types.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-cyclomatic-complexity-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-cyclomatic-complexity-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-il-cyclomatic-complexity-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-il-cyclomatic-complexity-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-il-nesting-depth-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-il-nesting-depth-for-methods.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-of-methods-for-types.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-methods-for-types.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-of-methods-for-interfaces.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-methods-for-interfaces.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-uncoverable.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-assemblies-used.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-namespaces-used.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-types-used.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-methods-used.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-fields-used.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules-violations.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules.svg' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules-violations.svg' />

### PNG

<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/new-issues-since-baseline.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-fixed-since-baseline.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-worsened-since-baseline.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-blocker.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-critical.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-high.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-medium.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues-with-severity-low.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/blocker-critical-high-issues.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/issues.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/suppressed-issues.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules-violated.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules-violated.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates-warn.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/quality-gates-fail.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/percentage-debt-metric.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/debt-metric.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/annual-interest-metric.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/breaking-point.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/breaking-point-of-blocker-critical-high-issues.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-justmycode.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-notmycode.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/source-files.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/il-instructions.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/il-instructions-notmycode.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/assemblies.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/namespaces.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/types.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/public-types.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/classes.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/abstract-classes.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/interfaces.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/structures.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/abstract-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/concrete-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/fields.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-lines-of-code-for-methods-justmycode.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-methods-with-at-least-3-lines-of-code.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-lines-of-code-for-types-justmycode.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-lines-of-code-for-types.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-cyclomatic-complexity-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-cyclomatic-complexity-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-il-cyclomatic-complexity-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-il-cyclomatic-complexity-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-il-nesting-depth-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-il-nesting-depth-for-methods.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-of-methods-for-types.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-methods-for-types.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/max-of-methods-for-interfaces.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/average-methods-for-interfaces.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/lines-of-code-uncoverable.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-assemblies-used.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-namespaces-used.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-types-used.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-methods-used.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/third-party-fields-used.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/rules-violations.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules.png' />
<img style='margin-top: .1em; margin-bottom: .1em;' src='https://swharden.com/blog/2021-11-17-ndepend-badges/badges/critical-rules-violations.png' />


## Resources
* Source code on GitHub: https://github.com/swharden/NDepend-Badges
* NDepend website: https://www.ndepend.com/
* NDepend sample reports: https://www.ndepend.com/sample-reports/
* [How to MeasureString() with Maui.Graphics](https://swharden.com/blog/2021-10-16-maui-graphics-measurestring/)
* [Status Badges with Maui.Graphics](https://swharden.com/blog/2021-11-16-maui-graphics-badges/)
* [Draw with Maui.Graphics and Skia in a C# Console Application](https://swharden.com/blog/2021-08-01-maui-skia-console/)
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 May 26th, 2022
---
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, graphics
---

# 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>


<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
  </symbol>
  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
  </symbol>
  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
  </symbol>
</svg>

<div class="alert alert-primary d-flex align-items-center" role="alert">
  <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
  <div>
    <strong>UPDATE:</strong> This article was written when <code>Microsoft.Maui.Graphics</code> was still in preview. See <a href="https://swharden.com/blog/2022-05-25-maui-graphics/" class="fw-bold">Drawing with Maui Graphics (blog post)</a> and <a href="https://swharden.com/csdv/" class="fw-bold">C# Data Visualization (website)</a> for updated code examples and information about using this library.
  </div>
</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/)
October 16th, 2021

How to MeasureString() with Maui.Graphics

Starting with .NET 6 Microsoft is sunsetting cross-platform support for System.Drawing.Common. Microsoft.Maui.Graphics is emerging as an excellent replacement and it can be used in any app (not just MAUI apps).

Code here demonstrates how I measure the pixel size of a string using Maui.Graphics in a console application.

/* Updated on 2022-02-19 */
using Microsoft.Maui.Graphics; // 6.0.200-preview.13.935
using Microsoft.Maui.Graphics.Skia; // 6.0.200-preview.13.935

// setup a canvas with a blue background
using BitmapExportContext bmp = new SkiaBitmapExportContext(450, 150, 1.0f);
ICanvas canvas = bmp.Canvas;
canvas.FillColor = Colors.Navy;
canvas.FillRectangle(0, 0, bmp.Width, bmp.Height);

// define and measure a string
PointF stringLocation = new(50, 50);
string stringText = "Hello, Maui.Graphics!";
Font font = new();
float fontSize = 32;
SizeF stringSize = canvas.GetStringSize(stringText, font, fontSize);
Rectangle stringRect = new(stringLocation, stringSize);

// draw the string and its outline
canvas.StrokeColor = Colors.White;
canvas.DrawRectangle(stringRect);
canvas.FontColor = Colors.Yellow;
canvas.Font = font;
canvas.FontSize = fontSize - 1; // NOTE: reduce to prevent clipping
canvas.DrawString(
    value: stringText,
    x: stringLocation.X,
    y: stringLocation.Y,
    width: stringSize.Width,
    height: stringSize.Height,
    horizontalAlignment: HorizontalAlignment.Left,
    verticalAlignment: VerticalAlignment.Top,
    textFlow: TextFlow.OverflowBounds,
    lineSpacingAdjustment: 0);

// save the result
string filePath = Path.GetFullPath("Issue279.png");
using FileStream fs = new(filePath, FileMode.Create);
bmp.WriteToStream(fs);
Console.WriteLine(filePath);

Note that in this example GetStringSize() respects font but DrawString() does not.

Resources

Markdown source code last modified on May 26th, 2022
---
Title: MeasureString() with Maui.Graphics
Description: How to measure the pixel dimensions of a string using Microsoft.Maui.Graphics
Date: 2021-10-16 4:15PM EST
tags: csharp, maui, graphics
---

# How to MeasureString() with Maui.Graphics

Starting with .NET 6 Microsoft is [sunsetting cross-platform support](https://github.com/dotnet/designs/blob/main/accepted/2021/system-drawing-win-only/system-drawing-win-only.md) for [`System.Drawing.Common`](https://www.nuget.org/packages/System.Drawing.Common/). [`Microsoft.Maui.Graphics`](https://github.com/dotnet/Microsoft.Maui.Graphics) is emerging as an excellent replacement and it can be used in any app (not just MAUI apps).

Code here demonstrates how I measure the pixel size of a string using `Maui.Graphics` in a console application.


<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
  </symbol>
  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
  </symbol>
  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
  </symbol>
</svg>

<div class="alert alert-primary d-flex align-items-center" role="alert">
  <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
  <div>
    <strong>UPDATE:</strong> This article was written when <code>Microsoft.Maui.Graphics</code> was still in preview. See <a href="https://swharden.com/blog/2022-05-25-maui-graphics/" class="fw-bold">Drawing with Maui Graphics (blog post)</a> and <a href="https://swharden.com/csdv/" class="fw-bold">C# Data Visualization (website)</a> for updated code examples and information about using this library.
  </div>
</div>

```cs
/* Updated on 2022-02-19 */
using Microsoft.Maui.Graphics; // 6.0.200-preview.13.935
using Microsoft.Maui.Graphics.Skia; // 6.0.200-preview.13.935

// setup a canvas with a blue background
using BitmapExportContext bmp = new SkiaBitmapExportContext(450, 150, 1.0f);
ICanvas canvas = bmp.Canvas;
canvas.FillColor = Colors.Navy;
canvas.FillRectangle(0, 0, bmp.Width, bmp.Height);

// define and measure a string
PointF stringLocation = new(50, 50);
string stringText = "Hello, Maui.Graphics!";
Font font = new();
float fontSize = 32;
SizeF stringSize = canvas.GetStringSize(stringText, font, fontSize);
Rectangle stringRect = new(stringLocation, stringSize);

// draw the string and its outline
canvas.StrokeColor = Colors.White;
canvas.DrawRectangle(stringRect);
canvas.FontColor = Colors.Yellow;
canvas.Font = font;
canvas.FontSize = fontSize - 1; // NOTE: reduce to prevent clipping
canvas.DrawString(
    value: stringText,
    x: stringLocation.X,
    y: stringLocation.Y,
    width: stringSize.Width,
    height: stringSize.Height,
    horizontalAlignment: HorizontalAlignment.Left,
    verticalAlignment: VerticalAlignment.Top,
    textFlow: TextFlow.OverflowBounds,
    lineSpacingAdjustment: 0);

// save the result
string filePath = Path.GetFullPath("Issue279.png");
using FileStream fs = new(filePath, FileMode.Create);
bmp.WriteToStream(fs);
Console.WriteLine(filePath);
```

<div class="text-center">

![](2022-02-19-maui-graphics-measure-string.png)

</div>

Note that in this example `GetStringSize()` respects font but `DrawString()` does not.

## Resources

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

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

* [https://Maui.Graphics](https://Maui.Graphics)

* [dotnet/Microsoft.Maui.Graphics issue #279](https://github.com/dotnet/Microsoft.Maui.Graphics/issues/279)
September 10th, 2021

Drawing with Maui.Graphics

.NET MAUI (Multi-Platform Application User Interface) is a new framework for creating cross-platform apps using C#. MAUI will be released as part of .NET 6 in November 2021 and it is expected to come with Maui.Graphics, a cross-platform drawing library superior to System.Drawing in many ways. Although System.Drawing.Common currently supports rendering in Linux and MacOS, cross-platform support for System.Drawing will sunset over the next few releases and begin throwing a PlatformNotSupportedException in .NET 6.

By creating a graphics model using only Maui.Graphics dependencies, users can share drawing code across multiple rendering technologies (GDI, Skia, SharpDX, etc.), operating systems (Windows, Linux, MacOS, etc.), and application frameworks (WinForms, WPF, Maui, WinUI, etc.). This page demonstrates how to create a platform-agnostic graphics model and render it using Windows Forms and WPF. Resources in the Maui.Graphics namespace can be used by any modern .NET application (not just Maui apps).

⚠️ WARNING: Maui.Graphics is still a pre-release experimental library (as noted on their GitHub page). Although the code examples on this page work presently, the API may change between now and the official release.

1. Create a new Project

For this example I will start by creating a a .NET 5.0 WinForms project from scratch. Later we will extend the solution to include a WPF project that uses the same graphics model.

2. Add References to Maui.Graphics

We need to get the windows forms MAUI control and all its dependencies. At the time of writing (September, 2021) these packages are not yet available on NuGet, but they can be downloaded from the Microsoft.Maui.Graphics GitHub page.

💡 Tip: If you're developing a desktop application you can improve the "rebuild all" time by editing the csproj files of your dependencies so TargetFrameworks only includes .NET Standard targets.

  • Add the Microsoft.Maui.Graphics project to your solution and add a reference to it from your project.

  • Windows Forms: Add the Microsoft.Maui.Graphics.GDI.Winforms project to your solution and add a reference to it in your WinForms project. A GDIGraphicsView control should appear in the toolbox.

  • WPF: Add the Microsoft.Maui.Graphics.Skia.WPF project to your solution and add a reference to it in your WPF project. A WDSkiaGraphicsView control should appear in the toolbox.

3. Create a Drawable Object

A drawable is a class that implements IDrawable, has a Draw() method, and can be rendered anywhere Maui.Graphics is supported. By only depending on Maui.Graphics it's easy to create a graphics model that can be used on any operating system using any supported graphical framework.

using Microsoft.Maui.Graphics;

This drawable fills the image blue and renders 1,000 randomly-placed anti-aliased semi-transparent white lines on it. Note that the location of the lines depends on the size of the render field (passed-in as an argument).

public class RandomLines : IDrawable
{
    public void Draw(ICanvas canvas, RectangleF dirtyRect)
    {
        canvas.FillColor = Color.FromArgb("#003366");
        canvas.FillRectangle(dirtyRect);

        canvas.StrokeSize = 1;
        canvas.StrokeColor = Color.FromRgba(255, 255, 255, 100);
        Random Rand = new();
        for (int i = 0; i < 1000; i++)
        {
            canvas.DrawLine(
                x1: (float)Rand.NextDouble() * dirtyRect.Width,
                y1: (float)Rand.NextDouble() * dirtyRect.Height,
                x2: (float)Rand.NextDouble() * dirtyRect.Width,
                y2: (float)Rand.NextDouble() * dirtyRect.Height);
        }
    }
}

4. Add a GraphicsView Control

Drag/drop a GraphicsView control from the Toolbox onto your application and assign your graphics model to its Drawable field.

For animations you can use a timer to invalidate the control (forcing a redraw) automatically every 20 ms.

Windows Forms (Rendering with GDI)

Windows Forms applications may also want to intercept SizeChanged events to force redraws as the window is resized.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        gdiGraphicsView1.Drawable = new RandomLines();
    }

    private void GdiGraphicsView1_SizeChanged(object? sender, EventArgs e) =>
        gdiGraphicsView1.Invalidate();

    private void timer1_Tick(object sender, EventArgs e) =>
        gdiGraphicsView1.Invalidate();
}

I found performance to be quite adequate. On my system 1,000 lines rendered on an 800x600 window at ~60 fps. Like System.Drawing this system slows down as a function of image size, so full-screen 1920x1080 animation was much slower (~10 fps).

WPF (Rendering with SkiaSharp)

<Skia:WDSkiaGraphicsView Name="MyGraphicsView" />
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        MyGraphicsView.Drawable = new RandomLines();

        DispatcherTimer timer = new();
        timer.Interval = TimeSpan.FromMilliseconds(20);
        timer.Tick += Timer_Tick; ;
        timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e) =>
        MyGraphicsView.Invalidate();
}

Extend the Graphics Model

Since our project is configured to display the same graphics model with both WinForms and WPF, it's easy to edit the model in one place and it's updated everywhere. We can replace the random lines model with one that manages randomly colored and sized balls that bounce off the edges of the window as the model advances.

BallField.cs

public class BallField : IDrawable
{
    private readonly Ball[] Balls;

    public BallField(int ballCount)
    {
        Balls = new Ball[ballCount];
    }

    public void Draw(ICanvas canvas, RectangleF dirtyRect)
    {
        canvas.FillColor = Colors.Navy;
        canvas.FillRectangle(dirtyRect);

        foreach (Ball ball in Balls)
        {
            ball?.Draw(canvas);
        }
    }

    public void Randomize(double width, double height)
    {
        Random rand = new();
        for (int i = 0; i < Balls.Length; i++)
        {
            Balls[i] = new()
            {
                X = rand.NextDouble() * width,
                Y = rand.NextDouble() * height,
                Radius = rand.NextDouble() * 5 + 5,
                XVel = rand.NextDouble() - .5,
                YVel = rand.NextDouble() - .5,
                R = (byte)rand.Next(50, 255),
                G = (byte)rand.Next(50, 255),
                B = (byte)rand.Next(50, 255),
            };
        }
    }

    public void Advance(double timeDelta, double width, double height)
    {
        foreach (Ball ball in Balls)
        {
            ball?.Advance(timeDelta, width, height);
        }
    }
}

Ball.cs

public class Ball
{
    public double X;
    public double Y;
    public double Radius = 5;
    public double XVel;
    public double YVel;
    public byte R, G, B;

    public void Draw(ICanvas canvas)
    {
        canvas.FillColor = Color.FromRgb(R, G, B);
        canvas.FillCircle((float)X, (float)Y, (float)Radius);
    }

    public void Advance(double timeDelta, double width, double height)
    {
        MoveForward(timeDelta);
        Bounce(width, height);
    }

    private void MoveForward(double timeDelta)
    {
        X += XVel * timeDelta;
        Y += YVel * timeDelta;
    }

    private void Bounce(double width, double height)
    {
        double minX = Radius;
        double minY = Radius;
        double maxX = width - Radius;
        double maxY = height - Radius;

        if (X < minX)
        {
            X = minX + (minX - X);
            XVel = -XVel;
        }
        else if (X > maxX)
        {
            X = maxX - (X - maxX);
            XVel = -XVel;
        }

        if (Y < minY)
        {
            Y = minY + (minY - Y);
            YVel = -YVel;
        }
        else if (Y > maxY)
        {
            Y = maxY - (Y - maxY);
            YVel = -YVel;
        }
    }
}

Download This Project

To build this project from source code you currently have to download Maui.Graphics source from GitHub and edit the solution file to point to the correct directory containing these projects. This will get a lot easier after Microsoft puts their WinForms and WPF controls on NuGet.

Coding Challenge

Can you recreate this classic screensaver using Maui.Graphics? Bonus points if the user can customize the number of shapes, the number of corners each shape has, and the number of lines drawn in each shape's history. It's a fun problem and I encourage you to give it a go! Here's how I did it: mystify-maui.zip

Resources

Markdown source code last modified on May 26th, 2022
---
Title: Drawing with Maui.Graphics
Description: How to use Maui.Graphics to draw and create animations in a Windows Forms and WPF applications
Date: 2021-09-10 10:30PM EST
Tags: csharp, maui, graphics
---

# Drawing with Maui.Graphics

**[.NET MAUI](https://docs.microsoft.com/en-us/dotnet/maui/what-is-maui) (Multi-Platform Application User Interface) is a new framework for creating cross-platform apps using C#.** MAUI will be released as part of .NET 6 in November 2021 and it is expected to come with [`Maui.Graphics`](https://github.com/dotnet/Microsoft.Maui.Graphics), a cross-platform drawing library superior to [`System.Drawing`](https://docs.microsoft.com/en-us/dotnet/api/system.drawing?view=net-5.0#remarks) in many ways. Although [`System.Drawing.Common`](https://www.nuget.org/packages/System.Drawing.Common) currently supports rendering in Linux and MacOS, [cross-platform support for System.Drawing will sunset](https://github.com/dotnet/designs/blob/main/accepted/2021/system-drawing-win-only/system-drawing-win-only.md) over the next few releases and begin throwing a `PlatformNotSupportedException` in .NET 6.

By creating a graphics model using only `Maui.Graphics` dependencies, users can share drawing code across multiple rendering technologies (GDI, Skia, SharpDX, etc.), operating systems (Windows, Linux, MacOS, etc.), and application frameworks (WinForms, WPF, Maui, WinUI, etc.). **This page demonstrates how to create a platform-agnostic graphics model and render it using Windows Forms and WPF.** Resources in the `Maui.Graphics` namespace can be used by any modern .NET application (not just Maui apps).

<div class="text-center">

![](maui-graphics-balls.gif)

</div>

> ⚠️ **WARNING:** `Maui.Graphics` is still a pre-release experimental library (as noted on [their GitHub page](https://github.com/dotnet/Microsoft.Maui.Graphics)). Although the code examples on this page work presently, the API may change between now and the official release.


<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
  <symbol id="check-circle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
  </symbol>
  <symbol id="info-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
  </symbol>
  <symbol id="exclamation-triangle-fill" fill="currentColor" viewBox="0 0 16 16">
    <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
  </symbol>
</svg>

<div class="alert alert-primary d-flex align-items-center" role="alert">
  <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Info:"><use xlink:href="#info-fill"/></svg>
  <div>
    <strong>UPDATE:</strong> This article was written when <code>Microsoft.Maui.Graphics</code> was still in preview. See <a href="https://swharden.com/blog/2022-05-25-maui-graphics/" class="fw-bold">Drawing with Maui Graphics (blog post)</a> and <a href="https://swharden.com/csdv/" class="fw-bold">C# Data Visualization (website)</a> for updated code examples and information about using this library.
  </div>
</div>

## 1. Create a new Project

For this example I will start by creating a a .NET 5.0 WinForms project from scratch. Later we will extend the solution to include a WPF project that uses the same graphics model.

## 2. Add References to Maui.Graphics

We need to get the windows forms MAUI control and all its dependencies. At the time of writing (September, 2021) these packages are not yet available on NuGet, but they can be downloaded from the [Microsoft.Maui.Graphics GitHub page](https://github.com/dotnet/Microsoft.Maui.Graphics).

> 💡 **Tip:** If you're developing a desktop application you can improve the "rebuild all" time by editing the csproj files of your dependencies so `TargetFrameworks` only includes .NET Standard targets.

* Add the `Microsoft.Maui.Graphics` project to your solution and add a reference to it from your project.

* **Windows Forms:** Add the `Microsoft.Maui.Graphics.GDI.Winforms` project to your solution and add a reference to it in your WinForms project. A `GDIGraphicsView` control should appear in the toolbox.

* **WPF:** Add the `Microsoft.Maui.Graphics.Skia.WPF` project to your solution and add a reference to it in your WPF project. A `WDSkiaGraphicsView` control should appear in the toolbox.

<div class="text-center img-border">

![](vs-maui.png)

</div>

## 3. Create a Drawable Object

**A _drawable_ is a class that implements `IDrawable`, has a `Draw()` method, and can be rendered anywhere Maui.Graphics is supported.** By only depending on `Maui.Graphics` it's easy to create a graphics model that can be used on any operating system using any supported graphical framework.

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

This _drawable_ fills the image blue and renders 1,000 randomly-placed anti-aliased semi-transparent white lines on it. Note that the location of the lines depends on the size of the render field (passed-in as an argument).

```cs
public class RandomLines : IDrawable
{
    public void Draw(ICanvas canvas, RectangleF dirtyRect)
    {
        canvas.FillColor = Color.FromArgb("#003366");
        canvas.FillRectangle(dirtyRect);

        canvas.StrokeSize = 1;
        canvas.StrokeColor = Color.FromRgba(255, 255, 255, 100);
        Random Rand = new();
        for (int i = 0; i < 1000; i++)
        {
            canvas.DrawLine(
                x1: (float)Rand.NextDouble() * dirtyRect.Width,
                y1: (float)Rand.NextDouble() * dirtyRect.Height,
                x2: (float)Rand.NextDouble() * dirtyRect.Width,
                y2: (float)Rand.NextDouble() * dirtyRect.Height);
        }
    }
}
```

## 4. Add a GraphicsView Control

Drag/drop a GraphicsView control from the Toolbox onto your application and assign your graphics model to its `Drawable` field.

For animations you can use a timer to invalidate the control (forcing a redraw) automatically every 20 ms.

### Windows Forms (Rendering with GDI)

Windows Forms applications may also want to intercept SizeChanged events to force redraws as the window is resized.

```cs
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        gdiGraphicsView1.Drawable = new RandomLines();
    }

    private void GdiGraphicsView1_SizeChanged(object? sender, EventArgs e) =>
        gdiGraphicsView1.Invalidate();

    private void timer1_Tick(object sender, EventArgs e) =>
        gdiGraphicsView1.Invalidate();
}
```

<div class="text-center">

![](maui-graphics-winforms.gif)

</div>

**I found performance to be quite adequate.** On my system 1,000 lines rendered on an 800x600 window at ~60 fps. Like `System.Drawing` this system slows down as a function of image size, so full-screen 1920x1080 animation was much slower (~10 fps).

### WPF  (Rendering with SkiaSharp)

```xml
<Skia:WDSkiaGraphicsView Name="MyGraphicsView" />
```

```cs
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        MyGraphicsView.Drawable = new RandomLines();

        DispatcherTimer timer = new();
        timer.Interval = TimeSpan.FromMilliseconds(20);
        timer.Tick += Timer_Tick; ;
        timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e) =>
        MyGraphicsView.Invalidate();
}
```

<div class="text-center">

![](maui-graphics-wpf.gif)

</div>

## Extend the Graphics Model

Since our project is configured to display the same graphics model with both WinForms and WPF, it's easy to edit the model in one place and it's updated everywhere. We can replace the random lines model with one that manages randomly colored and sized balls that bounce off the edges of the window as the model advances.

<div class="text-center">

![](maui-graphics-balls.gif)

</div>

### BallField.cs

```cs
public class BallField : IDrawable
{
    private readonly Ball[] Balls;

    public BallField(int ballCount)
    {
        Balls = new Ball[ballCount];
    }

    public void Draw(ICanvas canvas, RectangleF dirtyRect)
    {
        canvas.FillColor = Colors.Navy;
        canvas.FillRectangle(dirtyRect);

        foreach (Ball ball in Balls)
        {
            ball?.Draw(canvas);
        }
    }

    public void Randomize(double width, double height)
    {
        Random rand = new();
        for (int i = 0; i < Balls.Length; i++)
        {
            Balls[i] = new()
            {
                X = rand.NextDouble() * width,
                Y = rand.NextDouble() * height,
                Radius = rand.NextDouble() * 5 + 5,
                XVel = rand.NextDouble() - .5,
                YVel = rand.NextDouble() - .5,
                R = (byte)rand.Next(50, 255),
                G = (byte)rand.Next(50, 255),
                B = (byte)rand.Next(50, 255),
            };
        }
    }

    public void Advance(double timeDelta, double width, double height)
    {
        foreach (Ball ball in Balls)
        {
            ball?.Advance(timeDelta, width, height);
        }
    }
}
```

### Ball.cs

```cs
public class Ball
{
    public double X;
    public double Y;
    public double Radius = 5;
    public double XVel;
    public double YVel;
    public byte R, G, B;

    public void Draw(ICanvas canvas)
    {
        canvas.FillColor = Color.FromRgb(R, G, B);
        canvas.FillCircle((float)X, (float)Y, (float)Radius);
    }

    public void Advance(double timeDelta, double width, double height)
    {
        MoveForward(timeDelta);
        Bounce(width, height);
    }

    private void MoveForward(double timeDelta)
    {
        X += XVel * timeDelta;
        Y += YVel * timeDelta;
    }

    private void Bounce(double width, double height)
    {
        double minX = Radius;
        double minY = Radius;
        double maxX = width - Radius;
        double maxY = height - Radius;

        if (X < minX)
        {
            X = minX + (minX - X);
            XVel = -XVel;
        }
        else if (X > maxX)
        {
            X = maxX - (X - maxX);
            XVel = -XVel;
        }

        if (Y < minY)
        {
            Y = minY + (minY - Y);
            YVel = -YVel;
        }
        else if (Y > maxY)
        {
            Y = maxY - (Y - maxY);
            YVel = -YVel;
        }
    }
}
```

## Download This Project 

* **Source Code:** [**Balls.zip**](2021-09-12-Balls.zip) (10 kB)

* **WinForms (GDI) Demo:** [**Balls-WinForms.exe**](2021-09-12-Balls.exe-WinForms.zip) (237 kB)

* **WPF (Skia) Demo:** [**Balls-WPF.exe**](2021-09-12-Balls.exe-WPF.zip) (13 MB)

> To build this project from source code you currently have to download [Maui.Graphics source from GitHub](https://github.com/dotnet/Microsoft.Maui.Graphics) and edit the solution file to point to the correct directory containing these projects. This will get a lot easier after Microsoft puts their WinForms and WPF controls on NuGet.

## Coding Challenge

**Can you recreate this classic screensaver using Maui.Graphics?** Bonus points if the user can customize the number of shapes, the number of corners each shape has, and the number of lines drawn in each shape's history. It's a fun problem and I encourage you to give it a go! Here's how I did it: [mystify-maui.zip](2021-09-13-mystify-maui.zip)

![](maui-mystify.mp4)

## Resources

* [Draw with Maui.Graphics and Skia in a C# Console Application](https://swharden.com/blog/2021-08-01-maui-skia-console/)

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

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

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

* [C# Data Visualization](https://swharden.com/CsharpDataVis)

* [https://Maui.Graphics](https://maui.graphics)

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

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