Performance for simple code that converts a RGB tuple to hex string
You seem to be jumping through some unnecessary hoops. Just format a string directly:

from timeit import timeit


def _channel_to_hex(color_val: int) -&gt; str:
    raw: str = hex(color_val)[2:]
    return raw.zfill(2)


def rgb_to_hex(red: int, green: int, blue: int) -&gt; str:
    return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)


def direct_format(r, g, b):
    return f'#{r:02x}{g:02x}{b:02x}'


def one_word(r, g, b):
    rgb = r&lt;&lt;16 | g&lt;&lt;8 | b
    return f'#{rgb:06x}'



def main():
    N = 100000
    methods = (
        rgb_to_hex,
        direct_format,
        one_word,
    )
    for method in methods:
        hex = method(1, 2, 255)
        assert '#0102ff' == hex

        def run():
            return method(1, 2, 255)

        dur = timeit(run, number=N)
        print(f'{method.__name__:15} {1e6*dur/N:.2f} us')


main()


produces:

rgb_to_hex      6.75 us
direct_format   3.14 us
one_word        2.74 us


That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.