Search code examples
pythoncolorspygamejoystick

Converting joystick axis values to hex triplet codes


Using a PS4 controller in pygame, I already figured out how to capture axis rotation which can vary to a -1 or 1, but I don't know how to convert those numbers to a color ring like scale in order to turn it into a hex triplet number.
The values mimicking that of a color ring is more important than anything as I don't want the joystick capturing a color while it's not in motion. Picture
( Since that was a bit confusing, essentially I want to be able to move my joystick around and capture an accurate hex triplet number based on where it has moved )

This is my code so far:

import pygame

# Define some colors
BLACK    = (   0,   0,   0)
WHITE    = ( 62, 210, 255)

# This is a simple class that will help us print to the screen
# It has nothing to do with the joysticks, just outputting the
# information.
class TextPrint:
    def __init__(self):
        self.reset()
        self.font = pygame.font.Font(None, 25)

    def print(self, screen, textString):
        textBitmap = self.font.render(textString, True, BLACK)
        screen.blit(textBitmap, [self.x, self.y])
        self.y += self.line_height

    def reset(self):
        self.x = 25
        self.y = 25
        self.line_height = 30

    def indent(self):
        self.x += 10

    def unindent(self):
        self.x -= 10


pygame.init()

# Set the width and height of the screen [width,height]
size = [900, 1080]
screen = pygame.display.set_mode(size)

pygame.display.set_caption("PS4Testing")

#Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# Initialize the joysticks
pygame.joystick.init()

# Get ready to print
textPrint = TextPrint()

# -------- Main Program Loop -----------
while done==False:
    # EVENT PROCESSING STEP
    for event in pygame.event.get(): # User did something
        if event.type == pygame.QUIT: # If user clicked close
            done=True # Flag that we are done so we exit this loop




    screen.fill(WHITE)
    textPrint.reset()

    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()


    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()


        for i in range( axes ):
            axis = joystick.get_axis( i )
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
        textPrint.unindent()



    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

    # Limit to 20 frames per second
    clock.tick(20)

# Close the window and quit.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit ()

Modified code from official pygame documentation
Any help would be greatly appreciated


