Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
def test_subtraction():
    assert Number(123) - Number(456) == Number(-333)
    assert Number(456) - Number(123) == Number(333)
    # TODO test that subtracting e.g. strings doesn't work

    assert Color.from_hex('#0f0f0f') - Color.from_hex(
        '#050505') == Color.from_hex('#0a0a0a')
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
def background_noise(density=None,
                     opacity=None,
                     size=None,
                     monochrome=False,
                     intensity=(),
                     color=None,
                     background=None,
                     inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else 0
    if size < 1 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    background = Color(background).value if background else None

    new_image = Image.new(mode='RGBA', size=(size, size))

    pixdata = new_image.load()
    _image_noise(pixdata, size, density, intensity, color, opacity, monochrome)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome)
        asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size,
                                        size)
        # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []))
        asset_file += '-' + base64.urlsafe_b64encode(
            hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file += '.png'
        asset_path = os.path.join(
            config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'),
            asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = 'data:image/png;base64,' + base64.b64encode(contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Ejemplo n.º 13
0
def customise_css():
    colour = colours.get_from_qs(flask.request.args)
    namespace = Namespace()
    namespace.set_variable('$fg', Color.from_hex(colour.fg))
    namespace.set_variable('$bg', Color.from_hex(colour.bg))
    namespace.set_variable('$highFg', Color.from_hex(colour.highFg))
    namespace.set_variable('$highBg', Color.from_hex(colour.highBg))

    compiler = Compiler(namespace=namespace)
    res = make_response(compiler.compile('style/index.scss'))
    res.mimetype = 'text/css'
    return res
Ejemplo n.º 14
0
def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False):
    if not Image:
        raise Exception("Images manipulation require PIL")

    density = [Number(v).value for v in List.from_maybe(density)]
    intensity = [Number(v).value for v in List.from_maybe(intensity)]
    color = [Color(v).value for v in List.from_maybe(color) if v]
    opacity = [Number(v).value for v in List.from_maybe(opacity)]

    size = int(Number(size).value) if size else -1
    if size < 0 or size > 512:
        size = 200

    monochrome = bool(monochrome)

    direction = [Number(v).value for v in List.from_maybe(direction)]
    spread = [Number(v).value for v in List.from_maybe(spread)]

    background = Color(background).value if background else None

    new_image = Image.new(
        mode='RGBA',
        size=(size, size)
    )

    pixdata = new_image.load()
    _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background)

    if not inline:
        key = (size, density, intensity, color, opacity, monochrome, direction, spread, background)
        asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size)
        # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or []))
        asset_file += '-' + make_filename_hash(key)
        asset_file += '.png'
        asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file)
        try:
            new_image.save(asset_path)
        except IOError:
            log.exception("Error while saving image")
            inline = True  # Retry inline version
        url = '%s%s' % (config.ASSETS_URL, asset_file)
    if inline:
        output = six.BytesIO()
        new_image.save(output, format='PNG')
        contents = output.getvalue()
        output.close()
        url = make_data_url('image/png', contents)

    inline = 'url("%s")' % escape(url)
    return String.unquoted(inline)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
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)
Ejemplo n.º 17
0
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)
Ejemplo n.º 18
0
    def style_scss(self, *path):
        css_namespace = Namespace()
        for key, value in self.settings['keys'].items():
            if isinstance(value, LCText):
                css_value = String(value)
            elif isinstance(value, LCColour):
                css_value = Color.from_hex(value)
            elif isinstance(value, LCBool):
                css_value = Boolean(value.simple())
            elif isinstance(value, LCSpin):
                css_value = Number(value.simple())
            else:
                raise ValueError("Unable to find comparable values")
            css_namespace.set_variable('${}'.format(key), css_value)

        cherrypy.response.headers['Content-Type'] = 'text/css'
        with open(os.path.join(self.settings['location'], *path), 'r') as css:
            css_content = css.read()
            compiler = Compiler(namespace=css_namespace, output_style='nested')
            # Something wrong with PyScss,
            #  Syntax error: Found u'100%' but expected one of ADD.
            # Doesn't happen on next attempt, so we are doing bad thing
            attempts = 0
            while attempts < 100:
                try:
                    attempts += 1
                    ret_string = compiler.compile_string(css_content)
                    return ret_string
                except Exception as exc:
                    if attempts == 100:
                        log.debug(exc)
Ejemplo n.º 19
0
 def kwatom(self):
     _token_ = self._peek(self.kwatom_rsts)
     if _token_ == '":"':
         pass
     elif _token_ == 'KWID':
         KWID = self._scan('KWID')
         return Literal(parse_bareword(KWID))
     elif _token_ == 'KWNUM':
         KWNUM = self._scan('KWNUM')
         UNITS = None
         if self._peek(self.kwatom_rsts_) == 'UNITS':
             UNITS = self._scan('UNITS')
         return Literal(Number(float(KWNUM), unit=UNITS))
     elif _token_ == 'KWSTR':
         KWSTR = self._scan('KWSTR')
         return Literal(String(KWSTR[1:-1], quotes="'"))
     elif _token_ == 'KWQSTR':
         KWQSTR = self._scan('KWQSTR')
         return Literal(String(KWQSTR[1:-1], quotes='"'))
     elif _token_ == 'KWCOLOR':
         KWCOLOR = self._scan('KWCOLOR')
         return Literal(Color.from_hex(COLOR, literal=True))
     else:  # == 'KWVAR'
         KWVAR = self._scan('KWVAR')
         return Variable(KWVAR)
Ejemplo n.º 20
0
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)
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
 def kwatom(self):
     _token_ = self._peek(self.kwatom_rsts)
     if _token_ == '":"':
         pass
     elif _token_ == 'KWID':
         KWID = self._scan('KWID')
         return Literal(parse_bareword(KWID))
     elif _token_ == 'KWNUM':
         KWNUM = self._scan('KWNUM')
         UNITS = None
         if self._peek(self.kwatom_rsts_) == 'UNITS':
             UNITS = self._scan('UNITS')
         return Literal(Number(float(KWNUM), unit=UNITS))
     elif _token_ == 'KWSTR':
         KWSTR = self._scan('KWSTR')
         return Literal(String(dequote(KWSTR), quotes="'"))
     elif _token_ == 'KWQSTR':
         KWQSTR = self._scan('KWQSTR')
         return Literal(String(dequote(KWQSTR), quotes='"'))
     elif _token_ == 'KWCOLOR':
         KWCOLOR = self._scan('KWCOLOR')
         return Literal(Color.from_hex(KWCOLOR, literal=True))
     else:  # == 'KWVAR'
         KWVAR = self._scan('KWVAR')
         return Variable(KWVAR)
