1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
#!/usr/bin/env python
# A script to generate a color palette from an image which still complies with the
# general terminal colors (i.e. red remains red).
#
# Author: David 'davidpkj' Penkowoj <davidpenkow1@gmail.com>
# Version: 0.1.0
# License: EUPL v. 1.2 @ https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
from PIL import Image
from colour import Color
import sys
import math
import colorz as clz
import seaborn as sns
import matplotlib.pyplot as plt
# Rotate hue and saturation to get other colors
def rotate(input):
# TODO: instead of rotation, maybe calculate deviation from standard value (i.e. hue 60, sat 50)
# and then apply the same deviation to all standard colors
# TODO: also do something about these arbitrary factors
result = []
iterations = 6
sat_factor = -0.1
hue_factor = 1 / iterations
for i in range(iterations):
_hue = input.hue
_sat = input.saturation
_lum = input.luminance
_hue = _hue + i * hue_factor
_hue = _hue - math.floor(_hue)
_newsat = _sat + abs(i * sat_factor)
if _newsat <= 1 and _newsat >= 0:
_sat = _newsat
result.append(Color(hsl=(_hue, _sat, _lum)))
return sorted(result, key=lambda x: x.hue)
# Get the distance between two numbers (context: hue)
def get_distance(color, target):
return abs(color.hue - target)
# Returns the color which is closes in hue to the given value
def select_nearest(value, colors):
target = value / 360
color = colors[0]
distance = get_distance(color, target)
for c in colors:
new_distance = get_distance(c, target)
if new_distance < distance:
color = c
distance = new_distance
# NOTE: special case for red, since can be numerically closer to
# 0° or 360° (1) but not the other. not nice but works
if target == 0:
new_color, new_distance = select_nearest(360, colors)
if new_distance < distance:
color = new_color
return color, distance
# Show a palette
def show_palette(colors):
sns.palplot(sns.color_palette(colors))
plt.show()
# Save the original image with the attached palette back to disk.
def save_image(colors, path):
palette_size = 30
input = Image.open(path)
colors_len = len(colors)
output = Image.new("RGB", (input.size[0], input.size[1] + palette_size), (0, 0, 0))
p2 = (input.size[0], input.size[1] + palette_size)
# Attaches the colors at the bottom of the image
for i in range(colors_len):
p1 = (round(input.size[0] / colors_len) * i, input.size[1])
output.paste(colors[i], [p1[0], p1[1], p2[0], p2[1]])
output.paste(input)
# output.save("output.png")
output.show()
def main(file):
# NOTE: The arguments to this function were stolen from the library's internal code.
colorz_tuple = clz.colorz(file, 1, 170, 200, 50)[0][1]
normalized_tuple = (colorz_tuple[0] / 256, colorz_tuple[1] / 256, colorz_tuple[2] / 256)
input_color = Color(rgb=normalized_tuple)
colors = rotate(input_color)
terminal_colors = {"red": 0, "green": 120, "yellow": 60, "blue": 240, "magenta": 300, "cyan": 180}
for key in terminal_colors:
color, _= select_nearest(terminal_colors[key], colors)
terminal_colors[key] = color.hex
# Formulate a propper list of colors
palette = list(terminal_colors.values())
palette.insert(0, "#1a1a1a") # my favorite constant black
palette.append("#efefef") # my favorite constant white
return palette, input_color
if __name__ == "__main__":
path = sys.argv[1]
file = open(path, "rb")
palette, dominant_color = main(file)
save_image(palette, path)
print(dominant_color)
|