Commits

Robert Kern committed bd87b2c

ENH: Infinite palette experiment.

Comments (0)

Files changed (2)

colormap_explorer/hcl_opt.py

     def f(chroma):
         hcl = hcl_rainbow(chroma, lightness)
         return out_of_gamut(hcl).max()
-    chroma = optimize.bisect(f, 0., 175.)
+    try:
+        chroma = optimize.bisect(f, 0., 175.)
+    except ValueError, e:
+        # A ValueError signals that we couldn't find a root for high values of
+        # lightness. So let's just say it's 0.
+        chroma = 0.0
+    return chroma
+
+def find_chroma_for_hue(hue, lightness):
+    def f(chroma):
+        hcl = np.array([[hue, chroma, lightness]])
+        return out_of_gamut(hcl)[0]
+    try:
+        chroma = optimize.bisect(f, 0., 175.)
+    except ValueError, e:
+        # A ValueError signals that we couldn't find a root for high values of
+        # lightness. So let's just say it's 0.
+        chroma = 0.0
     return chroma
 
 def cubic_bezier(t, xy0, xy1, xy2, xy3):

colormap_explorer/hcl_palette.py

+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+
+""" Play with HCL color palettes.
+"""
+
+import itertools
+
+import numpy as np
+
+from enthought.traits.api import HasTraits, Instance, Int, List, Property, Range, RGBColor, Str, on_trait_change
+from enthought.traits.ui import api as tui
+
+from colormap_explorer import hcl_opt
+from colormap_explorer.conversion import hcl2xyz, xyz2srgb
+
+
+def gray():
+    n = 1
+    codes = [0, 1]
+    yield 1, 0
+    yield 1, 1
+    while True:
+        new_codes = []
+        n += 1
+        m = 1 << (n-1)
+        for x in codes[::-1]:
+            y = m | x
+            yield n, y
+            new_codes.append(y)
+        codes.extend(new_codes)
+
+def gray01():
+    for n, x in gray():
+        y = int(np.binary_repr(x, n)[::-1], 2)
+        yield float(y) / (1<<n)
+
+def gen_hues(m, theta0=0.0):
+    dtheta = 360.0 / m
+    for offset in gray01():
+        for i in range(m):
+            yield (i + theta0 + offset) * dtheta
+
+class APalette(HasTraits):
+    """ A dynamically adjustable palette.
+    """
+
+    # The number of colors in the palette.
+    n = Int(7)
+
+    # The names of all of the Color traits.
+    color_traits = List(Str)
+
+    def __init__(self, n=7, **traits):
+        super(APalette, self).__init__(n=n, **traits)
+
+        for i in range(n):
+            name = 'color_%r' % i
+            self.add_trait(name, RGBColor())
+            self.color_traits.append(name)
+
+    def default_traits_view(self):
+        items = []
+        for name in self.color_traits:
+            items.append(tui.Item(name, editor=tui.RGBColorEditor(),
+                style='readonly', show_label=False, width=-50, height=-50))
+        traits_view = tui.View(
+            tui.HFlow(*items),
+        )
+        return traits_view
+
+    def set_colors(self, colors):
+        for name, rgb in zip(self.color_traits, colors):
+            setattr(self, name, tuple(rgb))
+
+    def get_colors(self):
+        rgb = []
+        for name in self.color_traits:
+            rgb.append(getattr(self, name))
+        return rgb
+
+class HCLPalette(HasTraits):
+    """ A UI for playing with color palettes.
+    """
+
+    # The number of colors in the palette.
+    n = Property(depends_on=['nbase', 'nrep'])
+
+    # The base number of hues for the "helical" progression.
+    nbase = Int(6)
+
+    # The number of repetitions.
+    nrep = Int(3)
+
+    # The lightness of the colors.
+    lightness = Range(0.0, 100.0, value=75.0)
+
+    # The phase of rotation for the palette.
+    phase = Range(0.0, 1.0, value=0.0)
+
+    # The actual palette.
+    the_palette = Instance(APalette)
+
+    traits_view = tui.View(
+        tui.Group(
+            tui.Item('nbase', editor=tui.RangeEditor(low=2, high=30)),
+            tui.Item('nrep', editor=tui.RangeEditor(low=1, high=30)),
+            tui.Item('lightness'),
+            tui.Item('phase'),
+        ),
+        tui.Item('the_palette', style='custom', show_label=False),
+
+        width=800,
+        height=300,
+        resizable=True,
+    )
+
+    def _get_n(self):
+        return self.nbase * self.nrep
+
+    def _the_palette_default(self):
+        palette = APalette(self.n)
+        palette.set_colors(self.get_colors())
+        return palette
+
+    @on_trait_change('n')
+    def _n_changed(self, new):
+        self.the_palette = APalette(new)
+
+    @on_trait_change('the_palette,lightness,phase')
+    def set_the_colors(self):
+        self.the_palette.set_colors(self.get_colors())
+
+    def get_colors(self):
+        hcl = np.empty([self.n, 3], dtype=float)
+        hcl[:,0] = list(itertools.islice(gen_hues(self.nbase, self.phase), self.n))
+        hcl[:,2] = self.lightness
+        for i in range(self.n):
+            hcl[i,1] = hcl_opt.find_chroma_for_hue(hcl[i,0], self.lightness)
+        rgb = xyz2srgb(hcl2xyz(hcl)).clip(0.0, 1.0)
+        return rgb
+
+
+def main():
+    import argparse
+    import pprint
+    parser = argparse.ArgumentParser()
+
+    args = parser.parse_args()
+    p = HCLPalette()
+    p.configure_traits()
+    pprint.pprint(p.the_palette.get_colors())
+
+if __name__ == '__main__':
+    main()
+