Color be a harsh mistress.Aristotle developed the first color theory. He believed that colors were celestial rays sent by Gods for humans to perceive them based on four elements of fire, earth, air and water.

Aristotle/Da Vinci Color Theory

Two hundred years before Newton cracked the color spectrum, artist and heartthrob Leonardo of the planet Vinci theorized that whilst philosophers perceive white as the “receiver” and black as the “absence of color”, both are important. He later developed his color theory based on six colors that you see in the image above. Imagine colors are Boolean operators, white is True, black is False. If True is 1 and False is 0, blue is 0.5. That’s very close to how we perceive colors today.

Meanwhile, Persian painters who came before Da Vinci and Newton gave color theory an existentialist twist. Green was the color of Nobility. Blue was color of paradise. Red was color of passion. And so on.


A Persian miniature. Notice how Joseph Son of Israel is green, whilst the Potiphar’s wife is red. 

In 1666, Sir Isaac Newton, using two prisms, generated what we today know as the White Light Color Spectrum.

White Light Color Spectrum

Light is not particle. It’s not wave either. Light is made up of photons. Photons carry energy, and a prism cracks this energy and splits it into seven colors. That’s just White Light. If we heat up Hydrogen until it emits light, if we split up this light, it will create a 4-color spectrum called Emission Spectrum.

Shades of Color

In real life, all the colors in existence are made up of 3 color: Red, Green, Yellow. Imagine we have a base color, and we want to increase the red-green-yellow value incrementally until we get a shade. But if we increase the value of all three, we’ll get a random shade of color. But if we increase it based on a state, if we treat this as a 3! state machine, we’ll get six conditions. Depending on the condition that we want a red-green shade, we increase red and green values without touching yellow. And so on.

In computers, yellow is blue. But that’s just one of the colorspaces we use in computers. We have CMYK for print. HSL, HSV, YIQ and so on. In this program, we’ll use HSV and RGB. You’ll see.

Complementary Colors

Newton also discovered that if we create a circular spectrum, the opposite colors complement each other. Boutet created the following color wheel based on this discovery:

Boutet’s Color Wheel

I talked about RGB complementary colors in this post. So I’ll cut it short. Just know that in this program, once we generate the base color, we invert the color and generate an shade of six colors based on the opposite color. You’ll see. Let’s continue.

Ranginak Color Generator

Ranginak (meaning Small Color Shade in Persian) is a Python script that generates three six-colored shades of color and 3 original colors.

The source can be found here, also, you can learn about it in this post.

The first shade is used for background. The second shade is using for mid-ground. The third shade, which is the most saturated, is used for foreground.

Zoom in to read the RGB values

How does it work? Let’s start. This code requires Gizeh, which you can install by:

pip install gizeh

It also uses colorsys, which is a built-in Python library. Colorsys converts between color systems. We’ll only use it once in this code. We also make use of random and time.

import gizeh as gz
import colorsys
import random
import time

We then write our first function.

def generate_color(s_min, s_max):
    h = random.randint(0, 100) / 100
    v = random.randint(0, 100) / 100
    s = random.randint(s_min, s_max) / 100
    color = colorsys.hsv_to_rgb(h, s, v)

    return color

s_min and s_max are minimum and maximum saturation, respectively. Then we’ll fix the seed so each time we’ll call the function, randint() will generate a fixed random number. Otherwise it’ll go haywire and generate random colors. You’ll see why seed() is important in the next function. It then puts the converts the HSV value to RGB using colorsys and puts them in a tuple and returns them.

Which we’ll write now:

def generate_color_master():
    color_master = []

    color_master.append(generate_color(1, 33))
    color_master.append(generate_color(33, 66))
    color_master.append(generate_color(66, 100))

    return color_master

color_master[] is a list that contains three color tuples. One for background, one for mid-ground, one for foreground. We change s_min and s_max based on our desire to create a less saturated color for the background, and a more saturated color for the mid-ground and foreground.

def invert():
    inverted = []
    colors = generate_color_master()

    for color_tuple in colors:
        r = 1 - color_tuple[0]
        g = 1  - color_tuple[1]
        b = 1 - color_tuple[2]

        inverted.append((r, g, b))

    return inverted

Our next functions creates the complementary color based on the main colors, and returns them in a list accordingly. Now, our main function.

def generate_shade_color(r, g, b, color_tuple):
    new_color = 0

    addition_r = (random.randint(1, random.randint(5, 9)) / 10) * r
    addition_g = (random.randint(1, random.randint(5, 9)) / 10) * g
    addition_b = (random.randint(1, random.randint(5, 9)) / 10) * b

    new_r = 0
    new_g = 0
    new_b = 0

    if r == 0:
        new_r = color_tuple[0] * 255
        new_g = color_tuple[1] + addition_g * 255
        new_b = color_tuple[2] + addition_b * 255
    elif g == 0:
        new_g = color_tuple[1] * 255
        new_r = color_tuple[0] + addition_r * 255
        new_b = color_tuple[2] + addition_b * 255
    elif b == 0:
        new_b = color_tuple[2] * 255
        new_g = color_tuple[1] + addition_g * 255
        new_r = color_tuple[0] + addition_r * 255

    if int(new_r) <= 255 and int(new_g) <= 255 and int(new_g <= 255):
        new_color = (new_r / 255, new_g / 255, new_b / 255)
    elif int(new_r) > 255:
        new_color = (1.00, new_g / 255, new_b / 255)
    elif int(new_g) > 255:
        new_color = (new_r / 255, 1.00, new_b / 255)
    elif (new_b) > 255:
        new_color = (new_r / 255, new_g / 255 , 1.00)

    return new_color

