summaryrefslogtreecommitdiff
path: root/scss/functions/core.py
diff options
context:
space:
mode:
Diffstat (limited to 'scss/functions/core.py')
-rw-r--r--scss/functions/core.py130
1 files changed, 118 insertions, 12 deletions
diff --git a/scss/functions/core.py b/scss/functions/core.py
index 488c528..44ba7ab 100644
--- a/scss/functions/core.py
+++ b/scss/functions/core.py
@@ -3,6 +3,7 @@ Ruby implementation.
"""
from __future__ import absolute_import
+from __future__ import division
import colorsys
import logging
@@ -12,7 +13,7 @@ import operator
from six.moves import xrange
from scss.functions.library import FunctionLibrary
-from scss.types import Boolean, Color, List, Number, String, Map
+from scss.types import Boolean, Color, List, Number, String, Map, expect_type
log = logging.getLogger(__name__)
@@ -50,6 +51,40 @@ def _apply_percentage(n, relto=1):
raise TypeError("Expected unitless number or percentage, got %r" % (n,))
+def _interpret_percentage(n, relto=1.):
+ expect_type(n, Number, unit='%')
+
+ if n.is_unitless:
+ ret = n.value / relto
+ else:
+ ret = n.value / 100.
+
+ if ret < 0:
+ return 0.
+ elif ret > 1:
+ return 1.
+ else:
+ return ret
+
+
+def _interpret_rgb_args(r, g, b):
+ """Given arguments from Sass-land representing red, green, and blue
+ channels, return plain Python numbers appropriate for passing to `Color`
+ constructors.
+ """
+ ret = []
+ for channel in (r, g, b):
+ expect_type(channel, Number, unit='%')
+ if channel.is_simple_unit('%'):
+ value = channel.value / 100.
+ else:
+ value = channel.value / 255.
+
+ ret.append(_constrain(value, 0, 1))
+
+ return ret
+
+
def _color_type(color, a, type):
color = Color(color).value
a = Number(a).value if a is not None else color[3]
@@ -61,13 +96,12 @@ def _color_type(color, a, type):
@register('rgba', 4)
def rgba(r, g, b, a, type='rgba'):
- channels = []
- for ch in (r, g, b):
- channels.append(_constrain(_apply_percentage(ch, relto=255), 0, 255))
+ r = _interpret_percentage(r, relto=255.)
+ g = _interpret_percentage(g, relto=255.)
+ b = _interpret_percentage(b, relto=255.)
+ a = _interpret_percentage(a, relto=1.)
- channels.append(_constrain(_apply_percentage(a), 0, 1))
- channels.append(type)
- return Color(channels)
+ return Color.from_rgb(r, g, b, a)
@register('rgb', 3)
@@ -389,18 +423,90 @@ def _asc_color(op, color, saturation=None, lightness=None, red=None, green=None,
@register('adjust-color')
-def adjust_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
+def adjust_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None):
return _asc_color(operator.__add__, color, saturation, lightness, red, green, blue, alpha)
+def _scale_channel(channel, scaleby):
+ if scaleby is None:
+ return channel
+
+ expect_type(scaleby, Number)
+ if not scaleby.is_simple_unit('%'):
+ raise ValueError("Expected percentage, got %r" % (scaleby,))
+
+ factor = scaleby.value / 100
+ if factor > 0:
+ # Add x% of the remaining range, up to 1
+ return channel + (1 - channel) * factor
+ else:
+ # Subtract x% of the existing channel. We add here because the factor
+ # is already negative
+ return channel * (1 + factor)
+
+
@register('scale-color')
-def scale_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
- return _asc_color(operator.__mul__, color, saturation, lightness, red, green, blue, alpha)
+def scale_color(color, red=None, green=None, blue=None, saturation=None, lightness=None, alpha=None):
+ do_rgb = red or green or blue
+ do_hsl = saturation or lightness
+ if do_rgb and do_hsl:
+ raise ValueError("Can't scale both RGB and HSL channels at the same time")
+
+ scaled_alpha = _scale_channel(color.alpha, alpha)
+
+ if do_rgb:
+ channels = [
+ _scale_channel(channel, scaleby)
+ for channel, scaleby in zip(color.rgba, (red, green, blue))
+ ]
+
+ return Color.from_rgb(*channels, alpha=scaled_alpha)
+
+ else:
+ channels = [
+ _scale_channel(channel, scaleby)
+ for channel, scaleby in zip(color.hsl, (None, saturation, lightness))
+ ]
+
+ return Color.from_hsl(*channels, alpha=scaled_alpha)
@register('change-color')
-def change_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
- return _asc_color(None, color, saturation, lightness, red, green, blue, alpha)
+def change_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None):
+ do_rgb = red or green or blue
+ do_hsl = hue or saturation or lightness
+ if do_rgb and do_hsl:
+ raise ValueError("Can't change both RGB and HSL channels at the same time")
+
+ if alpha is None:
+ alpha = color.alpha
+ else:
+ alpha = alpha.value
+
+ if do_rgb:
+ channels = list(color.rgba[:3])
+ if red is not None:
+ channels[0] = _interpret_percentage(red, relto=255.)
+ if green is not None:
+ channels[1] = _interpret_percentage(green, relto=255.)
+ if blue is not None:
+ channels[2] = _interpret_percentage(blue, relto=255.)
+
+ return Color.from_rgb(*channels, alpha=alpha)
+
+ else:
+ channels = list(color.hsl)
+ if hue is not None:
+ expect_type(hue, Number, unit=None)
+ channels[0] = _constrain(hue / 360., 0, 1)
+ # Ruby sass treats plain numbers for saturation and lightness as though
+ # they were percentages, just without the %
+ if saturation is not None:
+ channels[1] = _interpret_percentage(saturation, relto=100.)
+ if lightness is not None:
+ channels[2] = _interpret_percentage(lightness, relto=100.)
+
+ return Color.from_hsl(*channels, alpha=alpha)
# ------------------------------------------------------------------------------