Font Anti-Aliasing in PowerShell GUIs


The text on winform projects may seem blocky sometimes, particularly at larger font sizes. It never seems to look as polished as those we see with Microsoft products.

Here’s a standard form using Segio 24pt in white. It’s pretty blocky, especially around letters such as ‘S’ and ‘P’. Click on the image below to see it in normal size to get an idea how this looks.


Text without Anti-Aliasing

In order to make our text smoother and more, anti-aliasing will need to be used.

What’s Anti-Aliasing?

Anti-aliasing is the name given to actions which aim to minimise this type of blockiness. It works by shading the pixels along the borders of an image. In order to achieve this for ourselves, we need to dig into some .NET methods and events.

This post shows how we do just that for our fonts in PowerShell Studio, but can be applied in whatever way you develop your GUI apps.

Create a Project

Create a new forms project, making the form a good size. Now, drag a PictureBox control onto the form, and set the size of it close to the size of the form itself.

Set the properties for the PictureBox as follows :

Dock = ‘Top’
BackColour = 1;3;86

Create Positioning Functions

Because we’re going to be working with graphics, we need to define placement based on a horizontal and vertical basis. Whilst we could use absolute positioning, where we manually specify a fixed location, this style is rendered invalid if we make changes to size of the control or the graphic itself. Its relative positioning is changed.

Instead, if we use calculations for positioning, it gives us the flexibility to change control and graphic sizes after the code has been written without having to modify it later.

Thinking about this, there are several things we need to do :

  • Get the midpoint of the control (our picture box)
  • Get the midpoint of the graphic (our drawn string)
  • Get the position for placement of the graphic on the control

Picture Box Control

In this function, we simply pass in the PictureBox control, and work out its center point by adding the left most point with its width, and then divide by two. This gives us the horizontal (aka x) location. Similarly, we add the control’s top most point with it’s height and divide by two to get its vertical center point. These values are then returned as an object.

function Get-PictureboxMidPoint
        [Parameter(Mandatory = $True)][System.Windows.Forms.PictureBox]$PictureBox
    $objPictureBoxMidPoint = New-Object -TypeName System.Object | Select-Object -Property x, y
    $objPictureBoxMidPoint.x = ([int32] (($PictureBox.Left + $PictureBox.Width) /2))
    $objPictureBoxMidPoint.y = ([int32] (($PictureBox.Top + $PictureBox.Height) /2))

Drawn String

We’re working in a similar manner to the above function, but require to do a bit more calculation. We pass in a graphics object, previously instantiated, the string to be drawn, and the font itself. The MeasureString method of the grahpics control is then invoked, using the other two parameters we’ve just mentioned. Once that is completed, we obtain the horizontal and vertical midpoints.

function Get-TextMidPoint
        [Parameter(Mandatory = $True)][System.Drawing.Graphics]$Graphics,
        [Parameter(Mandatory = $True)][String]$Text,
        [Parameter(Mandatory = $True)][System.Drawing.Font]$Font
    $TextSize = $Graphics.MeasureString($Text, $Font)
    [single]$x = [int32] ($TextSize.Width / 2)
    [single]$y = [int32] ($TextSize.Height/ 2)
    $TextMiddlePoint = New-Object -TypeName System.Object | Select-Object -Property x, y
    $TextMiddlePoint.x = $x
    $TextMiddlePoint.y = $y

Position from Drawn String

Now that we have our two sets of midpoints, we can calculate where the drawn string should be placed. This is done by subtracting the result of the latter function from the former for both x and y positions, and returning the results.

function Get-TextPosition
        [Parameter(Mandatory = $True)][System.Object]$MiddlePoint,
        [Parameter(Mandatory = $True)][System.Object]$TextSize
    [int32]$x = ($MiddlePoint.X - $TextSize.X)
    [int32]$y = ($MiddlePoint.Y - $TextSize.Y)
    $textPosition = New-Object -TypeName System.Object | Select-Object -Property X, y
    $textPosition.x = $x
    $textPosition.y = $y

Create Event Code

Now that we’ve defined out functions, we can setup the event action and code.

The Paint event occurs when a control requires repainting. This can happen as a result of other actions on a form or control, such as scrolling to the bottom of a form at to the top again.
Go to the Paint event of the PictureBox in the properties panel, and double click on it. Insert the following :

#Event Argument: $_ = [System.Windows.Forms.PaintEventArgs]
    $g = $_.Graphics
    $picturebox1.Dock = 'Top'
    $picturebox1.BackColor = '1,36,86'
    $drawString = 'PowerShell Studio'
    $white = [System.Drawing.Color]::White
    $drawfont = New-Object -TypeName System.Drawing.Font -Args ('Segoe UI', 84)
    $drawBrush = New-Object -TypeName System.Drawing.SolidBrush -Args ($white)
    $middlePoint = Get-PictureboxMidPoint -PictureBox $picturebox1
    $middleText = Get-TextMidPoint -Graphics $g -Text $drawString -Font $drawfont
    $textposition = Get-TextPosition -MiddlePoint $middlePoint -TextSize $middleText
    $drawpoint = New-Object -TypeName System.Drawing.PointF -Args ($textposition.x, $textposition.y)
    $g.TextRenderingHint = 'AntiAliasGridFit'
    $g.DrawString($drawString, $drawfont, $drawBrush, $drawpoint)

Code explanation

