#!/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 # 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)