skip to Main Content

I’m trying to create a color selection scale for a Tkinter application.

I’m not interested in the tkinter.colorchooser module or separate scales for each RGB channel. I need a single scale like the color slider in Photoshop.

What I currently have is a vertical scale widget with RGB integer values going from 0 (#000) to 16777215 (#FFF). The problem is that using a range of RGB integers results in a very odd sequence of colors—the selection doesn’t go from red to pink to blue, etc. as in the first image below.

Instead I get the sequence of colors in the second image. It’s heavily compressed because there are 16777215 different colors squeezed into 300 pixels. The third picture is a zoom. You can see that it goes from black to green, then almost black to green, etc.

Color slider in Photoshop
Gradient based on RGB integers
Closer look at the gradient

Here’s the relevant part of my code:

MAXRGBINT = 16777215  # Corresponds to (255, 255, 255)
scale = ttk.Scale(slidframe, orient=VERTICAL, length=75,
                  from_=0, to=MAXRGBINT,
                  command=lambda _: set_color('sprite'))

Basically I want a scale widget that will go through colors in the “normal” order. How can I do this with numerical values?

3

Answers


  1. You could use either use matplotlib’s premade colormaps (jet, rainbow, etc.)from matplotlib import cm or create your own and then go through the rgb values. You can use the fact that the standard ones are usually separated in 256 values (last paragraph). If that is enough for you the latter would be probably the easiest.

    Is this what you were asking?

    Login or Signup to reply.
  2. Your problem is that you are using a one-dimensional scale for a three-dimensional color space.

    For what you’re looking for, I would suggest using the HSL or HSV color space instead, with the scale in question manipulating the Hue parameter. When you do need RGB values, you can obtain them by conversion from the HSL/HSV values (the Wikipedia page I linked explains how).

    Login or Signup to reply.
  3. It’s a bit late answer but my solution is creating a custom class. I have used it many times in my image processing projects in order to get color mask (or filter) from the user. Here comes the class…

    import tkinter as tk
    from PIL import Image, ImageDraw, ImageTk
    import colorsys
    
    class ColorRangeSelector:
        def __init__(self, root, x, y, w=255 , h=255 , bg='white', lineColor='black' , textColor = 'gray' , initial = [0,255] ):
            self.root=root
            self.x = x
            self.y = y
            self.w = w
            h=min(h, 255)
            self.h = h
    
            self.clicked_id = -1
            self.padding = 16
            self.canvas = tk.Canvas(self.root, width=w + 2 * self.padding, height=h + 2 * self.padding, bg=bg)
            self.result = initial.copy()
    
            image = Image.new("RGB", (w, h))
            draw = ImageDraw.Draw(image)
    
            w_adim= w/255
            for i in range(255):
                for j in range(255-h, 255):
                    rgb = tuple(round(c * 255) for c in colorsys.hsv_to_rgb(i / 255, 1, j / 255))
                    color= '#%02x%02x%02x' % rgb
                    draw.rectangle( [i * w_adim , (j + h - 255) ,(i + 1) * w_adim , (j + h - 255) + 1 ], outline=None,fill=color)
    
            ph = ImageTk.PhotoImage(image)
            self.canvas.create_image(self.padding, self.padding, anchor='nw', image = ph)
            self.canvas.image = ph
    
            start_line = self.canvas.create_rectangle(initial[0]+ self.padding, self.padding,initial[0]+  self.padding+1, h+self.padding, width=0, fill=lineColor)
            start_select = self.canvas.create_rectangle(initial[0]+ self.padding/2, self.padding/2, initial[0]+ self.padding*3/2, self.padding, width=0, fill='black')
            start_text = self.canvas.create_text(initial[0]+ self.padding/2+7 ,self.padding/2+4,text='S',fill="white", font=('Helvetica 6 bold'))
    
            end_line = self.canvas.create_rectangle(initial[1]*w_adim-1+ self.padding, self.padding,initial[1]*w_adim+ self.padding, h+self.padding, width=0, fill=lineColor)
            end_select = self.canvas.create_rectangle(initial[1]*w_adim + self.padding/2, h+self.padding, initial[1]*w_adim+self.padding*3/2, h +self.padding*3/2, width=0,
                                                      fill='black')
    
            end_text = self.canvas.create_text(initial[1]*w_adim + self.padding/2+8, h+ self.padding+4, text='E', fill="white", font=('Helvetica 6 bold'))
    
            self.text = self.canvas.create_text(self.w/2 + self.padding, self.padding/2, text='[{:.2f},{:.2f}]'.format(self.result[0],self.result[1]), fill=textColor, font=('Helvetica 7 bold'))
    
            self.selectors = [start_select,start_line,start_text,end_select,end_line,end_text]
    
            self.canvas.bind( '<B1-Motion>', self.motion)
            self.canvas.bind('<ButtonRelease-1>', self.mouseReleased)
            self.canvas.place(x=x,y=y)
    
        def motion(self,event):
            if self.clicked_id and self.clicked_id < 0:
                self.clicked_id= self.getRectangle(event.x, event.y)
            if self.clicked_id:
                index = self.selectors.index(self.clicked_id)
                rect_y = self.padding/2
                if index>2:
                    rect_y=self.h+self.padding
    
                line_id= self.selectors[index+1]
                text_id = self.selectors[index + 2]
    
                cur_x = event.x
                if event.x > self.w+self.padding:
                    cur_x = self.w +self.padding
                if event.x < self.padding:
                    cur_x = self.padding
    
                self.canvas.coords(line_id, cur_x, self.padding, cur_x + 1,self.h + self.padding)
                self.canvas.coords(self.clicked_id, cur_x-self.padding/2, rect_y, cur_x+self.padding/2, rect_y+self.padding/2)
    
                self.canvas.coords(text_id, cur_x, rect_y+4)
    
                self.result[int(index/3)] = (cur_x-self.padding)/self.w*255
                self.canvas.itemconfigure(self.text,text='[{:.2f},{:.2f}]'.format(self.result[0],self.result[1]))    
    
        def mouseReleased(self,event):
            self.clicked_id = -1
    
        def getRectangle(self,x, y):
            for i in range(2):
                sel = self.selectors[i*3]
                curr_xs, curr_ys, curr_xe, curr_ye = self.canvas.coords(sel)
                if (curr_xs <= x <= curr_xe) and (curr_ys <= y <= curr_ye):
                    return sel
    

    And call it like that;

    import tkinter as tk
    from ColorRangeSelector import ColorRangeSelector
    
    def getResults():
      label['text']= "FROM BUTTON:ns1= {}ns2={}ns3={}".format(selector.result,selector2.result,selector3.result)
    
    def getEventResults(self):
      label['text'] = "FROM EVENT ON ROOT:ns1= {}ns2={}ns3={}".format(selector.result,selector2.result,selector3.result)
    
    root = tk.Tk()
    root.geometry("1000x500")
    
    selector = ColorRangeSelector(root,10,70,900,30, bg='#ccc', lineColor='white', textColor='#222')
    selector2 = ColorRangeSelector(root,50,150)
    selector3 = ColorRangeSelector(root,400,150,510,100, initial=[70.5,203])
    
    button = tk.Button(root, text ="Print Results", command = getResults)
    label = tk.Label( root, text="")
    button.place(x=0,y=0)
    label.place(x=500,y=0)
    
    root.bind( '<B1-Motion>', getEventResults)
    
    root.mainloop()
    

    And results:

    Just drag&drop [S]tart and [E]nd sliders on selector’s canvas
    click here for screenshot

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search