What happens in this code is the following:

  • We obtain the Graphics properties from the event data and assigning it to a variable $g.
  • We define some of the properties of the picturebox control.
  • A Font type variable is created using Sergoe UI typeface in size 84 as the supplied parameters
  • A SoldBrush object is created. This is the drawing style we use, just like in the Paint application.
  • The functions mentioned previously are then used
  • With the above completed, we define a floating point coordinate object
  • We also set the type of anti-alias rendering to be carried out. Several options are available. See the links at the bottom of this page to get more information on these
  • We now run the DrawString method to create and position the anti aliased rendering of the font.

View the New Results

Now run the project to see a much smoother version.


Text with Anti-Aliasing

I’d recommend taking a look at the System.Drawing namespaces documentation on MSDN to get more information on the methods, properties, and enumerations used in this code, which will explain in more detail about each.

You can find the Project code and exported code (for those not using PSStudio) at my GitHub repository, and also a video of this in action on my YouTube channel.




Extracting Subtitles from MKV Files

I’m not certain whether it’s linked to being an expat living in the Netherlands trying to understand what’s being said on their television channels, or just that my hearing isn’t is what it used to be, but it’s quite rare for me to watch anything without subtitles, whatever the language…. Often though, it’s the case that my TV is not able to use the subtitles embedded within an MKV file. Instead, I need to extract the subtitles data stream to a separate SRT file, and ensure the filename matches the MKV one, apart from the suffic.

This post follows on from one of a couple of weeks ago about getting MKV file information, and goes into how we can also extract the subtitles for one of these files.

The script is posted below. As you’ll probably already have noticed, it’s quite similar to the cmdlet from the earlier post. We’re accepting ‘FullName’ as an alias of the Path, and we’re constructing the command to be executed dynamically by combining the command parameters with the values that have been passed in. Also, the filename of the SRT file is matched to the original MKV file so we don’t need to change it after.

function Get-MKVSubtitles            
        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)][Alias('FullName')] [string] $Path,            
        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)] [string] $Track                   
        $Destination = ($Path.Substring(0,$Path.Length - 4)) + '.srt'            
        $command = "D:portablemkvtoolnixmkvextract.exe tracks -q `"$Path`" $Track`:`"$Destination`""            
        Write-Verbose -Message "Path        : $Path"            
        Write-Verbose -Message "Track       : $Track"            
        Write-Verbose -Message "Destination : $Destination"            
        Write-Verbose -Message "Command     : $command"            
        Invoke-Expression -Command $command                     

If we have one file, we can use the cmdlet like this:

Get-MKVSubtitles -Path C:DataVideosMyMovie.mkv -Track 0

PowerShell’s pipeline functionality comes into great effect if we have multiple MKV files with subtitles in a directory.  We could use a combination of Get-ChildItem, and Get-MKVTrackInfo with Get-MKVSubtitles :

Get-ChildItem -Filter *.mkv | Get-MKVTrackInfo | Where-Object -Property TrackType -EQ -Value 'Subtitles' | Get-MKVSubtitles

If your MKV files were spread over a series of sub-directories, you could even add the -recurse parameter to Get-ChildItem, since FullName is passed through the pipeline to Get-MKVInfo.


Getting MKV Stream Data Information with PowerShell and the MKVToolnix Toolkit

I often want to get information about an MKV file, usually to find out if it has one or more subtitle tracks. MKVToolNix is my toolset of choice for this. Automation of this process turned out to be relatively straight forward with PowerShell (naturally!) and one of their tools, mkvinfo.

Before we go into the cmdlet details, you will need to download and install the MKVToolNix toolset if you do not already have it already You can get this by visiting the site of the author, Moritz Bunkus, at

A word of warning. We’re using ‘Prayer Based Parsing’. If a future revision of mkvinfo changes the format of output, there’s a good chance our script will cease to work. I’m pretty certain more RegEx aware gurus will be able to tighten the parsing a bit to lessen the chance of this, but it’s still something to think about.

Looking at the code, ‘FullName’ is defined as an alias for Path in the cmdlet, to allow the use of pipeline output from cmdlets such as Get-ChildItem. That way, track information from multiple files can be obtained quite simply.

Also remember to change the path in the code below to where your mkvinfo.exe file exists.

Once you’ve loaded the function into memory, it can be used simply the following way :

Get-MKVTrackInfo -Path C:tempmovie.mkv

An example, also showing how we can combine it with Get-ChildItem is below.

Get-MKVInfoChildItemIn the next post, we’ll make use of another MKVToolnix tool and PowerShell to allow us to extract subtitle files from MKV files.

Any feedback, comments, errata always welcome. 🙂

function Get-MKVTrackInfo
        [Parameter(Mandatory = $True, ValueFromPipelineByPropertyName = $True)][Alias('FullName')] [string] $Path
        $info = & 'C:Program FilesMKVToolNixmkvinfo.exe' $Path  | Where-Object -FilterScript {
            ($_ -like '*Track number*') -or ($_ -like '*Track type*')
        $info = $info | ForEach-Object -Process {
            $_.replace('|  + ','')
        $info = $info | ForEach-Object -Process {
            $_.replace('(track ID for mkvmerge & mkvextract: ',':')
        $info = $info | ForEach-Object -Process {
        $info = $info | ForEach-Object -Process {
            $_.replace(': ',':')
        $info = $info | ForEach-Object -Process {
            $_.replace(' :',':')
        $info = $info | ForEach-Object -Process {
            $_.replace('Track number:','')
        $info = $info | ForEach-Object -Process {
            $_.replace('Track type:','')
        For ($index = 0;$index -lt $info.count;$index = $index + 2) 
            $tmpArray = $info[$index].Split(':')

            $hash = @{
                Track     = $tmpArray[1]
                TrackType = $info[$index+1]
                Path      = (Get-ChildItem -Path $Path).FullName
            New-Object -TypeName PsObject -Property $hash