Ejemplo n.º 23
0
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)
Ejemplo n.º 24
0
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)
Ejemplo n.º 25
0
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)
Ejemplo n.º 26
0
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)
Ejemplo n.º 27
0
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)
Ejemplo n.º 28
0
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)
Ejemplo n.º 29
0
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)
Ejemplo n.º 30
0
def ie_hex_str(color):
    c = Color(color).value
    return String("#{3:02X}{0:02X}{1:02X}{2:02X}".format(
        int(round(c[0])),
        int(round(c[1])),
        int(round(c[2])),
        int(round(c[3] * 255)),
    ))
Ejemplo n.º 31
0
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
Ejemplo n.º 32
0
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='"'))
Ejemplo n.º 33
0
def hsla(h, s, l, a):
    return Color.from_hsl(
        h.value / 360 % 1,
        # Ruby sass treats plain numbers for saturation and lightness as though
        # they were percentages, just without the %
        _interpret_percentage(s, relto=100),
        _interpret_percentage(l, relto=100),
        alpha=a.value,
    )
Ejemplo n.º 34
0
def test_linear_gradient():
    # Set up some values
    to = String.unquoted('to')
    bottom = String.unquoted('bottom')
    left = String.unquoted('left')

    red = Color.from_name('red')
    blue = Color.from_name('blue')

    start = Number(0, "%")
    middle = Number(50, "%")
    end = Number(100, "%")

    assert (linear_gradient(left, List((red, start)), List(
        (blue, middle))) == String('linear-gradient(left, red, blue 50%)'))

    assert (linear_gradient(List((to, bottom)), blue, List(
        (red, end))) == String('linear-gradient(to bottom, blue, red)'))
Ejemplo n.º 35
0
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
Ejemplo n.º 36
0
def hsla(h, s, l, a):
    return Color.from_hsl(
        h.value / 360 % 1,
        # Ruby sass treats plain numbers for saturation and lightness as though
        # they were percentages, just without the %
        _interpret_percentage(s, relto=100),
        _interpret_percentage(l, relto=100),
        alpha=a.value,
    )
Ejemplo n.º 37
0
def test_addition(calc):
    assert calc('123 + 456') == Number(579)

    assert calc('1px + 2px') == Number(3, "px")

    assert calc('123 + abc') == String('123abc')
    assert calc('abc + 123') == String('abc123')

    assert calc('abc + def') == String('abcdef')
    assert calc('abc + "def"') == String('abcdef')
    ret = calc('"abc" + def')
    assert ret == String('abcdef')
    assert ret.quotes == '"'
    ret = calc('"abc" + "def"')
    assert ret == String('abcdef')
    assert ret.quotes == '"'

    assert calc('#010305 + #050301') == Color.from_hex('#060606')
    assert calc('#ffffff + #ffffff') == Color.from_name('white')
Ejemplo n.º 38
0
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
Ejemplo n.º 39
0
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)
Ejemplo n.º 40
0
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)
Ejemplo n.º 41
0
def test_addition():
    # Numbers are a little complicated, what with all the units
    # Simple case
    assert Number(123) + Number(456) == Number(579)
    # Simple equal units
    assert Number(1, "px") + Number(2, "px") == Number(3, "px")
    # Unitless values inherit units of the other operand
    assert Number(5) + Number(6, "px") == Number(11, "px")
    # Zero values can cast to any units
    assert Number(0, "in") + Number(24, "deg") == Number(24, "deg")
    # With different units, the left operand wins
    assert Number(10, "cm") + Number(100, "mm") == Number(20, "cm")
    assert Number(100, "mm") + Number(10, "cm") == Number(200, "mm")
    # Unconvertible units raise an error
    with pytest.raises(ValueError):
        Number(1, "px") + Number(1, "em")

    # Adding anything to a string makes a string
    assert Number(123) + String('abc') == String('123abc')
    assert String('abc') + Number(123) == String('abc123')

    ret = String('abc', quotes=None) + String('def', quotes=None)
    assert ret == String('abcdef')
    assert ret.quotes is None

    ret = String('abc', quotes='"') + String('def', quotes=None)
    assert ret == String('abcdef')
    assert ret.quotes is '"'

    ret = String('abc', quotes=None) + String('def', quotes='"')
    assert ret == String('abcdef')
    assert ret.quotes is None

    assert Color.from_hex('#010305') + Color.from_hex('#050301') == Color.from_hex('#060606')
    assert Color.from_name('white') + Color.from_name('white') == Color.from_name('white')
Ejemplo n.º 42
0
def parse_bareword(word):
    if word in COLOR_NAMES:
        return Color.from_name(word)
    elif word == 'null':
        return Null()
    elif word == 'undefined':
        return Undefined()
    elif word == 'true':
        return Boolean(True)
    elif word == 'false':
        return Boolean(False)
    else:
        return String(word, quotes=None)
Ejemplo n.º 43
0
def parse_bareword(word):
    if word in COLOR_NAMES:
        return Color.from_name(word)
    elif word == 'null':
        return Null()
    elif word == 'undefined':
        return Undefined()
    elif word == 'true':
        return Boolean(True)
    elif word == 'false':
        return Boolean(False)
    else:
        return String(word, quotes=None)
Ejemplo n.º 44
0
def test_linear_gradient():
    # Set up some values
    to = String.unquoted('to')
    bottom = String.unquoted('bottom')
    left = String.unquoted('left')

    red = Color.from_name('red')
    blue = Color.from_name('blue')

    start = Number(0, "%")
    middle = Number(50, "%")
    end = Number(100, "%")

    assert (
        linear_gradient(left, List((red, start)), List((blue, middle)))
        == String('linear-gradient(left, red, blue 50%)')
    )

    assert (
        linear_gradient(List((to, bottom)), blue, List((red, end)))
        == String('linear-gradient(to bottom, blue, red)')
    )
Ejemplo n.º 45
0
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='"'))
Ejemplo n.º 46
0
    def from_bareword(cls, word):
        if word in COLOR_NAMES:
            value = Color.from_name(word)
        elif word == 'null':
            value = Null()
        elif word == 'undefined':
            value = Undefined()
        elif word == 'true':
            value = Boolean(True)
        elif word == 'false':
            value = Boolean(False)
        else:
            value = String(word, quotes=None)

        return cls(value)
