The personal website of Scott W Harden
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 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/)
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 February 20th, 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
---

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

```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 October 16th, 2021
---
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
---

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

## 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)
August 1st, 2021

Draw with Maui.Graphics and Skia in a C# Console Application

Microsoft's System.Drawing.Common package is commonly used for cross-platform graphics in .NET Framework and .NET Core applications, but according to the dotnet roadmap System.Drawing will soon only support Windows. As Microsoft sunsets cross-platform support for System.Drawing they will be simultaneously developing Microsoft.Maui.Graphics, a cross-platform graphics library for iOS, Android, Windows, macOS, Tizen and Linux completely in C#.

The Maui.Graphics library can be used in any .NET application (not just MAUI applications). This page documents how I used the Maui.Drawing package to render graphics in memory (using a Skia back-end) and save them as static images from a console application.

I predict Maui.Graphics will eventually evolve to overtake System.Drawing in utilization. It has many advantages for performance and memory management (discussed extensively elsewhere on the internet), but it is still early in development. As of today (July 2021) the Maui.Graphics GitHub page warns "This is an experimental library ... There is no official support. Use at your own Risk."

Maui Graphics Skia Console Quickstart

This program will create an image, fill it with blue, add 1,000 random lines, then draw some text. It is written as a .NET 5 top-level console application and requires the Microsoft.Maui.Graphics and Microsoft.Maui.Graphics.Skia NuGet packages (both are currently in preview).

We use SkiaSharp to create a canvas, but importantly that canvas implements Microsoft.Maui.Graphics.ICanvas (it's not Skia-specific) so all the methods that draw on it can be agnostic to which rendering system was used. This makes it easy to write generic rendering methods now and have the option to switch the rendering system later.

Program.cs

using System;
using System.IO;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

// Use Skia to create a Maui graphics context and canvas
BitmapExportContext bmpContext = SkiaGraphicsService.Instance.CreateBitmapExportContext(600, 400);
SizeF bmpSize = new(bmpContext.Width, bmpContext.Height);
ICanvas canvas = bmpContext.Canvas;

// Draw on the canvas with abstract methods that are agnostic to the renderer
ClearBackground(canvas, bmpSize, Colors.Navy);
DrawRandomLines(canvas, bmpSize, 1000);
DrawBigTextWithShadow(canvas, "This is Maui.Graphics with Skia");
SaveFig(bmpContext, Path.GetFullPath("quickstart.jpg"));

static void ClearBackground(ICanvas canvas, SizeF bmpSize, Color bgColor)
{
    canvas.FillColor = Colors.Navy;
    canvas.FillRectangle(0, 0, bmpSize.Width, bmpSize.Height);
}

static void DrawRandomLines(ICanvas canvas, SizeF bmpSize, int count = 1000)
{
    Random rand = new();
    for (int i = 0; i < count; i++)
    {
        canvas.StrokeSize = (float)rand.NextDouble() * 10;

        canvas.StrokeColor = new Color(
            red: (float)rand.NextDouble(),
            green: (float)rand.NextDouble(),
            blue: (float)rand.NextDouble(),
            alpha: .2f);

        canvas.DrawLine(
            x1: (float)rand.NextDouble() * bmpSize.Width,
            y1: (float)rand.NextDouble() * bmpSize.Height,
            x2: (float)rand.NextDouble() * bmpSize.Width,
            y2: (float)rand.NextDouble() * bmpSize.Height);
    }
}

static void DrawBigTextWithShadow(ICanvas canvas, string text)
{
    canvas.FontSize = 36;
    canvas.FontColor = Colors.White;
    canvas.SetShadow(offset: new SizeF(2, 2), blur: 1, color: Colors.Black);
    canvas.DrawString(text, 20, 50, HorizontalAlignment.Left);
}

static void SaveFig(BitmapExportContext bmp, string filePath)
{
    bmp.WriteToFile(filePath);
    Console.WriteLine($"WROTE: {filePath}");
}

MauiGraphicsDemo.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Maui.Graphics" Version="6.0.100-preview.6.299" />
    <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="6.0.100-preview.6.299" />
  </ItemGroup>

</Project>

Resources

Markdown source code last modified on October 16th, 2021
---
Title: Draw with Maui.Graphics and Skia in a C# Console Application
Description: This page describes how to draw graphics in a console application with Maui Graphics and Skia
Date: 2021-08-01 7:15PM EST
Tags: csharp, maui
---

# Draw with Maui.Graphics and Skia in a C# Console Application

**Microsoft's `System.Drawing.Common` package is commonly used for cross-platform graphics in .NET Framework and .NET Core applications, but according to the dotnet roadmap [System.Drawing will soon only support Windows](https://github.com/dotnet/designs/blob/main/accepted/2021/system-drawing-win-only/system-drawing-win-only.md).** As Microsoft sunsets cross-platform support for `System.Drawing` they will be simultaneously developing [`Microsoft.Maui.Graphics`](https://github.com/dotnet/Microsoft.Maui.Graphics), a cross-platform graphics library for iOS, Android, Windows, macOS, Tizen and Linux completely in C#.

**The `Maui.Graphics` library can be used in any .NET application (not just MAUI applications).** This page documents how I used the Maui.Drawing package to render graphics in memory (using a Skia back-end) and save them as static images from a console application.

**I predict `Maui.Graphics` will eventually evolve to overtake `System.Drawing` in utilization.** It has many advantages for performance and memory management (discussed extensively elsewhere on the internet), but it is still early in development. As of today (July 2021) [the Maui.Graphics GitHub page](https://github.com/dotnet/Microsoft.Maui.Graphics) warns "This is an experimental library ... There is no official support. Use at your own Risk."

## Maui Graphics Skia Console Quickstart

This program will create an image, fill it with blue, add 1,000 random lines, then draw some text. It is written as a .NET 5 top-level console application and requires the [Microsoft.Maui.Graphics](https://www.nuget.org/packages/Microsoft.Maui.Graphics) and [Microsoft.Maui.Graphics.Skia](https://www.nuget.org/packages/Microsoft.Maui.Graphics.Skia) NuGet packages (both are currently in preview). 

We use SkiaSharp to create a canvas, but importantly that canvas implements `Microsoft.Maui.Graphics.ICanvas` (it's not Skia-specific) so all the methods that draw on it can be agnostic to which rendering system was used. This makes it easy to write generic rendering methods now and have the option to switch the rendering system later.

<div class="text-center">

![](maui-graphics-quickstart.jpg)

</div>

### Program.cs
```cs
using System;
using System.IO;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Skia;

