'''
Version: 2.0
Last Modified: 9/28/2021 10:00 pm

Module: ECGUI.py

Python Version: 3.9

Description: This module contains a collection of functions designed to make 
             creating a GUI in Python easier for the beginning programmer. 
             The programmer need only add this module to their project and 
             place 'import ECGUI' (without the single quotation marks) at the 
             top of their python file to use it.

             It purposefully avoids using OOP to make it more understandable 
             to the beginning programmer. There are also no libraries that 
             need to be added to the standard Python installation to make this
             module work.

             Note that the GUI design concept is one of rows of controls in an 
             ECGUI window, where any control can be a frame that contains more 
             rows of controls. When any function is called, the programmer
             has the option of providing (1) only those arguments without 
             keywords, (2) a sequential subset of arguments starting at the 
             first, (3) those arguments without keywords followed by key-value 
             pairs of selected arguments, or (4) a sequential subset of 
             arguments starting at the first followed by key-value pairs of 
             selected arguments.

             Examples:
                 lbl_ex1 = ECGUI.add_label(root_window)
                 lbl_ex2 = ECGUI.add_label(root_window, 'Hello World', 'red', 
                                          'yellow')
                 lbl_ex3 = ECGUI.add_label(root_window, message='Hello World', 
                                           side='left', padding_left=5)
                 lbl_ex4 = ECGUI.add_label(root_window, 'Hello World', 'red', 
                                          'yellow', side='left', fill='x')

Author: Linda Zuvich (linda.zuvich@email.edcc.edu)
        CS Department, Edmonds College, Lynnwood, WA        

License: This module may be used or distributed without modification by anyone 
         for personal or educational use. It may not be sold individually or 
         as part of any other program or collection of modules. Any 
         modification to the code requires that the module name be amended, 
         a list of the modifications be added to this header, and the authors 
         of the modification be added under the original author, with contact 
         information. The Version and Last Modified date must also be updated. 
         This license agreement must remain unchanged as it applies to all 
         future variants of this software.
'''

#from _typeshed import NoneType
import tkinter as tk
from tkinter import messagebox

def make_window(title='ECGUI Window', bg_color='white'):
    '''
    Function: make_window
    Description: Creates the ECGUI window in which all other components will
                be added.
    Input: title (optional - default is 'ECGUI Window') - the text that
                appears in the top left corner of the window's title bar
        bg_color (optional - default is 'white') - the background color
                                of the window, which can be a basic text
                                color or a CSS-style hexadecimal value
                                beginning with a hashtag
                                (ex. 'white' or '#990000')
    Output: a reference to the window object created
    '''
    window = tk.Tk()
    window.title(title)
    window['background'] = bg_color
    return window


def add_frame(container, bg_color='white', padding_top=0, padding_right=0,
              padding_bottom=0, padding_left=0, side='', fill='none'):
    '''
    Function: add_frame
    Description: Creates a frame (a container control) and places is in the
                given container.
    Input: container - the window or frame that will hold this frame
        bg_color (optional - default is 'white') - the background color of the
                    frame, which can be a basic text color or a CSS-style
                    hexadecimal value beginning with a hashtag (ex. 'white' or
                    '#990000')
        padding_top (optional - default is 0) - the amount of space above the
                    label, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the label, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the label, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the label, measured in pixels
        side (optional - default is '') - determines if this control should be
                placed in its own row in the container or if it should be moved
                to the 'left' or 'right' to make room for other controls in the
                same row
        fill (optional - default is 'none') - expands the frame to the
                available space in the container, with options being 'x' for
                horizontal, 'y' for vertical, 'both' for horizontal and vertical,
                or 'none'
    Output: a reference to the frame created
    '''
    frame = tk.Frame(container)
    if side == '' or (side != 'left' and side != 'right'):
        frame.pack(padx=(padding_left, padding_right),
                   pady=(padding_top, padding_bottom), expand=1, fill=fill)
    else:
        frame.pack(padx=(padding_left, padding_right),
                   pady=(padding_top, padding_bottom), side=side, expand=1,
                   fill=fill)
    frame['background'] = bg_color
    return frame


def add_label(container, message='', fg_color='black', bg_color='white',
              font_family='', font_size=12, bold=False, italics=False,
              padding_top=0, padding_right=0, padding_bottom=0,
              padding_left=0, side='', fill='none'):
    '''
    Function: add_label
    Description: Adds a label control to a container, such as a window or a frame.
                A label control displays a single line of text and can be used
                as a heading, a prompt for an entry box, or a place to output
                results.
    Input: container - the window or frame that will hold this label control
        message (optional - default is '') - what the label should say
        fg_color (optional - default is 'black') - the text color, which can
                    be a basic text color or a CSS-style hexadecimal value
                    beginning with a hashtag (ex. 'black' or '#990000')
        bg_color (optional - default is 'white') - the background color, which
                    can be a basic text color or a CSS-style hexadecimal value
                    beginning with a hashtag (ex. 'black' or '#990000')
        font_family (optional - default is the Tk default font for labels) -
                    this may be any of the Tk platform-independent fonts such
                    as 'TkFixedFont' or a system-specific font such as 'Arial'
        font_size (optional - default is 12) - the size of the text, measured
                    in points
        bold (optional - default is False) - a Boolean (True or False) that
                makes the label text bold or normal weight
        italics (optional - default is False - a Boolean (True or False) that
                makes the label text italics or not
        padding_top (optional - default is 0) - the amount of space above the
                    label text, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the label text, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the label text, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the label text, measured in pixels
        side (optional - default is '') - determines if this control should be
                placed in its own row in the container or if it should be moved
                to the 'left' or 'right' to make room for other controls in the
                same row
        fill (optional - default is 'none') - expands the label to the
                available space in the container, with options being 'x' for
                horizontal, 'y' for vertical, 'both' for horizontal and vertical,
    #             or 'none'
    Output: a reference to the label control created
    '''
    if font_family != '':
        font_style = font_family + ' ' + str(font_size)
        if bold:
            font_style += ' bold'
        if italics:
            font_style += ' italic'
        label = tk.Label(container, text=message, fg=fg_color, bg=bg_color,
                         font=font_style)
    else:
        label = tk.Label(container, text=message, fg=fg_color, bg=bg_color)
    if side == 'left' or side == 'right':
        label.pack(padx=(padding_left, padding_right),
                   pady=(padding_top, padding_bottom), side=side,
                   expand=0 if fill == 'none' else 1, fill=fill)
    else:
        label.pack(padx=(padding_left, padding_right),
                   pady=(padding_top, padding_bottom),
                   expand=0 if fill == 'none' else 1, fill=fill)
    return label


def change_label(label, message='_SAME_', fg_color='', bg_color='',
                 font_family='', font_size=12, bold=False, italics=False):
    '''
    Function: change_label
    Description: Changes the setting and/or text of an existing label control.
    Input: label - the reference to the label control that should be changed
        message (optional - default is the current message) - what the label
                should say
        fg_color (optional - default is the current color) - the text color,
                    which can be a basic text color or a CSS-style hexadecimal
                    value beginning with a hashtag (ex. 'black' or '#990000')
        bg_color (optional - default is the current color) - the background
                    color, which can be a basic text color or a CSS-style
                    hexadecimal value beginning with a hashtag
                    (ex. 'black' or '#990000')
        font_family (optional - default is the current font) - this may be any
                    of the Tk platform-independent font such as 'TkFixedFont'
                    or a system-specific font such as 'Arial'
        font_size (optional - default is the current size if no font family is
                    selected or 12 pt. if one is) - the size of the text,
                    measured in points
        bold (optional - default is False) - a Boolean (True or False) that
                makes the label text bold or normal weight, which only changes if
                a font_family is provided
        italics (optional - default is False) - a Boolean (True or False) that
                makes the label text italics or not, which only changes if a
                font_family is provided
    Output: nothing
    '''
    if message != '_SAME_':
        label.config(text=message)
    if fg_color != '':
        label['foreground'] = fg_color
    if bg_color != '':
        label['background'] = bg_color
    if font_family != '':
        font_style = font_family + ' ' + str(font_size)
        if bold:
            font_style += ' bold'
        if italics:
            font_style += ' italic'
        label.config(font=font_style)


def load_image(file_name):
    '''
    Function: load_image
    Description: Loads a gif or ppm image into the program, allowing it to be
                added to a container using the add_image function. Note that
                only images saved as a gif or ppm will work. There are free
                converters online that will allow a jpg to be converted into a
                ppm, such as:
                https://onlineconvertfree.com/convert-format/jpg-to-ppm/
    Input: file_name - if the file is stored in the same directory as the program
                    files, this should be just the file name with extension
                    (ex. 'photo.ppm') or provide either the relative or
                    complete directory path and the file name with extension
                    (ex. 'images/photo.ppm')
    Output: a reference to the image stored in the program
    '''
    img = tk.PhotoImage(file=file_name)
    return img


def add_image(container, image, width=300, height=200, bg_color='white',
              padding_top=0, padding_right=0, padding_bottom=0, padding_left=0):
    '''
    Function: add_image
    Description: Adds an image control to a container, such as a window or a
                frame. Usually the width and height of the control matches the
                dimensions of the image being displayed. The background color
                will only show if the image is smaller thant the control or if
                padding has been applied.
    Input: container - the window or frame that will hold this image control
        image - the reference to the image already loaded into the program
                that should be displayed in this control
        width (optional - default is 300) - the width of the image control
                in pixels
        height (optional - default is 200) - the height of the image control
                in pixels
        bg_color (optional - default is 'white') - the background color, which
                    can be a basic text color or a CSS-style hexadecimal value
                    beginning with a hashtag (ex. 'black' or '#990000')
        padding_top (optional - default is 0) - the amount of space above the
        image, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the image, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the image, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the image, measured in pixels
        side (optional - default is '') - determines if this control should be
                placed in its own row in the container or if it should be moved
                to the 'left' or 'right' to make room for other controls in the
                same row
    Output: a reference to the image control created
    '''
    canvas = tk.Canvas(container, width=width, height=height)
    canvas['background'] = bg_color
    canvas.pack(padx=(padding_left, padding_right),
                pady=(padding_top, padding_bottom))
    canvas.create_image(width / 2, height / 2, anchor=tk.CENTER, image=image)
    return canvas


def change_image(canvas, image, width=300, height=200):
    '''
    Function: change_image
    Description: Changes the image in an existing image control.
    Input: canvas - the reference to the image control that should be changed
        image - the reference to the image already loaded into the program
                that should be displayed in this control
        width (optional - default is 300) - the width of the image control
                in pixels
        height (optional - default is 200) - the height of the image control
                in pixels
    Output: nothing
    '''
    canvas.delete('all')
    canvas.config(width=width, height=height)
    canvas.create_image(width / 2, height / 2, anchor=tk.CENTER, image=image)


def add_button(container, message='', padding_top=0, padding_right=0,
               padding_bottom=0, padding_left=0, side=''):
    '''
    Function: add_button
    Description: Adds a button control to a container, such as a window or a
                frame. To program the button's click event, set the button's
                'command' option equal to (1) the name of a function that has
                no parameters, (2) lambda: followed by a single function call,
                or (3) lambda: followed by a list of function calls in square
                brackets and separated by commas. Examples (assuming a reference
                to a button named btn_submit):
                    btn_submit['command'] = btn_submit_clicked
                    btn_submit['command'] = lambda: ECGUI.change_image(
                                            img_control, image2, 320, 240)
                    btn_submit['command'] = lambda: [ECGUI.change_image(
                                            img_control, image2, 320, 240),
                                            ECGUI.change_label(lbl_heading,
                                            'Second Image')]
    Input: container - the window or frame that will hold this button control
        message (optional - default is '') - what the button should say
        padding_top (optional - default is 0) - the amount of space above the
                    button, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the button, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the button, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the button, measured in pixels
        side (optional - default is '') - determines if this control should
                be placed in its own row in the container or if it should be
                moved to the 'left' or 'right' to make room for other controls
                in the same row
    Output: a reference to the button control created
    '''
    button = tk.Button(container, text=message)
    if side == 'left' or side == 'right':
        button.pack(padx=(padding_left, padding_right),
                    pady=(padding_top, padding_bottom), side=side)
    else:
        button.pack(padx=(padding_left, padding_right),
                    pady=(padding_top, padding_bottom))
    return button


def add_entry_box(container, width=10, padding_top=0, padding_right=0,
                  padding_bottom=0, padding_left=0, side=''):
    '''
    Function: add_entry_box
    Description: Adds an entry box control to a container, such as a window or
                a frame.
    Input: container - the window or frame that will hold this button control
        message (optional - default is '') - what the button should say
        padding_top (optional - default is 0) - the amount of space above
                    the button, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the button, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the button, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the button, measured in pixels
        side (optional - default is '') - determines if this control should
                be placed in its own row in the container or if it should be
                moved to the 'left' or 'right' to make room for other controls
                in the same row
    Output: a reference to the button control created
    '''
    entry_box = tk.Entry(container, width=width)
    if side == 'left' or side == 'right':
        entry_box.pack(padx=(padding_left, padding_right),
                       pady=(padding_top, padding_bottom), side=side)
    else:
        entry_box.pack(padx=(padding_left, padding_right),
                       pady=(padding_top, padding_bottom))
    return entry_box


def clear_entry_box(entry_box):
    '''
    Function: clear_entry_box
    Description: Clears the text from an existing entry box control.
    Input: entry_box - the entry box to clear
    Output: nothing
    '''
    entry_box.delete(0, tk.END)


def add_radio_buttons(container, names, selected=0, horizontal=False,
                      button_message='', spacing=5):
    '''
    Function: add_radio_buttons
    Description: Adds a series of connected radio buttons to the given container.
                If a button_message is provided, a button is added to program a
                selection event. If no button_message is provided, no button is
                added, allowing for each radio button click event to be
                programmed separately.
    Input: container - the window or frame that will hold the radio button series
                    and optional button
        names - a list of strings used to label the radio buttons, contained
                within square brackets and separated by a comma
        selected (optional - default is 0) - the zero-based index of the radio
                    button that should be selected at start up
        horizontal (optional - default is False) - a Boolean (True or False)
                    that adds the buttons horizontally across the container or
                    vertically in the container
        button_message (optional - default is '') - determines if a button is
                        to be added at the end of the radio buttons and if so,
                        what the text in the button should be, with a blank
                        button_message indicating that no button should be added
        spacing (optional - default is 5) - the amount of padding around each
                radio button and optional button
    Output: a reference to the button if one is added or a reference to the list
            of radio buttons and, in either case, the zero-based index of the
            currently selected radio button as an IntVar. To get the integer value
            out of this, use the get() method. Example:
                btn_go, choice = ECGUI.add_radio_buttons(my_frame,
                                    ['one', 'two', 'three'], button_message='GO')
                btn_go['command'] = lambda: change_image(img_top,
                                                            images[choice.get()])
    '''
    rb_choice = tk.IntVar()
    rb_list = []
    for index in range(0, len(names)):
        rb_list.append(tk.Radiobutton(container, text=names[index],
                                      variable=rb_choice, value=index))
        if horizontal:
            rb_list[index].pack(padx=5, pady=5, side='left')
        else:
            rb_list[index].pack(padx=5, pady=5)
    rb_choice.set(selected)
    if button_message != '':
        button = tk.Button(container, text=button_message)
        if horizontal:
            button.pack(padx=spacing, pady=spacing, side='left')
        else:
            button.pack(padx=spacing, pady=spacing)
        return button, rb_choice
    else:
        return rb_list, rb_choice


def add_check_box(container, name, padding_top=0, padding_right=0,
                  padding_bottom=0, padding_left=0, side=''):
    '''
    Function: add_check_box
    Description: Adds a check box control to a container, such as a window or
                a frame.
    Input: container - the window or frame that will hold this button control
        name - the text to associate with this check box
        padding_top (optional - default is 0) - the amount of space above the
                    check box, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the check box, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the check box, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the check box, measured in pixels
        side (optional - default is '') - determines if this control should be
                placed in its own row in the container or if it should be moved
                to the 'left' or 'right' to make room for other controls in the
                same row
    Output: a reference to the check box control created
    '''
    cb_choice = tk.IntVar()
    cb = tk.Checkbutton(container, text=name, variable=cb_choice)
    if side == 'left' or side == 'right':
        cb.pack(padx=(padding_left, padding_right),
                pady=(padding_top, padding_bottom), side=side)
    else:
        cb.pack(padx=(padding_left, padding_right),
                pady=(padding_top, padding_bottom))
    return cb_choice


def add_check_boxes(container, names, horizontal=False, spacing=5):
    '''
    Function: add_check_boxes
    Description: Adds a series of check boxes to the given container.
    Input: container - the window or frame that will hold the check box series
        names - a list of strings used to label the check boxes, contained
                within square brackets and separated by a comma
        horizontal (optional - default is False) - a Boolean (True or False)
                    that adds the check boxes horizontally across the container
                    or vertically in the container
        spacing (optional - default is 5) - the amount of padding around
                each check box
    Output: a reference to the list of IntVal objects with values of 1 or 0,
            indicating which check boxes are currently checked. These values can
            be used as Booleans. To get the value out an IntVal, use the get()
            method. Example:
                selections = ECGUI.add_check_boxes(cb_frame,
                                ['bold', 'italics'], True)
                bold_setting = selections[0].get()
    '''
    cb_choices = []
    for index in range(0, len(names)):
        cb_choices.append(tk.IntVar())
        cb_choices[index].set(0)
    cb_list = []
    for index in range(0, len(names)):
        cb_list.append(tk.Checkbutton(container, text=names[index],
                                      variable=cb_choices[index]))
        if horizontal:
            cb_list[index].pack(padx=spacing, pady=spacing, side='left')
        else:
            cb_list[index].pack(padx=spacing, pady=spacing)
    return cb_choices


