Source code for hbutils.color.utils

"""
Overview:
    Useful color utilities based on color model
"""
import math
import random
from typing import Iterator, Union, Sequence, Tuple, Callable

from .model import Color
from ..algorithm import linear_map

__all__ = [
    'visual_distance',
    'rnd_colors',
    'linear_gradient',
]


def _to_color(color: Color):
    if isinstance(color, Color):
        return color
    else:
        return Color(color)


[docs]def visual_distance(c1: Union[Color, str], c2: Union[Color, str]) -> float: """ Overview: Get distance of 2 colors. Arguments: - c1 (:obj:`Color`): First color. - c2 (:obj:`Color`): Second color. Returns: - distance (:obj:`float`): Distance of the colors. Examples:: >>> from hbutils.color import visual_distance, Color >>> visual_distance( ... '#ff0000', ... '#00ff00' ... ) 2.5495097567963922 >>> visual_distance( ... '#778800', ... '#887700' ... ) 0.16996731711975946 """ c1, c2 = _to_color(c1), _to_color(c2) rgb1, rgb2 = c1.rgb, c2.rgb rmean = (rgb1.red + rgb2.red) / 2 dr = rgb1.red - rgb2.red dg = rgb1.green - rgb2.green db = rgb1.blue - rgb2.blue return math.sqrt( (2 + rmean) * dr * dr + 4 * dg * dg + (3 - rmean) * db * db )
def _dis_ratio(k): if k < 3: return 6.0 if k < 6: return 2.0 elif k < 8: return 0.7 else: return 1.0
[docs]def rnd_colors( count, lightness=0.5, saturation=1.0, alpha=None, init_dis=4.0, lr=0.95, ur=1.5, rnd=None ) -> Iterator[Color]: """ Overview: Generating random colors which are not similar. :param count: Count of colors. :param lightness: Lightness of the colors (in HLS color space), default is ``0.5``. :param saturation: Saturation of the colors (in HLS color space), default is ``1.0``. :param alpha: Alpha of the colors, default is ``None``. :param init_dis: Initial distance of colors, default is ``4.0``. :param lr: Lower ratio when generating, default is ``0.95``. :param ur: Upper ratio when generating, default is ``1.5``. :param rnd: Random object to be used, default is ``random.Random(0)``. Returns: - colors (:obj:`Iterator[Color]`): A iterator of colors. Examples:: >>> from hbutils.color import rnd_colors >>> for c in rnd_colors(12): ... print(c) #ff00ee #00ff00 #009cff #ff006c #c9ff00 #00f3ff #d100ff #ffaf00 #00ff6c #4100ff #ff5300 #46ff00 >>> for c in rnd_colors(12, 0.8, 0.9): ... print(c) #fa9ef4 #9efaa1 #9eb4fa #faa69e #c5fa9e #9ed6fa #f09efa #faf89e #9ef9fa #c09efa #fabe9e #9efaca """ rnd = rnd or random.Random(0) min_distance = init_dis _exist_colors = [] for i in range(count): try_cnt, total_try_cnt = 0, 0 while True: new_color = Color.from_hls(rnd.random(), lightness, saturation) if not _exist_colors or all( [visual_distance(color_, new_color) >= min_distance * _dis_ratio(i - j) for j, color_ in enumerate(_exist_colors)]): _exist_colors.append(new_color) if total_try_cnt <= count * 2: min_distance *= ur yield Color(new_color, alpha) break else: try_cnt += 1 total_try_cnt += 1 if try_cnt >= count * 2: min_distance *= lr try_cnt = 0
[docs]def linear_gradient(colors: Union[Sequence[Union[Color, str]], Sequence[Tuple[float, Union[Color, str]]]]) \ -> Callable[[float], Color]: """ Overview: Linear gradient of the colors. :param colors: Colors to gradient. :return: A mapping function for gradient. Examples:: - Simple Linear Gradientation >>> from hbutils.color import linear_gradient >>> >>> f = linear_gradient(('red', 'yellow', 'lime')) >>> f(0) <Color red> >>> f(0.25) <Color #ff8000> >>> f(1 / 3) <Color #ffaa00> >>> f(0.5) <Color yellow> >>> f(2 / 3) <Color #aaff00> >>> f(0.75) <Color #80ff00> >>> f(1) <Color lime> - Complex Linear Gradientation >>> f = linear_gradient(((-0.2, 'red'), (0.7, '#ffff0044'), (1.1, 'lime'))) >>> f(-0.2) <Color red, alpha: 1.000> >>> f(0) <Color #ff3900, alpha: 0.837> >>> f(0.25) <Color #ff8000, alpha: 0.633> >>> f(1 / 3) <Color #ff9700, alpha: 0.565> >>> f(0.5) <Color #ffc600, alpha: 0.430> >>> f(2 / 3) <Color #fff600, alpha: 0.294> >>> f(0.7) <Color yellow, alpha: 0.267> >>> f(0.75) <Color #dfff00, alpha: 0.358> >>> f(0.8) <Color #bfff00, alpha: 0.450> >>> f(1) <Color #40ff00, alpha: 0.817> >>> f(1.1) <Color lime, alpha: 1.000> """ try: xys = [(x, _to_color(y)) for x, y in colors] except ValueError: pts = list(colors) n = len(pts) xys = [(i / (n - 1), _to_color(y)) for i, y in enumerate(colors)] rmap = linear_map([(x, y.rgb.red) for x, y in xys]) gmap = linear_map([(x, y.rgb.green) for x, y in xys]) bmap = linear_map([(x, y.rgb.blue) for x, y in xys]) if any([y.alpha is not None for _, y in xys]): amap = linear_map([(x, y.alpha if y.alpha is not None else 1.0) for x, y in xys]) else: # noinspection PyUnusedLocal def amap(x): return None def _gradient(x: float) -> Color: return Color((rmap(x), gmap(x), bmap(x)), amap(x)) return _gradient