I have a Python module that provides color palettes and utilities for dealing with them. A color palette object simply inherits from list
and is just a list of colors specified in HEX strings. A color palette object has the ability to extend itself to provide as many colors as needed. Imagine a graph with many different datasets being represented: the palette can be asked to extend the number of colors it has to the extent needed to provide unique colors for each graph dataset. It does this by simply taking the mean of adjacent colors and inserting this new mean color.
The extend_palette
function works, but it doesn't extend the palette uniformly. For example, a palette could look like the following to begin with:
Extending it to 15 colors is still usable:
Extending it to 30 colors makes the problem with the extend algorithm apparent; new colors are being added only at one end of the list of colors:
How should the function extend_palette
of the module be changed to make the extended new colors more uniformly distributed in the palette?
The code follows (with the function extend_palette
being of particular focus and other bits of code there for convenience of experimentation):
def clamp(x):
return max(0, min(x, 255))
def RGB_to_HEX(RGB_tuple):
# This function returns a HEX string given an RGB tuple.
r = RGB_tuple[0]
g = RGB_tuple[1]
b = RGB_tuple[2]
return "#{0:02x}{1:02x}{2:02x}".format(clamp(r), clamp(g), clamp(b))
def HEX_to_RGB(HEX_string):
# This function returns an RGB tuple given a HEX string.
HEX = HEX_string.lstrip("#")
HEX_length = len(HEX)
return tuple(
int(HEX[i:i + HEX_length // 3], 16) for i in range(
0,
HEX_length,
HEX_length // 3
)
)
def mean_color(colors_in_HEX):
# This function returns a HEX string that represents the mean color of a
# list of colors represented by HEX strings.
colors_in_RGB = []
for color_in_HEX in colors_in_HEX:
colors_in_RGB.append(HEX_to_RGB(color_in_HEX))
sum_r = 0
sum_g = 0
sum_b = 0
for color_in_RGB in colors_in_RGB:
sum_r += color_in_RGB[0]
sum_g += color_in_RGB[1]
sum_b += color_in_RGB[2]
mean_r = sum_r / len(colors_in_RGB)
mean_g = sum_g / len(colors_in_RGB)
mean_b = sum_b / len(colors_in_RGB)
return RGB_to_HEX((mean_r, mean_g, mean_b))
class Palette(list):
def __init__(
self,
name = None, # string name
description = None, # string description
colors = None, # list of colors
*args
):
super(Palette, self).__init__(*args)
self._name = name
self._description = description
self.extend(colors)
def name(
self
):
return self._name
def set_name(
self,
name = None
):
self._name = name
def description(
self
):
return self._description
def set_description(
self,
description = None
):
self._description = description
def extend_palette(
self,
minimum_number_of_colors_needed = 15
):
colors = extend_palette(
colors = self,
minimum_number_of_colors_needed = minimum_number_of_colors_needed
)
self = colors
def save_image_of_palette(
self,
filename = "palette.png"
):
save_image_of_palette(
colors = self,
filename = filename
)
def extend_palette(
colors = None, # list of HEX string colors
minimum_number_of_colors_needed = 15
):
while len(colors) < minimum_number_of_colors_needed:
for index in range(1, len(colors), 2):
colors.insert(index, mean_color([colors[index - 1], colors[index]]))
return colors
def save_image_of_palette(
colors = None, # list of HEX string colors
filename = "palette.png"
):
import numpy
import Image
scale_x = 200
scale_y = 124
data = numpy.zeros((1, len(colors), 3), dtype = numpy.uint8)
index = -1
for color in colors:
index += 1
color_RGB = HEX_to_RGB(color)
data[0, index] = [color_RGB[0], color_RGB[1], color_RGB[2]]
data = numpy.repeat(data, scale_x, axis=0)
data = numpy.repeat(data, scale_y, axis=1)
image = Image.fromarray(data)
image.save(filename)
# Define color palettes.
palettes = []
palettes.append(Palette(
name = "palette1",
description = "primary colors for white background",
colors = [
"#fc0000",
"#ffae3a",
"#00ac00",
"#6665ec",
"#a9a9a9",
]
))
palettes.append(Palette(
name = "palette2",
description = "ATLAS clarity",
colors = [
"#FEFEFE",
"#AACCFF",
"#649800",
"#9A33CC",
"#EE2200",
]
))
def save_images_of_palettes():
for index, palette in enumerate(palettes):
save_image_of_palette(
colors = palette,
filename = "palette_{index}.png".format(index = index + 1)
)
def access_palette(
name = "palette1"
):
for palette in palettes:
if palette.name() == name:
return palette
return None