Icon Color Replacement Fun with PowerShell

I like to keep interfaces simple and tend to use icons in my GUI designs. Recently, when making a PowerShell GUI to alert myself to alarm status changes in vCenter, I decided to reuse the same icon over and over for different statuses, but I made them meaningful by dynamically changing the color.

The Goal

My ultimate goal was to have 3 icons that were colored white, yellow and red.

white
yellow
red

How I did it

The first thing I did was search iconfinder for a free icon, and decided on this one:

1442847887__server

Using paint.net, I determined that the base color of iconfinder's server icon was #444444. Now what's really cool about iconfinder, is they also offer the base64 code. As discussed in my earlier post, base64 can easily be used as icons and images in WPF forms.

So let's take a look at what the code below executes to accomplish this task

  1. Loads WPF assemblies and sets base64 icon variable
  2. Creates a bitmapimage object to enable streaming of the base64 image
  3. Creates a colormap. Colormap is simple and just contains .OldColor and .NewColor.
  4. Passes colormap to an imageattribute which performs a SetRemapTable()
  5. Graphics.DrawImage draws an image based on these new imageattributes
  6. Saves the icon to variable for later use within the script ($iconwhite, $bmpwhite)
  7. Saves and opens the new icon so that you can confirm it worked.
  8. Do this for all colors within the newcolor array

# Add assemblies Add-Type -AssemblyName PresentationFramework, System.Drawing

set base64 image

$base64 = "iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA80lEQVRoQ+2ZUQ6DMAxDy9167 N5tEx8gpI3K9mbQFu87Nc1zHKSxNOLXe38Q5XLpGGNBD8OFq2AaALHGgTNQfzVCjM3I5BzhMNpyiJmHpIEJAd kBhKpaw7ibBlTKs3OXOMA8BGkyW8hJyamdECN02RomX3UdYCghDmQLOSk5tetmAKGq1jD5qusAQwlxIlvIScm pXTcDCFW1hslXXQcYSogT2UJOSk7tuhlAqKo1TL7igEr59j+2HBffNDNCn9I9+5LDkEXvQIUYFU0DKKnWWhx4 BysjpIzQVd+AibtNS7eNtmcgDXwLLajz4sB67ldcOL4Qnz7Y/DHodqPAAAAAAElFTkSuQmCC"

Create bitmapimage to enable streaming

$script:bitmap = New-Object System.Windows.Media.Imaging.BitmapImage $bitmap.BeginInit() $bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64) $bitmap.EndInit() $bitmap.Freeze()

Setup reusable objects

$colormap = New-Object System.Drawing.Imaging.ColorMap $attributes = New-Object System.Drawing.Imaging.ImageAttributes $rectangle = New-Object System.Drawing.Rectangle(0, 0, $bitmap.Width, $bitmap.Height)

$newcolors = "white","yellow","red"

foreach ($color in $newcolors) { $bmp = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($bitmap.StreamSource) $colormap.OldColor = "#444444" $colormap.NewColor = $color $attributes.SetRemapTable($colormap) $graphics = [System.Drawing.Graphics]::FromImage($bmp) $graphics.DrawImage($bmp, $rectangle, 0, 0, $rectangle.Width, $rectangle.Height, "Pixel", $attributes)

$newicon = \[System.Drawing.Icon\]::FromHandle($bmp.GetHicon())
# Set new variable to use icons and bmps within your script ($iconwhite,$bmpwhite)
Set-Variable -Name "bmp$color" -value $bmp
Set-Variable -Name "icon$color" -value $newicon
$bmp.save("$env:temp\\$color.png", "png")
Invoke-Item "$env:temp\\$color.png"

}

System.Drawing.Graphics likes to leak memory. Dispose it.

$graphics.Dispose()

This is high performance, and the total conversion time is less than 5 ms, which makes this technique efficient enough to use in all of your PowerShell GUI apps, if you're so inclined :)