def add_dropdown(container, options, selected='', padding_top=0,
                 padding_right=0, padding_bottom=0, padding_left=0, width=10,
                 side=''):
    '''
    Function: add_dropdown
    Description: Adds a single-selection dropdown menu control to a container,
                such as a window or a frame.
    Input: container - the window or frame that will hold this dropdown menu
                    control
        options - a list of strings that are the menu options, contained within
                    square brackets and separated by a comma
        selected (optional - default is none) - the string option that should
                    be selected by default
        padding_top (optional - default is 0) - the amount of space above the
                    dropdown, measured in pixels
        padding_right (optional - default is 0) - the amount of space after
                        the dropdown, measured in pixels
        padding_bottom (optional - default is 0) - the amount of space below
                        the dropdown, measured in pixels
        padding_left (optional - default is 0) - the amount of space before
                        the dropdown, measured in pixels
        side (optional - default is '') - determines if this control should be
                placed in its own row in the container or if it should be moved
                to the 'left' or 'right' to make room for other controls in the
                same row
    Output: a reference to a StringVar that holds the currently selected menu
            option. To get the string value from this, use the get method.
            Example:
                color_option = ECGUI.add_dropdown(frame,
                                                    ['red', 'white', 'blue'],
                                                    'white')
                color_selected = color_option.get()
    '''
    menu_choice = tk.StringVar()
    menu_choice.set(selected)
    menu = tk.OptionMenu(container, menu_choice, *options)
    menu.config(width=width)
    if side == 'left' or side == 'right':
        menu.pack(padx=(padding_left, padding_right),
                  pady=(padding_top, padding_bottom), side=side)
    else:
        menu.pack(padx=(padding_left, padding_right),
                  pady=(padding_top, padding_bottom))
    return menu_choice


def add_multiline_label(container, message='', height=10, width=25, wrap=True,
                        scroll=False):
    '''
    Function: add_multiline_label
    Description: Adds a multiline label control to a container, such as a window
                or a frame. A multiline label is a non-editable box that holds
                multiple lines of text. Lines longer than the box width can be
                automatically word-wrapped. Text may be appended
                programmatically with the append_multiline_label function.
    Input: container - the window or frame that will hold this multiline label
                    control
        message (optional - default is '') - the text that should be placed
                    in the box to start with. Note that this function does not
                    add a newline character at the end of this text. To cause
                    appended text to start on the next line, add a '\n' to the
                    end of this message.
        height (optional - default is 10) - the number of lines high to make
                the box that holds the multiline label
        width (optional - default is 25) - the number of characters across to
                make the box that holds the multiline label
        wrap (optional - default is True) - a Boolean (True or False) that
                turns word wrapping on or off
        scroll (optional - default is False) - a Boolean (True or False) that
                makes a vertical scrollbar appear to the left of the text
                or not, which, when applied, allows the user to scroll through
                more text than can fit in the height of the box.
    Output: a reference to the multiline label control created
    '''
    frame = add_frame(container)
    if scroll:
        scroll_bar = tk.Scrollbar(frame)
        scroll_bar.pack(side='right', fill='y')
        multi_label = tk.Text(frame, height=height, width=width,
                              yscrollcommand=scroll_bar.set)
        multi_label.pack(side='left')
        scroll_bar.config(command=multi_label.yview)
    else:
        multi_label = tk.Text(frame, height=height, width=width)
        multi_label.pack()
    if wrap:
        multi_label.config(wrap='word')
    if message != '':
        multi_label.configure(state='normal')
        multi_label.insert(tk.END, message)
    multi_label.configure(state='disabled')
    return multi_label


def append_multiline_label(multi_label, message, skip_a_line=False):
    '''
    Function: append_multiline_label
    Description: Adds text to any text already in an existing multiline label.
                Each append creates a new line of text with the option of
                double spacing between lines.
    Input: multi_label - the multiline label control to append
        message - the text to append to the multiline label
        skip_a_line (optional - default is False) - a Boolean (True or False)
                        that turns double spacing on or off
    Output: nothing
    '''
    multi_label.configure(state='normal')
    if skip_a_line:
        multi_label.insert(tk.END, message + '\n\n')
    else:
        multi_label.insert(tk.END, message + '\n')
    multi_label.configure(state='disabled')


def clear_multiline_label(multi_label):
    '''
    Function: clear_multiline_label
    Description: Deletes all text in an existing multiline label.
    Input: multi_label - the multiline label control to clear.
    Output: nothing
    '''
    multi_label.configure(state='normal')
    multi_label.delete("1.0", tk.END)
    multi_label.configure(state='disabled')


def add_list_box(container, entries, height=10, width=25,
                 select_color='white', highlight='#000000', scroll=False):
    '''
    Function: add_list_box
    Description: Adds a list box control to a container, such as a window
                or a frame. A list box is a single-selection list of
                options. The currently selected option is highlighted and
                can be accessed using the get('anchor') function. Example:
                    selection = lst_options.get['anchor']
                The selection variable would contain the string currently
                selected.
    Input: container - the window or frame that will hold this list box control
        entries - a list of strings that make up the options that should
                    go into the list box, contained within square brackets and
                    separated by a comma
        height (optional - default is 10) - the number of lines high to make
                the list box
        width (optional - default is 25) - the number of characters across to
                make the lis box
        scroll (optional - default is False) - a Boolean (True or False) that
                makes a vertical scrollbar appear to the left of the list
                or not, which, when applied, allows the user to scroll through
                more options than can fit in the height of the box.
    Output: a reference to the list box control created
    '''
    if scroll:
        scroll_bar = tk.Scrollbar(container)
        scroll_bar.pack(side='right', fill='y')
        list_box = tk.Listbox(container, yscrollcommand=scroll_bar.set,
                              selectforeground=select_color,
                              selectbackground=highlight, activestyle='none',
                              height=height, width=width)
        list_box.pack(side='left', fill='y')
        scroll_bar.config(command=list_box.yview)
    else:
        list_box = tk.Listbox(container, selectforeground=select_color,
                              selectbackground=highlight, activestyle='none',
                              height=height, width=width)
        list_box.pack()
    for line in entries:
        list_box.insert(tk.END, line)
    list_box.select_set(0)
    list_box.see(0)
    list_box.select_anchor(0)
    return list_box


def show_message_box(title, message, message_type='info'):
    '''
    Function: show_message_box
    Description: Displays a popup message box with an OK button.
    Input: title - the text that will appear in the title bar of the popup window
        message - the text that will appear in the body of the popup window
        message_type (optional - default is 'info') - the type of message the
                        popup window is providing. Options are 'info',
                        'warning', or 'error'. Each option produces a different
                        icon next to the popup message.
    Output: nothing
    '''
    if message_type == 'info':
        messagebox.showinfo(title, message)
    elif message_type == 'warning':
        messagebox.showwarning(title, message)
    else:
        messagebox.showerror(title, message)


def add_canvas(container, width=500, height=500, background="white", 
               padding_top=0, padding_right=0, padding_bottom=0, padding_left=0, 
               side=''):
    drawing_canvas = DrawingPanel(container, width, height, background)
    if side == 'left' or side == 'right':
        drawing_canvas.pack(padding_left, padding_right, padding_top, 
                            padding_bottom, side=side)
    else:
        drawing_canvas.pack(padding_left, padding_right, padding_top, 
                            padding_bottom)
    return drawing_canvas


def drawing_panel(width, height, background="white"):
    window = make_window('Canvas')
    canvas = add_canvas(window, width, height, background)
    return canvas




################################################################################


# DrawingPanel.py
# =====================================================================
# Simplified Python drawing window class
# to accompany Building Python Programs textbook and associated materials.
# This library is based on Python's Tkinter GUI system.
# see also: http://effbot.org/tkinterbook/canvas.htm
# =====================================================================
# authors:
# Marty Stepp, Stanford University
# Allison Obourn, University of Arizona
# Stuart Reges, University of Washington
# =====================================================================
# To generate the documentation for this library, run this command:
# epydoc --html --no-imports --no-private --no-sourcecode --no-frames -o doc DrawingPanel.py
# =====================================================================

# =====================================================================
# COMPATIBILITY NOTE: This library was designed for Python 3 primarily.
# But most of this library generally should work with Python 2 or 3+.
# Some features such as property getters/setters may not function perfectly on Python 2.
# If you discover functionality that fails on Python 2, please notify the author.
# =====================================================================

import atexit
import os
import sys
import time
import types

# python3.0 uses "tkinter," python2.x uses "Tkinter."
# detect version info and import appropriate graphics
# code added by UW CSE TA Steve Geluso
if (sys.version_info >= (3, 0)):
    from tkinter import Canvas, Image, Label, Menu, PhotoImage, Tk, TclError
    from tkinter import BOTTOM, LEFT, RIGHT, TOP, W, NW, SW
    from tkinter import filedialog
else:
    from Tkinter import Tk, Canvas, Image, Menu, PhotoImage, TclError
    import tkFileDialog