Ejemplo n.º 47
0
    def from_bareword(cls, word):
        if word in COLOR_NAMES:
            value = Color.from_name(word)
        elif word == 'null':
            value = Null()
        elif word == 'undefined':
            value = Undefined()
        elif word == 'true':
            value = Boolean(True)
        elif word == 'false':
            value = Boolean(False)
        else:
            value = String(word, quotes=None)

        return cls(value)
Ejemplo n.º 48
0
 def atom(self):
     _token_ = self._peek(self.u_expr_chks)
     if _token_ == 'LPAR':
         LPAR = self._scan('LPAR')
         _token_ = self._peek(self.atom_rsts)
         if _token_ == 'RPAR':
             v = ListLiteral([], comma=False)
         elif _token_ not in self.argspec_item_chks:
             expr_map = self.expr_map()
             v = expr_map
         else:  # in self.argspec_item_chks
             expr_lst = self.expr_lst()
             v = expr_lst
         RPAR = self._scan('RPAR')
         return Parentheses(v)
     elif _token_ == 'FNCT':
         FNCT = self._scan('FNCT')
         LPAR = self._scan('LPAR')
         argspec = self.argspec()
         RPAR = self._scan('RPAR')
         return CallOp(FNCT, argspec)
     elif _token_ == 'BANG_IMPORTANT':
         BANG_IMPORTANT = self._scan('BANG_IMPORTANT')
         return Literal(String(BANG_IMPORTANT, quotes=None))
     elif _token_ == 'ID':
         ID = self._scan('ID')
         return Literal(parse_bareword(ID))
     elif _token_ == 'NUM':
         NUM = self._scan('NUM')
         UNITS = None
         if self._peek(self.atom_rsts_) == 'UNITS':
             UNITS = self._scan('UNITS')
         return Literal(Number(float(NUM), unit=UNITS))
     elif _token_ == 'STR':
         STR = self._scan('STR')
         return Literal(String(STR[1:-1], quotes="'"))
     elif _token_ == 'QSTR':
         QSTR = self._scan('QSTR')
         return Literal(String(QSTR[1:-1], quotes='"'))
     elif _token_ == 'COLOR':
         COLOR = self._scan('COLOR')
         return Literal(Color.from_hex(COLOR, literal=True))
     else:  # == 'VAR'
         VAR = self._scan('VAR')
         return Variable(VAR)
Ejemplo n.º 49
0
 def atom(self):
     _token_ = self._peek(self.u_expr_chks)
     if _token_ == 'LPAR':
         LPAR = self._scan('LPAR')
         _token_ = self._peek(self.atom_rsts)
         if _token_ not in self.argspec_item_chks:
             expr_map = self.expr_map()
             v = expr_map
         else:  # in self.argspec_item_chks
             expr_lst = self.expr_lst()
             v = expr_lst
         RPAR = self._scan('RPAR')
         return Parentheses(v)
     elif _token_ == 'FNCT':
         FNCT = self._scan('FNCT')
         LPAR = self._scan('LPAR')
         argspec = self.argspec()
         RPAR = self._scan('RPAR')
         return CallOp(FNCT, argspec)
     elif _token_ == 'BANG_IMPORTANT':
         BANG_IMPORTANT = self._scan('BANG_IMPORTANT')
         return Literal(String(BANG_IMPORTANT, quotes=None))
     elif _token_ == 'ID':
         ID = self._scan('ID')
         return Literal(parse_bareword(ID))
     elif _token_ == 'NUM':
         NUM = self._scan('NUM')
         UNITS = None
         if self._peek(self.atom_rsts_) == 'UNITS':
             UNITS = self._scan('UNITS')
         return Literal(Number(float(NUM), unit=UNITS))
     elif _token_ == 'STR':
         STR = self._scan('STR')
         return Literal(String(STR[1:-1], quotes="'"))
     elif _token_ == 'QSTR':
         QSTR = self._scan('QSTR')
         return Literal(String(QSTR[1:-1], quotes='"'))
     elif _token_ == 'COLOR':
         COLOR = self._scan('COLOR')
         return Literal(Color.from_hex(COLOR, literal=True))
     else:  # == 'VAR'
         VAR = self._scan('VAR')
         return Variable(VAR)
Ejemplo n.º 50
0
def test_addition():
    # Numbers are a little complicated, what with all the units
    # Simple case
    assert Number(123) + Number(456) == Number(579)
    # Simple equal units
    assert Number(1, "px") + Number(2, "px") == Number(3, "px")
    # Unitless values inherit units of the other operand
    assert Number(5) + Number(6, "px") == Number(11, "px")
    # Zero values can cast to any units
    assert Number(0, "in") + Number(24, "deg") == Number(24, "deg")
    # With different units, the left operand wins
    assert Number(10, "cm") + Number(100, "mm") == Number(20, "cm")
    assert Number(100, "mm") + Number(10, "cm") == Number(200, "mm")
    # Unconvertible units raise an error
    with pytest.raises(ValueError):
        Number(1, "px") + Number(1, "em")

    # Adding anything to a string makes a string
    assert Number(123) + String('abc') == String('123abc')
    assert String('abc') + Number(123) == String('abc123')

    ret = String('abc', quotes=None) + String('def', quotes=None)
    assert ret == String('abcdef')
    assert ret.quotes is None

    ret = String('abc', quotes='"') + String('def', quotes=None)
    assert ret == String('abcdef')
    assert ret.quotes == '"'

    ret = String('abc', quotes=None) + String('def', quotes='"')
    assert ret == String('abcdef')
    assert ret.quotes is None

    assert Color.from_hex('#010305') + Color.from_hex(
        '#050301') == Color.from_hex('#060606')
    assert Color.from_name('white') + Color.from_name(
        'white') == Color.from_name('white')
