HSL GUI in Python

I recently wrote a post on RGB/HSL Conversions in Python and as a follow-up I decided to put together a simple Tkinter application which enables users to display a colour by selecting any combination of hue, saturation and lightness.

This application is only a toy but playing with it for a few minutes does give an intuitive understanding of the HSL system, and reinforces my conviction that it is easier to comprehend than RGB. And if you have never used Tkinter this project also provides a simple introduction to the topic.

The project consists of a single file called hslgui.py which you can download as a zip, or clone/download the Github repository if you prefer.

Source Code Links

ZIP File
GitHub

hslgui.py

from tkinter import *
import colorsys


class ApplicationWindow(Frame):

    def __init__(self, master=None):

        # Properties for the current colour in 3 forms
        self.hsl = {"hue": 0.75, "saturation": 0.5, "lightness": 0.5}
        self.rgb = {"red": 0, "green": 0, "blue": 0}
        self.hexcolor = "#000000"

        self.window = Tk()
        self.window.resizable(width=False, height=False)
        self.window.title("CodeDrome HSL GUI")
        self.window.geometry("800x600")

        self.createWidgets()

        # Set the slider positions to the hard-coded defaults.
        # This forces the sliders to call onchange and shoe the colour.
        self.hueslider.set(self.hsl["hue"] * 360)
        self.saturationslider.set(self.hsl["saturation"] * 100)
        self.lightnessslider.set(self.hsl["lightness"] * 100)

        self.window.mainloop()

    def createWidgets(self):

        # A few common values to use in all widgets.
        # A rough analogy with CSS!
        styles = \
            {
                "borderwidth": 0,
                "bg": "#282C34",
                "fg": "#FFFFFF",
                "troughcolor": "#FFFFFF",
                "sliderlength": 16,
                "highlightthickness": 0
            }

        # Hue
        self.hueslider = Scale(self.window, width=24, from_=0, to=360, orient=HORIZONTAL, command=self.onchange, borderwidth=styles["borderwidth"], bg=styles["bg"], fg=styles["fg"], sliderlength=styles["sliderlength"], troughcolor=styles["troughcolor"], highlightthickness=styles["highlightthickness"], label="Hue")
        self.hueslider.grid(sticky="NSEW")

        # Saturation
        self.saturationslider = Scale(self.window, width=24, from_=0, to=100, orient=HORIZONTAL, command=self.onchange, borderwidth=styles["borderwidth"], bg=styles["bg"], fg=styles["fg"], sliderlength=styles["sliderlength"], troughcolor=styles["troughcolor"], highlightthickness=styles["highlightthickness"], label="Saturation")
        self.saturationslider.grid(sticky="NSEW")

        # Lightness
        self.lightnessslider = Scale(self.window, width=24, from_=0, to=100, orient=HORIZONTAL, command=self.onchange, borderwidth=styles["borderwidth"], bg=styles["bg"], fg=styles["fg"], sliderlength=styles["sliderlength"], troughcolor=styles["troughcolor"], highlightthickness=styles["highlightthickness"], label="Lightness")
        self.lightnessslider.grid(sticky="NSEW")

        # RGB
        self.rvalue = Label(self.window, width=48, height=3, text="0", bg="#FF0000", fg="#FFFFFF", borderwidth=styles["borderwidth"])
        self.rvalue.grid(sticky="NSEW")

        self.gvalue = Label(self.window, height=3, text="0", bg="#00FF00", fg="#FFFFFF", borderwidth=styles["borderwidth"])
        self.gvalue.grid(sticky="NSEW")

        self.bvalue = Label(self.window, height=3, text="0", bg="#0000FF", fg="#FFFFFF", borderwidth=styles["borderwidth"])
        self.bvalue.grid(sticky="NSEW")

    def onchange(self, event):

        # Called when any of the sliders are changed.
        # Sets and shows the current colour.
        self.set_hsl()
        self.set_rgb()
        self.show_rgb()
        self.set_hexcolor()
        self.show_color()

    def set_hsl(self):

        # Pick up the user-set HSL values

        self.hsl["hue"] = self.hueslider.get() / 360
        self.hsl["saturation"] = self.saturationslider.get() / 100
        self.hsl["lightness"] = self.lightnessslider.get() / 100

    def set_rgb(self):

        # Set the RGB values from the HSL values.

        rgb = colorsys.hls_to_rgb(self.hsl["hue"], self.hsl["lightness"], self.hsl["saturation"])

        self.rgb["red"] = int(rgb[0]*255)
        self.rgb["green"] = int(rgb[1]*255)
        self.rgb["blue"] = int(rgb[2]*255)

    def show_rgb(self):

        # Show the RGB values in their respective Label widgets.

        self.rvalue.config(text="{}".format(self.rgb["red"]))
        self.gvalue.config(text="{}".format(self.rgb["green"]))
        self.bvalue.config(text="{}".format(self.rgb["blue"]))

    def set_hexcolor(self):

        # Set the hexadecimal colour from RGB values.

        self.hexcol = "#{}{}{}".format(hex(self.rgb["red"])[2:].zfill(2), hex(self.rgb["green"])[2:].zfill(2), hex(self.rgb["blue"])[2:].zfill(2))

    def show_color(self):

        # Set the main window background to the current colour.

        self.window.config(bg=self.hexcol)


