top of page
Search
Writer's pictureWill

Learn python through Hex-to-RGB conversion


To continue the exercise in python scripting from my last blog post, here is a trick to convert hex color codes back into RGB values. If you’ve used some of the color components in Rhino GH, you’ve probably noticed that there isn’t one that lets you input the six letter string (remember from last post that a string is just the data type for plain English texts) of hex color code. For example, FFFFFF stands for RGB 255, 255, 255, which is white. 48E195 stands for a blue-ish green. The mechanisms behind the conversion can easily be found on Wikipedia and other websites but I’ll include my version of explaining it here as well.

Like always, it is highly likely that you can script this up with native GH components. But just like always, our goal is to NOT deal with a whole bunch of GH noodle wires.

Let’s go over the mechanism briefly. Hex color code gets its name from its hexadecimal numeric system, which has 0 to 9 to represent themselves and letter A to F to represent 10 to 15. Hexadecimal means every time a number is added up to 16, it moves up a digit. In decimal system (adding up to 10), we can count to 9 and the next one is no longer one digit but two. The tens digit gets a 1 and the singles go back to 0. So 10. Next time the singles goes over 9 again, the tens gets another 1. Hexadecimal just means that “jump-up” threshold is not 10 anymore but 16. I count to 35 below in hexadecimal.

0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,10,11,12,13,14,15,16,17,19,1A,1B,1C,1D,1E,1F,20,21,22,23

Pretty fun stuff. 23 in sixteen-base/hexadecimal denotes actually the number 35 in our decimal/ten-base system. Anything in red is no longer what it looks like. Who knew a letter can represent a number?

Hex code is simply smashing the RGB values all together in hexadecimal. So 255 in hex is FF, the color RGB of 255,255,255 is naturally FFFFFF. To convert it back, first tri-sect it. FF is two digits. The ones and the sixteens both are 15. That means we need to multiply the first 15 by 16 and add the ones, 15, to the result. 15 * 16 + 15 = 255. I mentioned 48E195. First tri-sect it into 48, E1, 95. For 48, the sixteens digit is 4, that means it’s 16 * 4. The ones is 8, which is still 8 in ten-base. 16 * 4 + 8 = 72. So the red value of this color is 72. Similarly, green is E (i.e. 14) * 16 + 1 = 225 and blue is 9 * 16 + 5 = 149. You can verify if RGB 72, 225, 149 is in fact 48E195 here.

Time to write this into python codes.

First we need to lay down a few ground rules. There is no single digit notation for number 10 to 15 in our regular decimal system. If you type A, the computer doesn’t know it’s 10 in hex. We need a dictionary where the computer can look up the notation A and see that it means number 10. A dictionary is in fact a data type in python, denoted by {}.

Our dictionary in full is this: {‘A’:10, ‘B’:11, ‘C’:12, ‘D’:13, ‘E’:14, ‘F’:15}

Note that a dictionary consists of pairs. Comma separate between pairs and colon separate the key and its value. ‘A’ is a key. Remember the letter must be in quotation marks to be a string. Single letter A will send python looking for the value of variable A elsewhere. To use this dictionary, we can assign it to variable ‘cd’, for ‘color dictionary’.

cd = {‘A’:10, ‘B’:11, ‘C’:12, ‘D’:13, ‘E’:14, ‘F’:15}

To get values out of the dictionary, we can use the [] such as cd[‘B’], which is effectively 11. With the cd established, you can now type in python cd[‘E’]+3 and it will return a value of 17 (14+3). Dictionary is a mutable object (please see python documentation), so you can give new key-value pairs to it. There is a .get() method that conveniently helps us set up key-value pairs that are not in the dictionary. See following.

cd = {}

lttrs = ‘ABCDEF’

for i in range(len(lttrs)):

cd[lttrs[i]] = cd.get(lttrs[i],i+10)

We actually need these lines of codes in our final python script. What happens is the variable cd is first initiated as an empty dictionary, similar to how we set up an empty list in the last post. Then we define a string that has all the letters in the sixteen-base system. Lastly, we use a for loop that goes through 0 to 5, the length of the string “lttrs”, and assign a number to a key in the dictionary. The .get() method takes two parameters. The first is the key name in the dictionary. If there is that key in the dictionary, the method returns its value. Otherwise, establish that key in the dictionary and use the second parameter as its value. Notice here that we are subscribing to the string “lttrs” as if subscribing to a dictionary or list with the brackets. They work in the same way. If we type “bobbyishome”[4], python knows it evaluates to the single letter “y”, fifth in the string.

The code above is a slightly smarter way of establishing that color dictionary, rather than hard-coding each key-value pair. With the dictionary set up, we can later make references to it and get the familiar ten-base number for anything above 9 in the sixteen-base.

On our python GH component, we must set up one input parameter that will take in the hex code. Since our dictionary has all letters in upper case, it is easier if we unify the case. A string has a method called upper(), which simply make every lower case letter an upper case. Below is the entirety of the code, which I will break down and explain. For now we can see that line 5 is where the upper method is getting called on hex.


code in GHPython

Line 6 uses an if statement. The basic syntax is such that “if a condition is true, execute whatever is after the colon”. There are several condition evaluations such as the one shown above. The exclamation mark means “not”. “!=” means not equal to. Our conditional statement is asking if the length of our string (how many letters in it) is not equal to 6. If this condition is satisfied, the “raise” after colon will be executed. Keyword “raise” raises errors in the code and stop execution. For our conversion to work, we need this to prevent people from inputting nonsensical strings. For example, if we feed in a single letter “n” into the hex parameter, the code would stop and spit out an error because of this conditional statement we set up.

Anything with a colon in python can be indented on the next lines, as seen in for loops of last blog. However, if there is only one statement after the colon, both sides of the colon can be on the same line. Python is an implicitly line-based language, unlike for example C# where one has to use a semi-colon to denote end of a line. Line-based means that no two statements or commands can be jammed on one line. The following codes will execute.

A = 5

B = 6

C = A + B

print ( C )

The following is gonna generate errors

A = 5 B = 6 C = A + B print ( C )

Sometimes for legibility, we can write indents into the single statement conditionals. Our line 6 could be written like this:

if len(hex) != 6:

raise

Line 7 and 8 are a for loop that checks whether a given string contains out of range characters. The correct range is number 0 to 9 and the six letters defined in our “lttrs” variable. In plain English, the condition is “if a letter in hex is not a number digit and not within ‘ABCDEF’, do what’s after the colon”. Here we have an “and” keyword, meaning that both conditions must evaluate to true for stuff after colon to execute. In our case, we want the code to break intentionally when there is a character in the string hex that is neither a number nor a letter in “ABCDEF”. We use the for loop to iterate through the characters in hex. The first condition, whether it’s a number digit, is done by the .isdigit() method. This is a string method. “A”.isdigit() will yield a false; “6”.isdigit() will yield a true. Again remember that “6” is a string because of the quotation marks. “6” + “2” will produce “62” and not 8. The “not” keyword is to flip the Boolean value so the condition is really testing if the letter is NOT a number. After keyword “and” is our second condition evaluating if the letter is NOT part of “lttrs” (‘ABCDEF’).

This is a lot of condition statement talk. Let’s step away from our main code and look at some examples. Say we want to look at whether a letter is part of a string. If it is, we print out some texts. We need to use the following code.

A = ‘billywantsicecream’

if ‘w’ in A:

print ( ‘w is in the string’ )

Of course when we run this script, the module will just show ‘w is in the string’. If we test letter “d”, nothing would happen. Sometimes we want to perform actions if the condition is not met. For instance, if the letter is in the string, print a statement. If not, add that letter to the end of the string. We use a keyword “else” for this situation. See below.

A = ‘billywantsicecream’

if ‘d’ in A:

Print ( ‘d is in the string’ )

else:

A = A + ‘d’

Sometimes we need more than two scenarios. The keyword “elif” comes into play. It is just short for “else if”.

A = ‘billywantsicecream’

if ‘d’ in A:

Print ( ‘d is in the string’ )

elif ‘b’ in A:

Print ( ‘at lease b is in the string’ )

else:

A = A + ‘d’

This code will execute the second scenario because ‘b’ is in A. The else scenario will be skipped. We can have as many “elif” as possible. Remember that as soon as one condition is true, the rest of the “elif” and “else” will be skipped. Also “elif” must follow a previous “if” and “else” must follow either an “if” or an “elif”. Below is a non-example. It will create an error.

if ‘t’ in ‘abc’:

Print ( ‘ok’ )

num = 5 + 7

elif ‘c’ in ‘abc’:

Print ( ‘yes’ )

The variable assignment of “num” disrupts the conditional statements. We can stack “if” statements without the “elif” but they will be individual clauses, meaning that a satisfied “if” doesn’t prevent the next “if” from evaluating or executing.

A = ‘billywantsicecream’

if ‘b’ in A:

Print ( ‘b is in the string’ )

if ‘i’ in A:

Print ( ‘i is in string’ )