Ejemplo n.º 51
0
 def atom(self):
     _token_ = self._peek(self.u_expr_chks)
     if _token_ == 'LPAR':
         LPAR = self._scan('LPAR')
         _token_ = self._peek(self.atom_rsts)
         if _token_ == 'RPAR':
             v = ListLiteral([], comma=False)
         else:  # in self.argspec_item_chks
             expr_map_or_list = self.expr_map_or_list()
             v = expr_map_or_list
         RPAR = self._scan('RPAR')
         return Parentheses(v)
     elif _token_ == 'URL_FUNCTION':
         URL_FUNCTION = self._scan('URL_FUNCTION')
         LPAR = self._scan('LPAR')
         interpolated_url = self.interpolated_url()
         RPAR = self._scan('RPAR')
         return interpolated_url
     elif _token_ == 'ALPHA_FUNCTION':
         ALPHA_FUNCTION = self._scan('ALPHA_FUNCTION')
         LPAR = self._scan('LPAR')
         _token_ = self._peek(self.atom_rsts_)
         if _token_ == 'OPACITY':
             OPACITY = self._scan('OPACITY')
             self._scan('"="')
             atom = self.atom()
             RPAR = self._scan('RPAR')
             return AlphaFunctionLiteral(atom)
         else:  # in self.atom_chks
             argspec = self.argspec()
             RPAR = self._scan('RPAR')
             return CallOp("alpha", argspec)
     elif _token_ == 'IF_FUNCTION':
         IF_FUNCTION = self._scan('IF_FUNCTION')
         LPAR = self._scan('LPAR')
         expr_lst = self.expr_lst()
         RPAR = self._scan('RPAR')
         return TernaryOp(expr_lst)
     elif _token_ == 'LITERAL_FUNCTION':
         LITERAL_FUNCTION = self._scan('LITERAL_FUNCTION')
         LPAR = self._scan('LPAR')
         interpolated_function = self.interpolated_function()
         RPAR = self._scan('RPAR')
         return Interpolation.maybe(interpolated_function, type=Function, function_name=LITERAL_FUNCTION)
     elif _token_ == 'FNCT':
         FNCT = self._scan('FNCT')
         LPAR = self._scan('LPAR')
         argspec = self.argspec()
         RPAR = self._scan('RPAR')
         return CallOp(FNCT, argspec)
     elif _token_ == 'BANG_IMPORTANT':
         BANG_IMPORTANT = self._scan('BANG_IMPORTANT')
         return Literal(String.unquoted("!important", literal=True))
     elif _token_ in self.atom_chks_:
         interpolated_bareword = self.interpolated_bareword()
         return Interpolation.maybe(interpolated_bareword)
     elif _token_ == 'NUM':
         NUM = self._scan('NUM')
         UNITS = None
         if self._peek(self.atom_rsts__) == 'UNITS':
             UNITS = self._scan('UNITS')
         return Literal(Number(float(NUM), unit=UNITS))
     elif _token_ not in self.atom_chks__:
         interpolated_string = self.interpolated_string()
         return interpolated_string
     elif _token_ == 'COLOR':
         COLOR = self._scan('COLOR')
         return Literal(Color.from_hex(COLOR, literal=True))
     else:  # == 'VAR'
         VAR = self._scan('VAR')
         return Variable(VAR)