Because it might get complicated, let me explain what it does in the list format:

1- r, g, and b are binary coefficients. If either is 0, it won’t change the color in our shade. If we want to disable, let’s say, r in our shade, we’ll pass r as 0 and the other two as 1. And so on. Color_tuple is the main color we wish to create a shade from.

2- addition_[channel] are random numbers between 0.1 and 0.9 that we add to the main color’s respective channel in order to create an increasing shade. Note that we multiply it by the coefficient so it’ll be 0 if the coefficient is 0.

3- We multiply the color by 255 so we can have an easier time checking if the color is out of bounds.

4- We check if the color is out of bounds. If it is, we make it 1, the maximum color.

5- We divide the color by 255 again and return the new color.

def generate_shade(r, g, b):
    colors = invert()
    bg = []
    mg = []
    fg = []

    for i in range(6):
        bg.append(generate_shade_color(r, g, b, colors[0]))
        mg.append(generate_shade_color(r, g, b, colors[1]))
        fg.append(generate_shade_color(r, g, b, colors[2]))

    return [bg, mg, fg]

In this function, we create six colors for each layer.

Now, we get to the drawing part using Gizeh.

rect_w = 500
rect_h = 500

def generate_surface():
    surface = gz.Surface(width=int(rect_w * 7), height=int(rect_h * 3))

We create a 3500*1500 window.

def draw_sqr(color, x, y):
    sqr = gz.square(l=500, fill=color, xy=(x, y))

    r = int(color[0] * 255)
    g = int(color[1] * 255)
    b = int(color[2] * 255)
    string = "(" + str(r) + ", " + str(g) + ", " + str(b) + ")"
    text2 = gz.text(string, fontfamily="Tahoma", fontsize=24, fill=(0, 0, 0), xy=(x + 20, y + 20))
    text3 = gz.text(string, fontfamily="Tahoma", fontsize=23, fill=(1, 1, 1), xy=(x + 20, y + 20))

    return gz.Group([sqr, text2, text3])

This function generates a square of the color color, and a text showing the colros RGB value.

Now, the main function.

def main_func(r, g, b):
    colors = generate_shade(r, g, b)
    original_color = generate_color_master()
    items = []

    bg = colors[0]
    mg = colors[1]
    fg = colors[2]

    items.append(draw_sqr(bg[0], 250, 250))
    items.append(draw_sqr(bg[1], 750, 250))
    items.append(draw_sqr(bg[2], 750 + 500, 250))
    items.append(draw_sqr(bg[3], 750 + 1000, 250))
    items.append(draw_sqr(bg[4], 750 + 1500, 250))
    items.append(draw_sqr(bg[5], 750 + 2000, 250))
    items.append(draw_sqr(original_color[0], 750 + 2500, 250))

    items.append(draw_sqr(mg[0], 250, 250 + 500))
    items.append(draw_sqr(mg[1], 750, 250 + 500))
    items.append(draw_sqr(mg[2], 750 + 500, 250 + 500))
    items.append(draw_sqr(mg[3], 750 + 1000, 250 + 500))
    items.append(draw_sqr(mg[4], 750 + 1500, 250 + 500))
    items.append(draw_sqr(mg[5], 750 + 2000, 250 + 500))
    items.append(draw_sqr(original_color[1], 750 + 2500, 250 + 500))

    items.append(draw_sqr(fg[0], 250, 250 + 1000))
    items.append(draw_sqr(fg[1], 750, 250 + 1000))
    items.append(draw_sqr(fg[2], 750 + 500, 250 + 1000))
    items.append(draw_sqr(fg[3], 750 + 1000, 250 + 1000))
    items.append(draw_sqr(fg[4], 750 + 1500, 250 + 1000))
    items.append(draw_sqr(fg[5], 750 + 2000, 250 + 1000))
    items.append(draw_sqr(original_color[2], 750 + 2500, 250 + 1000))

    return gz.Group(items)

First, we prepare our colors. Then, draw 21 square of different colors. Don’t ask why I didn’t use a loop. It brings back bad, bad memories. We return everything as a Gizeh group. Now, the near the end. We don’t want to make a function anymore, just a top-level code:

if __name__ == "__main__":
    for i in range(12):
        group = main_func(0, 1, 1)
        surface = generate_surface()
        surface.write_to_png("shade_" + str(i) + ".png")

This code will create 12 images with a Green-Blue shade. Change g or b to 0 and change r to 1 to experiment with it. Don’t generate lots of images, just change the name of the image if you want more.

Well, that’s it. I hope you enjoyed it. Remember, the code can be found here. For now, hope you’ll have colorful dreams!