{"id":6256,"date":"2026-06-15T13:09:34","date_gmt":"2026-06-15T12:09:34","guid":{"rendered":"https:\/\/pmortensen.eu\/world2\/?p=6256"},"modified":"2026-06-26T14:32:30","modified_gmt":"2026-06-26T13:32:30","slug":"converting-to-and-from-rgb-and-hsv-values","status":"publish","type":"post","link":"https:\/\/pmortensen.eu\/world2\/2026\/06\/15\/converting-to-and-from-rgb-and-hsv-values\/","title":{"rendered":"Converting to and from RGB and HSV colour values"},"content":{"rendered":"<p><!-- T H E   B E G I N N I N G . . .   Marker for editing in WordPress: qqqq --><\/p>\n<p><!--\n  Future:\n\n    * Add the unit for the angle (degrees)\n\n    * Refactoring the code, e.g., to use small\n      helper function, instead of the massive\n      copy-paste reuse (for example, for\n      the formatting)\n\n    * Expand section \"Handling unbalanced RGB LEDs\"\n--><\/p>\n<p>The short version: A few lines of <a href=\"https:\/\/en.wikipedia.org\/wiki\/Python_%28programming_language%29\">Python<\/a> code using the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Matplotlib\">Matplotlib<\/a> library will convert between <a href=\"https:\/\/en.wikipedia.org\/wiki\/HSL_and_HSV\">HSV<\/a> and <a href=\"https:\/\/en.wikipedia.org\/wiki\/RGB_color_model\">RGB<\/a> colour values. An online tool is not required.<\/p>\n<p>For example, the open source keyboard firmware <a href=\"https:\/\/docs.qmk.fm\/#\/faq_general?id=what-is-qmk\">QMK<\/a> tends to use HSV values instead of RGB. Some common values are in file <em><a href=\"https:\/\/github.com\/qmk\/qmk_firmware\/blob\/master\/quantum\/color.h#L61\">color.h<\/a><\/em>.<\/p>\n<p>A trap for young players: Many online tools and code snippets don&#8217;t reveal what units they use (and thus expect) for HSV. For example, there is a difference between the <strong><em>formal<\/em><\/strong> HSV values and those scaled to fit the range for a byte (0-255). The latter is used in QMK. For RGB values, there isn&#8217;t any ambiguity (as far as I know).<\/p>\n<h2>Using Python<\/h2>\n<p>One option is using Python. The Python library <a href=\"https:\/\/en.wikipedia.org\/wiki\/Matplotlib\">Matplotlib<\/a> is used here, but it may not be installed by default:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nImportError: No module named matplotlib\r\n<\/pre>\n<p>On <a href=\"https:\/\/en.wikipedia.org\/wiki\/Debian\">Debian<\/a>-like systems (for example, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint#LMDE\">LMDE<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Mint\">Linux Mint<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Ubuntu_%28operating_system%29\">Ubuntu<\/a>, Debian itself, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Lubuntu\">Lubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Kubuntu\">Kubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Xubuntu\">Xubuntu<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/CrunchBang_Linux#BunsenLabs\">BunsenLabs<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Linux_Lite\">Linux Lite<\/a>, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Windows_Subsystem_for_Linux\">WSL<\/a>, etc.), install it by:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsudo apt update\r\nsudo pip install matplotlib\r\n<\/pre>\n<p>On a newer Linux system, you will probably get something like this error message:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nThis environment is externally managed\r\n<\/pre>\n<p>This cryptic error message (it does not even identify itself as an error message) means that it is too risky to change the <strong><em>global<\/em><\/strong> Python installation, as the operating system has become too dependent on Python (the operating system may become completely inoperable, requiring a reinstallation).<\/p>\n<p>Instead, the change can be isolated by using a so-called virtual Python environment (so the system\/global Python installation is not affected):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n# Create the virtual Python environment\r\npython3 -m venv ~\/.our_Matplotlib_environment\r\n\r\n# Enter the virtual Python environment\r\nsource ~\/.our_Matplotlib_environment\/bin\/activate\r\n\r\n# Install Matplotlib\r\npip install matplotlib\r\n<\/pre>\n<p>Module &#8216;venv&#8217; is probably installed by default. If it isn&#8217;t, it can be installed by:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nsudo apt update\r\nsudo apt install python3-venv python3-pip\r\n<\/pre>\n<p>After use, exit the virtual Python environment:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\n# Exit the virtual Python environment\r\ndeactivate\r\n<\/pre>\n<p>As an alternative method to the overhead and complexity of a virtual Python environment, including starting and exiting the virtual Python environment, a newfangled method is using &#8216;<a href=\"https:\/\/github.com\/astral-sh\/uv\">uv<\/a>&#8216; (a terrible, terrible name is the search engine era). It hides all the virtual Python stuff and takes take of it automatically. Thus, it <strong><em>appears<\/em><\/strong> to install Matplotlib globally, but behind the scenes it doesn&#8217;t.<\/p>\n<h3>The actual conversion<\/h3>\n<p>With Matplotlib installed, to convert, for example, interactively, from the command line on, for Linux:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\npython\r\n<\/pre>\n<p>On some Linux systems, this is instead executable &#8216;python3&#8217; (both newer and older Linux systems). In a virtual Python environment, &#8216;python&#8217; may work fine, even if it doesn&#8217;t for the global Python installation.<\/p>\n<p>After use, exit the interactive session:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nquit()\r\n<\/pre>\n<p>Convert an HSV value to RGB (before the exit):<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nimport matplotlib\r\nimport numpy as np # For set_printoptions()\r\n\r\nnp.set_printoptions(precision=4) # With byte values,\r\n                                 # 0-255, not more than 4 is warranted\r\n\r\n# Quarter intensity for pure HSV RED\r\n# (hue = 0 degrees, scaled to\r\n# [0.0; 1.0] for 360 degrees)\r\n256 * matplotlib.colors.hsv_to_rgb([[[0 \/ 256, 256 \/ 256, 256 \/ 256 \/ 4]]])\r\n# Result: array([[[64.,  0.,  0.]]]),\r\n#         that is, RGB value 400000\r\n#\r\n<\/pre>\n<p>Notes:<\/p>\n<ul>\n<li><a href=\"https:\/\/matplotlib.org\/stable\/api\/_as_gen\/matplotlib.colors.hsv_to_rgb.html#matplotlib-colors-hsv-to-rgb\"><em>hsv_to_rgb()<\/em><\/a> uses the (floating point) range [0.0;1.0] for all three values for both RGB and HSV. Thus, both the input and output values must be scaled (as is done in this example).<\/li>\n<li>It isn&#8217;t 100% clear if 255 or 256 should be used. Or maybe a linear transformation (with an offset) instead of only a scaling factor?<\/li>\n<li>&#8216;import numpy as np&#8217; works, because <a href=\"https:\/\/en.wikipedia.org\/wiki\/NumPy\">NumPy<\/a> is a dependency of Matplotlib (and thus will be installed when Matplotlib is installed)<\/li>\n<\/ul>\n<p>And for <a href=\"https:\/\/github.com\/qmk\/qmk_firmware\/blob\/master\/quantum\/color.h#L70\">HSV_TURQUOISE (0x7B5A70)<\/a> = <a href=\"https:\/\/github.com\/qmk\/qmk_firmware\/blob\/master\/quantum\/color.h#L44\">RGB_TURQUOISE (0x476E6A)<\/a> from the QMK project, converting from RGB to HSV:<\/p>\n<pre class=\"brush: plain; gutter: false; title: ; notranslate\" title=\"\">\r\nimport matplotlib\r\nimport numpy as np # For set_printoptions()\r\n\r\nnp.set_printoptions(precision=4) # With byte values,\r\n                                 # 0-255, not more than 4 is warranted\r\n\r\n# RGB_TURQUOISE (0x476E6A,  71, 110, 106) =\r\n# HSV_TURQUOISE (0x7B5A70, 123,  90, 112)\r\n#\r\nhsv2 = 255 * matplotlib.colors.rgb_to_hsv(\r\n               [[[0x47 \/ 255, 0x6E \/ 255, 0x6A \/ 255]]])\r\nhsv2\r\n# Result: array([[[123.141 ,  90.4091, 110.]]])\r\n\r\n# As hexadecimal HSV values rounded\r\n# to the nearest integer:\r\nprint(f&quot;{round(hsv2[0, 0, 0]):02X}&quot;)\r\nprint(f&quot;{round(hsv2[0, 0, 1]):02X}&quot;)\r\nprint(f&quot;{round(hsv2[0, 0, 2]):02X}&quot;)\r\n\r\n# Or formatted as an HSV hexadecimal value,\r\n# followed by the three decimal numbers,\r\n# plus the angle (0-360) for hue:\r\n#\r\nprint(f&quot;HSV 0x{round(hsv2[0, 0, 0]):02X}{round(hsv2[0, 0, 1]):02X}{round(hsv2[0, 0, 2]):02X}, {round(hsv2[0, 0, 0])} (angle {round(hsv2[0, 0, 0]\/255*360)}), {round(hsv2[0, 0, 1])}, {round(hsv2[0, 0, 2])}&quot;)\r\n#\r\n# Output:\r\n#\r\n#     HSV 0x7B5A6E, 123 (angle 174), 90, 110\r\n#\r\n<\/pre>\n<p>Note some advantages of this Python method:<\/p>\n<ul>\n<li>We have full control over the output, e.g., decimal or hexadecimal (or both), angle vs. byte value for the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hue\">H<\/a> (hue) value (or both), providing the exact kind of output we want, without having to do (manual) post converting and formatting<\/li>\n<li>It can easily be extended to handle a number of RGB values, instead of just one. With an online (or offline) tool, it would be by painstakingly converting one value at a time, using multiple steps (though it could be automated using a <a href=\"https:\/\/keyboardsexpert.com\/macropad-mechanical-keyboard-faqs\/\">macro keyboard<\/a>)<\/li>\n<\/ul>\n<p>The discrepancy for <a href=\"https:\/\/en.wikipedia.org\/wiki\/Brightness\">value, V<\/a> (brightness), 112 vs. 110 seems to be too high. Perhaps the QMK values are in error?<\/p>\n<p><!-- Later...\nThe units are:\n\n\n\n<ul>\n    \n\n<li>H: <\/li>\n\n\n    \n\n<li>S: <\/li>\n\n\n    \n\n<li>V: <\/li>\n\n\n<\/ul>\n\n\n--><\/p>\n<h2>Bonus: Handling unbalanced RGB LEDs<\/h2>\n<p>For example, on Keychron keyboards, probably depending on the model, for the same current, the red LEDs are only about half as bright as the blue and green LEDs.<\/p>\n<p>So the idea is to scale the RGB values recipocally, optionally rescaling the brightness to the maximum (convert to HSV, set the &#8216;V&#8217; to 255 and convert back to RGB).<\/p>\n<h2>References<\/h2>\n<ul>\n<li><em><a href=\"https:\/\/stackoverflow.com\/questions\/15278323\">Converting an image from RGB to HSV color space<\/a><\/em> (Stack Overflow. For Python). <a href=\"https:\/\/stackoverflow.com\/questions\/63066842\/how-can-i-convert-an-hsv-value-to-rgb-in-python\">Actual sample code<\/a>.<\/li>\n<li><a href=\"https:\/\/matplotlib.org\/stable\/api\/_as_gen\/matplotlib.colors.rgb_to_hsv.html\">matplotlib.colors.rgb_to_hsv<\/a> (a function in Python library <a href=\"https:\/\/en.wikipedia.org\/wiki\/Matplotlib\">Matplotlib<\/a>)<\/li>\n<li><a href=\"https:\/\/code.activestate.com\/recipes\/576554-covert-color-space-from-hsv-to-rgb-and-rgb-to-hsv\/\">Convert color space from HSV to RGB and RGB to HSV (Python recipe)<\/a>. A code snippet for conversion to and from RGB and HSV values. <strong><em>But it may be flawed<\/em><\/strong>. Or at least it does not state the units of the input values<\/li>\n<li><a href=\"https:\/\/stackoverflow.com\/questions\/8605847\/how-to-install-matplotlib\/40703800#40703800\">Installing Matplotlib<\/a> (Stack Overflow). At least on some versions of Ubuntu.<\/li>\n<li><a href=\"https:\/\/matplotlib.org\/stable\/gallery\/color\/named_colors.html\">A list of named colors<\/a><\/li>\n<\/ul>\n<p><!--\nExtra:\n\n--><\/p>\n<p><!-- T H E   E N D . . .   Marker for editing in WordPress: pppp --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The short version: A few lines of Python code using the Matplotlib library will convert between HSV and RGB colour values. An online tool is not required. For example, the open source keyboard firmware QMK tends to use HSV values &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"more-link\" href=\"https:\/\/pmortensen.eu\/world2\/2026\/06\/15\/converting-to-and-from-rgb-and-hsv-values\/\"> <span class=\"screen-reader-text\">Converting to and from RGB and HSV colour values<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[45,23,28,26,35,46,38,41,40],"tags":[],"_links":{"self":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6256"}],"collection":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/comments?post=6256"}],"version-history":[{"count":68,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6256\/revisions"}],"predecessor-version":[{"id":6532,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/posts\/6256\/revisions\/6532"}],"wp:attachment":[{"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/media?parent=6256"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/categories?post=6256"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pmortensen.eu\/world2\/wp-json\/wp\/v2\/tags?post=6256"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}