Ejemplo n.º 52
0
def sprite_map(g, **kwargs):
    """
    Generates a sprite map from the files matching the glob pattern.
    Uses the keyword-style arguments passed in to control the placement.

    $direction - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`.

    $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
    $<sprite>-position - Position of a given sprite.

    $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`)
    $<sprite>-padding, $<sprite>-spacing - Padding for a given sprite.

    $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`).
    $<sprite>-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`)

    $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`)
    $<sprite>-dst-color - Source colors for a given sprite. (defaults to `$src-color`)

    $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`).
    $collapse-x  - Collapses a size for `x`.
    $collapse-y  - Collapses a size for `y`.
    """
    if not Image:
        raise SassMissingDependency('PIL', 'image manipulation')

    sprite_maps = _get_cache('sprite_maps')

    now_time = time.time()

    globs = String(g, quotes=None).value
    globs = sorted(g.strip() for g in globs.split(','))

    _k_ = ','.join(globs)

    files = None
    rfiles = None
    tfiles = None
    map_name = None

    if _k_ in sprite_maps:
        sprite_maps[_k_]['*'] = now_time
    else:
        files = []
        rfiles = []
        tfiles = []
        for _glob in globs:
            if '..' not in _glob:  # Protect against going to prohibited places...
                if callable(config.STATIC_ROOT):
                    _glob_path = _glob
                    _rfiles = _files = sorted(config.STATIC_ROOT(_glob))
                else:
                    _glob_path = os.path.join(config.STATIC_ROOT, _glob)
                    _files = glob.glob(_glob_path)
                    _files = sorted((f, None) for f in _files)
                    _rfiles = [(rf[len(config.STATIC_ROOT):], s)
                               for rf, s in _files]
                if _files:
                    files.extend(_files)
                    rfiles.extend(_rfiles)
                    base_name = os.path.normpath(
                        os.path.dirname(_glob)).replace('\\',
                                                        '_').replace('/', '_')
                    _map_name, _, _map_type = base_name.partition('.')
                    if _map_type:
                        _map_type += '-'
                    if not map_name:
                        map_name = _map_name
                    tfiles.extend([_map_type] * len(_files))
                else:
                    glob_path = _glob_path

    if files is not None:
        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = map_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        ASSETS_ROOT = _assets_root()
        asset_path = os.path.join(ASSETS_ROOT, asset_file)
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT,
                                  asset_file + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        sprite_map = None
        asset = None
        file_asset = None
        inline_asset = None
        if os.path.exists(asset_path) or inline:
            try:
                save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(
                    open(cache_path))
                if file_asset:
                    sprite_maps[file_asset.render()] = sprite_map
                if inline_asset:
                    sprite_maps[inline_asset.render()] = sprite_map
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if sprite_map:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning(
                                "File '%s' has a date in the future (cache ignored)"
                                % file_)
                        sprite_map = None  # Invalidate cached sprite map
                        break

        if sprite_map is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            direction = String.unquoted(
                kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value
            repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value
            collapse = kwargs.get('collapse', Number(0))
            if isinstance(collapse, List):
                collapse_x = int(Number(collapse[0]).value)
                collapse_y = int(Number(collapse[-1]).value)
            else:
                collapse_x = collapse_y = int(Number(collapse).value)
            if 'collapse_x' in kwargs:
                collapse_x = int(Number(kwargs['collapse_x']).value)
            if 'collapse_y' in kwargs:
                collapse_y = int(Number(kwargs['collapse_y']).value)

            position = Number(kwargs.get('position', 0))
            if not position.is_simple_unit('%') and position.value > 1:
                position = position.value / 100.0
            else:
                position = position.value
            if position < 0:
                position = 0.0
            elif position > 1:
                position = 1.0

            padding = kwargs.get('padding', kwargs.get('spacing', Number(0)))
            padding = [int(Number(v).value) for v in List.from_maybe(padding)]
            padding = (padding * 4)[:4]

            dst_colors = kwargs.get('dst_color')
            dst_colors = [
                list(Color(v).value[:3]) for v in List.from_maybe(dst_colors)
                if v
            ]
            src_colors = kwargs.get('src_color', Color.from_name('black'))
            src_colors = [
                tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)
            ]
            len_colors = max(len(dst_colors), len(src_colors))
            dst_colors = (dst_colors * len_colors)[:len_colors]
            src_colors = (src_colors * len_colors)[:len_colors]

            def images(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = file_
                    _image = Image.open(_file)
                    yield _image

            names = tuple(
                os.path.splitext(os.path.basename(file_))[0]
                for file_, storage in files)
            tnames = tuple(tfiles[i] + n for i, n in enumerate(names))

            has_dst_colors = False
            all_dst_colors = []
            all_src_colors = []
            all_positions = []
            all_paddings = []

            for name in names:
                name = name.replace('-', '_')

                _position = kwargs.get(name + '_position')
                if _position is None:
                    _position = position
                else:
                    _position = Number(_position)
                    if not _position.is_simple_unit(
                            '%') and _position.value > 1:
                        _position = _position.value / 100.0
                    else:
                        _position = _position.value
                    if _position < 0:
                        _position = 0.0
                    elif _position > 1:
                        _position = 1.0
                all_positions.append(_position)

                _padding = kwargs.get(name + '_padding',
                                      kwargs.get(name + '_spacing'))
                if _padding is None:
                    _padding = padding
                else:
                    _padding = [
                        int(Number(v).value) for v in List.from_maybe(_padding)
                    ]
                    _padding = (_padding * 4)[:4]
                all_paddings.append(_padding)

                _dst_colors = kwargs.get(name + '_dst_color')
                if _dst_colors is None:
                    _dst_colors = dst_colors
                    if dst_colors:
                        has_dst_colors = True
                else:
                    has_dst_colors = True
                    _dst_colors = [
                        list(Color(v).value[:3])
                        for v in List.from_maybe(_dst_colors) if v
                    ]
                _src_colors = kwargs.get(name + '_src_color',
                                         Color.from_name('black'))
                if _src_colors is None:
                    _src_colors = src_colors
                else:
                    _src_colors = [
                        tuple(Color(v).value[:3])
                        for v in List.from_maybe(_src_colors)
                    ]
                _len_colors = max(len(_dst_colors), len(_src_colors))
                _dst_colors = (_dst_colors * _len_colors)[:_len_colors]
                _src_colors = (_src_colors * _len_colors)[:_len_colors]
                all_dst_colors.append(_dst_colors)
                all_src_colors.append(_src_colors)

            sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1])
                          for i in images())

            if direction == 'horizontal':
                layout = HorizontalSpritesLayout(sizes,
                                                 all_paddings,
                                                 position=all_positions)
            elif direction == 'vertical':
                layout = VerticalSpritesLayout(sizes,
                                               all_paddings,
                                               position=all_positions)
            elif direction == 'diagonal':
                layout = DiagonalSpritesLayout(sizes, all_paddings)
            elif direction == 'smart':
                layout = PackedSpritesLayout(sizes, all_paddings)
            else:
                raise Exception("Invalid direction %r" % (direction, ))
            layout_positions = list(layout)

            new_image = Image.new(mode='RGBA',
                                  size=(layout.width, layout.height),
                                  color=(0, 0, 0, 0))

            useless_dst_color = has_dst_colors

            offsets_x = []
            offsets_y = []
            for i, image in enumerate(images()):
                x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[
                    i]
                iwidth, iheight = image.size

                if has_dst_colors:
                    pixdata = image.load()
                    for _y in xrange(iheight):
                        for _x in xrange(iwidth):
                            pixel = pixdata[_x, _y]
                            a = pixel[3] if len(pixel) == 4 else 255
                            if a:
                                rgb = pixel[:3]
                                for j, dst_color in enumerate(
                                        all_dst_colors[i]):
                                    if rgb == all_src_colors[i][j]:
                                        new_color = tuple(
                                            [int(c) for c in dst_color] + [a])
                                        if pixel != new_color:
                                            pixdata[_x, _y] = new_color
                                            useless_dst_color = False
                                        break

                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            new_image = alpha_composite(
                                new_image, image, (x, y),
                                (cx, cy, cx + width, cy + height))
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (x, y))
                offsets_x.append(cssx)
                offsets_y.append(cssy)

            if useless_dst_color:
                log.warning(
                    "Useless use of $dst-color in sprite map for files at '%s' (never used for)"
                    % glob_path)

            filetime = int(now_time)

            if not inline:
                try:
                    new_image.save(asset_path)
                    url = '%s%s' % (config.ASSETS_URL, asset_file)
                    if cache_buster:
                        url += '?_=%s' % filetime
                except IOError:
                    log.exception("Error while saving image")
                    inline = True
            if inline:
                output = six.BytesIO()
                new_image.save(output, format='PNG')
                contents = output.getvalue()
                output.close()
                mime_type = 'image/png'
                url = make_data_url(mime_type, contents)

            url = 'url(%s)' % escape(url)
            if inline:
                asset = inline_asset = List(
                    [String.unquoted(url),
                     String.unquoted(repeat)])
            else:
                asset = file_asset = List(
                    [String.unquoted(url),
                     String.unquoted(repeat)])

            # Add the new object:
            sprite_map = dict(
                zip(tnames, zip(sizes, rfiles, offsets_x, offsets_y)))
            sprite_map['*'] = now_time
            sprite_map['*f*'] = asset_file
            sprite_map['*k*'] = key
            sprite_map['*n*'] = map_name
            sprite_map['*t*'] = filetime

            sizes = zip(files, sizes)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False,
                                                    dir=ASSETS_ROOT)
            pickle.dump(
                (now_time, file_asset, inline_asset, sprite_map, sizes),
                cache_tmp)
            cache_tmp.close()
            if sys.platform == 'win32' and os.path.isfile(cache_path):
                # on windows, cannot rename a file to a path that matches
                # an existing file, we have to remove it first
                os.remove(cache_path)
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(sprite_maps) > MAX_SPRITE_MAPS:
                for a in sorted(sprite_maps,
                                key=lambda a: sprite_maps[a]['*'],
                                reverse=True)[KEEP_SPRITE_MAPS:]:
                    del sprite_maps[a]
                log.warning("Exceeded maximum number of sprite maps (%s)" %
                            MAX_SPRITE_MAPS)
            sprite_maps[asset.render()] = sprite_map
        image_size_cache = _get_cache('image_size_cache')
        for file_, size in sizes:
            image_size_cache[file_] = size
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Ejemplo n.º 53
0
def _image_url(path,
               only_path=False,
               cache_buster=True,
               dst_color=None,
               src_color=None,
               inline=False,
               mime_type=None,
               spacing=None,
               collapse_x=None,
               collapse_y=None):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = String.unquoted(path).value
    fileext = os.path.splitext(filepath)[1].lstrip('.').lower()
    if mime_type:
        mime_type = String.unquoted(mime_type).value
    if not mime_type:
        mime_type = mimetypes.guess_type(filepath)[0]
    if not mime_type:
        mime_type = 'image/%s' % fileext
    path = None
    IMAGES_ROOT = _images_root()
    if callable(IMAGES_ROOT):
        try:
            _file, _storage = list(IMAGES_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = _storage.open(_file)
    else:
        _path = os.path.join(IMAGES_ROOT.rstrip('/'), filepath.strip('/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = open(_path, 'rb')

    BASE_URL = config.IMAGES_URL or config.STATIC_URL
    if path:
        dst_colors = [
            list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v
        ]

        src_color = Color.from_name(
            'black') if src_color is None else src_color
        src_colors = [
            tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)
        ]

        len_colors = max(len(dst_colors), len(src_colors))
        dst_colors = (dst_colors * len_colors)[:len_colors]
        src_colors = (src_colors * len_colors)[:len_colors]

        spacing = Number(0) if spacing is None else spacing
        spacing = [int(Number(v).value) for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(
            os.path.normpath(filepath).replace('\\', '_').replace('/', '_'))
        key = (filetime, src_color, dst_color, spacing)
        key = file_name + '-' + base64.urlsafe_b64encode(
            hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_')
        asset_file = key + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(
            config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, 'rb')
                url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                    path.read())
            else:
                url = '%s%s' % (BASE_URL, filepath)
                if cache_buster:
                    filetime = getmtime(asset_path)
                    url = add_cache_buster(url, filetime)
        else:
            simply_process = False
            image = None

            if fileext in ('cur', ):
                simply_process = True
            else:
                try:
                    image = Image.open(path)
                except IOError:
                    if not collapse_x and not collapse_y and not dst_colors:
                        simply_process = True

            if simply_process:
                if inline:
                    url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                        path.read())
                else:
                    url = '%s%s' % (BASE_URL, filepath)
                    if cache_buster:
                        filetime = getmtime(asset_path)
                        url = add_cache_buster(url, filetime)
            else:
                width, height = collapse_x or image.size[
                    0], collapse_y or image.size[1]
                new_image = Image.new(mode='RGBA',
                                      size=(width + spacing[1] + spacing[3],
                                            height + spacing[0] + spacing[2]),
                                      color=(0, 0, 0, 0))
                for i, dst_color in enumerate(dst_colors):
                    src_color = src_colors[i]
                    pixdata = image.load()
                    for _y in xrange(image.size[1]):
                        for _x in xrange(image.size[0]):
                            pixel = pixdata[_x, _y]
                            if pixel[:3] == src_color:
                                pixdata[_x, _y] = tuple(
                                    [int(c) for c in dst_color] +
                                    [pixel[3] if len(pixel) == 4 else 255])
                iwidth, iheight = image.size
                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            cropped_image = image.crop(
                                (cx, cy, cx + width, cy + height))
                            new_image.paste(cropped_image,
                                            (int(spacing[3]), int(spacing[0])),
                                            cropped_image)
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (int(spacing[3]), int(spacing[0])))

                if not inline:
                    try:
                        new_image.save(asset_path)
                        filepath = asset_file
                        BASE_URL = config.ASSETS_URL
                        if cache_buster:
                            filetime = getmtime(asset_path)
                    except IOError:
                        log.exception("Error while saving image")
                        inline = True  # Retry inline version
                    url = os.path.join(config.ASSETS_URL.rstrip('/'),
                                       asset_file.lstrip('/'))
                    if cache_buster:
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format='PNG')
                    contents = output.getvalue()
                    output.close()
                    url = 'data:' + mime_type + ';base64,' + base64.b64encode(
                        contents)
    else:
        url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not only_path:
        url = 'url(%s)' % escape(url)
    return String.unquoted(url)
