HSV to RGB Color Conversion

Question:

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?

Asked By: AvZ

||

Answers:

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)
Answered By: Cory Kramer

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

Here’s the exact code from colorsys slightly modified to make the byte-code slightly faster:

    def hsv_to_rgb(h, s, v):
        if s == 0.0: return (v, v, v)
        i = int(h*6.) # XXX assume int() truncates!
        f = (h*6.)-i; p,q,t = v*(1.-s), v*(1.-s*f), v*(1.-s*(1.-f)); i%=6
        if i == 0: return (v, t, p)
        if i == 1: return (q, v, p)
        if i == 2: return (p, v, t)
        if i == 3: return (p, q, v)
        if i == 4: return (t, p, v)
        if i == 5: return (v, p, q)

output:

>>> hsv_to_rgb(359,1,1)
[1, 0.0, 0.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)

    def hsv_to_rgb(h, s, v):
        if s == 0.0: v*=255; return (v, v, v)
        i = int(h*6.) # XXX assume int() truncates!
        f = (h*6.)-i; p,q,t = int(255*(v*(1.-s))), int(255*(v*(1.-s*f))), int(255*(v*(1.-s*(1.-f)))); v*=255; i%=6
        if i == 0: return (v, t, p)
        if i == 1: return (q, v, p)
        if i == 2: return (p, v, t)
        if i == 3: return (p, q, v)
        if i == 4: return (t, p, v)
        if i == 5: return (v, p, q)

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

>>> hsv_to_rgb(359./360.,1,1)
(255, 0, 0)

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)

Answered By: Tcll

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,))
Answered By: Tomas

The Hue argument should also vary from 0-1.

import colorsys
test_color = colorsys.hsv_to_rgb(359/360.0, 1, 1)
Answered By: Paul Beloff

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].

Answered By: buzjwa

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.

Answered By: YakovK

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)
Answered By: Gabriel123

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()
Answered By: Rabbid76

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)
Answered By: Joel Teply

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…

Answered By: aleferna2001
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.