File size: 4,655 Bytes
54a7220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
126
127
128
129
130
131
132
133
"""
Encapsulate the list of hex colors and array of Lab values representations
of a palette (codebook) of colors.

Provide methods to work with color conversion and the Palette class.

Provide a parametrized method to generate a palette that covers the range
of colors.
"""

import os
import numpy as np
from skimage.color import hsv2rgb, rgb2lab
from skimage.io import imsave
from sklearn.metrics import euclidean_distances

from .util import rgb2hex


class Palette(object):
    """
    Create a color palette (codebook) in the form of a 2D grid of colors,
    as described in the parameters list below.
    Further, the rightmost column has num_hues gradations from black to white.

    Parameters
    ----------
    num_hues : int
        number of colors with full lightness and saturation, in the middle
    sat_range : int
        number of rows above middle row that show
        the same hues with decreasing saturation.
    light_range : int
        number of rows below middle row that show
        the same hues with decreasing lightness.

    Returns
    -------
    palette: rayleigh.Palette
    """

    def __init__(self, num_hues=8, sat_range=2, light_range=2):
        height = 1 + sat_range + (2 * light_range - 1)
        # generate num_hues+1 hues, but don't take the last one:
        # hues are on a circle, and we would be oversampling the origin
        hues = np.tile(np.linspace(0, 1, num_hues + 1)[:-1], (height, 1))
        if num_hues == 8:
            hues = np.tile(np.array(
                [0.,  0.10,  0.15,  0.28, 0.51, 0.58, 0.77,  0.85]), (height, 1))
        if num_hues == 9:
            hues = np.tile(np.array(
                [0.,  0.10,  0.15,  0.28, 0.49, 0.54, 0.60, 0.7, 0.87]), (height, 1))
        if num_hues == 10:
            hues = np.tile(np.array(
                [0.,  0.10,  0.15,  0.28, 0.49, 0.54, 0.60, 0.66, 0.76, 0.87]), (height, 1))
        elif num_hues == 11:
            hues = np.tile(np.array(
                [0.0, 0.0833, 0.166, 0.25,
                 0.333, 0.5, 0.56333,
                 0.666, 0.73, 0.803,
                 0.916]), (height, 1))
        
        sats = np.hstack((
            np.linspace(0, 1, sat_range + 2)[1:-1],
            1,
            [1] * (light_range),
            [.4] * (light_range - 1),
        ))
        lights = np.hstack((
            [1] * sat_range,
            1,
            np.linspace(1, 0.2, light_range + 2)[1:-1],
            np.linspace(1, 0.2, light_range + 2)[1:-2],
        ))

        sats = np.tile(np.atleast_2d(sats).T, (1, num_hues))
        lights = np.tile(np.atleast_2d(lights).T, (1, num_hues))
        colors = hsv2rgb(np.dstack((hues, sats, lights)))
        grays = np.tile(
            np.linspace(1, 0, height)[:, np.newaxis, np.newaxis], (1, 1, 3))

        self.rgb_image = np.hstack((colors, grays))

        # Make a nice histogram ordering of the hues and grays
        h, w, d = colors.shape
        color_array = colors.T.reshape((d, w * h)).T
        h, w, d = grays.shape
        gray_array = grays.T.reshape((d, w * h)).T
        
        self.rgb_array = np.vstack((color_array, gray_array))
        self.lab_array = rgb2lab(self.rgb_array[None, :, :]).squeeze()
        self.hex_list = [rgb2hex(row) for row in self.rgb_array]
        #assert(np.all(self.rgb_array == self.rgb_array[None, :, :].squeeze()))

        self.distances = euclidean_distances(self.lab_array, squared=True)

    def output(self, dirname, html=False):
        """
        Output an image of the palette, josn list of the hex
        colors, and an HTML color picker for it.

        Parameters
        ----------
        dirname : string
            directory for the files to be output
        """
        def get_palette_html():
            """
            Return HTML for a color picker using the given palette.
            """
            html = """
            <style>
                span {
                    width: 20px;
                    height: 20px;
                    margin: 2px;
                    padding: 0px;
                    display: inline-block;
                }
            </style>
            """
            for row in self.rgb_image:
                for rgb_color in row:
                    s = '<a id="{0}"><span style="background-color: {0}" /></a>\n'
                    html += s.format(rgb2hex(rgb_color))
                html += "<br />\n"
            return html

        imsave(os.path.join(dirname, 'palette.png'), (self.rgb_image*255).astype(np.uint8))
        if html:
            with open(os.path.join(dirname, 'palette.html'), 'w') as f:
                f.write(get_palette_html())