// Use Skia to create a Maui graphics context and canvas
BitmapExportContext bmpContext = SkiaGraphicsService.Instance.CreateBitmapExportContext(600, 400);
SizeF bmpSize = new(bmpContext.Width, bmpContext.Height);
ICanvas canvas = bmpContext.Canvas;

// Draw on the canvas with abstract methods that are agnostic to the renderer
ClearBackground(canvas, bmpSize, Colors.Navy);
DrawRandomLines(canvas, bmpSize, 1000);
DrawBigTextWithShadow(canvas, "This is Maui.Graphics with Skia");
SaveFig(bmpContext, Path.GetFullPath("quickstart.jpg"));

static void ClearBackground(ICanvas canvas, SizeF bmpSize, Color bgColor)
{
    canvas.FillColor = Colors.Navy;
    canvas.FillRectangle(0, 0, bmpSize.Width, bmpSize.Height);
}

static void DrawRandomLines(ICanvas canvas, SizeF bmpSize, int count = 1000)
{
    Random rand = new();
    for (int i = 0; i < count; i++)
    {
        canvas.StrokeSize = (float)rand.NextDouble() * 10;

        canvas.StrokeColor = new Color(
            red: (float)rand.NextDouble(),
            green: (float)rand.NextDouble(),
            blue: (float)rand.NextDouble(),
            alpha: .2f);

        canvas.DrawLine(
            x1: (float)rand.NextDouble() * bmpSize.Width,
            y1: (float)rand.NextDouble() * bmpSize.Height,
            x2: (float)rand.NextDouble() * bmpSize.Width,
            y2: (float)rand.NextDouble() * bmpSize.Height);
    }
}

static void DrawBigTextWithShadow(ICanvas canvas, string text)
{
    canvas.FontSize = 36;
    canvas.FontColor = Colors.White;
    canvas.SetShadow(offset: new SizeF(2, 2), blur: 1, color: Colors.Black);
    canvas.DrawString(text, 20, 50, HorizontalAlignment.Left);
}

static void SaveFig(BitmapExportContext bmp, string filePath)
{
    bmp.WriteToFile(filePath);
    Console.WriteLine($"WROTE: {filePath}");
}
```

### MauiGraphicsDemo.csproj
```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Maui.Graphics" Version="6.0.100-preview.6.299" />
    <PackageReference Include="Microsoft.Maui.Graphics.Skia" Version="6.0.100-preview.6.299" />
  </ItemGroup>

</Project>
```

## Resources
* [Animated Rendering with SkiaSharp and OpenGL](https://swharden.com/CsharpDataVis/)
* [Microsoft.Maui.Graphics on GitHub](https://github.com/dotnet/Microsoft.Maui.Graphics)
* [Microsoft.Maui.Graphics on NuGet](https://www.nuget.org/packages/Microsoft.Maui.Graphics/)
* [SkiaSharp Graphics in Xamarin.Forms](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/)
* [Maui.Graphics](https://maui.graphics)