48

Is there a way to convert HSV color arguments to RGB type color arguments using pygame modules in python? I tried the following code, but it returns ridiculous values.

import colorsys
test_color = colorsys.hsv_to_rgb(359, 100, 100)
print(test_color)

and this code returns the following nonsense

(100, -9900.0, -9900.0)

This obviously isn't RGB. What am I doing wrong?

bjb568
  • 11,089
  • 11
  • 50
  • 71
AvZ
  • 997
  • 3
  • 14
  • 25

10 Answers10

61

That function expects decimal for s (saturation) and v (value), not percent. Divide by 100.

>>> import colorsys

# Using percent, incorrect
>>> test_color = colorsys.hsv_to_rgb(359,100,100)
>>> test_color
(100, -9900.0, -9900.0)

# Using decimal, correct
>>> test_color = colorsys.hsv_to_rgb(1,1,1)
>>> test_color
(1, 0.0, 0.0)

If you would like the non-normalized RGB tuple, here is a function to wrap the colorsys function.

def hsv2rgb(h,s,v):
    return tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h,s,v))

Example functionality

>>> hsv2rgb(0.5,0.5,0.5)
(64, 128, 128)
Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
24

If you like performance, it's best to avoid imports and use your own optimized code

Here's GIMP's code ported to python, tested at 99% acuracy, and improved for performance:
(this code happens to match colorsys almost exactly)

scalar = float # a scale value (0.0 to 1.0)
def hsv_to_rgb( h:scalar, s:scalar, v:scalar, a:scalar ) -> tuple:
    if s:
        if h == 1.0: h = 0.0
        i = int(h*6.0); f = h*6.0 - i
        
        w = v * (1.0 - s)
        q = v * (1.0 - s * f)
        t = v * (1.0 - s * (1.0 - f))
        
        if i==0: return (v, t, w, a)
        if i==1: return (q, v, w, a)
        if i==2: return (w, v, t, a)
        if i==3: return (w, q, v, a)
        if i==4: return (t, w, v, a)
        if i==5: return (v, w, q, a)
    else: return (v, v, v, a)

output:

>>> hsv_to_rgb( 359/360.0, 1.0, 1.0, 1.0 )
(1.0, 0.0, 0.016666666666666607, 1.0)

Using an if-chain like above is actually faster than using elif

Using a wrapper, like in Cyber's answer, takes a few extra steps for the interpreter to perform.
To add, the for loop in Cyber's example is a real performance killer when used like that

If you want slightly more performance, simply do this:
(I won't say this is the best possible performance, but it's certainly better)

scalar = float # a scale value (0.0 to 1.0)
def hsv_to_rgb( h:scalar, s:scalar, v:scalar, a:scalar ) -> tuple:
    a = int(255*a)
    if s:
        if h == 1.0: h = 0.0
        i = int(h*6.0); f = h*6.0 - i
        
        w = int(255*( v * (1.0 - s) ))
        q = int(255*( v * (1.0 - s * f) ))
        t = int(255*( v * (1.0 - s * (1.0 - f)) ))
        v = int(255*v)
        
        if i==0: return (v, t, w, a)
        if i==1: return (q, v, w, a)
        if i==2: return (w, v, t, a)
        if i==3: return (w, q, v, a)
        if i==4: return (t, w, v, a)
        if i==5: return (v, w, q, a)
    else: v = int(255*v); return (v, v, v, a)

^ this guarantees int() output with a range of 255 (the input is still the same)

>>> hsv_to_rgb( 359/360.0, 1.0, 1.0, 1.0 )
(255, 0, 4, 255)

NOTE: this code tests just as accurate as colorsys to GIMP (99% correct):

TIP: stay away from 3rd-party where possible, try the direct approach if you can.
exculusions: compiled C extensions such as PIL or NumPy, or ctypes wrappers such as PyOpenGL (uses the DLL)