The script will print out both ‘b is in the string’ and ‘i is in the string’.

Back to our code, the first 8 lines are all setup work, making sure that we take in a string that can be successfully converted, like 48E195. The next lines define the math to get the RGB values. Here I introduce custom defined functions. Things like len() and range() are functions python already knows how to execute. There isn’t a pre-loaded one that converts 16-base to 10-base numbers. We can make our own.

Functions are usually defined for better clarity of the code and repeating calls. They make the code easier to read because they take in a fix number of things and spit out a fixed number of things. A function is very much like a component in GH. Once made, we don’t need to trace through every line of code in it to know what it does. It’s also a clean way to reuse code. If a task consists of ten lines and we need to do this task for a hundred times, it get ridiculous to copy and paste the same ten lines for a hundred times. We define it once and call it by its name later.

The “def” keyword signifies a custom function. Following it is the name of the function and its intended input parameters in parentheses. In our code, the first function is called “ToTen()” and it takes in only one parameter, temporarily denoted as “s”. Within the “def” block of codes, every time we refer to “s”, we are referring to the value of the parameter that is passed inside the function.

“ToTen()” is essentially a huge try-except construct. The try keyword is used when one wants to wrap a dangerous part of code in a safe blanket. By dangerous I mean snippets of code that may cause the program to throw an error and stop executing. In our ToTen function we first try to convert the string into an integer. If it succeeds, we use the “return” keyword to return the integer times 16 as our function value output. Within a function “def” block, the return keyword will stop executing any code below. For a function, the parentheses following the name takes in parameters. Those are in-place variables that are fed into the function later. Here a name is used just for reference. "ToTen(s)" gives a temporary variable name s to anything we will pass in later when we actually call this function. Within the "def" block, anytime we refer to s, the function knows in a called situation we mean to do stuff on the passed in variable.

So now if we call this function anywhere else, we simply need to put an input string in the parentheses. If we call ToTen(“3”), we will get 48. What happens if we do ToTen(“B”)? That’s where the “except” keyword comes in. Obviously if we try to execute int(“B”), python will give an error because letter cannot be cast into an integer. Because the int(“B”) is wrapped in the try keyword, the program knows NOT to stop executing. It knows to look for the except, under which condition the function should return the value associated with the key “B” in the dictionary “cd”.

You may have noticed that this is still not failsafe. If we run ToTen(“M”), there is still an error because it first tries to convert letter M into an integer, then tries to look for the value under key M in the dictionary. M isn’t one of the keys so eventually the program will still give up. For brevity, we are assuming that the six character string coming into our python program is in fact a valid hex code. If not, an error will be raised and so be it.

Now we can use this ToTen() function everywhere else to convert the sixteens into tens. Very similarly, a ToSingle() function is defined to get us the digit below sixteen. (In sixteen base, let us conceptualize the amount of 13 as a single digit, since it is denoted by a single character)

The third function that we define is the one that takes in the hex code in whole and spits out the RGB values. First, as on line 27, we initiate an empty list. Then, since we know that hex codes have six characters/digits, we can iterate through only the sixteens digit. It’s as simple as explicitly spelling out the 0, 2, and 4 indices and use it in a “for” loop. The notation of “(0, 2, 4)” means it’s a tuple containing those three integers. A tuple is very much like a list but it is fixed. My understanding of it is that it is stripped of many of the list functions that allow modifications to lists so a tuple is a lighter-weight memory object in the computer. It may speed up computing time if there is no need to shuffle around the objects contained within.

As we iterate through the indices, we can use the “append” function to add values of the sixteens and the singles to the list “rgb”. Note that if “i” is on a sixteens index, the single after it is at “i+1” index. In the end, we return the list out of the function.

On the python module we rename the default “a” output to “RGB” and use line 32 to execute the conversion. Now we should be able to see RGB values of a valid hex code. Both this blog and the last one may be a little verbose. That’s probably because I over explained a few thing. However, there is much ground covered in these two posts. Although short, the two python scripts touch on many useful routines and keywords in python programming. I hope they are helpful.

Recent Posts

See All

How to pass all ARE exams

Now that I’ve finished all my ARE exams and registered as an architect, I can summarize a few pointers for anyone still in pursuit. These...

ARE 5 and prep materials

Here is an update on my progress on ARE 5 exams and my experiences with the preparation materials on the market. Ever since I passed my...

GhPy can multi-thread too! But wait...

GhPy component is known to be a bit slow in its performance because of the JIT compiler it utilizes. However, GhPy is a powerful widget...

bottom of page