def test_rgba(): # four args (css-style) assert calc('rgba(128, 192, 224, 0.5)') == Color.from_rgb(128/255, 192/255, 224/255, 0.5) assert calc('rgba(20%, 40%, 60%, 0.7)') == Color.from_rgb(0.2, 0.4, 0.6, 0.7) # two args (modify alpha of existing color) assert calc('rgba(red, 0.4)') == Color.from_rgb(1., 0., 0., 0.4)
def test_rgba(calc): # four args (css-style) assert calc('rgba(128, 192, 224, 0.5)') == Color.from_rgb(128/255, 192/255, 224/255, 0.5) assert calc('rgba(20%, 40%, 60%, 0.7)') == Color.from_rgb(0.2, 0.4, 0.6, 0.7) # two args (modify alpha of existing color) assert calc('rgba(red, 0.4)') == Color.from_rgb(1., 0., 0., 0.4)
def test_reference_operations(): """Test the example expressions in the reference document: http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#operations """ # TODO: break this into its own file and add the entire reference guide # Need to build the calculator manually to get at its namespace, and need # to use calculate() instead of evaluate_expression() so interpolation # works ns = CoreExtension.namespace.derive() calc = Calculator(ns).calculate # Simple example assert calc('1in + 8pt') == Number(1.1111111111111112, "in") # Division ns.set_variable('$width', Number(1000, "px")) ns.set_variable('$font-size', Number(12, "px")) ns.set_variable('$line-height', Number(30, "px")) assert calc('10px/8px') == String('10px / 8px') # plain CSS; no division assert calc('$width/2') == Number(500, "px") # uses a variable; does division assert calc('(500px/2)') == Number(250, "px") # uses parens; does division assert calc('5px + 8px/2px') == Number(9, "px") # uses +; does division # TODO, again: Ruby Sass correctly renders this without spaces assert calc('#{$font-size}/#{$line-height}') == String('12px / 30px') # uses #{}; does no division # Color operations ns.set_variable('$translucent-red', Color.from_rgb(1, 0, 0, 0.5)) ns.set_variable('$green', Color.from_name('lime')) assert calc('#010203 + #040506') == Color.from_hex('#050709') assert calc('#010203 * 2') == Color.from_hex('#020406') assert calc( 'rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75)') == Color.from_rgb( 1, 1, 0, 0.75) assert calc('opacify($translucent-red, 0.3)') == Color.from_rgb( 1, 0, 0, 0.8) assert calc('transparentize($translucent-red, 0.25)') == Color.from_rgb( 1, 0, 0, 0.25) assert calc( "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}')" ).render( ) == "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#FF00FF00', endColorstr='#80FF0000')" # String operations ns.set_variable('$value', Null()) assert_strict_string_eq(calc('e + -resize'), String('e-resize', quotes=None)) assert_strict_string_eq(calc('"Foo " + Bar'), String('Foo Bar', quotes='"')) assert_strict_string_eq(calc('sans- + "serif"'), String('sans-serif', quotes=None)) assert calc('3px + 4px auto') == List( [Number(7, "px"), String('auto', quotes=None)]) assert_strict_string_eq(calc('"I ate #{5 + 10} pies!"'), String('I ate 15 pies!', quotes='"')) assert_strict_string_eq(calc('"I ate #{$value} pies!"'), String('I ate pies!', quotes='"'))
def test_hsl(calc): # Examples from the CSS 3 color spec, which Sass uses # (http://www.w3.org/TR/css3-color/#hsl-color) assert calc('hsl(0, 100%, 50%)') == Color.from_rgb(1., 0., 0.) assert calc('hsl(120, 100%, 50%)') == Color.from_rgb(0., 1., 0.) assert calc('hsl(120, 100%, 25%)') == Color.from_rgb(0., 0.5, 0.) assert calc('hsl(120, 100%, 75%)') == Color.from_rgb(0.5, 1., 0.5) assert calc('hsl(120, 75%, 75%)') == Color.from_rgb(0.5625, 0.9375, 0.5625)
def test_hsl(): # Examples from the CSS 3 color spec, which Sass uses # (http://www.w3.org/TR/css3-color/#hsl-color) assert calc('hsl(0, 100%, 50%)') == Color.from_rgb(1., 0., 0.) assert calc('hsl(120, 100%, 50%)') == Color.from_rgb(0., 1., 0.) assert calc('hsl(120, 100%, 25%)') == Color.from_rgb(0., 0.5, 0.) assert calc('hsl(120, 100%, 75%)') == Color.from_rgb(0.5, 1., 0.5) assert calc('hsl(120, 75%, 75%)') == Color.from_rgb(0.5625, 0.9375, 0.5625)
def test_hsla(): # Examples from the CSS 3 color spec assert calc('hsla(120, 100%, 50%, 1)') == Color.from_rgb( 0., 1., 0., ) assert calc('hsla(240, 100%, 50%, 0.5)') == Color.from_rgb(0., 0., 1., 0.5) assert calc('hsla(30, 100%, 50%, 0.1)') == Color.from_rgb(1., 0.5, 0., 0.1)
def test_reference_operations(): """Test the example expressions in the reference document: http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#operations """ # TODO: break this into its own file and add the entire reference guide # Need to build the calculator manually to get at its namespace, and need # to use calculate() instead of evaluate_expression() so interpolation # works ns = CoreExtension.namespace.derive() calc = Calculator(ns).calculate # Simple example assert calc('1in + 8pt') == Number(1.1111111111111112, "in") # Division ns.set_variable('$width', Number(1000, "px")) ns.set_variable('$font-size', Number(12, "px")) ns.set_variable('$line-height', Number(30, "px")) assert calc('10px/8px') == String('10px / 8px') # plain CSS; no division assert calc('$width/2') == Number(500, "px") # uses a variable; does division assert calc('(500px/2)') == Number(250, "px") # uses parens; does division assert calc('5px + 8px/2px') == Number(9, "px") # uses +; does division # TODO, again: Ruby Sass correctly renders this without spaces assert calc('#{$font-size}/#{$line-height}') == String('12px / 30px') # uses #{}; does no division # Modulo assert calc('29 % 12') == Number(5) assert calc('29px % 12') == Number(5, 'px') assert calc('29px % 12px') == Number(5, 'px') # Color operations ns.set_variable('$translucent-red', Color.from_rgb(1, 0, 0, 0.5)) ns.set_variable('$green', Color.from_name('lime')) assert calc('#010203 + #040506') == Color.from_hex('#050709') assert calc('#010203 * 2') == Color.from_hex('#020406') assert calc('rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75)') == Color.from_rgb(1, 1, 0, 0.75) assert calc('opacify($translucent-red, 0.3)') == Color.from_rgb(1, 0, 0, 0.8) assert calc('transparentize($translucent-red, 0.25)') == Color.from_rgb(1, 0, 0, 0.25) assert calc("progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}')" ).render() == "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#FF00FF00', endColorstr='#80FF0000')" # String operations ns.set_variable('$value', Null()) assert_strict_string_eq(calc('e + -resize'), String('e-resize', quotes=None)) assert_strict_string_eq(calc('"Foo " + Bar'), String('Foo Bar', quotes='"')) assert_strict_string_eq(calc('sans- + "serif"'), String('sans-serif', quotes=None)) assert calc('3px + 4px auto') == List([Number(7, "px"), String('auto', quotes=None)]) assert_strict_string_eq(calc('"I ate #{5 + 10} pies!"'), String('I ate 15 pies!', quotes='"')) assert_strict_string_eq(calc('"I ate #{$value} pies!"'), String('I ate pies!', quotes='"'))
def transparentize(color, amount): r, g, b, a = color.rgba if amount.is_simple_unit('%'): amt = amount.value / 100 else: amt = amount.value return Color.from_rgb(r, g, b, alpha=a - amt)
def opacify(color, amount): r, g, b, a = color.rgba if amount.is_simple_unit('%'): amt = amount.value / 100 else: amt = amount.value return Color.from_rgb(r, g, b, alpha=a + amt)
def transparentize(color, amount): r, g, b, a = color.rgba if amount.is_simple_unit("%"): amt = amount.value / 100 else: amt = amount.value return Color.from_rgb(r, g, b, alpha=a - amt)
def opacify(color, amount): r, g, b, a = color.rgba if amount.is_simple_unit("%"): amt = amount.value / 100 else: amt = amount.value return Color.from_rgb(r, g, b, alpha=a + amt)
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: channels[0] = _interpret_percentage(red, relto=255) if green: channels[1] = _interpret_percentage(green, relto=255) if blue: channels[2] = _interpret_percentage(blue, relto=255) return Color.from_rgb(*channels, alpha=alpha) else: channels = list(color.hsl) if hue: expect_type(hue, Number, unit=None) channels[0] = (hue.value / 360) % 1 # Ruby sass treats plain numbers for saturation and lightness as though # they were percentages, just without the % if saturation: channels[1] = _interpret_percentage(saturation, relto=100) if lightness: channels[2] = _interpret_percentage(lightness, relto=100) return Color.from_hsl(*channels, alpha=alpha)
def rgba(r, g, b, a): r = _interpret_percentage(r, relto=255) g = _interpret_percentage(g, relto=255) b = _interpret_percentage(b, relto=255) a = _interpret_percentage(a, relto=1) return Color.from_rgb(r, g, b, a)
def adjust_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 adjust both RGB and HSL channels at the same time") zero = Number(0) a = color.alpha + (alpha or zero).value if do_rgb: r, g, b = color.rgba[:3] channels = [ current + (adjustment or zero).value / 255 for (current, adjustment) in zip(color.rgba, (red, green, blue))] return Color.from_rgb(*channels, alpha=a) else: h, s, l = color.hsl h = (h + (hue or zero).value / 360) % 1 s += _interpret_percentage(saturation or zero, relto=100, clamp=False) l += _interpret_percentage(lightness or zero, relto=100, clamp=False) return Color.from_hsl(h, s, l, a)
def rgba_(color, a=None): if a is None: alpha = 1 else: alpha = _interpret_percentage(a) return Color.from_rgb(*color.rgba[:3], alpha=alpha)
def rgba_(color, a=None): if a is None: alpha = 1 else: alpha = _interpret_percentage(a) return Color.from_rgb(*color.rgba[:3], alpha=alpha)
def adjust_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 adjust both RGB and HSL channels at the same time") zero = Number(0) a = color.alpha + (alpha or zero).value if do_rgb: r, g, b = color.rgba[:3] channels = [ current + (adjustment or zero).value / 255 for (current, adjustment) in zip(color.rgba, (red, green, blue))] return Color.from_rgb(*channels, alpha=a) else: h, s, l = color.hsl h = (h + (hue or zero).value / 360) % 1 s += _interpret_percentage(saturation or zero, relto=100, clamp=False) l += _interpret_percentage(lightness or zero, relto=100, clamp=False) return Color.from_hsl(h, s, l, a)
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)
def rgba(r, g, b, a): r = _interpret_percentage(r, relto=255) g = _interpret_percentage(g, relto=255) b = _interpret_percentage(b, relto=255) a = _interpret_percentage(a, relto=1) return Color.from_rgb(r, g, b, a)
def invert(color): """ Returns the inverse (negative) of a color. The red, green, and blue values are inverted, while the opacity is left alone. """ r, g, b, a = color.rgba return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a)
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: channels[0] = _interpret_percentage(red, relto=255) if green: channels[1] = _interpret_percentage(green, relto=255) if blue: channels[2] = _interpret_percentage(blue, relto=255) return Color.from_rgb(*channels, alpha=alpha) else: channels = list(color.hsl) if hue: expect_type(hue, Number, unit=None) channels[0] = (hue.value / 360) % 1 # Ruby sass treats plain numbers for saturation and lightness as though # they were percentages, just without the % if saturation: channels[1] = _interpret_percentage(saturation, relto=100) if lightness: channels[2] = _interpret_percentage(lightness, relto=100) return Color.from_hsl(*channels, alpha=alpha)
def invert(color): """ Returns the inverse (negative) of a color. The red, green, and blue values are inverted, while the opacity is left alone. """ r, g, b, a = color.rgba return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a)
def test_adjust_hue(): # Examples from the Ruby docs assert calc('adjust-hue(hsl(120, 30%, 90%), 60deg)') == calc( 'hsl(180, 30%, 90%)') assert calc('adjust-hue(hsl(120, 30%, 90%), -60deg)') == calc( 'hsl(60, 30%, 90%)') assert calc('adjust-hue(#811, 45deg)') == Color.from_rgb( 136 / 255, 106.25 / 255, 17 / 255)
def test_functions(calc): calc = Calculator(CoreExtension.namespace).calculate assert calc('grayscale(red)') == Color.from_rgb(0.5, 0.5, 0.5) assert calc('grayscale(1)') == String('grayscale(1)', quotes=None) # Misusing css built-in functions (with scss counterpart) assert calc('skew(1)') == String('skew(1)', quotes=None) # Missing css-only built-in functions with pytest.raises(SassEvaluationError): calc('unitless("X")') # Misusing non-css built-in scss funtions
def test_functions(calc): ns = Namespace(functions=CORE_LIBRARY) calc = Calculator(ns).calculate assert calc('grayscale(red)') == Color.from_rgb(0.5, 0.5, 0.5) assert calc('grayscale(1)') == String('grayscale(1)', quotes=None) # Misusing css built-in functions (with scss counterpart) assert calc('skew(1)') == String('skew(1)', quotes=None) # Missing css-only built-in functions with pytest.raises(TypeError): calc('unitless("X")') # Misusing non-css built-in scss funtions
def test_functions(calc): calc = Calculator(CoreExtension.namespace).calculate assert calc('grayscale(red)') == Color.from_rgb(0.5, 0.5, 0.5) assert calc('grayscale(1)') == String( 'grayscale(1)', quotes=None) # Misusing css built-in functions (with scss counterpart) assert calc('skew(1)') == String( 'skew(1)', quotes=None) # Missing css-only built-in functions with pytest.raises(SassEvaluationError): calc('unitless("X")') # Misusing non-css built-in scss funtions
def invert(color): """Returns the inverse (negative) of a color. The red, green, and blue values are inverted, while the opacity is left alone. """ if isinstance(color, Number): # invert(n) and invert(n%) are CSS3 filters and should be left # intact return String.unquoted("invert(%s)" % (color.render(),)) expect_type(color, Color) r, g, b, a = color.rgba return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a)
def invert(color): """Returns the inverse (negative) of a color. The red, green, and blue values are inverted, while the opacity is left alone. """ if isinstance(color, Number): # invert(n) and invert(n%) are CSS3 filters and should be left # intact return String.unquoted("invert(%s)" % (color.render(), )) expect_type(color, Color) r, g, b, a = color.rgba return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a)
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)
def test_grayscale(): assert calc('grayscale(black)') == Color.from_rgb(0., 0., 0.) assert calc('grayscale(white)') == Color.from_rgb(1., 1., 1.) assert calc('grayscale(yellow)') == Color.from_rgb(0.5, 0.5, 0.5)
def test_saturate(): # Examples from the Ruby docs assert calc('saturate(hsl(120, 30%, 90%), 20%)') == calc( 'hsl(120, 50%, 90%)') assert calc('saturate(#855, 20%)') == Color.from_rgb( 158.1 / 255, 62.9 / 255, 62.9 / 255)
def test_desaturate(): # Examples from the Ruby docs assert calc('desaturate(hsl(120, 30%, 90%), 20%)') == calc( 'hsl(120, 10%, 90%)') assert calc('desaturate(#855, 20%)') == Color.from_rgb( 113.9 / 255, 107.1 / 255, 107.1 / 255)
def test_adjust_hue(calc): # Examples from the Ruby docs assert calc('adjust-hue(hsl(120, 30%, 90%), 60deg)') == calc('hsl(180, 30%, 90%)') assert calc('adjust-hue(hsl(120, 30%, 90%), -60deg)') == calc('hsl(60, 30%, 90%)') assert calc('adjust-hue(#811, 45deg)') == Color.from_rgb(136/255, 106.25/255, 17/255)
def opacify(color, amount): r, g, b, a = color.rgba return Color.from_rgb(r, g, b, alpha=color.alpha + amount.value)
def mix(color1, color2, weight=Number(50, "%")): """ Mixes together two colors. Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage. The opacity of the colors is also considered when weighting the components. Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage. The opacity of the colors is also considered when weighting the components. The weight specifies the amount of the first color that should be included in the returned color. 50%, means that half the first color and half the second color should be used. 25% means that a quarter of the first color and three quarters of the second color should be used. For example: mix(#f00, #00f) => #7f007f mix(#f00, #00f, 25%) => #3f00bf mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) """ # This algorithm factors in both the user-provided weight # 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. # # Algorithm from the Sass project: http://sass-lang.com/ p = _interpret_percentage(weight) # Scale weight to [-1, 1] w = p * 2 - 1 # Compute difference in alpha channels a = color1.alpha - color2.alpha # Weight of first color if w * a == -1: # Avoid zero-div case scaled_weight1 = w else: scaled_weight1 = (w + a) / (1 + w * a) # Unscale back to [0, 1] and get the weight of the other color w1 = (scaled_weight1 + 1) / 2 w2 = 1 - w1 # Do the scaling. Note that alpha isn't scaled by alpha, as that wouldn't # make much sense; it uses the original untwiddled weight, p. channels = [ ch1 * w1 + ch2 * w2 for (ch1, ch2) in zip(color1.rgba[:3], color2.rgba[:3])] alpha = color1.alpha * p + color2.alpha * (1 - p) return Color.from_rgb(*channels, alpha=alpha)
def test_hsla(calc): # Examples from the CSS 3 color spec assert calc('hsla(120, 100%, 50%, 1)') == Color.from_rgb(0., 1., 0.,) assert calc('hsla(240, 100%, 50%, 0.5)') == Color.from_rgb(0., 0., 1., 0.5) assert calc('hsla(30, 100%, 50%, 0.1)') == Color.from_rgb(1., 0.5, 0., 0.1)
def test_rgb(calc): assert calc('rgb(128, 192, 224)') == Color.from_rgb(128/255, 192/255, 224/255) assert calc('rgb(20%, 40%, 60%)') == Color.from_rgb(0.2, 0.4, 0.6)
def test_rgb(): assert calc('rgb(128, 192, 224)') == Color.from_rgb( 128 / 255, 192 / 255, 224 / 255) assert calc('rgb(20%, 40%, 60%)') == Color.from_rgb(0.2, 0.4, 0.6)
def test_complement(calc): assert calc('complement(black)') == Color.from_rgb(0., 0., 0.) assert calc('complement(white)') == Color.from_rgb(1., 1., 1.) assert calc('complement(yellow)') == Color.from_rgb(0., 0., 1.)
def test_invert(calc): assert calc('invert(black)') == Color.from_rgb(1., 1., 1.) assert calc('invert(white)') == Color.from_rgb(0., 0., 0.) assert calc('invert(yellow)') == Color.from_rgb(0., 0., 1.)
def test_grayscale(calc): assert calc('grayscale(black)') == Color.from_rgb(0., 0., 0.) assert calc('grayscale(white)') == Color.from_rgb(1., 1., 1.) assert calc('grayscale(yellow)') == Color.from_rgb(0.5, 0.5, 0.5)
def test_desaturate(calc): # Examples from the Ruby docs assert calc('desaturate(hsl(120, 30%, 90%), 20%)') == calc('hsl(120, 10%, 90%)') assert calc('desaturate(#855, 20%)') == Color.from_rgb(113.9/255, 107.1/255, 107.1/255)
def transparentize(color, amount): r, g, b, a = color.rgba return Color.from_rgb(r, g, b, alpha=color.alpha - amount.value)
def test_complement(): assert calc('complement(black)') == Color.from_rgb(0., 0., 0.) assert calc('complement(white)') == Color.from_rgb(1., 1., 1.) assert calc('complement(yellow)') == Color.from_rgb(0., 0., 1.)
def opacify(color, amount): r, g, b, a = color.rgba return Color.from_rgb( r, g, b, alpha=color.alpha + amount.value)
def test_invert(): assert calc('invert(black)') == Color.from_rgb(1., 1., 1.) assert calc('invert(white)') == Color.from_rgb(0., 0., 0.) assert calc('invert(yellow)') == Color.from_rgb(0., 0., 1.)
def mix(color1, color2, weight=Number(50, "%")): """ Mixes together two colors. Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage. The opacity of the colors is also considered when weighting the components. Specifically, takes the average of each of the RGB components, optionally weighted by the given percentage. The opacity of the colors is also considered when weighting the components. The weight specifies the amount of the first color that should be included in the returned color. 50%, means that half the first color and half the second color should be used. 25% means that a quarter of the first color and three quarters of the second color should be used. For example: mix(#f00, #00f) => #7f007f mix(#f00, #00f, 25%) => #3f00bf mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) """ # This algorithm factors in both the user-provided weight # 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. # # Algorithm from the Sass project: http://sass-lang.com/ p = _interpret_percentage(weight) # Scale weight to [-1, 1] w = p * 2 - 1 # Compute difference in alpha channels a = color1.alpha - color2.alpha # Weight of first color if w * a == -1: # Avoid zero-div case scaled_weight1 = w else: scaled_weight1 = (w + a) / (1 + w * a) # Unscale back to [0, 1] and get the weight of the other color w1 = (scaled_weight1 + 1) / 2 w2 = 1 - w1 # Do the scaling. Note that alpha isn't scaled by alpha, as that wouldn't # make much sense; it uses the original untwiddled weight, p. channels = [ ch1 * w1 + ch2 * w2 for (ch1, ch2) in zip(color1.rgba[:3], color2.rgba[:3])] alpha = color1.alpha * p + color2.alpha * (1 - p) return Color.from_rgb(*channels, alpha=alpha)
def test_image_color(): libextra.image_color(Color.from_rgb(1, 1, 0)) assert True
def transparentize(color, amount): r, g, b, a = color.rgba return Color.from_rgb( r, g, b, alpha=color.alpha - amount.value)
def test_saturate(calc): # Examples from the Ruby docs assert calc('saturate(hsl(120, 30%, 90%), 20%)') == calc('hsl(120, 50%, 90%)') assert calc('saturate(#855, 20%)') == Color.from_rgb(158.1/255, 62.9/255, 62.9/255)
def test_image_color(): libextra.image_color(Color.from_rgb(1, 1, 0)) assert True