Ejemplo n.º 54
0
def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, inline=False, mime_type=None, spacing=None, collapse_x=None, collapse_y=None):
    """
    src_color - a list of or a single color to be replaced by each corresponding dst_color colors
    spacing - spaces to be added to the image
    collapse_x, collapse_y - collapsable (layered) image of the given size (x, y)
    """
    if inline or dst_color or spacing:
        if not Image:
            raise Exception("Images manipulation require PIL")
    filepath = String.unquoted(path).value
    fileext = os.path.splitext(filepath)[1].lstrip('.').lower()
    if mime_type:
        mime_type = String.unquoted(mime_type).value
    if not mime_type:
        mime_type = mimetypes.guess_type(filepath)[0]
    if not mime_type:
        mime_type = 'image/%s' % fileext
    path = None
    IMAGES_ROOT = _images_root()
    if callable(IMAGES_ROOT):
        try:
            _file, _storage = list(IMAGES_ROOT(filepath))[0]
        except IndexError:
            filetime = None
        else:
            filetime = getmtime(_file, _storage)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = _storage.open(_file)
    else:
        _path = os.path.join(IMAGES_ROOT.rstrip(os.sep), filepath.strip('\\/'))
        filetime = getmtime(_path)
        if filetime is None:
            filetime = 'NA'
        elif inline or dst_color or spacing:
            path = open(_path, 'rb')

    BASE_URL = config.IMAGES_URL or config.STATIC_URL
    if path:
        dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v]

        src_color = Color.from_name('black') if src_color is None else src_color
        src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)]

        len_colors = max(len(dst_colors), len(src_colors))
        dst_colors = (dst_colors * len_colors)[:len_colors]
        src_colors = (src_colors * len_colors)[:len_colors]

        spacing = Number(0) if spacing is None else spacing
        spacing = [int(Number(v).value) for v in List.from_maybe(spacing)]
        spacing = (spacing * 4)[:4]

        file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace(os.sep, '_'))
        key = (filetime, src_color, dst_color, spacing)
        asset_file = file_name + '-' + make_filename_hash(key) + file_ext
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)

        if os.path.exists(asset_path):
            filepath = asset_file
            BASE_URL = config.ASSETS_URL
            if inline:
                path = open(asset_path, 'rb')
                url = make_data_url(mime_type, path.read())
            else:
                url = '%s%s' % (BASE_URL, filepath)
                if cache_buster:
                    filetime = getmtime(asset_path)
                    url = add_cache_buster(url, filetime)
        else:
            simply_process = False
            image = None

            if fileext in ('cur',):
                simply_process = True
            else:
                try:
                    image = Image.open(path)
                except IOError:
                    if not collapse_x and not collapse_y and not dst_colors:
                        simply_process = True

            if simply_process:
                if inline:
                    url = make_data_url(mime_type, path.read())
                else:
                    url = '%s%s' % (BASE_URL, filepath)
                    if cache_buster:
                        filetime = getmtime(asset_path)
                        url = add_cache_buster(url, filetime)
            else:
                width, height = collapse_x or image.size[0], collapse_y or image.size[1]
                new_image = Image.new(
                    mode='RGBA',
                    size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]),
                    color=(0, 0, 0, 0)
                )
                for i, dst_color in enumerate(dst_colors):
                    src_color = src_colors[i]
                    pixdata = image.load()
                    for _y in xrange(image.size[1]):
                        for _x in xrange(image.size[0]):
                            pixel = pixdata[_x, _y]
                            if pixel[:3] == src_color:
                                pixdata[_x, _y] = tuple([int(c) for c in dst_color] + [pixel[3] if len(pixel) == 4 else 255])
                iwidth, iheight = image.size
                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            cropped_image = image.crop((cx, cy, cx + width, cy + height))
                            new_image.paste(cropped_image, (int(spacing[3]), int(spacing[0])), cropped_image)
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (int(spacing[3]), int(spacing[0])))

                if not inline:
                    try:
                        new_image.save(asset_path)
                        filepath = asset_file
                        BASE_URL = config.ASSETS_URL
                        if cache_buster:
                            filetime = getmtime(asset_path)
                    except IOError:
                        log.exception("Error while saving image")
                        inline = True  # Retry inline version
                    url = os.path.join(config.ASSETS_URL.rstrip(os.sep), asset_file.lstrip(os.sep))
                    if cache_buster:
                        url = add_cache_buster(url, filetime)
                if inline:
                    output = six.BytesIO()
                    new_image.save(output, format='PNG')
                    contents = output.getvalue()
                    output.close()
                    url = make_data_url(mime_type, contents)
    else:
        url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('\\/'))
        if cache_buster and filetime != 'NA':
            url = add_cache_buster(url, filetime)

    if not os.sep == '/':
        url = url.replace(os.sep, '/')

    if only_path:
        return String.unquoted(url)
    else:
        return Url.unquoted(url)
