2012-03-30 16:12:24 +00:00
|
|
|
# -*- coding: utf8 -*-
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
2012-03-30 16:12:24 +00:00
|
|
|
.. module:: lesscpy.lessc.color
|
|
|
|
:synopsis: Lesscpy Color functions
|
2012-01-28 14:52:09 +00:00
|
|
|
|
|
|
|
Copyright (c)
|
|
|
|
See LICENSE for details.
|
2012-03-30 16:12:24 +00:00
|
|
|
.. moduleauthor:: Jóhann T. Maríusson <jtm@robot.is>
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
|
|
|
import colorsys
|
2012-03-10 16:27:14 +00:00
|
|
|
from . import utility
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
class Color():
|
2012-01-28 14:52:09 +00:00
|
|
|
def process(self, expression):
|
|
|
|
""" Process color expression
|
2012-03-30 16:12:24 +00:00
|
|
|
args:
|
|
|
|
expression (tuple): color expression
|
|
|
|
returns:
|
|
|
|
str
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
|
|
|
a, o, b = expression
|
2012-03-10 16:27:14 +00:00
|
|
|
c1 = self._hextorgb(a)
|
|
|
|
c2 = self._hextorgb(b)
|
2012-01-28 14:52:09 +00:00
|
|
|
r = ['#']
|
|
|
|
for i in range(3):
|
|
|
|
v = self.operate(c1[i], c2[i], o)
|
|
|
|
if v > 0xff: v = 0xff
|
|
|
|
if v < 0: v = 0
|
|
|
|
r.append("%02x" % v)
|
|
|
|
return ''.join(r)
|
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def operate(self, left, right, operation):
|
2012-01-28 14:52:09 +00:00
|
|
|
""" Do operation on colors
|
2012-03-30 16:12:24 +00:00
|
|
|
args:
|
|
|
|
left (str): left side
|
|
|
|
right (str): right side
|
|
|
|
operation (str): Operation
|
|
|
|
returns:
|
|
|
|
str
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
|
|
|
operation = {
|
|
|
|
'+': '__add__',
|
|
|
|
'-': '__sub__',
|
|
|
|
'*': '__mul__',
|
|
|
|
'/': '__truediv__'
|
2012-03-30 16:12:24 +00:00
|
|
|
}.get(operation)
|
|
|
|
return getattr(left, operation)(right)
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def rgb(self, *args):
|
2012-03-30 16:12:24 +00:00
|
|
|
""" Translate rgb(...) to color string
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
2012-03-10 16:27:14 +00:00
|
|
|
"""
|
|
|
|
if len(args) == 4:
|
|
|
|
return self.rgba(*args)
|
|
|
|
elif len(args) == 3:
|
|
|
|
try:
|
|
|
|
return self._rgbatohex(map(int, args))
|
|
|
|
except ValueError:
|
|
|
|
if all((a for a in args
|
|
|
|
if a[-1] == '%'
|
|
|
|
and 100 >= int(a[:-1]) >= 0)):
|
|
|
|
return self._rgbatohex([int(a[:-1]) * 255 / 100.0
|
|
|
|
for a in args])
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def rgba(self, *args):
|
2012-03-30 16:12:24 +00:00
|
|
|
""" Translate rgba(...) to color string
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
2012-03-10 16:27:14 +00:00
|
|
|
"""
|
|
|
|
if len(args) == 4:
|
|
|
|
try:
|
|
|
|
return self._rgbatohex(map(int, args))
|
|
|
|
except ValueError:
|
|
|
|
if all((a for a in args
|
|
|
|
if a[-1] == '%'
|
|
|
|
and 100 >= int(a[:-1]) >= 0)):
|
|
|
|
return self._rgbatohex([int(a[:-1]) * 255 / 100.0
|
|
|
|
for a in args])
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def hsl(self, *args):
|
2012-03-30 16:12:24 +00:00
|
|
|
""" Translate hsl(...) to color string
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
2012-03-10 16:27:14 +00:00
|
|
|
"""
|
|
|
|
if len(args) == 4:
|
|
|
|
return self.hsla(*args)
|
|
|
|
elif len(args) == 3:
|
|
|
|
h, s, l = args
|
|
|
|
if type(l) == str: l = int(l.strip('%'))
|
|
|
|
if type(s) == str: s = int(s.strip('%'))
|
|
|
|
rgb = colorsys.hls_to_rgb(int(h) / 360, l / 100, s / 100)
|
|
|
|
color = (round(c * 255) for c in rgb)
|
|
|
|
return self._rgbatohex(color)
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def hsla(self, *args):
|
2012-03-30 16:12:24 +00:00
|
|
|
""" Translate hsla(...) to color string
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
2012-03-10 16:27:14 +00:00
|
|
|
"""
|
|
|
|
if len(args) == 4:
|
|
|
|
h, s, l, a = args
|
|
|
|
if type(l) == str: l = int(l.strip('%'))
|
|
|
|
if type(s) == str: s = int(s.strip('%'))
|
|
|
|
rgb = colorsys.hls_to_rgb(int(h) / 360, l / 100, s / 100)
|
|
|
|
color = [round(c * 255) for c in rgb]
|
|
|
|
color.append(round(float(a[:-1]) / 100.0, 2))
|
|
|
|
return "rgba(%s,%s,%s,%s)" % tuple(color)
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def hue(self, color, *args):
|
|
|
|
""" Return the hue value of a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
float
|
|
|
|
"""
|
|
|
|
if color:
|
|
|
|
h, l, s = self._hextohls(color)
|
|
|
|
return round(h * 360.0, 3)
|
2012-03-11 14:47:58 +00:00
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def saturation(self, color, *args):
|
|
|
|
""" Return the saturation value of a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
float
|
|
|
|
"""
|
|
|
|
if color:
|
|
|
|
h, l, s = self._hextohls(color)
|
|
|
|
return s * 100.0
|
2012-03-11 14:47:58 +00:00
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def lightness(self, color, *args):
|
|
|
|
""" Return the lightness value of a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
float
|
|
|
|
"""
|
|
|
|
if color:
|
|
|
|
h, l, s = self._hextohls(color)
|
|
|
|
return l * 100.0
|
2012-03-11 14:47:58 +00:00
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def opacity(self, *args):
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
|
|
|
"""
|
2012-03-10 16:27:14 +00:00
|
|
|
pass
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def lighten(self, color, diff, *args):
|
|
|
|
""" Lighten a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
diff (str): percentage
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color and diff:
|
2012-03-10 16:27:14 +00:00
|
|
|
return self._ophsl(color, diff, 1, '__add__')
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def darken(self, color, diff, *args):
|
|
|
|
""" Darken a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
diff (str): percentage
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color and diff:
|
2012-03-10 16:27:14 +00:00
|
|
|
return self._ophsl(color, diff, 1, '__sub__')
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def saturate(self, color, diff, *args):
|
|
|
|
""" Saturate a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
diff (str): percentage
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color and diff:
|
2012-03-10 16:27:14 +00:00
|
|
|
return self._ophsl(color, diff, 2, '__add__')
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def desaturate(self, color, diff, *args):
|
|
|
|
""" Desaturate a color
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
diff (str): percentage
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color and diff:
|
2012-03-10 16:27:14 +00:00
|
|
|
return self._ophsl(color, diff, 2, '__sub__')
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def _clamp(self, value):
|
|
|
|
# Clamp value
|
|
|
|
return min(1, max(0, value))
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def grayscale(self, color, *args):
|
|
|
|
""" Simply 100% desaturate.
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color:
|
|
|
|
return self.desaturate(color, 100.0)
|
2012-03-10 16:27:14 +00:00
|
|
|
raise ValueError('Illegal color values')
|
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def greyscale(self, color, *args):
|
|
|
|
"""Wrapper for grayscale, other spelling
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
2012-03-30 16:12:24 +00:00
|
|
|
return self.grayscale(color, *args)
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def spin(self, color, degree, *args):
|
|
|
|
""" Spin color by degree. (Increase / decrease hue)
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
degree (str): percentage
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color and degree:
|
|
|
|
if type(degree) == str:
|
|
|
|
degree = int(degree.strip('%'))
|
2012-03-10 16:27:14 +00:00
|
|
|
h, l, s = self._hextohls(color)
|
2012-03-30 16:12:24 +00:00
|
|
|
h = ((h * 360.0) + degree) % 360.0
|
|
|
|
h = 360.0 + h if h < 0 else h
|
|
|
|
rgb = colorsys.hls_to_rgb(h / 360.0, l, s)
|
2012-03-10 16:27:14 +00:00
|
|
|
color = (round(c * 255) for c in rgb)
|
|
|
|
return self._rgbatohex(color)
|
|
|
|
raise ValueError('Illegal color values')
|
2012-01-28 14:52:09 +00:00
|
|
|
|
2012-03-30 16:12:24 +00:00
|
|
|
def mix(self, color1, color2, weight=50, *args):
|
|
|
|
"""This algorithm factors in both the user-provided weight
|
2012-03-11 14:47:58 +00:00
|
|
|
and the difference between the alpha values of the two colors
|
|
|
|
to decide how to perform the weighted average of the two RGB values.
|
|
|
|
|
|
|
|
It works by first normalizing both parameters to be within [-1, 1],
|
|
|
|
where 1 indicates "only use color1", -1 indicates "only use color 0",
|
|
|
|
and all values in between indicated a proportionately weighted average.
|
|
|
|
|
|
|
|
Once we have the normalized variables w and a,
|
|
|
|
we apply the formula (w + a)/(1 + w*a)
|
|
|
|
to get the combined weight (in [-1, 1]) of color1.
|
|
|
|
This formula has two especially nice properties:
|
|
|
|
|
|
|
|
* When either w or a are -1 or 1, the combined weight is also that number
|
|
|
|
(cases where w * a == -1 are undefined, and handled as a special case).
|
|
|
|
|
|
|
|
* When a is 0, the combined weight is w, and vice versa
|
|
|
|
|
|
|
|
Finally, the weight of color1 is renormalized to be within [0, 1]
|
|
|
|
and the weight of color2 is given by 1 minus the weight of color1.
|
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
|
|
|
|
http://sass-lang.com
|
2012-03-30 16:12:24 +00:00
|
|
|
args:
|
|
|
|
color1 (str): first color
|
|
|
|
color2 (str): second color
|
|
|
|
weight (int/str): weight
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
if color1 and color2:
|
|
|
|
if type(weight) == str:
|
|
|
|
weight = int(weight.strip('%'))
|
|
|
|
weight = ((weight / 100.0) * 2) - 1
|
|
|
|
rgb1 = self._hextorgb(color1)
|
|
|
|
rgb2 = self._hextorgb(color2)
|
|
|
|
alpha = 0
|
|
|
|
w1 = (((weight if weight * alpha == -1
|
|
|
|
else weight + alpha) / (1 + weight * alpha)) + 1)
|
2012-03-11 14:47:58 +00:00
|
|
|
w1 = w1 / 2.0
|
2012-03-10 16:27:14 +00:00
|
|
|
w2 = 1 - w1
|
|
|
|
rgb = [
|
|
|
|
rgb1[0] * w1 + rgb2[0] * w2,
|
|
|
|
rgb1[1] * w1 + rgb2[1] * w2,
|
|
|
|
rgb1[2] * w1 + rgb2[2] * w2,
|
|
|
|
]
|
|
|
|
return self._rgbatohex(rgb)
|
|
|
|
raise ValueError('Illegal color values')
|
|
|
|
|
|
|
|
def fmt(self, color):
|
2012-03-30 16:12:24 +00:00
|
|
|
""" Format CSS Hex color code.
|
|
|
|
uppercase becomes lowercase, 3 digit codes expand to 6 digit.
|
|
|
|
args:
|
|
|
|
color (str): color
|
|
|
|
raises:
|
|
|
|
ValueError
|
|
|
|
returns:
|
|
|
|
str
|
2012-01-28 14:52:09 +00:00
|
|
|
"""
|
2012-03-10 16:27:14 +00:00
|
|
|
if utility.is_color(color):
|
|
|
|
color = color.lower().strip('#')
|
|
|
|
if len(color) in [3, 4]:
|
2012-01-28 14:52:09 +00:00
|
|
|
color = ''.join([c * 2 for c in color])
|
|
|
|
return '#%s' % color
|
|
|
|
raise ValueError('Cannot format non-color')
|
|
|
|
|
2012-03-10 16:27:14 +00:00
|
|
|
def _rgbatohex(self, rgba):
|
2012-01-28 14:52:09 +00:00
|
|
|
return '#%s' % ''.join(["%02x" % v for v in
|
|
|
|
[0xff
|
|
|
|
if h > 0xff else
|
|
|
|
0 if h < 0 else h
|
2012-03-10 16:27:14 +00:00
|
|
|
for h in rgba]
|
2012-01-28 14:52:09 +00:00
|
|
|
])
|
2012-03-10 16:27:14 +00:00
|
|
|
def _hextorgb(self, hex):
|
|
|
|
hex = hex.strip()
|
|
|
|
if hex[0] == '#':
|
|
|
|
hex = hex.strip('#').strip(';')
|
|
|
|
if len(hex) == 3:
|
|
|
|
hex = [c * 2 for c in hex]
|
|
|
|
else:
|
|
|
|
hex = [hex[i:i+2] for i in range(0, len(hex), 2)]
|
|
|
|
return tuple(int(c, 16) for c in hex)
|
|
|
|
return [int(hex, 16)] * 3
|
|
|
|
|
|
|
|
def _hextohls(self, hex):
|
|
|
|
rgb = self._hextorgb(hex)
|
|
|
|
return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb])
|
|
|
|
|
|
|
|
def _ophsl(self, color, diff, idx, op):
|
|
|
|
if type(diff) == str: diff = int(diff.strip('%'))
|
|
|
|
hls = list(self._hextohls(color))
|
2012-03-30 16:12:24 +00:00
|
|
|
hls[idx] = self._clamp(getattr(hls[idx], op)(diff / 100))
|
2012-03-10 16:27:14 +00:00
|
|
|
rgb = colorsys.hls_to_rgb(*hls)
|
|
|
|
color = (round(c * 255) for c in rgb)
|
|
|
|
return self._rgbatohex(color)
|
|
|
|
|
|
|
|
|
2012-01-28 14:52:09 +00:00
|
|
|
|
|
|
|
|