def main():

    appwin = ApplicationWindow()


main()

Imports

Firstly we need to import tkinter and colorsys, the latter being a part of the Python Standard Library which provides us with a method for converting HSL (or actually HLS, hue, lightness and saturation - I don't know why...) to RGB.

__init__

Firstly we create three properties for the current colour in various forms: HSL and RGB are both held in dictionaries with keys/values for the three components, and the hexadecimal equivalent as a single string.

Next we create a Tkinter application and set a few properties before calling createWidgets, which I'll get to in a moment. We then set the slider values to the default HSL values hard-coded at the beginning of __init__. This causes the change events to fire, setting the window background colour; I'll describe how this works further down.

Lastly we call the Tkinter window's mainloop function - it is almost a defining characteristic of a GUI that once started it enters an infinite loop waiting for the user to hit a key or move/click the mouse. This is where we start that loop.

createWidgets

This function creates the six widgets or controls for our GUI, three sliders for the HSL values, and three labels to show the RGB equivalent.

If you are used to using CSS to create common sets of styles which can easily be applied to any element in an HTML page then Tkinter is going to be a bit annoying as it has no equivalent. In this project I have tried to provide an equivalent by adding a few styles to a dictionary and using them when creating widgets instead of hard-coding values every time. It doesn't provide the simples class-based convenience of CSS as you still need to set the attributes to the dictionary values, but having done so you only need to change them in one place.

After the styles dictionary we firstly create a set of three Scale widgets, which I will persist in calling sliders as I think it's a more sensible name. The hue slider has a range of 0-360, and the saturation and lightness sliders have ranges of 0-100. After being created they are added to the grid with sticky="NSEW" - one of the RGB labels has its width set and this will force all other controls to stretch to its size.

The three labels for RGB values have their backgrounds set to the colours they represent.

onchange

This function is an event handler called whenever a slider is moved. We just call various other functions to carry out the following tasks:

  • Retrieve the HSL values from the sliders
  • Calculate the RGB values from the HSL values
  • Set the text in the RGB labels to their current values
  • Calculate the hexadecimal equivalent of the current colour
  • Set the window background colour to the hexadecimal value

set_hsl

The three values in the hsl dictionary are set to the current slider values, but are stored as decimals between 0 and 1 so need to be divided by 360, 100 and 100 respectively.

set_rgb

This function first calls colorsys.hls_to_rgb with the hue, lightness and saturation to get an RGB tuple. The values returned are decimals between 0 and 1 so we need to multiply them by 255 and convert them to ints when setting the rgb dictionary values.

show_rgb

Here we just set the three RGB label text values.

set_hexcolor

A slightly messy function which converts the RGB values to hexadecimal, left-padding the results with zeroes.

show_color

A simple one-liner to set the main window's background colour.

Running the Program

Now we can run the program with this command...

Run the Program

python3.7 hslgui.py

...which will open up this application. I hope you have fun sliding the sliders!