Tcll
  • 7,140
  • 1
  • 20
  • 23
  • As @sfat mentioned... note that the first function in this answer assumes all `hsv` and `rgb` values are floats from 0 to 1 ([see `colorsys` docs](https://docs.python.org/3/library/colorsys.html)). The example given of `hsv_to_rgb(359,1,1)` is misleading. I'm saying this here because I assumed this mistake had been fixed with the 2017 edit of this answer (and therefore disregarded sfat's comment), but the error remains. – TimH May 08 '22 at 05:00
  • I think with code like this, you'd be better off writing a C extension, or using a fast library. This code kind of defeats one of the purposes of python - readability counts. IMHO "it's best to avoid imports and use your own optimized code" is a bad take - those who excel at writing performant code will be writing libraries with it. See also "hand assembling", "assembler vs C" and similar takes on performance. If you are converting a whole buffer, try using numpy arrays with matplotlib (detailed as https://stackoverflow.com/a/44623559/490188), or OpenCV. – Danny Staple Apr 23 '23 at 16:15
  • @DannyStaple I'm afraid I can't agree with you when python modules are written so poorly that the abstractions add way too much computing delay, not to mention they're bloated and unreadable, and numpy is one of those modules compared to array.array. It's best to make your code both readable and performant, using only imports that you've actually accurately tested to be performant. I'll admit to faltering a bit on readability here, in fact I could actually improve that along with the performance even further with updated knowledge. I'll rewrite an even better example code for this in time. – Tcll Apr 24 '23 at 13:33
  • updated, finally... I put way more work into this tiny code due to environment convolutions than I needed to, but [here's](https://jump.matthewevan.xyz/i2p/pxbcwxbhxj2wusliwhrb6ym4vcwpihztuf2tfjejrcdzqrmnjuoa.b32/Archive/Sources/HSV.py) the additional test code used in that image if anyone wants it :) – Tcll May 17 '23 at 15:31
10

The Hue argument should also vary from 0-1.

import colorsys
test_color = colorsys.hsv_to_rgb(359/360.0, 1, 1)
Paul Beloff
  • 121
  • 1
  • 7
5

If you are working with Numpy arrays then matplotlib.colors.hsv_to_rgb is quite direct:

import numpy as np
from matplotlib.colors import hsv_to_rgb
# This will create a nice image of varying hue and value
hsv = np.zeros((512, 512, 3))
hsv[..., 0] = np.linspace(0, 1, 512)
hsv[..., 1] = 1.
hsv[..., 2] = np.linspace(0, 1, 512)[:, np.newaxis]
rgb = hsv_to_rgb(hsv)

Note that the input and output images have values in the range [0, 1].

buzjwa
  • 2,632
  • 2
  • 24
  • 37
2

I have prepared a vectorized version, it is cca 10x faster

def hsv_to_rgb(h, s, v):
    shape = h.shape
    i = int_(h*6.)
    f = h*6.-i

    q = f
    t = 1.-f
    i = ravel(i)
    f = ravel(f)
    i%=6

    t = ravel(t)
    q = ravel(q)

    clist = (1-s*vstack([zeros_like(f),ones_like(f),q,t]))*v

    #0:v 1:p 2:q 3:t
    order = array([[0,3,1],[2,0,1],[1,0,3],[1,2,0],[3,1,0],[0,1,2]])
    rgb = clist[order[i], arange(prod(shape))[:,None]]

    return rgb.reshape(shape+(3,))
Tomas
  • 91
  • 2
2

Since the question is tagged , I want to mention that PyGame allows conversion between color schemes. The pygame.Color object can be used to convert between the RGB and HSL/HSV color schemes.

The hsva property:

Gets or sets the HSVA representation of the Color. The HSVA components are in the ranges H = [0, 360], S = [0, 100], V = [0, 100], A = [0, 100].

hsva = pygame.Color((red, green, blue, alpha)).hsva
color = pygame.Color(0)
color.hsva = (hue, saturation, value, alpha)
rgba = (color.r, color.g, color.b, color.a)

The hsla property:

Gets or sets the HSLA representation of the Color. The HSLA components are in the ranges H = [0, 360], S = [0, 100], V = [0, 100], A = [0, 100].

hsla = pygame.Color((red, green, blue, alpha)).hsla
color = pygame.Color(0)
color.hsla = (hue, saturation, lightness, alpha)
rgba = (color.r, color.g, color.b, color.a)

Minimal example:

import pygame

pygame.init()

window = pygame.display.set_mode((450, 300))
clock = pygame.time.Clock()

run = True
while run:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    window.fill((255, 255, 255))
    w, h = window.get_size()
    for i in range(6):
        color = pygame.Color(0)
        color.hsla = (i * 60, 100, 50, 100)
        pygame.draw.circle(window, color, 
            (w//6 + w//3 * (i%3), h//4 + h//2 * (i//3)), 
            round(min(window.get_width() * 0.16, window.get_height() * 0.2)))

    pygame.display.flip()

pygame.quit()
exit()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
0

I found the following code to work with images represented as numpy ndarrays:

from skimage.io import imread
import matplotlib.colors as mcolors
img = imread( 'my_image.png' )
img_hsv = mcolors.rgb_to_hsv( img )
img_hsv = img_hsv / (1.0, 1.0, 255.0)

The last division was useful to convert to a floating representation between 0.0 and 1.0, as for some reason the last component originally ranged between 0 and 255.

YakovK
  • 337
  • 2
  • 10
0

OpenCV also offers this possibility. Note that R and B channels are inverted, i.e. BGR. So uses the function that best fits your needs:

import cv2

rgbimg = cv2.cvtColor(hsvimg, cv2.COLOR_HSV2RGB)
bgrimg = cv2.cvtColor(hsvimg, cv2.COLOR_HSV2BGR)
Gabriel123
  • 426
  • 5
  • 11
0

In opencv, use any of their color conversions. Note that BGR and RGB are flipped in cv2 land.

def convertColor(hsv, conversion):
    return tuple(int(i) for i in cv2.cvtColor(np.uint8([[hsv]]), conversion).flatten())

Usage:

hsvColor = (0,255,255) #bright red
bgrColor = convertColor(hsvColor, cv2.COLOR_HSV2BGR)
Joel Teply
  • 3,260
  • 1
  • 31
  • 21
0

If you need more control over the format of the color while converting them (e.g. wants RGB max value to be 255 instead of 1) I would recommend colorir.

from colorir import HSV, sRGB
rgb = HSV(359, 100, 100, max_sva=100).rgb()
... your code

colorir is also designed to be fully compatible with pygame and other graphical libraries so you dont have to waste time doing this kind of manipulation of expected input and output max values for color components...

aleferna
  • 136
  • 6