Ejemplo n.º 55
0
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.)
Ejemplo n.º 56
0
def sprite_map(g, **kwargs):
    """
    Generates a sprite map from the files matching the glob pattern.
    Uses the keyword-style arguments passed in to control the placement.

    $direction - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`.

    $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`)
    $<sprite>-position - Position of a given sprite.

    $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`)
    $<sprite>-padding, $<sprite>-spacing - Padding for a given sprite.

    $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`).
    $<sprite>-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`)

    $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`)
    $<sprite>-dst-color - Source colors for a given sprite. (defaults to `$src-color`)

    $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`).
    $collapse-x  - Collapses a size for `x`.
    $collapse-y  - Collapses a size for `y`.
    """
    if not Image:
        raise Exception("Images manipulation require PIL")

    now_time = time.time()

    g = String(g, quotes=None).value

    if g in sprite_maps:
        sprite_maps[glob]['*'] = now_time
    elif '..' not in g:  # Protect against going to prohibited places...
        if callable(config.STATIC_ROOT):
            glob_path = g
            rfiles = files = sorted(config.STATIC_ROOT(g))
        else:
            glob_path = os.path.join(config.STATIC_ROOT, g)
            files = glob.glob(glob_path)
            files = sorted((f, None) for f in files)
            rfiles = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files]

        if not files:
            log.error("Nothing found at '%s'", glob_path)
            return String.unquoted('')

        map_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_')
        key = [f for (f, s) in files] + [repr(kwargs), config.ASSETS_URL]
        key = map_name + '-' + make_filename_hash(key)
        asset_file = key + '.png'
        ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets')
        asset_path = os.path.join(ASSETS_ROOT, asset_file)
        cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, asset_file + '.cache')

        inline = Boolean(kwargs.get('inline', False))

        sprite_map = None
        asset = None
        file_asset = None
        inline_asset = None
        if os.path.exists(asset_path) or inline:
            try:
                save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(open(cache_path))
                if file_asset:
                    sprite_maps[file_asset.render()] = sprite_map
                if inline_asset:
                    sprite_maps[inline_asset.render()] = sprite_map
                if inline:
                    asset = inline_asset
                else:
                    asset = file_asset
            except:
                pass

            if sprite_map:
                for file_, storage in files:
                    _time = getmtime(file_, storage)
                    if save_time < _time:
                        if _time > now_time:
                            log.warning("File '%s' has a date in the future (cache ignored)" % file_)
                        sprite_map = None  # Invalidate cached sprite map
                        break

        if sprite_map is None or asset is None:
            cache_buster = Boolean(kwargs.get('cache_buster', True))
            direction = String.unquoted(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value
            repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value
            collapse = kwargs.get('collapse', Number(0))
            if isinstance(collapse, List):
                collapse_x = int(Number(collapse[0]).value)
                collapse_y = int(Number(collapse[-1]).value)
            else:
                collapse_x = collapse_y = int(Number(collapse).value)
            if 'collapse_x' in kwargs:
                collapse_x = int(Number(kwargs['collapse_x']).value)
            if 'collapse_y' in kwargs:
                collapse_y = int(Number(kwargs['collapse_y']).value)

            position = Number(kwargs.get('position', 0))
            if not position.is_simple_unit('%') and position.value > 1:
                position = position.value / 100.0
            else:
                position = position.value
            if position < 0:
                position = 0.0
            elif position > 1:
                position = 1.0

            padding = kwargs.get('padding', kwargs.get('spacing', Number(0)))
            padding = [int(Number(v).value) for v in List.from_maybe(padding)]
            padding = (padding * 4)[:4]

            dst_colors = kwargs.get('dst_color')
            dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_colors) if v]
            src_colors = kwargs.get('src_color', Color.from_name('black'))
            src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)]
            len_colors = max(len(dst_colors), len(src_colors))
            dst_colors = (dst_colors * len_colors)[:len_colors]
            src_colors = (src_colors * len_colors)[:len_colors]

            def images(f=lambda x: x):
                for file_, storage in f(files):
                    if storage is not None:
                        _file = storage.open(file_)
                    else:
                        _file = file_
                    _image = Image.open(_file)
                    yield _image

            names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files)

            has_dst_colors = False
            all_dst_colors = []
            all_src_colors = []
            all_positions = []
            all_paddings = []

            for name in names:
                name = name.replace('-', '_')

                _position = kwargs.get(name + '_position')
                if _position is None:
                    _position = position
                else:
                    _position = Number(_position)
                    if not _position.is_simple_unit('%') and _position.value > 1:
                        _position = _position.value / 100.0
                    else:
                        _position = _position.value
                    if _position < 0:
                        _position = 0.0
                    elif _position > 1:
                        _position = 1.0
                all_positions.append(_position)

                _padding = kwargs.get(name + '_padding', kwargs.get(name + '_spacing'))
                if _padding is None:
                    _padding = padding
                else:
                    _padding = [int(Number(v).value) for v in List.from_maybe(_padding)]
                    _padding = (_padding * 4)[:4]
                all_paddings.append(_padding)

                _dst_colors = kwargs.get(name + '_dst_color')
                if _dst_colors is None:
                    _dst_colors = dst_colors
                    if dst_colors:
                        has_dst_colors = True
                else:
                    has_dst_colors = True
                    _dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(_dst_colors) if v]
                _src_colors = kwargs.get(name + '_src_color', Color.from_name('black'))
                if _src_colors is None:
                    _src_colors = src_colors
                else:
                    _src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(_src_colors)]
                _len_colors = max(len(_dst_colors), len(_src_colors))
                _dst_colors = (_dst_colors * _len_colors)[:_len_colors]
                _src_colors = (_src_colors * _len_colors)[:_len_colors]
                all_dst_colors.append(_dst_colors)
                all_src_colors.append(_src_colors)

            sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1]) for i in images())

            if direction == 'horizontal':
                layout = HorizontalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'vertical':
                layout = VerticalSpritesLayout(sizes, all_paddings, position=all_positions)
            elif direction == 'diagonal':
                layout = DiagonalSpritesLayout(sizes, all_paddings)
            elif direction == 'smart':
                layout = PackedSpritesLayout(sizes, all_paddings)
            else:
                raise Exception("Invalid direction %r" % (direction,))
            layout_positions = list(layout)

            new_image = Image.new(
                mode='RGBA',
                size=(layout.width, layout.height),
                color=(0, 0, 0, 0)
            )

            useless_dst_color = has_dst_colors

            offsets_x = []
            offsets_y = []
            for i, image in enumerate(images()):
                x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i]
                iwidth, iheight = image.size

                if has_dst_colors:
                    pixdata = image.load()
                    for _y in xrange(iheight):
                        for _x in xrange(iwidth):
                            pixel = pixdata[_x, _y]
                            a = pixel[3] if len(pixel) == 4 else 255
                            if a:
                                rgb = pixel[:3]
                                for j, dst_color in enumerate(all_dst_colors[i]):
                                    if rgb == all_src_colors[i][j]:
                                        new_color = tuple([int(c) for c in dst_color] + [a])
                                        if pixel != new_color:
                                            pixdata[_x, _y] = new_color
                                            useless_dst_color = False
                                        break

                if iwidth != width or iheight != height:
                    cy = 0
                    while cy < iheight:
                        cx = 0
                        while cx < iwidth:
                            new_image = alpha_composite(new_image, image, (x, y), (cx, cy, cx + width, cy + height))
                            cx += width
                        cy += height
                else:
                    new_image.paste(image, (x, y))
                offsets_x.append(cssx)
                offsets_y.append(cssy)

            if useless_dst_color:
                log.warning("Useless use of $dst-color in sprite map for files at '%s' (never used for)" % glob_path)

            filetime = int(now_time)

            if not inline:
                try:
                    new_image.save(asset_path)
                    url = '%s%s' % (config.ASSETS_URL, asset_file)
                    if cache_buster:
                        url += '?_=%s' % filetime
                except IOError:
                    log.exception("Error while saving image")
                    inline = True
            if inline:
                output = six.BytesIO()
                new_image.save(output, format='PNG')
                contents = output.getvalue()
                output.close()
                mime_type = 'image/png'
                url = make_data_url(mime_type, contents)

            url = 'url(%s)' % escape(url)
            if inline:
                asset = inline_asset = List([String.unquoted(url), String.unquoted(repeat)])
            else:
                asset = file_asset = List([String.unquoted(url), String.unquoted(repeat)])

            # Add the new object:
            sprite_map = dict(zip(names, zip(sizes, rfiles, offsets_x, offsets_y)))
            sprite_map['*'] = now_time
            sprite_map['*f*'] = asset_file
            sprite_map['*k*'] = key
            sprite_map['*n*'] = map_name
            sprite_map['*t*'] = filetime

            sizes = zip(files, sizes)
            cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT)
            pickle.dump((now_time, file_asset, inline_asset, sprite_map, sizes), cache_tmp)
            cache_tmp.close()
            os.rename(cache_tmp.name, cache_path)

            # Use the sorted list to remove older elements (keep only 500 objects):
            if len(sprite_maps) > MAX_SPRITE_MAPS:
                for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]['*'], reverse=True)[KEEP_SPRITE_MAPS:]:
                    del sprite_maps[a]
                log.warning("Exceeded maximum number of sprite maps (%s)" % MAX_SPRITE_MAPS)
            sprite_maps[asset.render()] = sprite_map
        for file_, size in sizes:
            _image_size_cache[file_] = size
    # TODO this sometimes returns an empty list, or is never assigned to
    return asset
Ejemplo n.º 57
0
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)
Ejemplo n.º 58
0
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)
Ejemplo n.º 59
0
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)