AvZ
AvZ

Reputation: 1035

HSV to RGB Color Conversion

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?

Upvotes: 49

Views: 93396

Answers (9)

Tcll
Tcll

Reputation: 7382

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)

Upvotes: 27

Joel Teply
Joel Teply

Reputation: 3296

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)

Upvotes: 1

Rabbid76
Rabbid76

Reputation: 211278

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()

Upvotes: 2

Gabriel123
Gabriel123

Reputation: 456

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)

Upvotes: 0

YakovK
YakovK

Reputation: 377

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.

Upvotes: 0

Cory Kramer
Cory Kramer

Reputation: 118021

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)

Upvotes: 62

buzjwa
buzjwa

Reputation: 2652

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

Upvotes: 5

Paul Beloff
Paul Beloff

Reputation: 145

The Hue argument should also vary from 0-1.

import colorsys
test_color = colorsys.hsv_to_rgb(359/360.0, 1, 1)

Upvotes: 10

Tomas
Tomas

Reputation: 91

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,))

Upvotes: 2

Related Questions