class DrawingPanel:
    '''!
    A DrawingPanel object represents a simple interface for creating
    graphical windows in Python for drawing shapes, lines, images, and colors.
    The DrawingPanel also supports getting and setting the RGB values of individual
    pixels, which makes it useful for learning about various looping and 2D list-
    processing algorithms.

    See PyDoc comments for individual functions below for more information.

    This library is based on Python's Tkinter GUI system.
    In particular, the drawing surface used by DrawingPanel is a Tkinter Canvas object,
    and many of our functions are wrappers around Tkinter Canvas functions.
    
    The DrawingPanel supports properties like color, background, width, height, etc.
    that can be accessed/modified either using traditional methods like get_color and set_background,
    or as properties named color or background.
    
    @author: Marty Stepp, Stanford University
    @author: Allison Obourn, University of Arizona
    @author: Stuart Reges, University of Washington

    @version: 2021/09/28
    
    @copyright: Marty Stepp, Allison Obourn, Stuart Reges; for individual, educational, non-commercial, private use only; all other rights reserved.

    @see: http://effbot.org/tkinterbook/canvas.htm
    '''

    @staticmethod
    def __adjust_coordinate(coord):
        '''!
        Converts between a single 0-based client coordinate and a 1-based Tkinter canvas coordinate.
        '''
        # return coord + 1
        return coord
    
    @staticmethod
    def __adjust_coordinates(*coords):
        '''!
        Converts between a list/tuple of 0-based client coordinates and one of 1-based Tkinter canvas coordinates.
        '''
        ADJUST_AMOUNT = 0
        if coords is None or len(coords) == 0:
            raise ValueError
        elif isinstance(coords[0], tuple):
            coords_to_use = coords[0]
        else:
            coords_to_use = coords
        if ADJUST_AMOUNT == 0:
            return coords_to_use
        else:
            return tuple([coord + ADJUST_AMOUNT for coord in coords_to_use])
    
    @staticmethod
    def __adjust_mouse_coordinate(coord):
        '''!
        Converts between a single 1-based client mouse coordinate and a 0-based code coordinate.
        '''
        # return coord - 1
        return coord
    
    
    def __init__(self, window, width=500, height=500, background="white", **options):
        '''!
        Constructs a panel of a given width, height, and optional background color.
        
        Parameters:
        @param width: (optional) width of the DrawingPanel central canvas area in pixels (default 500)
        @param height: (optional) height of the DrawingPanel central canvas area in pixels (default 500)
        @param background: (optional) background color of the DrawingPanel canvas area (default "white")
        @param options: (optional) keyword arguments to pass directly to Tk window constructor (see Tkinter documentation)
        '''

        # fill in window/object properties
        self._fill = "black"
        self._outline = "black"
        self._font = None
        self._images = []     # list of all drawn images
        self._image_map = dict()

        self._mouse_x = 0
        self._mouse_y = 0

        self._width = width
        self._height = height
        
        # create window
        self._stroke = 1
    
        # create window graphical canvas
        self._canvas = Canvas(window, width = width, height = height, bd = 0, highlightthickness = 0, relief="ridge")
        self._canvas["bg"] = background
        self._canvas.pack(side = TOP)
        self._canvas.bind("<Motion>", lambda event: self.__callback_mouse_moved(event))


    def __callback_key_pressed(self, event):
        '''!
        Private function that is called when keys are pressed.
        Not to be called by students.
        @see: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
        @see: http://www.tcl.tk/man/tcl8.4/TkCmd/keysyms.htm
        '''
        # notes: 
        # event.char contains printable character such as "a";
        # event.keysym contains string for key such as "Escape";
        # event.keycode contains ASCII code such as 97
        pass

    def get_mouse_x(self):
        return self._mouse_x
    
    def get_mouse_y(self):
        return self._mouse_y

    def __callback_mouse_moved(self, event):
        '''!
        Private function that is called when the mouse is moved.
        Not to be called by students.
        '''
        x = DrawingPanel.__adjust_mouse_coordinate(int(event.x))   # TODO: adjust?
        y = DrawingPanel.__adjust_mouse_coordinate(int(event.y))
        rgb = self.get_pixel_color_rgb(x, y)
        r, g, b = Color.to_rgb(rgb)
        self._mouse_x = x
        self._mouse_y = y

        self._canvas.update()
    
    def animate(self, time, func):
        while(True):
            self.sleep(time)
            func()

    def add_key_listener(self, function, event_type="press"):
        '''!
        Attaches the given function to be called when keyboard events occur in this DrawingPanel window.
        @param function: The function to be called; should accept an event as a parameter.
        @param event_type: (optional) What type of mouse event to respond to. The default is key presses,
        but you can pass any of "press" or "release".
        @see: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
        '''
        if event_type.lower() == "press":
            event_type = "<Key>"
        elif event_type.lower() == "release":
            event_type = "<Key>"   # TODO
        if hasattr(self, "_window"):
            self._canvas.bind(event_type, function)
    
    def add_mouse_listener(self, function, event_type="click"):
        '''!
        Attaches the given function to be called when mouse events occur in this DrawingPanel window.
        @param function: The function to be called; should accept an event as a parameter.
        @param event_type: (optional) What type of mouse event to respond to. The default is clicks, but you can pass
        any of "click", "doubleclick", "drag", "enter", "exit", "middleclick", "move", "press", "release", "rightclick", or "wheel".
        @see: http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
        '''
        if event_type.lower() == "click":
            event_type = "<Button-1>"
        elif event_type.lower() == "doubleclick":
            event_type = "<Double-Button-1>"
        elif event_type.lower() == "drag":
            event_type = "<B1-Motion>"
        elif event_type.lower() == "enter":
            event_type = "<Enter>"
        elif event_type.lower() == "exit":
            event_type = "<Leave>"
        elif event_type.lower() == "middleclick":
            event_type = "<Button-2>"
        elif event_type.lower() == "move":
            event_type = "<Motion>"
        elif event_type.lower() == "press":
            event_type = "<Button-1>"
        elif event_type.lower() == "release":
            event_type = "<ButtonRelease>"
        elif event_type.lower() == "rightclick":
            event_type = "<Button-3>"
        elif event_type.lower() == "wheel":
            event_type = "<MouseWheel>"
            if hasattr(self, "_window"):
                self._canvas.bind("<4>", function)   # wheel up
                self._canvas.bind("<5>", function)   # wheel down
            return
        if hasattr(self, "_canvas"):
            self._canvas.bind(event_type, function)

    def clear(self):
        '''!
        Erases all shapes from the panel and fills it with its background color.
        '''
        if hasattr(self, "_canvas"):
            self._canvas.create_rectangle(0, 0, self._width + 2, self._height + 2, \
                    outline=self._canvas["bg"], fill=self._canvas["bg"])
        self._images = []
        self._image_map = dict()


    def draw_arc(self, x, y, w, h, start_angle, extent, color="", **options):
        '''!
        Draws an outlined arc (a partial oval).
        The resulting arc begins at 'start_angle' and extends for 'extent' degrees.
        Angles are interpreted such that 0 degrees is at the 3 o'clock position.
        A positive value indicates a counter-clockwise rotation while a negative value indicates a clockwise rotation.
        
        If you want to draw a pie-slice style arc rather than just the outer line, pass an option of style="pieslice".
        
        @param x: left x coordinate of bounding box of arc's oval
        @param y: top y coordinate of bounding box of arc's oval
        @param w: width of bounding box of arc's oval
        @param h: height of bounding box of arc's oval
        @param start_angle: starting angle in degrees, where 0 is at "3 o'clock" position
        @param extent: degrees that the arc should extend from its start, where positive numbers are counter-clockwise and negative values are clockwise
        @param color: (optional) color to use for outline of shape (if absent, uses panel's default color as set via set_color)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_arc-method
        '''
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        color = self.__pick_color_with_default(color, self._outline, "outline", options)
        Color.normalize_all_colors(options)
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        if not "start" in options:
            options["start"] = start_angle
        if not "extent" in options:
            options["extent"] = extent
        if not "style" in options:
            options["style"] = "arc"
        bounding_box = (x, y, x + w, y + h)
        self._canvas.create_arc(bounding_box, **options)
    
    def draw_image(self, filename, x=0, y=0, w=0, h=0, **options):
        '''!
        Draws an image from a file or Image object with its top-left corner at the given (x, y) pixel.
        Due to tkinter limitations, can recognize PNG and GIF but not JPG. Sorry.  :-(

        @param filename: path to image file, or Tkinter Image object representing the image to draw
        @type filename: str or Image
        @param x: (optional) top-left x-coordinate at which to place the image; default 0
        @param y: (optional) top-left y-coordinate at which to place the image; default 0
        @param w: (optional) width at which to draw the image (not yet supported)
        @param h: (optional) height at which to draw the image (not yet supported)
        @param options: (optional) keyword arguments to pass directly to Tk create_image function (see Tkinter documentation)
        @todo: support resizing of images (does not work yet)
        @see: http://effbot.org/tkinterbook/canvas.htm#canvas.Canvas.create_image-method
        '''
        x = DrawingPanel.__adjust_coordinate(x)
        y = DrawingPanel.__adjust_coordinate(y)
        
        if type(filename) == str:
            image = PhotoImage(file=filename)
        elif type(filename) == Image or type(filename) == PhotoImage:
            # use image as-is
            image = filename
        else:
            raise TypeError("first positional parameter must be a string or image object")
        
        self._images.append(image)
        
        # image is centered in Python Tkinter; change it so that x/y = top/left corner
        if w > 0 and h > 0:
            # TODO: resize (does not work yet)
            w = image.width()
            h = image.height()
        else:
            # use existing size
            w = image.width()
            h = image.height()
        result_id = self._canvas.create_image(x + w / 2, y + h / 2, image=image, **options)
        self._image_map[result_id] = image
    
    def draw_line(self, x1, y1, x2, y2, color="", **options):
        '''!
        Draws a line between the two given points (x1, y1) and (x2, y2).

        @param x1: starting point's x-coordinate
        @param y1: starting point's y-coordinate
        @param x2: ending point's x-coordinate
        @param y2: ending point's y-coordinate
        @param color: (optional) color with which to draw line (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_line function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_line-method
        '''
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        x1, y1, x2, y2 = DrawingPanel.__adjust_coordinates(x1, y1, x2, y2)
        color = self.__pick_color_with_default(color, self._outline, "fill", options)

        self._canvas.create_line(x1, y1, x2, y2, **options)
    
    def draw_oval(self, x, y, w, h, color="", **options):
        '''!
        Draws an oval the given size with its top-left corner at the given (x, y) pixel.
        You can pass the outline color as a last positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default outline color.
        The polygon will not be filled unless a 'fill' named parameter is passed.
        
        @param x: left x coordinate of bounding box of oval
        @param y: top y coordinate of bounding box of oval
        @param w: width of bounding box of oval
        @param h: height of bounding box of oval
        @param color: (optional) color to use for outline of shape (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_oval function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_oval-method
        '''
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        color = self.__pick_color_with_default(color, self._outline, "outline", options)
        Color.normalize_all_colors(options)
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        self._canvas.create_oval(x, y, x + w, y + h, **options)

    def draw_pixel(self, x, y, color="", **options):
        '''!
        Changes the color at the given (x, y) pixel.
        Equivalent to drawing a 1x1 rectangle.
        
        @param x: x coordinate
        @param y: y coordinate
        @param color: (optional) color to use for pixel (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        if not "width" in options:
            options["width"] = 1
        color = self.__pick_color_with_default(color, self._outline, "outline", options)
        Color.normalize_all_colors(options)
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        self._canvas.create_rectangle(x, y, x, y, **options)

    def draw_polyline(self, *coords, **options):
        '''!
        Draws a group of lines between the given coordinates passed as individual x/y coordinate parameters or (x,y) tuples.
        
        You can pass the outline color as a last positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default outline color.
        The difference between draw_polyline and draw_polygon is that the polygon will connect its last line point
        back to the start point; the polygon can also be filled, while a polyline cannot.
        
        example: panel.draw_polyline(x1, y1, x2, y2, x3, y3)
        example: panel.draw_polyline(x1, y1, x2, y2, x3, y3, x4, y4, "red")
        example: panel.draw_polyline(p1, p2, p3)
        
        @param coords: x, y coordinate pairs of arbitrary number
        @param options: (optional) keyword arguments to pass directly to Tk create_line function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_line-method
        '''
        if len(coords) == 0:
            return
        if isinstance(coords[0], list):   # allow passing list or tuple
            coords = coords[0]
        if isinstance(coords[0], tuple):
            # unpack tuples: [(10, 20), (30, 40), (50, 60)] => [10, 20, 30, 40, 50, 60]
            coords = [xy for t in coords for xy in t]
        # if arg 'is' a valid color use as color and remove from coords
        if len(coords) % 2 != 0:
            if Color.is_valid_color(coords[-1]):
                options["fill"] = Color.to_hex(coords[-1])
                coords = coords[:-1]
        if len(coords) % 2 != 0:
            raise ValueError("must pass even number of coordinates")
        adjusted_coords = DrawingPanel.__adjust_coordinates(coords)
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)

        color = self.__pick_color_with_default(None, self._outline, "fill", options)
        for i in range(0, len(coords) - 3, 2):
            x1 = coords[i]
            y1 = coords[i + 1]
            x2 = coords[i + 2]
            y2 = coords[i + 3]
            self.draw_line(x1, y1, x2, y2, color, **options)
    
    def draw_polygon(self, *coords, **options):
        '''!
        Draws an outlined polygon between the given coordinates passed as individual x/y coordinate parameters or (x,y) tuples.
        You can pass the outline color as a last positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default outline color.
        The polygon will not be filled unless a 'fill' named parameter is passed.

        example: panel.draw_polygon(x1, y1, x2, y2, x3, y3)
        example: panel.draw_polygon(x1, y1, x2, y2, x3, y3, x4, y4, "red")
        example: panel.draw_polygon(p1, p2, p3)

        @param coords: x, y coordinate pairs of arbitrary number
        @param options: (optional) keyword arguments to pass directly to Tk create_polygon and/or create_line function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_polygon-method
        '''
        if len(coords) == 0:
            return
        if isinstance(coords[0], list):   # allow passing list or tuple
            coords = coords[0]
        if isinstance(coords[0], tuple):
            # unpack tuples: [(10, 20), (30, 40), (50, 60)] => [10, 20, 30, 40, 50, 60]
            coords = [xy for t in coords for xy in t]
        
        # if arg 'is' a valid color use as color and remove from coords
        outline_color = self._outline
        if len(coords) % 2 != 0:
            if Color.is_valid_color(coords[-1]):
                outline_color = Color.to_hex(coords[-1])
                coords = coords[:-1]
        if len(coords) % 2 != 0:
            raise ValueError("must pass even number of coordinates")
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        if not "fill" in options:
            # no fill; draw as poly line to avoid unwanted polygon fill;
            options["fill"] = outline_color

            # append start point as end point to close the polygon
            if coords[0] != coords[-2] or coords[1] != coords[-1]:
                coords += (coords[0], coords[1])
            self.draw_polyline(coords, **options)
            return

        adjusted_coords = DrawingPanel.__adjust_coordinates(coords)

        self.__pick_color_with_default(None, outline_color, "outline", options)
        Color.normalize_all_colors(options)
        self.__pick_color_with_default(None, self.background, "fill", options)   # default transparent fill
        self._canvas.create_polygon(adjusted_coords, **options)

    def draw_rect(self, x, y, w, h, color="", **options):
        '''!
        Draws a rectangle of the given size with its top-left corner at the given (x, y) pixel.
        Equivalent to draw_rectangle.
        
        @param x: left x coordinate of bounding box of rectangle
        @param y: top y coordinate of bounding box of rectangle
        @param w: width of bounding box of rectangle
        @param h: height of bounding box of rectangle
        @param color: (optional) color to use for outline of shape (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        color = self.__pick_color_with_default(color, self._outline, "outline", options)
        Color.normalize_all_colors(options)
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        #print("x = " + str(x) + "   y = " + str(y) + "     width = " + str(w) +   " height = " + str(h))
        self._canvas.create_rectangle(x, y, x + w, y + h, **options)
    
    def draw_rectangle(self, x, y, w, h, color="", **options):
        '''!
        Draws a rectangle of the given size with its top-left corner at the given (x, y) pixel.
        Equivalent to draw_rect.
        
        @param x: left x coordinate of bounding box of rectangle
        @param y: top y coordinate of bounding box of rectangle
        @param w: width of bounding box of rectangle
        @param h: height of bounding box of rectangle
        @param color: (optional) color to use for outline of shape (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        self.draw_rect(x, y, w, h, color, **options)

    def draw_string(self, text, x, y, color="", font="", **options):
        '''!
        Draws a text string with its top-left corner at the given (x, y) pixel.

        @param x: left x coordinate of start of text
        @param y: top y coordinate of start of text
        @param color: (optional) color to use for outline of shape (if absent, uses panel's default color as set via set_color)
        @param font: (optional) font face to use to render text, in "family size style" format, such as "Times New Roman 16 bold italic"
        @param options: (optional) keyword arguments to pass directly to Tk create_text function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_text-method
        '''
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        color = self.__pick_color_with_default(color, self._outline, "fill", options)
        if font is None or font == "":
            font = self._font
        if not "justify" in options:
            options["justify"] = LEFT
        if not "anchor" in options:
            options["anchor"] = NW
            self._canvas.create_text(x, y, text=text, font=font, **options)

    def fill_arc(self, x, y, w, h, start_angle, extent, color="", **options):
        '''!
        Draws a filled arc (a partial oval).
        The resulting arc begins at 'start_angle' and extends for 'extent' degrees.
        Angles are interpreted such that 0 degrees is at the 3 o'clock position.
        A positive value indicates a counter-clockwise rotation while a negative value indicates a clockwise rotation.
        You can pass the outline color as a named 'outline' parameter, else the fill color will be used as the outline color.
        
        @param x: left x coordinate of bounding box of arc's oval
        @param y: top y coordinate of bounding box of arc's oval
        @param w: width of bounding box of arc's oval
        @param h: height of bounding box of arc's oval
        @param start_angle: starting angle in degrees, where 0 is at "3 o'clock" position
        @param extent: degrees that the arc should extend from its start, where positive numbers are counter-clockwise and negative values are clockwise
        @param color: (optional) color to use for fill of shape (if absent, uses panel's default fill color as set via set_fill_color)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_arc-method
        '''
        if not "width" in options and self._stroke != 1: options["width"] = str(self._stroke)
        color = self.__pick_color_with_default(color, self._fill, "fill", options)
        self.__pick_color_with_default(None, color, "outline", options)
        Color.normalize_all_colors(options)
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        if not "start" in options:
            options["start"] = start_angle
        if not "extent" in options:
            options["extent"] = extent
        if not "style" in options:
            options["style"] = "pieslice"
        bounding_box = (x, y, x + w, y + h)
        self._canvas.create_arc(bounding_box, **options)
    
    def fill_oval(self, x, y, w, h, color="", **options):
        '''!
        Draws a filled oval of the given size with its top-left corner at the given (x, y) pixel.
        You can pass the fill color as a fifth positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default fill color.
        You can pass the outline color as a named 'outline' parameter, else the fill color will be used as the outline color.
        
        @param x: left x coordinate of bounding box of oval
        @param y: top y coordinate of bounding box of oval
        @param w: width of bounding box of oval
        @param h: height of bounding box of oval
        @param color: (optional) color to use for fill of shape (if absent, uses panel's default color as set via set_fill_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_oval function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_oval-method
        '''
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        color = self.__pick_color_with_default(color, self._fill, "fill", options)
        if not "width" in options and "outline" in options and self._stroke != 1: options["width"] = str(self._stroke)

        # if outline is present as named parameter in options dict, leave it;
        # else set equal to fill color
        self.__pick_color_with_default(None, color, "outline", options)
        
        # draw the filled oval
        self._canvas.create_oval(x, y, x + w, y + h, **options)

    def fill_polygon(self, *coords, **options):
        '''!
        Draws a filled polygon between the given coordinates passed as individual x/y coordinate parameters or (x,y) tuples.
        You can pass the 'fill' as a last positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default fill color.
        You can pass the outline color as a named 'outline' parameter, else the fill color will be used as the outline color.
        
        example: panel.fill_polygon(x1, y1, x2, y2, x3, y3)
        example: panel.fill_polygon(x1, y1, x2, y2, x3, y3, x4, y4, "red")
        example: panel.fill_polygon(p1, p2, p3)

        @param coords: x, y coordinate pairs of arbitrary number
        @param options: (optional) keyword arguments to pass directly to Tk create_polygon function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_polygon-method
        '''
        if len(coords) == 0:
            return
        if isinstance(coords[0], list):   # allow passing list or tuple
            coords = coords[0]
        if isinstance(coords[0], tuple):
            # unpack tuples: [(10, 20), (30, 40), (50, 60)] => [10, 20, 30, 40, 50, 60]
            coords = [xy for t in coords for xy in t]
        if not "width" in options and "outline" in options and self._stroke != 1: options["width"] = str(self._stroke)
        
        # if arg 'is' a valid color use as color and remove from coords
        fill_color = self._fill
        if len(coords) % 2 != 0:
            if Color.is_valid_color(coords[-1]):
                fill_color = options["fill"] = Color.to_hex(coords[-1])
                coords = coords[:-1]
        if len(coords) % 2 != 0:
            raise ValueError("must pass even number of coordinates")
        adjusted_coords = DrawingPanel.__adjust_coordinates(coords)

        self.__pick_color_with_default(None, fill_color, "fill", options)
        Color.normalize_all_colors(options)
        self._canvas.create_polygon(adjusted_coords, options)

    def fill_rect(self, x, y, w, h, color="", **options):
        '''!
        Draws a filled rectangle of the given size with its top-left corner at the given (x, y) pixel.
        You can pass the fill color as a fifth positional parameter, or it can be a keyword parameter (in options),
        or if none is passed at all, we will fall back to the panel's default fill color.
        You can pass the outline color as a named 'outline' parameter, else the fill color will be used as the outline color.
        Equivalent to fill_rectangle.
        
        @param x: left x coordinate of bounding box of rectangle
        @param y: top y coordinate of bounding box of rectangle
        @param w: width of bounding box of rectangle
        @param h: height of bounding box of rectangle
        @param color: (optional) color to use to fill shape (if absent, uses panel's default color as set via set_fill_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        x, y = DrawingPanel.__adjust_coordinates(x, y)
        color = self.__pick_color_with_default(color, self._fill, "fill", options)
        if not "width" in options and "outline" in options and self._stroke != 1: options["width"] = str(self._stroke)

        # if outline is present as named parameter in options dict, leave it;
        # else set equal to fill color
        self.__pick_color_with_default(None, color, "outline", options)
        
        # draw the filled rectangle
        self._canvas.create_rectangle(x, y, x + w, y + h, **options)

    def fill_rectangle(self, x, y, w, h, fill="", color="", **options):
        '''!
        Draws a filled rectangle of the given size with its top-left corner at the given (x, y) pixel.
        Equivalent to fill_rect.
        
        @param x: left x coordinate of bounding box of rectangle
        @param y: top y coordinate of bounding box of rectangle
        @param w: width of bounding box of rectangle
        @param h: height of bounding box of rectangle
        @param color: (optional) color to use to fill shape (if absent, uses panel's default color as set via set_fill_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        self.fill_rect(x, y, w, h, fill, color, **options)

    def __pick_color_with_default(self, color, default, key, options):
        '''!
        Private helper function to help us allow for explicitly passed or implicit default colors.
        Chooses a color based on the given value, default, and what option key to store it as.
        If the given color is None/empty and default is present, uses that default.
        If a non-empty color or default is found, places this into options[key] for each key in keys.
        Not to be called by students.
        '''
        if color is None or color == "":
            found = False
            if key in options:
                color = options[key]
                found = True
            if not found:
                color = default
        if not (color is None or color == ""):
            color = Color.to_hex(color)
            options[key] = color
        return color

    @property
    def background(self):
        '''!
        The panel's background color. Default is white.
        @return type str
        '''
        return self._canvas["bg"]

    def get_background(self):
        '''!
        Returns the panel's background color. Default is white.
        @return str color as hex string such as "#ff00cc"
        @return type str
        '''
        return self._canvas["bg"]

    @property
    def background_rgb(self):
        '''!
        The panel's background color as an RGB tuple such as (255, 0, 192). Default is white.
        @return type tuple
        '''
        return Color.to_rgb(self.background)

    def get_background_rgb(self):
        '''!
        Returns the panel's background color as an RGB tuple. Default is white.
        @return: color as a 3-element rgb tuple such as (255, 0, 192)
        @return type tuple
        '''
        return Color.to_rgb(self.background)

    @property
    def color(self):
        '''!
        The default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to foreground.
        @return type str
        '''
        return self._outline

    def get_color(self):
        '''!
        Returns the default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to get_foreground.
        @return type str
        '''
        return self._outline

    @property
    def fill_color(self):
        '''!
        The default color used to fill in shapes.
        This color will be used unless you explicitly pass a color to the filling function to draw a particular shape.
        @return type str
        '''
        return self._fill

    def get_fill_color(self):
        '''!
        Returns the default color used to fill in shapes.
        This color will be used unless you explicitly pass a color to the filling function to draw a particular shape.
        @return type str
        '''
        return self._fill

    @property
    def font(self):
        '''!
        The font used for drawn strings.
        Pass a string in "FONTNAME SIZE STYLE" format, with the last two tokens optional,
        such as "Helvetica" or "Consolas 14" or "Times New Roman 16 bold".
        @return type str
        @see: http://effbot.org/tkinterbook/tkinter-widget-styling.htm
        '''
        return self._font

    def get_font(self):
        '''!
        Returns the font used for drawn strings.
        Pass a string in "FONTNAME SIZE STYLE" format, with the last two tokens optional,
        such as "Helvetica" or "Consolas 14" or "Times New Roman 16 bold".
        @return: font as a "family size style" string such as "Times New Roman 16 bold italic"
        @return type str
        @see: http://effbot.org/tkinterbook/tkinter-widget-styling.htm
        '''
        return self._font

    @property
    def foreground(self):
        '''!
        The default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to color.
        @return type str
        '''
        return self._outline

    def get_foreground(self):
        '''!
        Returns the default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to get_color.
        @return type str
        '''
        return self._outline

    @property
    def height(self):
        '''!
        The height of the panel's canvas area.
        @return type int
        '''
        return self._height

    def get_height(self):
        '''!
        Returns the height of the panel's canvas area.
        @return: height in pixels as an int
        @return type int
        '''
        return self._height

    @property
    def outline_color(self):
        '''!
        The default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to color.
        @return type str
        '''
        return self._outline

    def get_outline_color(self):
        '''!
        Returns the default color used to outline drawn shapes.
        This color will be used unless you explicitly pass a color to the drawing function to draw a particular shape.
        Equivalent to get_color.
        @return type str
        '''
        return self._outline

    # helper to implement get_pixel_color and get_pixel_color_rgb
    def _get_pixel_color_helper(self, x, y, convert_to_hex=False):
        x = DrawingPanel.__adjust_coordinate(x)
        y = DrawingPanel.__adjust_coordinate(y)
        ids = self._canvas.find_overlapping(x, y, x, y)
        result = None
        if len(ids) > 0:
            index = ids[-1]
            if index in self._image_map:
                # if PhotoImage, get(x, y) to get RGB
                img = self._image_map[index]
                coords = self._canvas.coords(index)
                x = int(x - int(coords[0]) + img.width() / 2)
                y = int(y - int(coords[1]) + img.height() / 2)
                if x >= 0 and x < img.width() and y >= 0 and y < img.height():
                    rgb = img.get(x, y)
                    if convert_to_hex:
                        result = Color.to_hex(rgb[0], rgb[1], rgb[2])
                    else:
                        result = tuple(rgb)
            else:
                # get pixel from an ordinary shape
                color = self._canvas.itemcget(index, "fill")
                if color == "":
                    color = self._canvas.itemcget(index, "outline")
                if color == "":
                    color = self._canvas.itemcget(index, "color")
                if color != "":
                    result = color
        if result is None:
            result = self.background
            if convert_to_hex:
                result = Color.to_hex(result)
            else:
                result = Color.to_rgb(result)
        if result:
            return result
        
        temp = self._canvas.winfo_rgb(result)   # returns a 3-tuple of ints from 0-65535 representing R,G,B
        if temp:
            r = int(temp[0] / 256)
            g = int(temp[1] / 256)
            b = int(temp[2] / 256)
            if convert_to_hex:
                result = Color.to_hex(r, g, b)   # convert to 0-255
            else:
                result = (r, g, b)
        
        if convert_to_hex and type(result) != str:
            result = Color.to_hex(result)
        if not convert_to_hex and type(result) != tuple:
            result = Color.to_rgb(result)
        return result

    def get_pixel(self, x, y):
        '''!
        Returns the RGB color of the given (x, y) pixel on the canvas as a 3-tuple of three integers from 0-255
        representing the Red, Green, and Blue components.
        NOTE: This function currently fails when there are text strings drawn onto the canvas.
        It interprets every pixel in the bounding box of the text string to be the text string's color.
        Equivalent to get_pixel_color_rgb.
        
        @param x: x coordinate of pixel
        @param y: y coordinate of pixel
        @return type tuple
        '''
        return self._get_pixel_color_helper(x, y, False)

    def get_pixel_color(self, x, y):
        '''!
        Returns the RGB color of the given (x, y) pixel on the canvas as a string such as "#ff00ff".
        NOTE: This function currently fails when there are text strings drawn onto the canvas.
        It interprets every pixel in the bounding box of the text string to be the text string's color.
        
        @param x: x coordinate of pixel
        @param y: y coordinate of pixel
        @return type str
        '''
        return self._get_pixel_color_helper(x, y, True)

    def get_pixel_color_rgb(self, x, y):
        '''!
        Returns the RGB color of the given (x, y) pixel on the canvas as a 3-tuple of three integers from 0-255
        representing the Red, Green, and Blue components.
        NOTE: This function currently fails when there are text strings drawn onto the canvas.
        It interprets every pixel in the bounding box of the text string to be the text string's color.
        
        @param x: x coordinate of pixel
        @param y: y coordinate of pixel
        @return type tuple
        '''
        return self._get_pixel_color_helper(x, y, False)

    @property
    def pixels(self):
        '''!
        A 2D list storing the RGB colors of every pixel on the canvas as a 3-tuple of three integers from 0-255
        representing the Red, Green, and Blue components.
        The x-dimension is the first index and the y-dimension is the second index;
        for example, pixels[x][y] is the RGB color of the pixel at position (x, y).
        
        @return type list
        '''
        return self.get_pixels()

    @property
    def pixel_colors(self):
        '''!
        A 2D list storing the RGB colors of every pixel on the canvas as strings such as "#ff00ff".
        The x-dimension is the first index and the y-dimension is the second index;
        for example, pixels[x][y] is the RGB color of the pixel at position (x, y).
        
        @return type list
        '''
        return self.get_pixel_colors()

    def get_pixels(self):
        '''!
        Returns a 2D list storing the RGB colors of every pixel on the canvas as a 3-tuple of three integers from 0-255
        representing the Red, Green, and Blue components.
        The x-dimension is the first index and the y-dimension is the second index;
        for example, pixels[x][y] is the RGB color of the pixel at position (x, y).
        
        @return type list
        '''
        pixels = [None] * self.width
        for x in range(self.width):
            pixels[x] = [None] * self.height
            for y in range(self.height):
                pixels[x][y] = self.get_pixel_color_rgb(x, y)
        return pixels

    def get_pixel_colors(self):
        '''!
        Returns a 2D list storing the RGB colors of every pixel on the canvas as a string such as "#ff00ff".
        The x-dimension is the first index and the y-dimension is the second index;
        for example, pixels[x][y] is the RGB color of the pixel at position (x, y).
        
        @return type list
        '''
        pixels = [None] * self.width
        for x in range(self.width):
            pixels[x] = [None] * self.height
            for y in range(self.height):
                pixels[x][y] = self.get_pixel_color(x, y)
        return pixels

    @property
    def size(self):
        '''!
        A 2-element tuple of the width and height of the panel's canvas area.
        @return type tuple
        '''
        return (self._width, self._height)

    def get_size(self):
        '''!
        Returns a 2-element tuple of the width and height of the panel's canvas area.
        @return: 2-element tuple such as (400, 300)
        @return type tuple
        '''
        return (self._width, self._height)

    @property
    def stroke(self):
        '''!
        The width that will be used to draw lines on future shapes.
        Equivalent to stroke_width.
        @return type int
        '''
        return self._stroke

    def get_stroke(self):
        '''!
        Returns the width that will be used to draw lines on future shapes.
        Equivalent to get_stroke_width.
        @return: stroke width (default 1)
        @return type int
        '''
        return self._stroke

    @property
    def stroke_width(self):
        '''!
        The width that will be used to draw lines on future shapes.
        Equivalent to stroke.
        @return type int
        '''
        return self._stroke

    def get_stroke_width(self):
        '''!
        Returns the width that will be used to draw lines on future shapes.
        Equivalent to get_stroke.
        @return: stroke width (default 1)
        @return type int
        '''
        return self._stroke

    @property
    def width(self):
        '''!
        The width of the panel's canvas area.
        @return type int
        '''
        return self._width

    def get_width(self):
        '''!
        Returns the width of the panel's canvas area.
        @return type int
        '''
        return self._width

    @background.setter
    def background(self, value):
        '''!
        Sets the panel's background color to be the given color, as a string such as "yellow" or "black" or "yellow" or "#ff00cc"
        '''
        color = Color.to_hex(value)
        self._canvas["bg"] = color

    def set_background(self, *args):
        '''!
        Sets the panel's background color to be the given color.
        color -- the color to set, as a string such as "yellow" or "black"
        @param args: either a color string such as "yellow" or "#ff00cc", or a 3-element RGB tuple such as (255, 0, 192)
        '''
        color = Color.to_hex(*args)
        self._canvas["bg"] = color

    @color.setter
    def color(self, value):
        '''!
        Sets the color used to outline future drawn shapes.
        The color passed must be a color string like "purple" or a hex string like "#ff00ff".
        @see: http://wiki.tcl.tk/16166
        '''
        color = Color.to_hex(value)
        self._outline = color
    
    def set_color(self, *args):
        '''!
        Sets the color used to outline future drawn shapes.
        The color passed can be a color string like "purple" or a hex string like "#ff00ff" or an RGB tuple like (255, 0, 255).
        @param args: either a color string such as "yellow" or "#ff00cc", or a 3-element RGB tuple such as (255, 0, 192)
        @see: http://wiki.tcl.tk/16166
        '''
        color = Color.to_hex(*args)
        self._outline = color
    
    @fill_color.setter
    def fill_color(self, value):
        '''!
        Sets the color used to fill in future drawn shapes.
        The color passed must be a color string like "purple" or a hex string like "#ff00ff".
        '''
        color = Color.to_hex(value)
        self._fill = color
    
    def set_fill_color(self, *args):
        '''!
        Sets the color used to fill in future drawn shapes.
        The color passed can be a color string like "purple" or a hex string like "#ff00ff" or an RGB tuple like (255, 0, 255).
        @param args: either a color string such as "yellow" or "#ff00cc", or a 3-element RGB tuple such as (255, 0, 192)
        '''
        color = Color.to_hex(*args)
        self._fill = color
    
    @font.setter
    def font(self, value):
        '''!
        Sets the font used for future drawn strings.
        Pass a string in "FONTNAME SIZE STYLE" format, with the last two tokens optional,
        such as "Helvetica" or "Consolas 14" or "Times New Roman 16 bold".
        @see: http://effbot.org/tkinterbook/tkinter-widget-styling.htm
        '''
        self._font = value
    
    def set_font(self, font):
        '''!
        Sets the font used for future drawn strings.
        Pass a string in "FONTNAME SIZE STYLE" format, with the last two tokens optional,
        such as "Helvetica" or "Consolas 14" or "Times New Roman 16 bold".
        @param font: a font string in "FONTNAME SIZE STYLE" format, with the last two tokens optional, such as "Helvetica" or "Consolas 14" or "Times New Roman 16 bold".
        @see: http://effbot.org/tkinterbook/tkinter-widget-styling.htm
        '''
        self._font = font
    
    @foreground.setter
    def foreground(self, value):
        '''!
        Sets the color used to outline and fill future drawn shapes.
        The color passed must be a color string like "purple" or a hex string like "#ff00ff".
        Equivalent to setting color.
        '''
        self.set_color(value)
    
    def set_foreground(self, *args):
        '''!
        Sets the color used to outline and fill future drawn shapes.
        The color passed can be a color string like "purple" or a hex string like "#ff00ff" or an RGB tuple like (255, 0, 255).
        Equivalent to set_color.
        @param args: either a color string such as "yellow" or "#ff00cc", or a 3-element RGB tuple such as (255, 0, 192)
        '''
        self.set_color(*args)
    
    @outline_color.setter
    def outline_color(self, value):
        '''!
        Sets the color used to outline future drawn shapes.
        Equivalent to setting color.
        Pass either a color string such as "yellow" or "#ff00cc"
        '''
        color = Color.to_hex(value)
        self._outline = color

    def set_outline_color(self, *args):
        '''!
        Sets the color used to outline future drawn shapes.
        Equivalent to set_color.
        @param args: either a color string such as "yellow" or "#ff00cc", or a 3-element RGB tuple such as (255, 0, 192)
        '''
        color = Color.to_hex(*args)
        self._outline = color

    def set_pixel(self, x, y, color="", **options):
        '''!
        Changes the color at the given (x, y) pixel.
        Equivalent to drawing a 1x1 rectangle, or equivalent to draw_pixel.
        
        @param x: x coordinate
        @param y: y coordinate
        @param color: (optional) color to use for pixel (if absent, uses panel's default color as set via set_color)
        @param options: (optional) keyword arguments to pass directly to Tk create_rectangle function (see Tkinter documentation)
        @see: http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_rectangle-method
        '''
        self.draw_pixel(x, y, color, **options)

    @pixels.setter
    def pixels(self, px):
        '''!
        Sets the RGB values of all pixels in this panel's canvas based on the values in the given 2D list.
        Equivalent to set_pixels.
        Pass a 2D list of (r, g, b) tuples with the x-dimension as the first index and the y-dimension as the second index.
        '''
        self.set_pixels(px)

    def set_pixels(self, pixels):
        '''!
        Sets the RGB values of all pixels in this panel's canvas based on the values in the given 2D list.
        Pass a 2D list of (r, g, b) tuples with the x-dimension as the first index and the y-dimension as the second index.
        '''
        w = len(pixels)
        h = len(pixels[0])
        if w != self.width or h != self.height:
            self.set_size(w, h)
        img = PhotoImage(width=self.width, height=self.height)
        for x in range(w):
            for y in range(h):
                color = Color.to_hex(pixels[x][y])
                img.put(color, to=(x, y))
        self.draw_image(img)


    @pixel_colors.setter
    def pixel_colors(self, px):
        '''!
        Sets the RGB values of all pixels in this panel's canvas based on the values in the given 2D list.
        Pass a 2D list of strings such as "#ff00ff" with the x-dimension as the first index and the y-dimension as the second index.
        '''
        self.set_pixel_colors(px)

    def set_pixel_colors(self, pixels):
        '''!
        Sets the RGB values of all pixels in this panel's canvas based on the values in the given 2D list.
        Pass a 2D list of strings such as "#ff00ff" with the x-dimension as the first index and the y-dimension as the second index.
        '''
        w = len(pixels)
        h = len(pixels[0])
        if w != self.width or h != self.height:
            self.set_size(w, h)
        img = PhotoImage(width=self.width, height=self.height)
        for x in range(w):
            for y in range(h):
                img.put(pixels[x][y], (x, y))
        self.draw_image(img)

    @stroke.setter
    def stroke(self, width):
        '''!
        Sets the width that will be used to draw lines on future shapes.
        Equivalent to setting stroke_width.
        '''
        self._stroke = width

    def set_stroke(self, width):
        '''!
        Sets the width that will be used to draw lines on future shapes.
        Equivalent to set_stroke_width.
        '''
        self._stroke = width

    @stroke_width.setter
    def stroke_width(self, width):
        '''!
        Sets the width that will be used to draw lines on future shapes.
        Equivalent to set_stroke.
        '''
        self._stroke = width

    def set_stroke_width(self, width):
        '''!
        Sets the width that will be used to draw lines on future shapes.
        Equivalent to set_stroke.
        '''
        self._stroke = width

    def sleep(self, ms):
        '''!
        Causes the DrawingPanel to pause for the given number of milliseconds.
        Useful for creating simple animations.
        @param ms: number of milliseconds to pause
        '''
        if ms <= 0:
            return
        try:
            if hasattr(self, "_canvas"):
                self._canvas.update()
            time.sleep(ms / 1000.0)   # time.sleep takes a number of seconds (can be float)
            if hasattr(self, "_canvas"):
                self._canvas.update()
        except Exception:
            pass   # swallow exception

    def pack(self, padding_top=0, padding_right=0, padding_bottom=0, 
             padding_left=0, side=''):
        '''!
        Adds this canvas to the frame in the left, or right if side is specified. 
        
        example: panel.pack()
        example: panel.pack(5, 0, 0, 5, "left")

        @param padding_top: the amount of extra space between this component and the top
        @param padding_right: the amount of extra space between this component and the right edge
        @param padding_bottom: the amount of extra space between this component and the bottom
        @param padding_left: the amount of extra space between this component and the left edge
        @param side: the side of the GUI this component should be added to
        '''

        if side == 'left' or side == 'right':
            self._canvas.pack(padx=(padding_left, padding_right),
                         pady=(padding_top, padding_bottom), side=side)
        else:
            self._canvas.pack(padx=(padding_left, padding_right),
                         pady=(padding_top, padding_bottom))

class Color:
    '''!
    A class to deal with colors.
    A color can be specified in four ways by the client:
    1) as a Color constant like Color.RED or Color.LIGHT_SKY_BLUE
    2) as a name like "red" or "light sky blue"
    3) as a six-character hex string like "#ff00cc"
    4) as a three-integer tuple like (255, 0, 198) where each int is in range 0-255
    '''
    def __init__(self, *args):
        '''!
        Constructs a color.
        '''
        self.hex_string = Color.to_name(*args)
    
    def __str__(self):
        '''!
        Returns a string representation of this color.
        '''
        return self.hex_string
    
    @staticmethod
    def is_valid_color(color):
        '''!
        Returns True if the color passed is a valid color in any form, such as
        a Color object, a hex string such as "#ff00cc", a color name such as "red" or "light sky blue",
        or RGB color expressed as a 3-element tuple or list.
        '''
        return color is Color or Color.is_hex(color) or Color.is_name(color) or Color.is_rgb(color)

    @staticmethod
    def is_hex(color):
        '''!
        Returns True if the color passed is a valid hex color string such as "#ff00cc".
        '''
        if isinstance(color, str):
            color = color.lower()
            if color[0] != "#":
                return False
            for i in range(1, len(color)):
                if not color[i] in "0123456789abcdef":
                    return False
            return True
        else:
            return False

    @staticmethod
    def is_name(color):
        '''!
        Returns True if the color passed is a valid color name such as "red" or "light sky blue".
        '''
        return isinstance(color, str) and color.lower() in Color.NAME_TO_RGB_LC

    @staticmethod
    def is_rgb(color):
        '''!
        Returns True if the color passed is a valid RGB color expressed as a 3-element tuple or list.
        '''
        if color is None:
            return False
        elif isinstance(color, tuple) or isinstance(color, list):
            if len(color) != 3:
                return False
            return 0 <= color[0] <= 255 and 0 <= color[1] <= 255 and 0 <= color[2] <= 255
        else:
            return False

    @staticmethod
    def to_hex(*args):
        '''!
        Converts a color into a hex color string such as "#ff00ff".
        Accepts a Color in any format, such as a Color object, a color name, a hex string, or tuple of three RGB integers from 0-255.
        If an invalid color is passed, raises a ValueError.
        '''
        if len(args) == 0:
            raise ValueError
        if args[0] is None:
            return None
        elif isinstance(args[0], Color):
            # Color objects are always stored/printed as hex already
            return str(args[0])
        elif len(args) == 3:
            if Color.is_rgb(args):
                # RGB tuple
                r, g, b = args
                return "#%02x%02x%02x" % (int(r), int(g), int(b))
        elif isinstance(args[0], str):
            if Color.is_hex(args[0]):
                return args[0]   # already a hex color
            elif args[0].lower() in Color.NAME_TO_HEX_LC:
                # check for color name like "red"
                return Color.NAME_TO_HEX_LC[args[0].lower()]
            else:
                raise ValueError("invalid color: " + str(args[0]))
        elif isinstance(args[0], tuple) and len(args[0]) == 3:
            return "#%02x%02x%02x" % args[0]
        raise ValueError("invalid color: " + str(args))
    
    @staticmethod
    def to_name(*args):
        '''!
        Converts a color into a color name string such as "red" or "light sky blue".
        Accepts a Color in any format, such as a Color object, a color name, a hex string, or tuple of three RGB integers from 0-255.
        If an invalid color is passed, or one that does not correspond to a color name, returns the hex string version of the color.
        '''
        hex_string = Color.to_hex(*args)
        if hex_string in Color.HEX_TO_NAME:
            return Color.HEX_TO_NAME[hex_string]
        else:
            return hex_string
    
    @staticmethod
    def to_rgb(*args):
        '''!
        Converts a color into an RGB tuple such as (255, 0, 128).
        Accepts a Color in any format, such as a Color object, a color name, a hex string, or tuple of three RGB integers from 0-255.
        If an invalid color is passed, or one that does not correspond to a color name, raises a ValueError.
        '''
        hex_string = Color.to_hex(*args)
        hex_string = str(hex_string).replace("#", "")
        r = int(hex_string[0:2], 16)
        g = int(hex_string[2:4], 16)
        b = int(hex_string[4:6], 16)
        return (r, g, b)
    
    @staticmethod
    def normalize_all_colors(options):
        '''!
        Makes sure that any color values in the given dict are in normalized hex string form,
        not tuples or color names.
        Not to be called by students.
        '''
        keys = ["color", "fill", "outline"]
        for key in keys:
            if key in options:
                options[key] = Color.to_hex(options[key])

    # begin looong constants for colors and names
    ALICE_BLUE = "alice blue"
    ALICEBLUE = "AliceBlue"
    ANTIQUE_WHITE = "antique white"
    ANTIQUEWHITE = "AntiqueWhite"
    ANTIQUEWHITE1 = "AntiqueWhite1"
    ANTIQUEWHITE2 = "AntiqueWhite2"
    ANTIQUEWHITE3 = "AntiqueWhite3"
    ANTIQUEWHITE4 = "AntiqueWhite4"
    AQUAMARINE = "aquamarine"
    AQUAMARINE1 = "aquamarine1"
    AQUAMARINE2 = "aquamarine2"
    AQUAMARINE3 = "aquamarine3"
    AQUAMARINE4 = "aquamarine4"
    AZURE = "azure"
    AZURE1 = "azure1"
    AZURE2 = "azure2"
    AZURE3 = "azure3"
    AZURE4 = "azure4"
    BEIGE = "beige"
    BISQUE = "bisque"
    BISQUE1 = "bisque1"
    BISQUE2 = "bisque2"
    BISQUE3 = "bisque3"
    BISQUE4 = "bisque4"
    BLACK = "black"
    BLANCHED_ALMOND = "blanched almond"
    BLANCHEDALMOND = "BlanchedAlmond"
    BLUE = "blue"
    BLUE_VIOLET = "blue violet"
    BLUE1 = "blue1"
    BLUE2 = "blue2"
    BLUE3 = "blue3"
    BLUE4 = "blue4"
    BLUEVIOLET = "BlueViolet"
    BROWN = "brown"
    BROWN1 = "brown1"
    BROWN2 = "brown2"
    BROWN3 = "brown3"
    BROWN4 = "brown4"
    BURLYWOOD = "burlywood"
    BURLYWOOD1 = "burlywood1"
    BURLYWOOD2 = "burlywood2"
    BURLYWOOD3 = "burlywood3"
    BURLYWOOD4 = "burlywood4"
    CADET_BLUE = "cadet blue"
    CADETBLUE = "CadetBlue"
    CADETBLUE1 = "CadetBlue1"
    CADETBLUE2 = "CadetBlue2"
    CADETBLUE3 = "CadetBlue3"
    CADETBLUE4 = "CadetBlue4"
    CHARTREUSE = "chartreuse"
    CHARTREUSE1 = "chartreuse1"
    CHARTREUSE2 = "chartreuse2"
    CHARTREUSE3 = "chartreuse3"
    CHARTREUSE4 = "chartreuse4"
    CHOCOLATE = "chocolate"
    CHOCOLATE1 = "chocolate1"
    CHOCOLATE2 = "chocolate2"
    CHOCOLATE3 = "chocolate3"
    CHOCOLATE4 = "chocolate4"
    CORAL = "coral"
    CORAL1 = "coral1"
    CORAL2 = "coral2"
    CORAL3 = "coral3"
    CORAL4 = "coral4"
    CORNFLOWER_BLUE = "cornflower blue"
    CORNFLOWERBLUE = "CornflowerBlue"
    CORNSILK = "cornsilk"
    CORNSILK1 = "cornsilk1"
    CORNSILK2 = "cornsilk2"
    CORNSILK3 = "cornsilk3"
    CORNSILK4 = "cornsilk4"
    CYAN = "cyan"
    CYAN1 = "cyan1"
    CYAN2 = "cyan2"
    CYAN3 = "cyan3"
    CYAN4 = "cyan4"
    DARK_BLUE = "dark blue"
    DARK_CYAN = "dark cyan"
    DARK_GOLDENROD = "dark goldenrod"
    DARK_GRAY = "dark gray"
    DARK_GREEN = "dark green"
    DARK_GREY = "dark grey"
    DARK_KHAKI = "dark khaki"
    DARK_MAGENTA = "dark magenta"
    DARK_OLIVE_GREEN = "dark olive green"
    DARK_ORANGE = "dark orange"
    DARK_ORCHID = "dark orchid"
    DARK_RED = "dark red"
    DARK_SALMON = "dark salmon"
    DARK_SEA_GREEN = "dark sea green"
    DARK_SLATE_BLUE = "dark slate blue"
    DARK_SLATE_GRAY = "dark slate gray"
    DARK_SLATE_GREY = "dark slate grey"
    DARK_TURQUOISE = "dark turquoise"
    DARK_VIOLET = "dark violet"
    DARKBLUE = "DarkBlue"
    DARKCYAN = "DarkCyan"
    DARKGOLDENROD = "DarkGoldenrod"
    DARKGOLDENROD1 = "DarkGoldenrod1"
    DARKGOLDENROD2 = "DarkGoldenrod2"
    DARKGOLDENROD3 = "DarkGoldenrod3"
    DARKGOLDENROD4 = "DarkGoldenrod4"
    DARKGRAY = "DarkGray"
    DARKGREEN = "DarkGreen"
    DARKGREY = "DarkGrey"
    DARKKHAKI = "DarkKhaki"
    DARKMAGENTA = "DarkMagenta"
    DARKOLIVEGREEN = "DarkOliveGreen"
    DARKOLIVEGREEN1 = "DarkOliveGreen1"
    DARKOLIVEGREEN2 = "DarkOliveGreen2"
    DARKOLIVEGREEN3 = "DarkOliveGreen3"
    DARKOLIVEGREEN4 = "DarkOliveGreen4"
    DARKORANGE = "DarkOrange"
    DARKORANGE1 = "DarkOrange1"
    DARKORANGE2 = "DarkOrange2"
    DARKORANGE3 = "DarkOrange3"
    DARKORANGE4 = "DarkOrange4"
    DARKORCHID = "DarkOrchid"
    DARKORCHID1 = "DarkOrchid1"
    DARKORCHID2 = "DarkOrchid2"
    DARKORCHID3 = "DarkOrchid3"
    DARKORCHID4 = "DarkOrchid4"
    DARKRED = "DarkRed"
    DARKSALMON = "DarkSalmon"
    DARKSEAGREEN = "DarkSeaGreen"
    DARKSEAGREEN1 = "DarkSeaGreen1"
    DARKSEAGREEN2 = "DarkSeaGreen2"
    DARKSEAGREEN3 = "DarkSeaGreen3"
    DARKSEAGREEN4 = "DarkSeaGreen4"
    DARKSLATEBLUE = "DarkSlateBlue"
    DARKSLATEGRAY = "DarkSlateGray"
    DARKSLATEGRAY1 = "DarkSlateGray1"
    DARKSLATEGRAY2 = "DarkSlateGray2"
    DARKSLATEGRAY3 = "DarkSlateGray3"
    DARKSLATEGRAY4 = "DarkSlateGray4"
    DARKSLATEGREY = "DarkSlateGrey"
    DARKTURQUOISE = "DarkTurquoise"
    DARKVIOLET = "DarkViolet"
    DEEP_PINK = "deep pink"
    DEEP_SKY_BLUE = "deep sky blue"
    DEEPPINK = "DeepPink"
    DEEPPINK1 = "DeepPink1"
    DEEPPINK2 = "DeepPink2"
    DEEPPINK3 = "DeepPink3"
    DEEPPINK4 = "DeepPink4"
    DEEPSKYBLUE = "DeepSkyBlue"
    DEEPSKYBLUE1 = "DeepSkyBlue1"
    DEEPSKYBLUE2 = "DeepSkyBlue2"
    DEEPSKYBLUE3 = "DeepSkyBlue3"
    DEEPSKYBLUE4 = "DeepSkyBlue4"
    DIM_GRAY = "dim gray"
    DIM_GREY = "dim grey"
    DIMGRAY = "DimGray"
    DIMGREY = "DimGrey"
    DODGER_BLUE = "dodger blue"
    DODGERBLUE = "DodgerBlue"
    DODGERBLUE1 = "DodgerBlue1"
    DODGERBLUE2 = "DodgerBlue2"
    DODGERBLUE3 = "DodgerBlue3"
    DODGERBLUE4 = "DodgerBlue4"
    FIREBRICK = "firebrick"
    FIREBRICK1 = "firebrick1"
    FIREBRICK2 = "firebrick2"
    FIREBRICK3 = "firebrick3"
    FIREBRICK4 = "firebrick4"
    FLORAL_WHITE = "floral white"
    FLORALWHITE = "FloralWhite"
    FOREST_GREEN = "forest green"
    FORESTGREEN = "ForestGreen"
    GAINSBORO = "gainsboro"
    GHOST_WHITE = "ghost white"
    GHOSTWHITE = "GhostWhite"
    GOLD = "gold"
    GOLD1 = "gold1"
    GOLD2 = "gold2"
    GOLD3 = "gold3"
    GOLD4 = "gold4"
    GOLDENROD = "goldenrod"
    GOLDENROD1 = "goldenrod1"
    GOLDENROD2 = "goldenrod2"
    GOLDENROD3 = "goldenrod3"
    GOLDENROD4 = "goldenrod4"
    GRAY = "gray"
    GRAY0 = "gray0"
    GRAY1 = "gray1"
    GRAY2 = "gray2"
    GRAY3 = "gray3"
    GRAY4 = "gray4"
    GRAY5 = "gray5"
    GRAY6 = "gray6"
    GRAY7 = "gray7"
    GRAY8 = "gray8"
    GRAY9 = "gray9"
    GRAY10 = "gray10"
    GRAY11 = "gray11"
    GRAY12 = "gray12"
    GRAY13 = "gray13"
    GRAY14 = "gray14"
    GRAY15 = "gray15"
    GRAY16 = "gray16"
    GRAY17 = "gray17"
    GRAY18 = "gray18"
    GRAY19 = "gray19"
    GRAY20 = "gray20"
    GRAY21 = "gray21"
    GRAY22 = "gray22"
    GRAY23 = "gray23"
    GRAY24 = "gray24"
    GRAY25 = "gray25"
    GRAY26 = "gray26"
    GRAY27 = "gray27"
    GRAY28 = "gray28"
    GRAY29 = "gray29"
    GRAY30 = "gray30"
    GRAY31 = "gray31"
    GRAY32 = "gray32"
    GRAY33 = "gray33"
    GRAY34 = "gray34"
    GRAY35 = "gray35"
    GRAY36 = "gray36"
    GRAY37 = "gray37"
    GRAY38 = "gray38"
    GRAY39 = "gray39"
    GRAY40 = "gray40"
    GRAY41 = "gray41"
    GRAY42 = "gray42"
    GRAY43 = "gray43"
    GRAY44 = "gray44"
    GRAY45 = "gray45"
    GRAY46 = "gray46"
    GRAY47 = "gray47"
    GRAY48 = "gray48"
    GRAY49 = "gray49"
    GRAY50 = "gray50"
    GRAY51 = "gray51"
    GRAY52 = "gray52"
    GRAY53 = "gray53"
    GRAY54 = "gray54"
    GRAY55 = "gray55"
    GRAY56 = "gray56"
    GRAY57 = "gray57"
    GRAY58 = "gray58"
    GRAY59 = "gray59"
    GRAY60 = "gray60"
    GRAY61 = "gray61"
    GRAY62 = "gray62"
    GRAY63 = "gray63"
    GRAY64 = "gray64"
    GRAY65 = "gray65"
    GRAY66 = "gray66"
    GRAY67 = "gray67"
    GRAY68 = "gray68"
    GRAY69 = "gray69"
    GRAY70 = "gray70"
    GRAY71 = "gray71"
    GRAY72 = "gray72"
    GRAY73 = "gray73"
    GRAY74 = "gray74"
    GRAY75 = "gray75"
    GRAY76 = "gray76"
    GRAY77 = "gray77"
    GRAY78 = "gray78"
    GRAY79 = "gray79"
    GRAY80 = "gray80"
    GRAY81 = "gray81"
    GRAY82 = "gray82"
    GRAY83 = "gray83"
    GRAY84 = "gray84"
    GRAY85 = "gray85"
    GRAY86 = "gray86"
    GRAY87 = "gray87"
    GRAY88 = "gray88"
    GRAY89 = "gray89"
    GRAY90 = "gray90"
    GRAY91 = "gray91"
    GRAY92 = "gray92"
    GRAY93 = "gray93"
    GRAY94 = "gray94"
    GRAY95 = "gray95"
    GRAY96 = "gray96"
    GRAY97 = "gray97"
    GRAY98 = "gray98"
    GRAY99 = "gray99"
    GRAY100 = "gray100"
    GREEN = "green"
    GREEN_YELLOW = "green yellow"
    GREEN1 = "green1"
    GREEN2 = "green2"
    GREEN3 = "green3"
    GREEN4 = "green4"
    GREENYELLOW = "GreenYellow"
    GREY = "grey"
    GREY0 = "grey0"
    GREY1 = "grey1"
    GREY2 = "grey2"
    GREY3 = "grey3"
    GREY4 = "grey4"
    GREY5 = "grey5"
    GREY6 = "grey6"
    GREY7 = "grey7"
    GREY8 = "grey8"
    GREY9 = "grey9"
    GREY10 = "grey10"
    GREY11 = "grey11"
    GREY12 = "grey12"
    GREY13 = "grey13"
    GREY14 = "grey14"
    GREY15 = "grey15"
    GREY16 = "grey16"
    GREY17 = "grey17"
    GREY18 = "grey18"
    GREY19 = "grey19"
    GREY20 = "grey20"
    GREY21 = "grey21"
    GREY22 = "grey22"
    GREY23 = "grey23"
    GREY24 = "grey24"
    GREY25 = "grey25"
    GREY26 = "grey26"
    GREY27 = "grey27"
    GREY28 = "grey28"
    GREY29 = "grey29"
    GREY30 = "grey30"
    GREY31 = "grey31"
    GREY32 = "grey32"
    GREY33 = "grey33"
    GREY34 = "grey34"
    GREY35 = "grey35"
    GREY36 = "grey36"
    GREY37 = "grey37"
    GREY38 = "grey38"
    GREY39 = "grey39"
    GREY40 = "grey40"
    GREY41 = "grey41"
    GREY42 = "grey42"
    GREY43 = "grey43"
    GREY44 = "grey44"
    GREY45 = "grey45"
    GREY46 = "grey46"
    GREY47 = "grey47"
    GREY48 = "grey48"
    GREY49 = "grey49"
    GREY50 = "grey50"
    GREY51 = "grey51"
    GREY52 = "grey52"
    GREY53 = "grey53"
    GREY54 = "grey54"
    GREY55 = "grey55"
    GREY56 = "grey56"
    GREY57 = "grey57"
    GREY58 = "grey58"
    GREY59 = "grey59"
    GREY60 = "grey60"
    GREY61 = "grey61"
    GREY62 = "grey62"
    GREY63 = "grey63"
    GREY64 = "grey64"
    GREY65 = "grey65"
    GREY66 = "grey66"
    GREY67 = "grey67"
    GREY68 = "grey68"
    GREY69 = "grey69"
    GREY70 = "grey70"
    GREY71 = "grey71"
    GREY72 = "grey72"
    GREY73 = "grey73"
    GREY74 = "grey74"
    GREY75 = "grey75"
    GREY76 = "grey76"
    GREY77 = "grey77"
    GREY78 = "grey78"
    GREY79 = "grey79"
    GREY80 = "grey80"
    GREY81 = "grey81"
    GREY82 = "grey82"
    GREY83 = "grey83"
    GREY84 = "grey84"
    GREY85 = "grey85"
    GREY86 = "grey86"
    GREY87 = "grey87"
    GREY88 = "grey88"
    GREY89 = "grey89"
    GREY90 = "grey90"
    GREY91 = "grey91"
    GREY92 = "grey92"
    GREY93 = "grey93"
    GREY94 = "grey94"
    GREY95 = "grey95"
    GREY96 = "grey96"
    GREY97 = "grey97"
    GREY98 = "grey98"
    GREY99 = "grey99"
    GREY100 = "grey100"
    HONEYDEW = "honeydew"
    HONEYDEW1 = "honeydew1"
    HONEYDEW2 = "honeydew2"
    HONEYDEW3 = "honeydew3"
    HONEYDEW4 = "honeydew4"
    HOT_PINK = "hot pink"
    HOTPINK = "HotPink"
    HOTPINK1 = "HotPink1"
    HOTPINK2 = "HotPink2"
    HOTPINK3 = "HotPink3"
    HOTPINK4 = "HotPink4"
    INDIAN_RED = "indian red"
    INDIANRED = "IndianRed"
    INDIANRED1 = "IndianRed1"
    INDIANRED2 = "IndianRed2"
    INDIANRED3 = "IndianRed3"
    INDIANRED4 = "IndianRed4"
    IVORY = "ivory"
    IVORY1 = "ivory1"
    IVORY2 = "ivory2"
    IVORY3 = "ivory3"
    IVORY4 = "ivory4"
    KHAKI = "khaki"
    KHAKI1 = "khaki1"
    KHAKI2 = "khaki2"
    KHAKI3 = "khaki3"
    KHAKI4 = "khaki4"
    LAVENDER = "lavender"
    LAVENDER_BLUSH = "lavender blush"
    LAVENDERBLUSH = "LavenderBlush"
    LAVENDERBLUSH1 = "LavenderBlush1"
    LAVENDERBLUSH2 = "LavenderBlush2"
    LAVENDERBLUSH3 = "LavenderBlush3"
    LAVENDERBLUSH4 = "LavenderBlush4"
    LAWN_GREEN = "lawn green"
    LAWNGREEN = "LawnGreen"
    LEMON_CHIFFON = "lemon chiffon"
    LEMONCHIFFON = "LemonChiffon"
    LEMONCHIFFON1 = "LemonChiffon1"
    LEMONCHIFFON2 = "LemonChiffon2"
    LEMONCHIFFON3 = "LemonChiffon3"
    LEMONCHIFFON4 = "LemonChiffon4"
    LIGHT_BLUE = "light blue"
    LIGHT_CORAL = "light coral"
    LIGHT_CYAN = "light cyan"
    LIGHT_GOLDENROD = "light goldenrod"
    LIGHT_GOLDENROD_YELLOW = "light goldenrod yellow"
    LIGHT_GRAY = "light gray"
    LIGHT_GREEN = "light green"
    LIGHT_GREY = "light grey"
    LIGHT_PINK = "light pink"
    LIGHT_SALMON = "light salmon"
    LIGHT_SEA_GREEN = "light sea green"
    LIGHT_SKY_BLUE = "light sky blue"
    LIGHT_SLATE_BLUE = "light slate blue"
    LIGHT_SLATE_GRAY = "light slate gray"
    LIGHT_SLATE_GREY = "light slate grey"
    LIGHT_STEEL_BLUE = "light steel blue"
    LIGHT_YELLOW = "light yellow"
    LIGHTBLUE = "LightBlue"
    LIGHTBLUE1 = "LightBlue1"
    LIGHTBLUE2 = "LightBlue2"
    LIGHTBLUE3 = "LightBlue3"
    LIGHTBLUE4 = "LightBlue4"
    LIGHTCORAL = "LightCoral"
    LIGHTCYAN = "LightCyan"
    LIGHTCYAN1 = "LightCyan1"
    LIGHTCYAN2 = "LightCyan2"
    LIGHTCYAN3 = "LightCyan3"
    LIGHTCYAN4 = "LightCyan4"
    LIGHTGOLDENROD = "LightGoldenrod"
    LIGHTGOLDENROD1 = "LightGoldenrod1"
    LIGHTGOLDENROD2 = "LightGoldenrod2"
    LIGHTGOLDENROD3 = "LightGoldenrod3"
    LIGHTGOLDENROD4 = "LightGoldenrod4"
    LIGHTGOLDENRODYELLOW = "LightGoldenrodYellow"
    LIGHTGRAY = "LightGray"
    LIGHTGREEN = "LightGreen"
    LIGHTGREY = "LightGrey"
    LIGHTPINK = "LightPink"
    LIGHTPINK1 = "LightPink1"
    LIGHTPINK2 = "LightPink2"
    LIGHTPINK3 = "LightPink3"
    LIGHTPINK4 = "LightPink4"
    LIGHTSALMON = "LightSalmon"
    LIGHTSALMON1 = "LightSalmon1"
    LIGHTSALMON2 = "LightSalmon2"
    LIGHTSALMON3 = "LightSalmon3"
    LIGHTSALMON4 = "LightSalmon4"
    LIGHTSEAGREEN = "LightSeaGreen"
    LIGHTSKYBLUE = "LightSkyBlue"
    LIGHTSKYBLUE1 = "LightSkyBlue1"
    LIGHTSKYBLUE2 = "LightSkyBlue2"
    LIGHTSKYBLUE3 = "LightSkyBlue3"
    LIGHTSKYBLUE4 = "LightSkyBlue4"
    LIGHTSLATEBLUE = "LightSlateBlue"
    LIGHTSLATEGRAY = "LightSlateGray"
    LIGHTSLATEGREY = "LightSlateGrey"
    LIGHTSTEELBLUE = "LightSteelBlue"
    LIGHTSTEELBLUE1 = "LightSteelBlue1"
    LIGHTSTEELBLUE2 = "LightSteelBlue2"
    LIGHTSTEELBLUE3 = "LightSteelBlue3"
    LIGHTSTEELBLUE4 = "LightSteelBlue4"
    LIGHTYELLOW = "LightYellow"
    LIGHTYELLOW1 = "LightYellow1"
    LIGHTYELLOW2 = "LightYellow2"
    LIGHTYELLOW3 = "LightYellow3"
    LIGHTYELLOW4 = "LightYellow4"
    LIME_GREEN = "lime green"
    LIMEGREEN = "LimeGreen"
    LINEN = "linen"
    MAGENTA = "magenta"
    MAGENTA1 = "magenta1"
    MAGENTA2 = "magenta2"
    MAGENTA3 = "magenta3"
    MAGENTA4 = "magenta4"
    MAROON = "maroon"
    MAROON1 = "maroon1"
    MAROON2 = "maroon2"
    MAROON3 = "maroon3"
    MAROON4 = "maroon4"
    MEDIUM_AQUAMARINE = "medium aquamarine"
    MEDIUM_BLUE = "medium blue"
    MEDIUM_ORCHID = "medium orchid"
    MEDIUM_PURPLE = "medium purple"
    MEDIUM_SEA_GREEN = "medium sea green"
    MEDIUM_SLATE_BLUE = "medium slate blue"
    MEDIUM_SPRING_GREEN = "medium spring green"
    MEDIUM_TURQUOISE = "medium turquoise"
    MEDIUM_VIOLET_RED = "medium violet red"
    MEDIUMAQUAMARINE = "MediumAquamarine"
    MEDIUMBLUE = "MediumBlue"
    MEDIUMORCHID = "MediumOrchid"
    MEDIUMORCHID1 = "MediumOrchid1"
    MEDIUMORCHID2 = "MediumOrchid2"
    MEDIUMORCHID3 = "MediumOrchid3"
    MEDIUMORCHID4 = "MediumOrchid4"
    MEDIUMPURPLE = "MediumPurple"
    MEDIUMPURPLE1 = "MediumPurple1"
    MEDIUMPURPLE2 = "MediumPurple2"
    MEDIUMPURPLE3 = "MediumPurple3"
    MEDIUMPURPLE4 = "MediumPurple4"
    MEDIUMSEAGREEN = "MediumSeaGreen"
    MEDIUMSLATEBLUE = "MediumSlateBlue"
    MEDIUMSPRINGGREEN = "MediumSpringGreen"
    MEDIUMTURQUOISE = "MediumTurquoise"
    MEDIUMVIOLETRED = "MediumVioletRed"
    MIDNIGHT_BLUE = "midnight blue"
    MIDNIGHTBLUE = "MidnightBlue"
    MINT_CREAM = "mint cream"
    MINTCREAM = "MintCream"
    MISTY_ROSE = "misty rose"
    MISTYROSE = "MistyRose"
    MISTYROSE1 = "MistyRose1"
    MISTYROSE2 = "MistyRose2"
    MISTYROSE3 = "MistyRose3"
    MISTYROSE4 = "MistyRose4"
    MOCCASIN = "moccasin"
    NAVAJO_WHITE = "navajo white"
    NAVAJOWHITE = "NavajoWhite"
    NAVAJOWHITE1 = "NavajoWhite1"
    NAVAJOWHITE2 = "NavajoWhite2"
    NAVAJOWHITE3 = "NavajoWhite3"
    NAVAJOWHITE4 = "NavajoWhite4"
    NAVY = "navy"
    NAVY_BLUE = "navy blue"
    NAVYBLUE = "NavyBlue"
    OLD_LACE = "old lace"
    OLDLACE = "OldLace"
    OLIVE_DRAB = "olive drab"
    OLIVEDRAB = "OliveDrab"
    OLIVEDRAB1 = "OliveDrab1"
    OLIVEDRAB2 = "OliveDrab2"
    OLIVEDRAB3 = "OliveDrab3"
    OLIVEDRAB4 = "OliveDrab4"
    ORANGE = "orange"
    ORANGE_RED = "orange red"
    ORANGE1 = "orange1"
    ORANGE2 = "orange2"
    ORANGE3 = "orange3"
    ORANGE4 = "orange4"
    ORANGERED = "OrangeRed"
    ORANGERED1 = "OrangeRed1"
    ORANGERED2 = "OrangeRed2"
    ORANGERED3 = "OrangeRed3"
    ORANGERED4 = "OrangeRed4"
    ORCHID = "orchid"
    ORCHID1 = "orchid1"
    ORCHID2 = "orchid2"
    ORCHID3 = "orchid3"
    ORCHID4 = "orchid4"
    PALE_GOLDENROD = "pale goldenrod"
    PALE_GREEN = "pale green"
    PALE_TURQUOISE = "pale turquoise"
    PALE_VIOLET_RED = "pale violet red"
    PALEGOLDENROD = "PaleGoldenrod"
    PALEGREEN = "PaleGreen"
    PALEGREEN1 = "PaleGreen1"
    PALEGREEN2 = "PaleGreen2"
    PALEGREEN3 = "PaleGreen3"
    PALEGREEN4 = "PaleGreen4"
    PALETURQUOISE = "PaleTurquoise"
    PALETURQUOISE1 = "PaleTurquoise1"
    PALETURQUOISE2 = "PaleTurquoise2"
    PALETURQUOISE3 = "PaleTurquoise3"
    PALETURQUOISE4 = "PaleTurquoise4"
    PALEVIOLETRED = "PaleVioletRed"
    PALEVIOLETRED1 = "PaleVioletRed1"
    PALEVIOLETRED2 = "PaleVioletRed2"
    PALEVIOLETRED3 = "PaleVioletRed3"
    PALEVIOLETRED4 = "PaleVioletRed4"
    PAPAYA_WHIP = "papaya whip"
    PAPAYAWHIP = "PapayaWhip"
    PEACH_PUFF = "peach puff"
    PEACHPUFF = "PeachPuff"
    PEACHPUFF1 = "PeachPuff1"
    PEACHPUFF2 = "PeachPuff2"
    PEACHPUFF3 = "PeachPuff3"
    PEACHPUFF4 = "PeachPuff4"
    PERU = "peru"
    PINK = "pink"
    PINK1 = "pink1"
    PINK2 = "pink2"
    PINK3 = "pink3"
    PINK4 = "pink4"
    PLUM = "plum"
    PLUM1 = "plum1"
    PLUM2 = "plum2"
    PLUM3 = "plum3"
    PLUM4 = "plum4"
    POWDER_BLUE = "powder blue"
    POWDERBLUE = "PowderBlue"
    PURPLE = "purple"
    PURPLE1 = "purple1"
    PURPLE2 = "purple2"
    PURPLE3 = "purple3"
    PURPLE4 = "purple4"
    RED = "red"
    RED1 = "red1"
    RED2 = "red2"
    RED3 = "red3"
    RED4 = "red4"
    ROSY_BROWN = "rosy brown"
    ROSYBROWN = "RosyBrown"
    ROSYBROWN1 = "RosyBrown1"
    ROSYBROWN2 = "RosyBrown2"
    ROSYBROWN3 = "RosyBrown3"
    ROSYBROWN4 = "RosyBrown4"
    ROYAL_BLUE = "royal blue"
    ROYALBLUE = "RoyalBlue"
    ROYALBLUE1 = "RoyalBlue1"
    ROYALBLUE2 = "RoyalBlue2"
    ROYALBLUE3 = "RoyalBlue3"
    ROYALBLUE4 = "RoyalBlue4"
    SADDLE_BROWN = "saddle brown"
    SADDLEBROWN = "SaddleBrown"
    SALMON = "salmon"
    SALMON1 = "salmon1"
    SALMON2 = "salmon2"
    SALMON3 = "salmon3"
    SALMON4 = "salmon4"
    SANDY_BROWN = "sandy brown"
    SANDYBROWN = "SandyBrown"
    SEA_GREEN = "sea green"
    SEAGREEN = "SeaGreen"
    SEAGREEN1 = "SeaGreen1"
    SEAGREEN2 = "SeaGreen2"
    SEAGREEN3 = "SeaGreen3"
    SEAGREEN4 = "SeaGreen4"
    SEASHELL = "seashell"
    SEASHELL1 = "seashell1"
    SEASHELL2 = "seashell2"
    SEASHELL3 = "seashell3"
    SEASHELL4 = "seashell4"
    SIENNA = "sienna"
    SIENNA1 = "sienna1"
    SIENNA2 = "sienna2"
    SIENNA3 = "sienna3"
    SIENNA4 = "sienna4"
    SKY_BLUE = "sky blue"
    SKYBLUE = "SkyBlue"
    SKYBLUE1 = "SkyBlue1"
    SKYBLUE2 = "SkyBlue2"
    SKYBLUE3 = "SkyBlue3"
    SKYBLUE4 = "SkyBlue4"
    SLATE_BLUE = "slate blue"
    SLATE_GRAY = "slate gray"
    SLATE_GREY = "slate grey"
    SLATEBLUE = "SlateBlue"
    SLATEBLUE1 = "SlateBlue1"
    SLATEBLUE2 = "SlateBlue2"
    SLATEBLUE3 = "SlateBlue3"
    SLATEBLUE4 = "SlateBlue4"
    SLATEGRAY = "SlateGray"
    SLATEGRAY1 = "SlateGray1"
    SLATEGRAY2 = "SlateGray2"
    SLATEGRAY3 = "SlateGray3"
    SLATEGRAY4 = "SlateGray4"
    SLATEGREY = "SlateGrey"
    SNOW = "snow"
    SNOW1 = "snow1"
    SNOW2 = "snow2"
    SNOW3 = "snow3"
    SNOW4 = "snow4"
    SPRING_GREEN = "spring green"
    SPRINGGREEN = "SpringGreen"
    SPRINGGREEN1 = "SpringGreen1"
    SPRINGGREEN2 = "SpringGreen2"
    SPRINGGREEN3 = "SpringGreen3"
    SPRINGGREEN4 = "SpringGreen4"
    STEEL_BLUE = "steel blue"
    STEELBLUE = "SteelBlue"
    STEELBLUE1 = "SteelBlue1"
    STEELBLUE2 = "SteelBlue2"
    STEELBLUE3 = "SteelBlue3"
    STEELBLUE4 = "SteelBlue4"
    TAN = "tan"
    TAN1 = "tan1"
    TAN2 = "tan2"
    TAN3 = "tan3"
    TAN4 = "tan4"
    THISTLE = "thistle"
    THISTLE1 = "thistle1"
    THISTLE2 = "thistle2"
    THISTLE3 = "thistle3"
    THISTLE4 = "thistle4"
    TOMATO = "tomato"
    TOMATO1 = "tomato1"
    TOMATO2 = "tomato2"
    TOMATO3 = "tomato3"
    TOMATO4 = "tomato4"
    TURQUOISE = "turquoise"
    TURQUOISE1 = "turquoise1"
    TURQUOISE2 = "turquoise2"
    TURQUOISE3 = "turquoise3"
    TURQUOISE4 = "turquoise4"
    VIOLET = "violet"
    VIOLET_RED = "violet red"
    VIOLETRED = "VioletRed"
    VIOLETRED1 = "VioletRed1"
    VIOLETRED2 = "VioletRed2"
    VIOLETRED3 = "VioletRed3"
    VIOLETRED4 = "VioletRed4"
    WHEAT = "wheat"
    WHEAT1 = "wheat1"
    WHEAT2 = "wheat2"
    WHEAT3 = "wheat3"
    WHEAT4 = "wheat4"
    WHITE = "white"
    WHITE_SMOKE = "white smoke"
    WHITESMOKE = "WhiteSmoke"
    YELLOW = "yellow"
    YELLOW_GREEN = "yellow green"
    YELLOW1 = "yellow1"
    YELLOW2 = "yellow2"
    YELLOW3 = "yellow3"
    YELLOW4 = "yellow4"
    YELLOWGREEN = "YellowGreen"
    
    # A map from Tcl/Tk color names like "red" to the corresponding RGB values as tuples.
    # See also:
    # https://www.tcl.tk/man/tcl8.4/TkCmd/colors.htm
    # http://wiki.tcl.tk/1424
    NAME_TO_RGB = {\
        "alice blue": (240, 248, 255), \
        "AliceBlue": (240, 248, 255), \
        "antique white": (250, 235, 215), \
        "AntiqueWhite": (250, 235, 215), \
        "AntiqueWhite1": (255, 239, 219), \
        "AntiqueWhite2": (238, 223, 204), \
        "AntiqueWhite3": (205, 192, 176), \
        "AntiqueWhite4": (139, 131, 120), \
        "aquamarine": (127, 255, 212), \
        "aquamarine1": (127, 255, 212), \
        "aquamarine2": (118, 238, 198), \
        "aquamarine3": (102, 205, 170), \
        "aquamarine4": (69, 139, 116), \
        "azure": (240, 255, 255), \
        "azure1": (240, 255, 255), \
        "azure2": (224, 238, 238), \
        "azure3": (193, 205, 205), \
        "azure4": (131, 139, 139), \
        "beige": (245, 245, 220), \
        "bisque": (255, 228, 196), \
        "bisque1": (255, 228, 196), \
        "bisque2": (238, 213, 183), \
        "bisque3": (205, 183, 158), \
        "bisque4": (139, 125, 107), \
        "black": (0, 0, 0), \
        "blanched almond": (255, 235, 205), \
        "BlanchedAlmond": (255, 235, 205), \
        "blue": (0, 0, 255), \
        "blue violet": (138, 43, 226), \
        "blue1": (0, 0, 255), \
        "blue2": (0, 0, 238), \
        "blue3": (0, 0, 205), \
        "blue4": (0, 0, 139), \
        "BlueViolet": (138, 43, 226), \
        "brown": (165, 42, 42), \
        "brown1": (255, 64, 64), \
        "brown2": (238, 59, 59), \
        "brown3": (205, 51, 51), \
        "brown4": (139, 35, 35), \
        "burlywood": (222, 184, 135), \
        "burlywood1": (255, 211, 155), \
        "burlywood2": (238, 197, 145), \
        "burlywood3": (205, 170, 125), \
        "burlywood4": (139, 115, 85), \
        "cadet blue": (95, 158, 160), \
        "CadetBlue": (95, 158, 160), \
        "CadetBlue1": (152, 245, 255), \
        "CadetBlue2": (142, 229, 238), \
        "CadetBlue3": (122, 197, 205), \
        "CadetBlue4": (83, 134, 139), \
        "chartreuse": (127, 255, 0), \
        "chartreuse1": (127, 255, 0), \
        "chartreuse2": (118, 238, 0), \
        "chartreuse3": (102, 205, 0), \
        "chartreuse4": (69, 139, 0), \
        "chocolate": (210, 105, 30), \
        "chocolate1": (255, 127, 36), \
        "chocolate2": (238, 118, 33), \
        "chocolate3": (205, 102, 29), \
        "chocolate4": (139, 69, 19), \
        "coral": (255, 127, 80), \
        "coral1": (255, 114, 86), \
        "coral2": (238, 106, 80), \
        "coral3": (205, 91, 69), \
        "coral4": (139, 62, 47), \
        "cornflower blue": (100, 149, 237), \
        "CornflowerBlue": (100, 149, 237), \
        "cornsilk": (255, 248, 220), \
        "cornsilk1": (255, 248, 220), \
        "cornsilk2": (238, 232, 205), \
        "cornsilk3": (205, 200, 177), \
        "cornsilk4": (139, 136, 120), \
        "cyan": (0, 255, 255), \
        "cyan1": (0, 255, 255), \
        "cyan2": (0, 238, 238), \
        "cyan3": (0, 205, 205), \
        "cyan4": (0, 139, 139), \
        "dark blue": (0, 0, 139), \
        "dark cyan": (0, 139, 139), \
        "dark goldenrod": (184, 134, 11), \
        "dark gray": (169, 169, 169), \
        "dark green": (0, 100, 0), \
        "dark grey": (169, 169, 169), \
        "dark khaki": (189, 183, 107), \
        "dark magenta": (139, 0, 139), \
        "dark olive green": (85, 107, 47), \
        "dark orange": (255, 140, 0), \
        "dark orchid": (153, 50, 204), \
        "dark red": (139, 0, 0), \
        "dark salmon": (233, 150, 122), \
        "dark sea green": (143, 188, 143), \
        "dark slate blue": (72, 61, 139), \
        "dark slate gray": (47, 79, 79), \
        "dark slate grey": (47, 79, 79), \
        "dark turquoise": (0, 206, 209), \
        "dark violet": (148, 0, 211), \
        "DarkBlue": (0, 0, 139), \
        "DarkCyan": (0, 139, 139), \
        "DarkGoldenrod": (184, 134, 11), \
        "DarkGoldenrod1": (255, 185, 15), \
        "DarkGoldenrod2": (238, 173, 14), \
        "DarkGoldenrod3": (205, 149, 12), \
        "DarkGoldenrod4": (139, 101, 8), \
        "DarkGray": (169, 169, 169), \
        "DarkGreen": (0, 100, 0), \
        "DarkGrey": (169, 169, 169), \
        "DarkKhaki": (189, 183, 107), \
        "DarkMagenta": (139, 0, 139), \
        "DarkOliveGreen": (85, 107, 47), \
        "DarkOliveGreen1": (202, 255, 112), \
        "DarkOliveGreen2": (188, 238, 104), \
        "DarkOliveGreen3": (162, 205, 90), \
        "DarkOliveGreen4": (110, 139, 61), \
        "DarkOrange": (255, 140, 0), \
        "DarkOrange1": (255, 127, 0), \
        "DarkOrange2": (238, 118, 0), \
        "DarkOrange3": (205, 102, 0), \
        "DarkOrange4": (139, 69, 0), \
        "DarkOrchid": (153, 50, 204), \
        "DarkOrchid1": (191, 62, 255), \
        "DarkOrchid2": (178, 58, 238), \
        "DarkOrchid3": (154, 50, 205), \
        "DarkOrchid4": (104, 34, 139), \
        "DarkRed": (139, 0, 0), \
        "DarkSalmon": (233, 150, 122), \
        "DarkSeaGreen": (143, 188, 143), \
        "DarkSeaGreen1": (193, 255, 193), \
        "DarkSeaGreen2": (180, 238, 180), \
        "DarkSeaGreen3": (155, 205, 155), \
        "DarkSeaGreen4": (105, 139, 105), \
        "DarkSlateBlue": (72, 61, 139), \
        "DarkSlateGray": (47, 79, 79), \
        "DarkSlateGray1": (151, 255, 255), \
        "DarkSlateGray2": (141, 238, 238), \
        "DarkSlateGray3": (121, 205, 205), \
        "DarkSlateGray4": (82, 139, 139), \
        "DarkSlateGrey": (47, 79, 79), \
        "DarkTurquoise": (0, 206, 209), \
        "DarkViolet": (148, 0, 211), \
        "deep pink": (255, 20, 147), \
        "deep sky blue": (0, 191, 255), \
        "DeepPink": (255, 20, 147), \
        "DeepPink1": (255, 20, 147), \
        "DeepPink2": (238, 18, 137), \
        "DeepPink3": (205, 16, 118), \
        "DeepPink4": (139, 10, 80), \
        "DeepSkyBlue": (0, 191, 255), \
        "DeepSkyBlue1": (0, 191, 255), \
        "DeepSkyBlue2": (0, 178, 238), \
        "DeepSkyBlue3": (0, 154, 205), \
        "DeepSkyBlue4": (0, 104, 139), \
        "dim gray": (105, 105, 105), \
        "dim grey": (105, 105, 105), \
        "DimGray": (105, 105, 105), \
        "DimGrey": (105, 105, 105), \
        "dodger blue": (30, 144, 255), \
        "DodgerBlue": (30, 144, 255), \
        "DodgerBlue1": (30, 144, 255), \
        "DodgerBlue2": (28, 134, 238), \
        "DodgerBlue3": (24, 116, 205), \
        "DodgerBlue4": (16, 78, 139), \
        "firebrick": (178, 34, 34), \
        "firebrick1": (255, 48, 48), \
        "firebrick2": (238, 44, 44), \
        "firebrick3": (205, 38, 38), \
        "firebrick4": (139, 26, 26), \
        "floral white": (255, 250, 240), \
        "FloralWhite": (255, 250, 240), \
        "forest green": (34, 139, 34), \
        "ForestGreen": (34, 139, 34), \
        "gainsboro": (220, 220, 220), \
        "ghost white": (248, 248, 255), \
        "GhostWhite": (248, 248, 255), \
        "gold": (255, 215, 0), \
        "gold1": (255, 215, 0), \
        "gold2": (238, 201, 0), \
        "gold3": (205, 173, 0), \
        "gold4": (139, 117, 0), \
        "goldenrod": (218, 165, 32), \
        "goldenrod1": (255, 193, 37), \
        "goldenrod2": (238, 180, 34), \
        "goldenrod3": (205, 155, 29), \
        "goldenrod4": (139, 105, 20), \
        "gray": (190, 190, 190), \
        "gray0": (0, 0, 0), \
        "gray1": (3, 3, 3), \
        "gray2": (5, 5, 5), \
        "gray3": (8, 8, 8), \
        "gray4": (10, 10, 10), \
        "gray5": (13, 13, 13), \
        "gray6": (15, 15, 15), \
        "gray7": (18, 18, 18), \
        "gray8": (20, 20, 20), \
        "gray9": (23, 23, 23), \
        "gray10": (26, 26, 26), \
        "gray11": (28, 28, 28), \
        "gray12": (31, 31, 31), \
        "gray13": (33, 33, 33), \
        "gray14": (36, 36, 36), \
        "gray15": (38, 38, 38), \
        "gray16": (41, 41, 41), \
        "gray17": (43, 43, 43), \
        "gray18": (46, 46, 46), \
        "gray19": (48, 48, 48), \
        "gray20": (51, 51, 51), \
        "gray21": (54, 54, 54), \
        "gray22": (56, 56, 56), \
        "gray23": (59, 59, 59), \
        "gray24": (61, 61, 61), \
        "gray25": (64, 64, 64), \
        "gray26": (66, 66, 66), \
        "gray27": (69, 69, 69), \
        "gray28": (71, 71, 71), \
        "gray29": (74, 74, 74), \
        "gray30": (77, 77, 77), \
        "gray31": (79, 79, 79), \
        "gray32": (82, 82, 82), \
        "gray33": (84, 84, 84), \
        "gray34": (87, 87, 87), \
        "gray35": (89, 89, 89), \
        "gray36": (92, 92, 92), \
        "gray37": (94, 94, 94), \
        "gray38": (97, 97, 97), \
        "gray39": (99, 99, 99), \
        "gray40": (102, 102, 102), \
        "gray41": (105, 105, 105), \
        "gray42": (107, 107, 107), \
        "gray43": (110, 110, 110), \
        "gray44": (112, 112, 112), \
        "gray45": (115, 115, 115), \
        "gray46": (117, 117, 117), \
        "gray47": (120, 120, 120), \
        "gray48": (122, 122, 122), \
        "gray49": (125, 125, 125), \
        "gray50": (127, 127, 127), \
        "gray51": (130, 130, 130), \
        "gray52": (133, 133, 133), \
        "gray53": (135, 135, 135), \
        "gray54": (138, 138, 138), \
        "gray55": (140, 140, 140), \
        "gray56": (143, 143, 143), \
        "gray57": (145, 145, 145), \
        "gray58": (148, 148, 148), \
        "gray59": (150, 150, 150), \
        "gray60": (153, 153, 153), \
        "gray61": (156, 156, 156), \
        "gray62": (158, 158, 158), \
        "gray63": (161, 161, 161), \
        "gray64": (163, 163, 163), \
        "gray65": (166, 166, 166), \
        "gray66": (168, 168, 168), \
        "gray67": (171, 171, 171), \
        "gray68": (173, 173, 173), \
        "gray69": (176, 176, 176), \
        "gray70": (179, 179, 179), \
        "gray71": (181, 181, 181), \
        "gray72": (184, 184, 184), \
        "gray73": (186, 186, 186), \
        "gray74": (189, 189, 189), \
        "gray75": (191, 191, 191), \
        "gray76": (194, 194, 194), \
        "gray77": (196, 196, 196), \
        "gray78": (199, 199, 199), \
        "gray79": (201, 201, 201), \
        "gray80": (204, 204, 204), \
        "gray81": (207, 207, 207), \
        "gray82": (209, 209, 209), \
        "gray83": (212, 212, 212), \
        "gray84": (214, 214, 214), \
        "gray85": (217, 217, 217), \
        "gray86": (219, 219, 219), \
        "gray87": (222, 222, 222), \
        "gray88": (224, 224, 224), \
        "gray89": (227, 227, 227), \
        "gray90": (229, 229, 229), \
        "gray91": (232, 232, 232), \
        "gray92": (235, 235, 235), \
        "gray93": (237, 237, 237), \
        "gray94": (240, 240, 240), \
        "gray95": (242, 242, 242), \
        "gray96": (245, 245, 245), \
        "gray97": (247, 247, 247), \
        "gray98": (250, 250, 250), \
        "gray99": (252, 252, 252), \
        "gray100": (255, 255, 255), \
        "green": (0, 255, 0), \
        "green yellow": (173, 255, 47), \
        "green1": (0, 255, 0), \
        "green2": (0, 238, 0), \
        "green3": (0, 205, 0), \
        "green4": (0, 139, 0), \
        "GreenYellow": (173, 255, 47), \
        "grey": (190, 190, 190), \
        "grey0": (0, 0, 0), \
        "grey1": (3, 3, 3), \
        "grey2": (5, 5, 5), \
        "grey3": (8, 8, 8), \
        "grey4": (10, 10, 10), \
        "grey5": (13, 13, 13), \
        "grey6": (15, 15, 15), \
        "grey7": (18, 18, 18), \
        "grey8": (20, 20, 20), \
        "grey9": (23, 23, 23), \
        "grey10": (26, 26, 26), \
        "grey11": (28, 28, 28), \
        "grey12": (31, 31, 31), \
        "grey13": (33, 33, 33), \
        "grey14": (36, 36, 36), \
        "grey15": (38, 38, 38), \
        "grey16": (41, 41, 41), \
        "grey17": (43, 43, 43), \
        "grey18": (46, 46, 46), \
        "grey19": (48, 48, 48), \
        "grey20": (51, 51, 51), \
        "grey21": (54, 54, 54), \
        "grey22": (56, 56, 56), \
        "grey23": (59, 59, 59), \
        "grey24": (61, 61, 61), \
        "grey25": (64, 64, 64), \
        "grey26": (66, 66, 66), \
        "grey27": (69, 69, 69), \
        "grey28": (71, 71, 71), \
        "grey29": (74, 74, 74), \
        "grey30": (77, 77, 77), \
        "grey31": (79, 79, 79), \
        "grey32": (82, 82, 82), \
        "grey33": (84, 84, 84), \
        "grey34": (87, 87, 87), \
        "grey35": (89, 89, 89), \
        "grey36": (92, 92, 92), \
        "grey37": (94, 94, 94), \
        "grey38": (97, 97, 97), \
        "grey39": (99, 99, 99), \
        "grey40": (102, 102, 102), \
        "grey41": (105, 105, 105), \
        "grey42": (107, 107, 107), \
        "grey43": (110, 110, 110), \
        "grey44": (112, 112, 112), \
        "grey45": (115, 115, 115), \
        "grey46": (117, 117, 117), \
        "grey47": (120, 120, 120), \
        "grey48": (122, 122, 122), \
        "grey49": (125, 125, 125), \
        "grey50": (127, 127, 127), \
        "grey51": (130, 130, 130), \
        "grey52": (133, 133, 133), \
        "grey53": (135, 135, 135), \
        "grey54": (138, 138, 138), \
        "grey55": (140, 140, 140), \
        "grey56": (143, 143, 143), \
        "grey57": (145, 145, 145), \
        "grey58": (148, 148, 148), \
        "grey59": (150, 150, 150), \
        "grey60": (153, 153, 153), \
        "grey61": (156, 156, 156), \
        "grey62": (158, 158, 158), \
        "grey63": (161, 161, 161), \
        "grey64": (163, 163, 163), \
        "grey65": (166, 166, 166), \
        "grey66": (168, 168, 168), \
        "grey67": (171, 171, 171), \
        "grey68": (173, 173, 173), \
        "grey69": (176, 176, 176), \
        "grey70": (179, 179, 179), \
        "grey71": (181, 181, 181), \
        "grey72": (184, 184, 184), \
        "grey73": (186, 186, 186), \
        "grey74": (189, 189, 189), \
        "grey75": (191, 191, 191), \
        "grey76": (194, 194, 194), \
        "grey77": (196, 196, 196), \
        "grey78": (199, 199, 199), \
        "grey79": (201, 201, 201), \
        "grey80": (204, 204, 204), \
        "grey81": (207, 207, 207), \
        "grey82": (209, 209, 209), \
        "grey83": (212, 212, 212), \
        "grey84": (214, 214, 214), \
        "grey85": (217, 217, 217), \
        "grey86": (219, 219, 219), \
        "grey87": (222, 222, 222), \
        "grey88": (224, 224, 224), \
        "grey89": (227, 227, 227), \
        "grey90": (229, 229, 229), \
        "grey91": (232, 232, 232), \
        "grey92": (235, 235, 235), \
        "grey93": (237, 237, 237), \
        "grey94": (240, 240, 240), \
        "grey95": (242, 242, 242), \
        "grey96": (245, 245, 245), \
        "grey97": (247, 247, 247), \
        "grey98": (250, 250, 250), \
        "grey99": (252, 252, 252), \
        "grey100": (255, 255, 255), \
        "honeydew": (240, 255, 240), \
        "honeydew1": (240, 255, 240), \
        "honeydew2": (224, 238, 224), \
        "honeydew3": (193, 205, 193), \
        "honeydew4": (131, 139, 131), \
        "hot pink": (255, 105, 180), \
        "HotPink": (255, 105, 180), \
        "HotPink1": (255, 110, 180), \
        "HotPink2": (238, 106, 167), \
        "HotPink3": (205, 96, 144), \
        "HotPink4": (139, 58, 98), \
        "indian red": (205, 92, 92), \
        "IndianRed": (205, 92, 92), \
        "IndianRed1": (255, 106, 106), \
        "IndianRed2": (238, 99, 99), \
        "IndianRed3": (205, 85, 85), \
        "IndianRed4": (139, 58, 58), \
        "ivory": (255, 255, 240), \
        "ivory1": (255, 255, 240), \
        "ivory2": (238, 238, 224), \
        "ivory3": (205, 205, 193), \
        "ivory4": (139, 139, 131), \
        "khaki": (240, 230, 140), \
        "khaki1": (255, 246, 143), \
        "khaki2": (238, 230, 133), \
        "khaki3": (205, 198, 115), \
        "khaki4": (139, 134, 78), \
        "lavender": (230, 230, 250), \
        "lavender blush": (255, 240, 245), \
        "LavenderBlush": (255, 240, 245), \
        "LavenderBlush1": (255, 240, 245), \
        "LavenderBlush2": (238, 224, 229), \
        "LavenderBlush3": (205, 193, 197), \
        "LavenderBlush4": (139, 131, 134), \
        "lawn green": (124, 252, 0), \
        "LawnGreen": (124, 252, 0), \
        "lemon chiffon": (255, 250, 205), \
        "LemonChiffon": (255, 250, 205), \
        "LemonChiffon1": (255, 250, 205), \
        "LemonChiffon2": (238, 233, 191), \
        "LemonChiffon3": (205, 201, 165), \
        "LemonChiffon4": (139, 137, 112), \
        "light blue": (173, 216, 230), \
        "light coral": (240, 128, 128), \
        "light cyan": (224, 255, 255), \
        "light goldenrod": (238, 221, 130), \
        "light goldenrod yellow": (250, 250, 210), \
        "light gray": (211, 211, 211), \
        "light green": (144, 238, 144), \
        "light grey": (211, 211, 211), \
        "light pink": (255, 182, 193), \
        "light salmon": (255, 160, 122), \
        "light sea green": (32, 178, 170), \
        "light sky blue": (135, 206, 250), \
        "light slate blue": (132, 112, 255), \
        "light slate gray": (119, 136, 153), \
        "light slate grey": (119, 136, 153), \
        "light steel blue": (176, 196, 222), \
        "light yellow": (255, 255, 224), \
        "LightBlue": (173, 216, 230), \
        "LightBlue1": (191, 239, 255), \
        "LightBlue2": (178, 223, 238), \
        "LightBlue3": (154, 192, 205), \
        "LightBlue4": (104, 131, 139), \
        "LightCoral": (240, 128, 128), \
        "LightCyan": (224, 255, 255), \
        "LightCyan1": (224, 255, 255), \
        "LightCyan2": (209, 238, 238), \
        "LightCyan3": (180, 205, 205), \
        "LightCyan4": (122, 139, 139), \
        "LightGoldenrod": (238, 221, 130), \
        "LightGoldenrod1": (255, 236, 139), \
        "LightGoldenrod2": (238, 220, 130), \
        "LightGoldenrod3": (205, 190, 112), \
        "LightGoldenrod4": (139, 129, 76), \
        "LightGoldenrodYellow": (250, 250, 210), \
        "LightGray": (211, 211, 211), \
        "LightGreen": (144, 238, 144), \
        "LightGrey": (211, 211, 211), \
        "LightPink": (255, 182, 193), \
        "LightPink1": (255, 174, 185), \
        "LightPink2": (238, 162, 173), \
        "LightPink3": (205, 140, 149), \
        "LightPink4": (139, 95, 101), \
        "LightSalmon": (255, 160, 122), \
        "LightSalmon1": (255, 160, 122), \
        "LightSalmon2": (238, 149, 114), \
        "LightSalmon3": (205, 129, 98), \
        "LightSalmon4": (139, 87, 66), \
        "LightSeaGreen": (32, 178, 170), \
        "LightSkyBlue": (135, 206, 250), \
        "LightSkyBlue1": (176, 226, 255), \
        "LightSkyBlue2": (164, 211, 238), \
        "LightSkyBlue3": (141, 182, 205), \
        "LightSkyBlue4": (96, 123, 139), \
        "LightSlateBlue": (132, 112, 255), \
        "LightSlateGray": (119, 136, 153), \
        "LightSlateGrey": (119, 136, 153), \
        "LightSteelBlue": (176, 196, 222), \
        "LightSteelBlue1": (202, 225, 255), \
        "LightSteelBlue2": (188, 210, 238), \
        "LightSteelBlue3": (162, 181, 205), \
        "LightSteelBlue4": (110, 123, 139), \
        "LightYellow": (255, 255, 224), \
        "LightYellow1": (255, 255, 224), \
        "LightYellow2": (238, 238, 209), \
        "LightYellow3": (205, 205, 180), \
        "LightYellow4": (139, 139, 122), \
        "lime green": (50, 205, 50), \
        "LimeGreen": (50, 205, 50), \
        "linen": (250, 240, 230), \
        "magenta": (255, 0, 255), \
        "magenta1": (255, 0, 255), \
        "magenta2": (238, 0, 238), \
        "magenta3": (205, 0, 205), \
        "magenta4": (139, 0, 139), \
        "maroon": (176, 48, 96), \
        "maroon1": (255, 52, 179), \
        "maroon2": (238, 48, 167), \
        "maroon3": (205, 41, 144), \
        "maroon4": (139, 28, 98), \
        "medium aquamarine": (102, 205, 170), \
        "medium blue": (0, 0, 205), \
        "medium orchid": (186, 85, 211), \
        "medium purple": (147, 112, 219), \
        "medium sea green": (60, 179, 113), \
        "medium slate blue": (123, 104, 238), \
        "medium spring green": (0, 250, 154), \
        "medium turquoise": (72, 209, 204), \
        "medium violet red": (199, 21, 133), \
        "MediumAquamarine": (102, 205, 170), \
        "MediumBlue": (0, 0, 205), \
        "MediumOrchid": (186, 85, 211), \
        "MediumOrchid1": (224, 102, 255), \
        "MediumOrchid2": (209, 95, 238), \
        "MediumOrchid3": (180, 82, 205), \
        "MediumOrchid4": (122, 55, 139), \
        "MediumPurple": (147, 112, 219), \
        "MediumPurple1": (171, 130, 255), \
        "MediumPurple2": (159, 121, 238), \
        "MediumPurple3": (137, 104, 205), \
        "MediumPurple4": (93, 71, 139), \
        "MediumSeaGreen": (60, 179, 113), \
        "MediumSlateBlue": (123, 104, 238), \
        "MediumSpringGreen": (0, 250, 154), \
        "MediumTurquoise": (72, 209, 204), \
        "MediumVioletRed": (199, 21, 133), \
        "midnight blue": (25, 25, 112), \
        "MidnightBlue": (25, 25, 112), \
        "mint cream": (245, 255, 250), \
        "MintCream": (245, 255, 250), \
        "misty rose": (255, 228, 225), \
        "MistyRose": (255, 228, 225), \
        "MistyRose1": (255, 228, 225), \
        "MistyRose2": (238, 213, 210), \
        "MistyRose3": (205, 183, 181), \
        "MistyRose4": (139, 125, 123), \
        "moccasin": (255, 228, 181), \
        "navajo white": (255, 222, 173), \
        "NavajoWhite": (255, 222, 173), \
        "NavajoWhite1": (255, 222, 173), \
        "NavajoWhite2": (238, 207, 161), \
        "NavajoWhite3": (205, 179, 139), \
        "NavajoWhite4": (139, 121, 94), \
        "navy": (0, 0, 128), \
        "navy blue": (0, 0, 128), \
        "NavyBlue": (0, 0, 128), \
        "old lace": (253, 245, 230), \
        "OldLace": (253, 245, 230), \
        "olive drab": (107, 142, 35), \
        "OliveDrab": (107, 142, 35), \
        "OliveDrab1": (192, 255, 62), \
        "OliveDrab2": (179, 238, 58), \
        "OliveDrab3": (154, 205, 50), \
        "OliveDrab4": (105, 139, 34), \
        "orange": (255, 165, 0), \
        "orange red": (255, 69, 0), \
        "orange1": (255, 165, 0), \
        "orange2": (238, 154, 0), \
        "orange3": (205, 133, 0), \
        "orange4": (139, 90, 0), \
        "OrangeRed": (255, 69, 0), \
        "OrangeRed1": (255, 69, 0), \
        "OrangeRed2": (238, 64, 0), \
        "OrangeRed3": (205, 55, 0), \
        "OrangeRed4": (139, 37, 0), \
        "orchid": (218, 112, 214), \
        "orchid1": (255, 131, 250), \
        "orchid2": (238, 122, 233), \
        "orchid3": (205, 105, 201), \
        "orchid4": (139, 71, 137), \
        "pale goldenrod": (238, 232, 170), \
        "pale green": (152, 251, 152), \
        "pale turquoise": (175, 238, 238), \
        "pale violet red": (219, 112, 147), \
        "PaleGoldenrod": (238, 232, 170), \
        "PaleGreen": (152, 251, 152), \
        "PaleGreen1": (154, 255, 154), \
        "PaleGreen2": (144, 238, 144), \
        "PaleGreen3": (124, 205, 124), \
        "PaleGreen4": (84, 139, 84), \
        "PaleTurquoise": (175, 238, 238), \
        "PaleTurquoise1": (187, 255, 255), \
        "PaleTurquoise2": (174, 238, 238), \
        "PaleTurquoise3": (150, 205, 205), \
        "PaleTurquoise4": (102, 139, 139), \
        "PaleVioletRed": (219, 112, 147), \
        "PaleVioletRed1": (255, 130, 171), \
        "PaleVioletRed2": (238, 121, 159), \
        "PaleVioletRed3": (205, 104, 127), \
        "PaleVioletRed4": (139, 71, 93), \
        "papaya whip": (255, 239, 213), \
        "PapayaWhip": (255, 239, 213), \
        "peach puff": (255, 218, 185), \
        "PeachPuff": (255, 218, 185), \
        "PeachPuff1": (255, 218, 185), \
        "PeachPuff2": (238, 203, 173), \
        "PeachPuff3": (205, 175, 149), \
        "PeachPuff4": (139, 119, 101), \
        "peru": (205, 133, 63), \
        "pink": (255, 192, 203), \
        "pink1": (255, 181, 197), \
        "pink2": (238, 169, 184), \
        "pink3": (205, 145, 158), \
        "pink4": (139, 99, 108), \
        "plum": (221, 160, 221), \
        "plum1": (255, 187, 255), \
        "plum2": (238, 174, 238), \
        "plum3": (205, 150, 205), \
        "plum4": (139, 102, 139), \
        "powder blue": (176, 224, 230), \
        "PowderBlue": (176, 224, 230), \
        "purple": (160, 32, 240), \
        "purple1": (155, 48, 255), \
        "purple2": (145, 44, 238), \
        "purple3": (125, 38, 205), \
        "purple4": (85, 26, 139), \
        "red": (255, 0, 0), \
        "red1": (255, 0, 0), \
        "red2": (238, 0, 0), \
        "red3": (205, 0, 0), \
        "red4": (139, 0, 0), \
        "rosy brown": (188, 143, 143), \
        "RosyBrown": (188, 143, 143), \
        "RosyBrown1": (255, 193, 193), \
        "RosyBrown2": (238, 180, 180), \
        "RosyBrown3": (205, 155, 155), \
        "RosyBrown4": (139, 105, 105), \
        "royal blue": (65, 105, 225), \
        "RoyalBlue": (65, 105, 225), \
        "RoyalBlue1": (72, 118, 255), \
        "RoyalBlue2": (67, 110, 238), \
        "RoyalBlue3": (58, 95, 205), \
        "RoyalBlue4": (39, 64, 139), \
        "saddle brown": (139, 69, 19), \
        "SaddleBrown": (139, 69, 19), \
        "salmon": (250, 128, 114), \
        "salmon1": (255, 140, 105), \
        "salmon2": (238, 130, 98), \
        "salmon3": (205, 112, 84), \
        "salmon4": (139, 76, 57), \
        "sandy brown": (244, 164, 96), \
        "SandyBrown": (244, 164, 96), \
        "sea green": (46, 139, 87), \
        "SeaGreen": (46, 139, 87), \
        "SeaGreen1": (84, 255, 159), \
        "SeaGreen2": (78, 238, 148), \
        "SeaGreen3": (67, 205, 128), \
        "SeaGreen4": (46, 139, 87), \
        "seashell": (255, 245, 238), \
        "seashell1": (255, 245, 238), \
        "seashell2": (238, 229, 222), \
        "seashell3": (205, 197, 191), \
        "seashell4": (139, 134, 130), \
        "sienna": (160, 82, 45), \
        "sienna1": (255, 130, 71), \
        "sienna2": (238, 121, 66), \
        "sienna3": (205, 104, 57), \
        "sienna4": (139, 71, 38), \
        "sky blue": (135, 206, 235), \
        "SkyBlue": (135, 206, 235), \
        "SkyBlue1": (135, 206, 255), \
        "SkyBlue2": (126, 192, 238), \
        "SkyBlue3": (108, 166, 205), \
        "SkyBlue4": (74, 112, 139), \
        "slate blue": (106, 90, 205), \
        "slate gray": (112, 128, 144), \
        "slate grey": (112, 128, 144), \
        "SlateBlue": (106, 90, 205), \
        "SlateBlue1": (131, 111, 255), \
        "SlateBlue2": (122, 103, 238), \
        "SlateBlue3": (105, 89, 205), \
        "SlateBlue4": (71, 60, 139), \
        "SlateGray": (112, 128, 144), \
        "SlateGray1": (198, 226, 255), \
        "SlateGray2": (185, 211, 238), \
        "SlateGray3": (159, 182, 205), \
        "SlateGray4": (108, 123, 139), \
        "SlateGrey": (112, 128, 144), \
        "snow": (255, 250, 250), \
        "snow1": (255, 250, 250), \
        "snow2": (238, 233, 233), \
        "snow3": (205, 201, 201), \
        "snow4": (139, 137, 137), \
        "spring green": (0, 255, 127), \
        "SpringGreen": (0, 255, 127), \
        "SpringGreen1": (0, 255, 127), \
        "SpringGreen2": (0, 238, 118), \
        "SpringGreen3": (0, 205, 102), \
        "SpringGreen4": (0, 139, 69), \
        "steel blue": (70, 130, 180), \
        "SteelBlue": (70, 130, 180), \
        "SteelBlue1": (99, 184, 255), \
        "SteelBlue2": (92, 172, 238), \
        "SteelBlue3": (79, 148, 205), \
        "SteelBlue4": (54, 100, 139), \
        "tan": (210, 180, 140), \
        "tan1": (255, 165, 79), \
        "tan2": (238, 154, 73), \
        "tan3": (205, 133, 63), \
        "tan4": (139, 90, 43), \
        "thistle": (216, 191, 216), \
        "thistle1": (255, 225, 255), \
        "thistle2": (238, 210, 238), \
        "thistle3": (205, 181, 205), \
        "thistle4": (139, 123, 139), \
        "tomato": (255, 99, 71 ), \
        "tomato1": (255, 99, 71), \
        "tomato2": (238, 92, 66), \
        "tomato3": (205, 79, 57), \
        "tomato4": (139, 54, 38), \
        "turquoise": (64, 224, 208), \
        "turquoise1": (0, 245, 255), \
        "turquoise2": (0, 229, 238), \
        "turquoise3": (0, 197, 205), \
        "turquoise4": (0, 134, 139), \
        "violet": (238, 130, 238), \
        "violet red": (208, 32, 144), \
        "VioletRed": (208, 32, 144), \
        "VioletRed1": (255, 62, 150), \
        "VioletRed2": (238, 58, 140), \
        "VioletRed3": (205, 50, 120), \
        "VioletRed4": (139, 34, 82), \
        "wheat": (245, 222, 179), \
        "wheat1": (255, 231, 186), \
        "wheat2": (238, 216, 174), \
        "wheat3": (205, 186, 150), \
        "wheat4": (139, 126, 102), \
        "white": (255, 255, 255), \
        "white smoke": (245, 245, 245), \
        "WhiteSmoke": (245, 245, 245), \
        "yellow": (255, 255, 0), \
        "yellow green": (154, 205, 50), \
        "yellow1": (255, 255, 0), \
        "yellow2": (238, 238, 0), \
        "yellow3": (205, 205, 0), \
        "yellow4": (139, 139, 0), \
        "YellowGreen": (154, 205, 50)}
    NAME_TO_RGB_LC = {k.lower() : v for (k, v) in NAME_TO_RGB.items()}
    NAME_TO_HEX_LC = {k.lower() : ("#%02x%02x%02x" % v) for (k, v) in NAME_TO_RGB.items()}
    RGB_TO_NAME = {v : k for (k, v) in NAME_TO_RGB.items()}
    HEX_TO_NAME = {("#%02x%02x%02x" % k) : v for (k, v) in RGB_TO_NAME.items()}