Creating Image Markup Tools Part 2
Let's continue the toolmaking process from the last few articles. I want to create a tool that will let me insert text into an image. Ultimately, I want to update the image I am inserting into a tag on an MP3 file for one of my compositions created with MuseScore Studio. I hope you've noticed that the code I've share thus far has nothing to do with my goal. I've been writing PowerShell code to insert text into any image. I don't want to restrict the function I plan on writing. Later, I will show you how I can leverage the function to meet my MuseScore Studio needs.
We ended last time with a set of PowerShell commands that insert text into an image.
$text = "Behind the PowerShell Pipeline"
$image = "C:\work\blue-robot-ps.jpg"
$imageObject = [System.Drawing.Image]::FromFile($image)
$rect = [System.Drawing.RectangleF]::new(0, 0, $imageObject.Width, $imageObject.Height)
$graphics = [System.Drawing.Graphics]::FromImage($imageObject)
$size = "36"
$font = "Cascadia Code"
[System.Drawing.FontStyle]$FontStyle = 'Italic'
$fontObject = New-Object System.Drawing.Font($Font, $Size, $FontStyle)
[System.Drawing.Color]$FontColor = 'DarkSlateGray'
$brush = New-Object System.Drawing.SolidBrush($FontColor)
$stringFormat = New-Object System.Drawing.stringFormat
$stringFormat.Alignment = "Center"
$stringFormat.LineAlignment = "Center"
$stringFormat.FormatFlags = [System.Drawing.stringFormatFlags]::NoClip
$graphics.DrawString($text, $fontObject, $brush, $rect, $stringFormat)
$output = "c:\temp\preview.jpg"
$imageObject.Save($output)
I can't stress enough the importance of validating your code. If your commands won't work in an interactive PowerShell console session, creating a script or function around them will be more difficult.
Creating a Function
Creating a function lets me take the code I have and make it reusable.
Naming
The first decision is what to call it. One easy approach is to complete the sentence, "This function will...". Ideally, the action will be a standard verb. However, sometimes you need to be creative. When I complete the sentence, I get "This function will insert text into an image." But Insert
is not a valid verb. Running Get-Verb
I would say Add
is the best choice. I can use the noun component to describe what I am adding. Thus, a name like Add-TextToImage
seems reasonable. I can also create an alias of "Insert-TextToImage
which satisfies the way I'm thinking about the function. And because I don't like to type any more than I have to, I can also define a short alias of ati
.
Function Add-TextToImage {
[CmdletBinding(SupportsShouldProcess)]
[Alias("Insert-TextToImage","ati")]
Because my function will be changing something, it needs to support -WhatIf. That's why I added the SupportsShouldProcess
attribute.
Parameters
Next, I want to parameterize my code. This should be self-evident. Obviously, I want to allow the user to specify the image and the text. Looking at my console code, any variable where I am assigning value, other than an object, is a good candidate for a parameter.
These would make good parameters:
$size = "36"
$font = "Cascadia Code"
[System.Drawing.FontStyle]$FontStyle = 'Italic'
$output = "c:\temp\preview.jpg"
[System.Drawing.Color]$FontColor = 'DarkSlateGray'
I should have the flexibility to specify these settings, but not be forced to. I can set default values which will make the function easier to run. But if I need to make a change to a setting like the font, I can do that too.
The variable name in my console code can become the parameter name.
Param(
[string]$Path,
[string]$Text,
[string]$Font = 'Arial',
[int]$Size = 72,
[System.Drawing.Color]$FontColor = 'CornSilk',
[System.Drawing.StringAlignment]$VerticalAlign = 'Center',
[System.Drawing.StringAlignment]$HorizontalAlign = 'Center',
[System.Drawing.FontStyle]$FontStyle = 'Regular',
[string]$PreviewPath,
You'll notice that I am casting some of the parameters to a specific type like System.Drawing.StringAlignment
. I could have made this a String
and passed the value later in my code. However. would need to validate any value the user entered. Using Get-TypeMember
, I can check if the class is an enum or has a set of valid values.
PS C:\> Get-TypeMember System.Drawing.StringAlignment
Type: System.Drawing.StringAlignment
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
Center Field System.Drawing.StringAlignment True
Far Field System.Drawing.StringAlignment True
Near Field System.Drawing.StringAlignment True
GetType Method Type
HasFlag Method Boolean
ToString Method String
By using the type instead of the more generic String
, PowerShell will automatically validate the value and provide tab completion! I don't have to do anything else. I see this is also true for the font style and color.
PS C:\> Get-TypeMember system.drawing.fontstyle
Type: System.Drawing.FontStyle
Name MemberType ResultType IsStatic IsEnum
---- ---------- ---------- -------- ------
Bold Field System.Drawing.FontStyle True
Italic Field System.Drawing.FontStyle True
Regular Field System.Drawing.FontStyle True
Strikeout Field System.Drawing.FontStyle True
Underline Field System.Drawing.FontStyle True
GetType Method Type
HasFlag Method Boolean
ToString Method String
The other way you can test if you can use a type as a parameter is to try to cast a string to that type. If it works, you can use it as a parameter type.
PS C:\> "violet" -as [system.drawing.color]
R : 238
G : 130
B : 238
A : 255
IsKnownColor : True
IsEmpty : False
IsNamedColor : True
IsSystemColor : False
Name : Violet
PS C:\> "italic" -as [system.drawing.fontstyle]
Italic
Although this isn't foolproof. You can also type the class name followed by ::
and press Ctrl+Space
to see if PSReadline can provide tab completion.