I want to convert an iTerm2 colour scheme such as this one (Ubuntu) to the Windows Terminal color scheme. I have no idea how to do this! I have no idea what those iTerm2 colour schemes even mean. It is an XML file with what looks like RGB values in decimal. Moreover, instead of specifying colours it has entries like “Ansi 1 Color” etc. Whatever that means!
Here’s an excerpt of the file:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Ansi 0 Color</key> <dict> <key>Blue Component</key> <real>0.21176470816135406</real> <key>Green Component</key> <real>0.20392157137393951</real> <key>Red Component</key> <real>0.18039216101169586</real> </dict> |
I want to try and figure out what these mean and how I can convert them to an iTerm2 format. Going to try and do this as I make notes in this blog. Hence some of the steps below might seem obvious or elementary.
I can download a colour scheme thus:
1 |
Invoke-WebRequest https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Ubuntu.itermcolors -Outfile download.xml |
And quickly view the colour keys thus:
1 |
$([xml](Get-Content ./download.xml)).plist.dict.key |
Here’s the output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Ansi 0 Color Ansi 1 Color Ansi 10 Color Ansi 11 Color Ansi 12 Color Ansi 13 Color Ansi 14 Color Ansi 15 Color Ansi 2 Color Ansi 3 Color Ansi 4 Color Ansi 5 Color Ansi 6 Color Ansi 7 Color Ansi 8 Color Ansi 9 Color Background Color Bold Color Cursor Color Cursor Text Color Foreground Color Selected Text Color Selection Color |
Since each <key>
element has a <dict>
too I can view those thus:
1 |
$([xml](Get-Content ./download.xml)).plist.dict.dict |
Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
key real --- ---- {Blue Component, Green Component, Red Component} {0.21176470816135406, 0.20392157137393951, 0.18039216101169586} {Blue Component, Green Component, Red Component} {0.0, 0.0, 0.80000001192092896} {Blue Component, Green Component, Red Component} {0.20392157137393951, 0.88627451658248901, 0.54117649793624878} {Blue Component, Green Component, Red Component} {0.30980393290519714, 0.91372549533843994, 0.98823529481887817} {Blue Component, Green Component, Red Component} {0.81176471710205078, 0.62352943420410156, 0.44705882668495178} {Blue Component, Green Component, Red Component} {0.65882354974746704, 0.49803921580314636, 0.67843139171600342} {Blue Component, Green Component, Red Component} {0.88627451658248901, 0.88627451658248901, 0.20392157137393951} {Blue Component, Green Component, Red Component} {0.92549020051956177, 0.93333333730697632, 0.93333333730697632} {Blue Component, Green Component, Red Component} {0.023529412224888802, 0.60392159223556519, 0.30588236451148987} {Blue Component, Green Component, Red Component} {0.0, 0.62745100259780884, 0.76862746477127075} {Blue Component, Green Component, Red Component} {0.64313727617263794, 0.3960784375667572, 0.20392157137393951} {Blue Component, Green Component, Red Component} {0.48235294222831726, 0.31372550129890442, 0.45882353186607361} {Blue Component, Green Component, Red Component} {0.60392159223556519, 0.59607845544815063, 0.023529412224888802} {Blue Component, Green Component, Red Component} {0.81176471710205078, 0.84313726425170898, 0.82745099067687988} {Blue Component, Green Component, Red Component} {0.32549020648002625, 0.34117648005485535, 0.3333333432674408} {Blue Component, Green Component, Red Component} {0.16078431904315948, 0.16078431904315948, 0.93725490570068359} {Blue Component, Green Component, Red Component} {0.14117647707462311, 0.039215687662363052, 0.18823529779911041} {Blue Component, Green Component, Red Component} {0.92549020051956177, 0.93333333730697632, 0.93333333730697632} {Blue Component, Green Component, Red Component} {0.73333334922790527, 0.73333334922790527, 0.73333334922790527} {Blue Component, Green Component, Red Component} {1, 1, 1} {Blue Component, Green Component, Red Component} {0.92549020051956177, 0.93333333730697632, 0.93333333730697632} {Blue Component, Green Component, Red Component} {0.0, 0.0, 0.0} {Blue Component, Green Component, Red Component} {1, 0.8353000283241272, 0.70980000495910645} |
What does this mean? RGB scales are usually on a 0 – 255 scale, so this confused me. After some Googling I realised you can have them on a 0 – 1 scale too. Thanks to StackOverflow. So a number X on the 0 – 255 scale can be converted to the 0 – 1 scale by dividing it by 255. Therefore a number in the 0 – 1 range above can be converted to 0 – 255 scale by multiplying it by 255. From there it’s an easy step to converting to RGB values in hex. Cool!
At this point I have the following rudimentary script:
1 2 3 4 5 |
$colorFileURL = "https://raw.githubusercontent.com/mbadolato/iTerm2-Color-Schemes/master/schemes/Ubuntu.itermcolors" [xml]$xmlObj = $(Invoke-WebRequest $colorFileURL).Content $keysArray = @($xmlObj.plist.dict.key) $valuesArray = @($xmlObj.plist.dict.dict) |
If I do $valuesArray[0]
I get the first line, and if I do $valuesArray[0].real
I get an array of 3 colors – Blue, Green, and Red.
1 2 3 4 |
❯ $valuesArray[0].real 0.21176470816135406 0.20392157137393951 0.18039216101169586 |
Ok, so I need something that will convert these to hex. Time to create a function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# function that takes as input an array of floating point numbers # it expects the first element to be the Blue, second to be Green, and last to be Red function ConvertReal2Hex { [CmdletBinding()] param( [Parameter()] [float[]]$Real ) # I take each decimal, multiply by 255, and cast to integer. This is because the -f operator is expecting an integer. # Learnt this through trial and error. [int]$Blue = $Real[0] * 255 [int]$Green = $Real[1] * 255 [int]$Red = $Real[2] * 255 "X{0:X2}{1:X2}{2:X2}" -f $Red,$Green,$Blue # The 0, 1, 2 stand for the place holder. The right side of the -f operator is an array, so 0, 1, 2 refer to the objects of this array. # Basically, one can mix up the array elements on the output (left side). The "X" tells the format operator to convert to a hex. # The 2 after the X tells it to do 2 places. I realized this when some of the output didn't have 2 places. } |
Let’s try it out:
1 2 |
❯ ConvertReal2Hex $valuesArray[0].real #2E3436 |
Cool! Quick test with my array:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
❯ foreach ($value in $valuesArray) { ConvertReal2Hex $value.real } #2E3436 #CC0000 #8AE234 #FCE94F #729FCF #AD7FA8 #34E2E2 #EEEEEC #4E9A06 #C4A000 #3465A4 #75507B #06989A #D3D7CF #555753 #EF2929 #300A24 #EEEEEC #BBBBBB #FFFFFF #EEEEEC #000000 #B5D5FF |
Nice! I can just capture this into a new array:
1 |
$hexColorsArray = foreach ($value in $valuesArray) { ConvertReal2Hex $value.real } |
So at this point I have a $hexColorsArray
with the hex values of the colors, and I have a $keysArray
with the “Ansi 0 Color” etc. whatever that is.
Here’s a place where one can find Windows Terminal themes. A sample theme looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ "name": "3024 Night", "black": "#090300", "red": "#db2d20", "green": "#01a252", "yellow": "#fded02", "blue": "#01a0e4", "purple": "#a16a94", "cyan": "#b5e4f4", "white": "#a5a2a2", "brightBlack": "#5c5855", "brightRed": "#e8bbd0", "brightGreen": "#3a3432", "brightYellow": "#4a4543", "brightBlue": "#807d7c", "brightPurple": "#d6d5d4", "brightCyan": "#cdab53", "brightWhite": "#f7f7f7", "background": "#090300", "foreground": "#a5a2a2" } |
I have no idea how to map the iTerm2 keys to these! Eugh.
Here’s what the colors section of my iTerm2 looks like:
Ok, so that’s 8 colours (Black – White) along with their bright variants. Hmm, those line up with the colours on this Wikipedia page too. And I have similar entries in Windows Terminals, so what I need is a mapping like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$colorMappings = @{ "Ansi 0 Color" = "black" "Ansi 1 Color" = "red" "Ansi 2 Color" = "green" "Ansi 3 Color" = "yellow" "Ansi 4 Color" = "blue" "Ansi 5 Color" = "purple" # I can't find magenta in the VSCode colors, so I go with purple "Ansi 6 Color" = "cyan" "Ansi 7 Color" = "white" "Ansi 8 Color" = "brightBlack" "Ansi 9 Color" = "brightRed" "Ansi 10 Color" = "brightGreen" "Ansi 11 Color" = "brightYellow" "Ansi 12 Color" = "brightBlue" "Ansi 13 Color" = "brightPurple" "Ansi 14 Color" = "brightCyan" "Ansi 15 Color" = "brightWhite" } |
That’s not all the colours as I am missing the following from the iTerm2 side:
1 2 3 4 5 6 7 |
Background Color Bold Color Cursor Color Cursor Text Color Foreground Color Selected Text Color Selection Color |
Hmm, turns out the sample colour scheme I was looking at is not complete. Looking at the official docs here’s a default colour scheme:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
{ "name" : "Campbell", "cursorColor": "#FFFFFF", "selectionBackground": "#FFFFFF", "background" : "#0C0C0C", "foreground" : "#CCCCCC", "black" : "#0C0C0C", "blue" : "#0037DA", "cyan" : "#3A96DD", "green" : "#13A10E", "purple" : "#881798", "red" : "#C50F1F", "white" : "#CCCCCC", "yellow" : "#C19C00", "brightBlack" : "#767676", "brightBlue" : "#3B78FF", "brightCyan" : "#61D6D6", "brightGreen" : "#16C60C", "brightPurple" : "#B4009E", "brightRed" : "#E74856", "brightWhite" : "#F2F2F2", "brightYellow" : "#F9F1A5" }, |
So I’ve got four more to add. There’s three entries that I don’t have a mapping for so I’ll make a dummy mapping for these now, and prefix with an exclamation mark to ignore them later. Let’s put this into a function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
# function that takes as input a string with the iTerm color and outputs the Windows Terminal Color function ConvertTerm2Terminals { [CmdletBinding()] param( [Parameter()] [string]$iTermColor ) $colorMappings = @{ "Ansi 0 Color" = "black" "Ansi 1 Color" = "red" "Ansi 2 Color" = "green" "Ansi 3 Color" = "yellow" "Ansi 4 Color" = "blue" "Ansi 5 Color" = "purple" # I can't find magenta in the VSCode colors, so I go with purple "Ansi 6 Color" = "cyan" "Ansi 7 Color" = "white" "Ansi 8 Color" = "brightBlack" "Ansi 9 Color" = "brightRed" "Ansi 10 Color" = "brightGreen" "Ansi 11 Color" = "brightYellow" "Ansi 12 Color" = "brightBlue" "Ansi 13 Color" = "brightPurple" "Ansi 14 Color" = "brightCyan" "Ansi 15 Color" = "brightWhite" "Cursor Color" = "cursorColor" "Cursor Text Color" = "!cursorTextColor" # I made this up. Ignore it. "Bold Color" = "!boldColor" # I made this up. Ignore it. "Selected Text Color" = "!selectionForeground" # I made this up. Ignore it. "Selection Color" = "selectionBackground" "Background Color" = "background" "Foreground Color" = "foreground" } $colorMappings.$iTermColor } |
I can convert from one to another thus:
1 |
$winColorNamesArray = foreach ($key in $keysArray) { ConvertTerm2Terminals $key } |
So at this point I have $winColorNamesArray and $hexColorsArray array. All I have to do now is output a JSON colour scheme. That’s easy, loop through the arrays, ignore the colours I marked earlier, make a hash-table of the rest, add a name key based on the URL, and put all this into JSON. That sounds big when I say, but is simple in PowerShell:
1 2 3 4 5 6 7 |
$finalOutput = @{} $finalOutput["name"] = $(Split-Path $colorFileURL -LeafBase) for ($i = 0; $i -lt $winColorNamesArray.Length; $i++) { if ($winColorNamesArray[$i] -notmatch "^!") { $finalOutput[$winColorNamesArray["$i"]] = $hexColorsArray[$i] } } $finalOutput | ConvertTo-Json |
Awesome. Let’s try this now:
Paste this block of JSON into my Terminals settings fine, assign one of my entries the theme “Ubuntu” (or whatever your theme name is), and voila! here’s my command prompt with this theme.
I’ve put the final script in my GitHub repo here in case it’s of use to anyone else.
Update: The script has been updated to also allow one to specify an already downloaded iTerm2 colours file as input.