Solution

  • My plan:

    1. Find the angle of stick on joystick
    2. Get RGB value using HSV and stick's angle
    3. Convert to HEX

    Finding the angle of joystick's stick

    First, we need to find the joystick's angle. We can do this using the law of cosines and axis statements as lengths of the sides of a triangle (because they are from one point/center).

    Store axis statements in this block:

    for i in range( axes ):
            axis = joystick.get_axis( i )
            #Storing axis statement
            if i == 0:
                Xaxis = axis
            elif i == 1:
                Yaxis = axis
    
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
    

    We store them because in for loop we can take only one statement at a time.

    Then define a function that will return the angle of the stick. We need to use math module for pi and acos.

    def get_angle(Xaxis,Yaxis):
        #To avoid ZeroDivisionError
        #P.S. - you can improve it a bit.
        if Xaxis == 0:
            Xaxis = 0.001
        if Yaxis == 0:
            Yaxis = 0.001
        #defining the third side of a triangle using the Pythagorean theorem
        b = ((Xaxis)**2 + (Yaxis)**2)**0.5
        c = Xaxis
        a = Yaxis
        #Using law of cosines we'll find angle using arccos of cos
        #math.acos returns angles in radians, so we need to multiply it by 180/math.pi
        angle =  math.acos((b**2 + c**2 - a**2) / (2*b*c)) * 180/math.pi
        #It'll fix angles to be in range of 0 to 360
        if Yaxis > 0:
            angle = 360 - angle
        return angle
    

    Now get_angle(Xaxis,Yaxis) will return stick's angle. East is 0°, north is 90°, west is 180°, south is 270°.

    Getting HSV and converting to RGB

    We have stick's angle and we can use it to make an HSV color and then convert it to RGB, because Color Wheel is based on hue of the colors.

    Hue has the same range as the stick's angle, so we can use stick's angle as hue.

    Using the formulas from Wikipedia, define a function that converts HSV to RGB.

    def hsv_to_rgb(H,S,V):
        #0 <= H <= 360
        #0 <= S <= 1
        #0 <= V <= 1
        C = V * S
        h = H/60
        X = C * (1 - abs(h % 2 -1))
    
        #Yes, Python can compare like "0 <= 2 > 1"
        if 0 <= h <= 1:
            r = C; g = X; b = 0
        elif 1 <= h <= 2:
            r = X; g = C; b = 0
        elif 2 <= h <= 3:
            r = 0; g = C; b = X
        elif 3 <= h <= 4:
            r = 0; g = X; b = C
        elif 4 <= h <= 5:
            r = X; g = 0; b = C
        elif 5 <= h < 6:
            r = C; g = 0; b = X
    
        m = V - C
    
        #Final computing and converting from 0 - 1 to 0 - 255 
        R = int((r+m)*255)
        G = int((g+m)*255)
        B = int((b+m)*255)
    
        return [R,G,B]
    

    Now hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1) will return list of red, green and blue in range of 0 - 255 (for an easier conversion in hex). Saturation and Value are not necessary in this case.

    Conversion to HEX

    The easiest part. We need to get the list of RGB and convert values to hex.

    colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
    #Converting to hex
    lst = list(map(hex,colors))
    #Cutting the "0x" part
    for i in range(len(lst)):
        lst[i] = lst[i][2:]
        #If one of the colors has only one digit, extra 0 will be added for a better look
        if len(lst[i]) == 1:
            lst[i] = "0"+str(lst[i])
    print(get_angle(Xaxis,Yaxis))
    

    Something like #ff0000, #00ff00, and #0000ff will be printed.

    You also can make your program to show color change in real time, simply add WHITE = colors in this block. DO NOT use it if you have photosensitive epilepsy.

    Merging everything

    Place both functions from first and second points somewhere at the beginning.

    Add the storing from the first point to the block

    for i in range( axes ):
        axis = joystick.get_axis( i )
        textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
    

    Add the conversion from the third point after the block. I recommend to make a death zone feature.

    death_zone = 0.1
    if abs(Xaxis) > death_zone or abs(Yaxis) > death_zone:
        #If you prefer HSV color wheel, use hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
        #Else if you prefer RGB color wheel, use hsv_to_rgb(360-get_angle(Xaxis,Yaxis),1,1)
        colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
        #Converting to hex
        lst = list(map(hex,colors))
        #Cutting the "0x" part
        for i in range(len(lst)):
            lst[i] = lst[i][2:]
            #If one of the colors has only one digit, extra 0 will be added for a better look
            if len(lst[i]) == 1:
                lst[i] = "0"+str(lst[i])
        print("#"+"".join(lst))
    

    That's how your code may look after all:

    P.S. - you will probably have to change some code, because I thing my joystick's axis weren't properly captured.

    import pygame
    import math
    
    # Define some colors
    BLACK    = (   0,   0,   0)
    WHITE    = ( 62, 210, 255)
    
    def hsv_to_rgb(H,S,V):
        #Accirding to https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
        #0 <= H <= 360
        #0 <= S <= 1
        #0 <= V <= 1
        C = V * S
        h = H/60
        X = C * (1 - abs(h % 2 -1))
    
        #Yes, Python can compare like "0 <= 2 > 1"
        if 0 <= h <= 1:
            r = C; g = X; b = 0
        elif 1 <= h <= 2:
            r = X; g = C; b = 0
        elif 2 <= h <= 3:
            r = 0; g = C; b = X
        elif 3 <= h <= 4:
            r = 0; g = X; b = C
        elif 4 <= h <= 5:
            r = X; g = 0; b = C
        elif 5 <= h < 6:
            r = C; g = 0; b = X
    
        m = V - C
    
        #Final computing and converting from 0 - 1 to 0 - 255 
        R = int((r+m)*255)
        G = int((g+m)*255)
        B = int((b+m)*255)
    
        return [R,G,B]
    
    def get_angle(Xaxis,Yaxis):
        #To avoid ZeroDivisionError
        #P.S. - you can improve it a bit.
        if Xaxis == 0:
            Xaxis = 0.001
        if Yaxis == 0:
            Yaxis = 0.001
        #defining the third side of a triangle using the Pythagorean theorem
        b = ((Xaxis)**2 + (Yaxis)**2)**0.5
        c = Xaxis
        a = Yaxis
        #Using law of cosines we'll fing angle using arccos of cos
        #math.acos returns angles in radians, so we need to multiply it by 180/math.pi
        angle =  math.acos((b**2 + c**2 - a**2) / (2*b*c)) * 180/math.pi
        #It'll fix angles to be in range of 0 to 360
        if Yaxis > 0:
            angle = 360 - angle
        return angle
    
    # This is a simple class that will help us print to the screen
    # It has nothing to do with the joysticks, just outputting the
    # information.
    class TextPrint:
        def __init__(self):
            self.reset()
            self.font = pygame.font.Font(None, 25)
    
        def print(self, screen, textString):
            textBitmap = self.font.render(textString, True, BLACK)
            screen.blit(textBitmap, [self.x, self.y])
            self.y += self.line_height
    
        def reset(self):
            self.x = 25
            self.y = 25
            self.line_height = 30
    
        def indent(self):
            self.x += 10
    
        def unindent(self):
            self.x -= 10
    
    
    pygame.init()
    
    # Set the width and height of the screen [width,height]
    size = [900, 1080]
    screen = pygame.display.set_mode(size)
    
    pygame.display.set_caption("PS4Testing")
    
    #Loop until the user clicks the close button.
    done = False
    
    # Used to manage how fast the screen updates
    clock = pygame.time.Clock()
    
    # Initialize the joysticks
    pygame.joystick.init()
    
    # Get ready to print
    textPrint = TextPrint()
    
    # -------- Main Program Loop -----------
    while done==False:
        # EVENT PROCESSING STEP
        for event in pygame.event.get(): # User did something
            if event.type == pygame.QUIT: # If user clicked close
                done=True # Flag that we are done so we exit this loop
    
    
    
    
        screen.fill(WHITE)
        textPrint.reset()
    
        # Get count of joysticks
        joystick_count = pygame.joystick.get_count()
    
    
        # For each joystick:
        for i in range(joystick_count):
            joystick = pygame.joystick.Joystick(i)
            joystick.init()
    
            # Usually axis run in pairs, up/down for one, and left/right for
            # the other.
            axes = joystick.get_numaxes()
    
    
            for i in range( axes ):
                axis = joystick.get_axis( i )
                #Storing axis statement
                if i == 0:
                    Xaxis = axis
                elif i == 1:
                    Yaxis = axis
    
                textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis) )
            textPrint.unindent()
    
            #If joystick is not in the center
            #Death zone is used to not capture joystick if it's very close to the center
            death_zone = 0.1
            if abs(Xaxis) > death_zone or abs(Yaxis) > death_zone:
                #If you prefer HSV color wheel, use hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
                #Else if you prefer RGB color wheel, use hsv_to_rgb(360-get_angle(Xaxis,Yaxis),1,1)
                colors = hsv_to_rgb(get_angle(Xaxis,Yaxis),1,1)
                #Converting to hex
                lst = list(map(hex,colors))
                #Cutting the "0x" part
                for i in range(len(lst)):
                    lst[i] = lst[i][2:]
                    #If one of the colors has only one digit, extra 0 will be added for a better look
                    if len(lst[i]) == 1:
                        lst[i] = "0"+str(lst[i])
                print("#"+"".join(lst))
                #You can use it to see color change in real time.
                #But I don't recomend to use it if you have photosensitive epilepsy.
                #WHITE = colors
    
        # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
    
        # Go ahead and update the screen with what we've drawn.
        pygame.display.flip()
    
        # Limit to 20 frames per second
        clock.tick(20)
    
    # Close the window and quit.
    # If you forget this line, the program will 'hang'
    # on exit if running from IDLE.
    pygame.quit ()