Skip to content

Instantly share code, notes, and snippets.

@ChuanyuXue
Last active May 9, 2024 07:30
Show Gist options
  • Save ChuanyuXue/3a377f7c1629b0ce68bc6b393340d0fb to your computer and use it in GitHub Desktop.
Save ChuanyuXue/3a377f7c1629b0ce68bc6b393340d0fb to your computer and use it in GitHub Desktop.
Morandi Palette: A palette with 18 colors inspired by Giorgio Morandi
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from gurobipy import Model, GRB
colors = [
'#686789', '#B77F70', '#E5E2B9', '#BEB1A8', '#A79A89', '#8A95A9',
'#ECCED0', '#7D7465', '#E8D3C0', '#7A8A71', '#789798', '#B57C82',
'#9FABB9', '#B0B1B6', '#99857E', '#88878D', '#91A0A5', '#9AA690'
]
def hex_to_rgb(value):
"""Convert a hex color to an RGB tuple."""
value = value.lstrip('#')
return tuple(int(value[i:i+2], 16) for i in (0, 2, 4))
def euclidean_distance(color1, color2):
"""Calculate the Euclidean distance between two RGB colors."""
return int(sum((c1 - c2) ** 2 for c1, c2 in zip(color1, color2)))
def find_next_color(palette, remaining_colors):
"""Find the color from remaining_colors that maximizes the average distance to the current palette."""
max_avg_distance = 0
next_color = None
for color in remaining_colors:
avg_distance = sum(euclidean_distance(hex_to_rgb(color), hex_to_rgb(p)) for p in palette) / len(palette)
if avg_distance > max_avg_distance:
max_avg_distance = avg_distance
next_color = color
return next_color
def sort_colors(colors):
""" This is only to demonstrate the algorithm to get the ordered colors."""
current_palette = ['#686789', '#B77F70', '#E5E2B9', '#BEB1A8',
'#A79A89', '#8A95A9']
remaining_colors = [color for color in colors if color not in current_palette]
for _ in range(len(remaining_colors)):
next_color = find_next_color(current_palette, remaining_colors)
current_palette.append(next_color)
remaining_colors.remove(next_color)
return current_palette
def get_colors(num: int = 18):
"""Get a list of colors from the Morandi palette."""
return colors[:num]
def group_colors(colors, num_per_group):
"""Group colors into groups of fixed size such that the sum of the distances between each pair of colors in the same group is maximized."""
# Create a new model
m = Model("color_grouping")
# Create variables
x = [[m.addVar(vtype=GRB.BINARY, name=f"{i}-{j}") for j in range(len(num_per_group))] for i in range(len(colors))]
# Set objective
objective = 0
for i in range(len(colors)):
for j in range(i+1, len(colors)):
for k in range(len(num_per_group)):
objective += x[i][k] * x[j][k] * euclidean_distance(hex_to_rgb(colors[i]), hex_to_rgb(colors[j]))
m.setObjective(objective, GRB.MAXIMIZE)
m.setParam('TimeLimit', 5 * 60)
# Add constraints
for i in range(len(colors)):
m.addConstr(sum(x[i]) <= 1) # each color can only be at most in one group
for j in range(len(num_per_group)):
m.addConstr(sum(x[i][j] for i in range(len(colors))) == num_per_group[j]) # each group has a fixed number of colors
# Optimize model
m.optimize()
# Check if a solution exists
if m.status in [GRB.OPTIMAL, GRB.SUBOPTIMAL, GRB.TIME_LIMIT]:
print("Warning: suboptimal solution found") if m.status == GRB.SUBOPTIMAL else None
result = [[] for _ in range(len(num_per_group))]
for i in range(len(colors)):
for k in range(len(num_per_group)):
if x[i][k].x == 1:
result[k].append(colors[i])
return result
else:
raise Exception("No solution found")
if __name__ == "__main__":
## Example 1: getting ordered K-colors
colors = get_colors(18)
sns.palplot(colors)
plt.title("Giorgio Morandi's palette")
plt.show()
## Example 2: getting grouped K-colors
groups = group_colors(colors, [2,2,2,1,2,2,3,2,1])
print("Grouped colors:", groups)
count, n = 0, len(groups)
fig, axes = plt.subplots(1, n, figsize=(15, 2))
for ax, group in zip(axes, groups):
ax.imshow([[np.array(hex_to_rgb(color)) / 255.0 for color in group]], aspect='auto')
ax.set_title(f"Group-{count}")
ax.axis('off')
count += 1
plt.show()
@ChuanyuXue
Copy link
Author

ChuanyuXue commented Aug 7, 2023

cc40baea-b71e-4d66-9111-ec38b31dbcc1

morandi_grouped

@ChuanyuXue
Copy link
Author

Untitled

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment