def image_height(image): """ Returns the height of the image found at the path supplied by `image` relative to your project's images directory. """ if not Image: raise Exception("Images manipulation require PIL") filepath = StringValue(image).value path = None try: height = _image_size_cache[filepath][1] except KeyError: height = 0 if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] path = _storage.open(_file) except: pass else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): path = open(_path, 'rb') if path: image = Image.open(path) size = image.size height = size[1] _image_size_cache[filepath] = size return NumberValue(height, 'px')
def enumerate_(prefix, frm, through, separator='-'): separator = StringValue(separator).value try: frm = int(getattr(frm, 'value', frm)) except ValueError: frm = 1 try: through = int(getattr(through, 'value', through)) except ValueError: through = frm if frm > through: frm, through = through, frm rev = reversed else: rev = lambda x: x ret = [] for i in rev(range(frm, through + 1)): if prefix.value: ret.append( StringValue(prefix.value + separator + str(i), quotes=None)) else: ret.append(NumberValue(i)) return List(ret, use_comma=True)
def quote(*args): arg = List.from_maybe_starargs(args).maybe() if isinstance(arg, StringValue): return StringValue(arg.value, quotes='"') else: return StringValue(arg.render(), quotes='"')
def stylesheet_url(path, only_path=False, cache_buster=True): """ Generates a path to an asset found relative to the project's css directory. Passing a true value as the second argument will cause the only the path to be returned instead of a `url()` function """ filepath = StringValue(path).value if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] d_obj = _storage.modified_time(_file) filetime = int(time.mktime(d_obj.timetuple())) except: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): filetime = int(os.path.getmtime(_path)) else: filetime = 'NA' BASE_URL = config.STATIC_URL url = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if not only_path: url = 'url("%s")' % (url) return StringValue(url)
def _font_url(path, only_path=False, cache_buster=True, inline=False): filepath = StringValue(path).value path = None if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] d_obj = _storage.modified_time(_file) filetime = int(time.mktime(d_obj.timetuple())) if inline: path = _storage.open(_file) except: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): filetime = int(os.path.getmtime(_path)) if inline: path = open(_path, 'rb') else: filetime = 'NA' BASE_URL = config.STATIC_URL if path and inline: mime_type = mimetypes.guess_type(filepath)[0] url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) else: url = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if not only_path: url = 'url("%s")' % escape(url) return StringValue(url)
def nest(*arguments): if isinstance(arguments[0], List): lst = arguments[0].value else: lst = StringValue(arguments[0]).value.split(',') ret = [unicode(s).strip() for s in lst if unicode(s).strip()] for arg in arguments[1:]: if isinstance(arg, List): lst = arg.value else: lst = StringValue(arg).value.split(',') new_ret = [] for s in lst: s = unicode(s).strip() if s: for r in ret: if '&' in s: new_ret.append(s.replace('&', r)) else: if r[-1] in ('.', ':', '#'): new_ret.append(r + s) else: new_ret.append(r + ' ' + s) ret = new_ret return List(sorted(set(ret)), use_comma=True)
def _inv(sign, value): if isinstance(value, NumberValue): return value * -1 elif isinstance(value, BooleanValue): return not value val = StringValue(value) val.value = sign + val.value return val
def append_selector(selector, to_append): if isinstance(selector, List): lst = selector.value else: lst = StringValue(selector).value.split(',') to_append = StringValue(to_append).value.strip() ret = sorted(set(s.strip() + to_append for s in lst if s.strip())) ret = dict(enumerate(ret)) ret['_'] = ',' return ret
def sprite_map_name(map): """ Returns the name of a sprite map The name is derived from the folder than contains the sprites. """ map = StringValue(map).value sprite_map = sprite_maps.get(map) if not sprite_map: log.error("No sprite map found: %s", map, extra={'stack': True}) if sprite_map: return StringValue(sprite_map['*n*']) return StringValue(None)
def linear_gradient(*args): if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)): args = ListValue(args[0]).values() position_and_angle = _get_gradient_position_and_angle(args) color_stops = _get_gradient_color_stops(args) if color_stops is None: raise Exception('No color stops provided to linear-gradient function') color_stops = __color_stops(False, *color_stops) args = [ position(position_and_angle) if position_and_angle is not None else None, ] args.extend('%s %s' % (c, to_str(s)) for s, c in color_stops) to__s = 'linear-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' ret = StringValue(to__s) def to__css2(): return StringValue('') ret.to__css2 = to__css2 def to__moz(): return StringValue('-moz-' + to__s) ret.to__moz = to__moz def to__pie(): return StringValue('-pie-' + to__s) ret.to__pie = to__pie def to__ms(): return StringValue('-ms-' + to__s) ret.to__ms = to__ms def to__o(): return StringValue('-o-' + to__s) ret.to__o = to__o def to__webkit(): return StringValue('-webkit-' + to__s) ret.to__webkit = to__webkit def to__owg(): args = [ 'linear', position(position_and_angle or 'center top'), opposite_position(position_and_angle or 'center top'), ] args.extend('color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops) ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' return StringValue(ret) ret.to__owg = to__owg def to__svg(): return linear_svg_gradient(color_stops, position_and_angle or 'top') ret.to__svg = to__svg return ret
def sprite_url(map): """ Returns a url to the sprite image. """ map = StringValue(map).value sprite_map = sprite_maps.get(map) if not sprite_map: log.error("No sprite map found: %s", map, extra={'stack': True}) if sprite_map: url = '%s%s?_=%s' % (config.ASSETS_URL, sprite_map['*f*'], sprite_map['*t*']) url = "url(%s)" % escape(url) return StringValue(url) return StringValue(None)
def _font_files(args, inline): args = List.from_maybe_starargs(args) n = 0 params = [[], []] for arg in args: if isinstance(arg, List): if len(arg) == 2: if n % 2 == 1: params[1].append(None) n += 1 params[0].append(arg[0]) params[1].append(arg[1]) n += 2 else: for arg2 in arg: params[n % 2].append(arg2) n += 1 else: params[n % 2].append(arg) n += 1 len0 = len(params[0]) len1 = len(params[1]) if len1 < len0: params[1] += [None] * (len0 - len1) elif len0 < len1: params[0] += [None] * (len1 - len0) fonts = [] for font, format in zip(params[0], params[1]): if format: fonts.append( '%s format("%s")' % (_font_url(font, inline=inline), StringValue(format).value)) else: fonts.append(_font_url(font, inline=inline)) return List(fonts)
def sprite_file(map, sprite): """ Returns the relative path (from the images directory) to the original file used when construction the sprite. This is suitable for passing to the image_width and image_height helpers. """ map = StringValue(map).value sprite_name = StringValue(sprite).value sprite_map = sprite_maps.get(map) sprite = sprite_map and sprite_map.get(sprite_name) if not sprite_map: log.error("No sprite map found: %s", map, extra={'stack': True}) elif not sprite: log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) if sprite: return QuotedStringValue(sprite[1][0]) return StringValue(None)
def unit(number): # -> px, em, cm, etc. numer = '*'.join(sorted(number.unit_numer)) denom = '*'.join(sorted(number.unit_denom)) if denom: ret = numer + '/' + denom else: ret = numer return StringValue(ret)
def to__owg(): args = [ 'linear', position(position_and_angle or None), opposite_position(position_and_angle or None), ] args.extend('color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops) ret = '-webkit-gradient(' + ', '.join( to_str(a) for a in args or [] if a is not None) + ')' return StringValue(ret, quotes=None)
def radial_gradient(*args): if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)): args = ListValue(args[0]).values() position_and_angle = _get_gradient_position_and_angle(args) shape_and_size = _get_gradient_shape_and_size(args) color_stops = _get_gradient_color_stops(args) if color_stops is None: raise Exception('No color stops provided to radial-gradient function') color_stops = __color_stops(False, *color_stops) args = [ position(position_and_angle) if position_and_angle is not None else None, shape_and_size if shape_and_size is not None else None, ] args.extend('%s %s' % (c, to_str(s)) for s, c in color_stops) to__s = 'radial-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' ret = StringValue(to__s) def to__css2(): return StringValue('') ret.to__css2 = to__css2 def to__moz(): return StringValue('-moz-' + to__s) ret.to__moz = to__moz def to__pie(): log.warn("PIE does not support radial-gradient.") return StringValue('-pie-radial-gradient(unsupported)') ret.to__pie = to__pie def to__webkit(): return StringValue('-webkit-' + to__s) ret.to__webkit = to__webkit def to__owg(): args = [ 'radial', grad_point(position_and_angle) if position_and_angle is not None else 'center', '0', grad_point(position_and_angle) if position_and_angle is not None else 'center', __grad_end_position(True, color_stops), ] args.extend('color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops) ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' return StringValue(ret) ret.to__owg = to__owg def to__svg(): return radial_svg_gradient(color_stops, position_and_angle or 'center') ret.to__svg = to__svg return ret
def sprite_position(map, sprite, offset_x=None, offset_y=None): """ Returns the position for the original image in the sprite. This is suitable for use as a value to background-position. """ map = StringValue(map).value sprite_name = StringValue(sprite).value sprite_map = sprite_maps.get(map) sprite = sprite_map and sprite_map.get(sprite_name) if not sprite_map: log.error("No sprite map found: %s", map, extra={'stack': True}) elif not sprite: log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) if sprite: x = None if offset_x is not None and not isinstance(offset_x, NumberValue): x = offset_x if x not in ('left', 'right', 'center'): if x: offset_x = None x = NumberValue(offset_x or 0, 'px') u = x.unit x = x.value if not x or (x <= -1 or x >= 1) and u != '%': x -= sprite[2] y = None if offset_y is not None and not isinstance(offset_y, NumberValue): y = offset_y if y not in ('top', 'bottom', 'center'): if y: offset_y = None y = NumberValue(offset_y or 0, 'px') u = y.unit y = y.value if not y or (y <= -1 or y >= 1) and u != '%': y -= sprite[3] pos = '%s %s' % (x, y) return StringValue(pos) return StringValue('0 0')
def radial_svg_gradient(*args): args = List.from_maybe_starargs(args) color_stops = args center = None if isinstance(args[-1], (StringValue, NumberValue, six.string_types)): center = args[-1] color_stops = args[:-1] color_stops = __color_stops(False, *color_stops) cx, cy = zip(*grad_point(center).items())[1] r = __grad_end_position(True, color_stops) svg = __radial_svg(color_stops, cx, cy, r) url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) inline = 'url("%s")' % escape(url) return StringValue(inline)
def linear_svg_gradient(*args): args = List.from_maybe_starargs(args) color_stops = args start = None if isinstance(args[-1], (StringValue, NumberValue, six.string_types)): start = args[-1] color_stops = args[:-1] color_stops = __color_stops(False, *color_stops) x1, y1 = zip(*grad_point(start).items())[1] x2, y2 = zip(*grad_point(opposite_position(start)).items())[1] svg = _linear_svg(color_stops, x1, y1, x2, y2) url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) inline = 'url("%s")' % escape(url) return StringValue(inline)
def grad_point(*p): pos = set() hrz = vrt = NumberValue(0.5, '%') for _p in p: pos.update(StringValue(_p).value.split()) if 'left' in pos: hrz = NumberValue(0, '%') elif 'right' in pos: hrz = NumberValue(1, '%') if 'top' in pos: vrt = NumberValue(0, '%') elif 'bottom' in pos: vrt = NumberValue(1, '%') return List([v for v in (hrz, vrt) if v is not None])
def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=None, spread=None, background=None, inline=False): if not Image: raise Exception("Images manipulation require PIL") density = [NumberValue(v).value for v in List.from_maybe(density)] intensity = [NumberValue(v).value for v in List.from_maybe(intensity)] color = [ColorValue(v).value for v in List.from_maybe(color) if v] opacity = [NumberValue(v).value for v in List.from_maybe(opacity)] size = int(NumberValue(size).value) if size else -1 if size < 0 or size > 512: size = 200 monochrome = bool(monochrome) direction = [NumberValue(v).value for v in List.from_maybe(direction)] spread = [NumberValue(v).value for v in List.from_maybe(spread)] background = ColorValue(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 += '-' + 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 StringValue(inline)
def nth(lst, n): """ Return the Nth item in the string """ n = StringValue(n).value lst = ListValue(lst).value try: n = int(float(n)) - 1 n = n % len(lst) except: if n.lower() == 'first': n = 0 elif n.lower() == 'last': n = -1 try: ret = lst[n] except KeyError: lst = [v for k, v in sorted(lst.items()) if isinstance(k, int)] try: ret = lst[n] except: ret = '' return ret.__class__(ret)
def to__owg(): args = [ 'radial', grad_point(position_and_angle) if position_and_angle is not None else 'center', '0', grad_point(position_and_angle) if position_and_angle is not None else 'center', __grad_end_position(True, color_stops), ] args.extend('color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops) ret = '-webkit-gradient(' + ', '.join( to_str(a) for a in args or [] if a is not None) + ')' return StringValue(ret)
def sprite(map, sprite, offset_x=None, offset_y=None): """ Returns the image and background position for use in a single shorthand property """ map = StringValue(map).value sprite_name = StringValue(sprite).value sprite_map = sprite_maps.get(map) sprite = sprite_map and sprite_map.get(sprite_name) if not sprite_map: log.error("No sprite map found: %s", map, extra={'stack': True}) elif not sprite: log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) if sprite: url = '%s%s?_=%s' % (config.ASSETS_URL, sprite_map['*f*'], sprite_map['*t*']) x = NumberValue(offset_x or 0, 'px') y = NumberValue(offset_y or 0, 'px') if not x or (x <= -1 or x >= 1) and x.unit != '%': x -= sprite[2] if not y or (y <= -1 or y >= 1) and y.unit != '%': y -= sprite[3] pos = "url(%s) %s %s" % (escape(url), x, y) return StringValue(pos) return StringValue('0 0')
def __parse_separator(separator, default_from=None): if separator is None: return None separator = StringValue(separator).value if separator == 'comma': return True elif separator == 'space': return False elif separator == 'auto': if not default_from: return True elif len(default_from) < 2: return True else: return default_from.use_comma else: raise ValueError('Separator must be auto, comma, or space')
def image_color(color, width=1, height=1): if not Image: raise Exception("Images manipulation require PIL") c = ColorValue(color).value w = int(NumberValue(width).value) h = int(NumberValue(height).value) if w <= 0 or h <= 0: raise ValueError new_image = Image.new( mode='RGB' if c[3] == 1 else 'RGBA', size=(w, h), color=(c[0], c[1], c[2], int(c[3] * 255.0)) ) output = six.BytesIO() new_image.save(output, format='PNG') contents = output.getvalue() output.close() mime_type = 'image/png' url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) inline = 'url("%s")' % escape(url) return StringValue(inline)
def headers(frm=None, to=None): if frm and to is None: if isinstance(frm, StringValue) and frm.value.lower() == 'all': frm = 1 to = 6 else: try: to = int(getattr(frm, 'value', frm)) except ValueError: to = 6 frm = 1 else: try: frm = 1 if frm is None else int(getattr(frm, 'value', frm)) except ValueError: frm = 1 try: to = 6 if to is None else int(getattr(to, 'value', to)) except ValueError: to = 6 ret = [StringValue('h' + str(i)) for i in range(frm, to + 1)] return List(ret, use_comma=True)
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 = StringValue(g).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 StringValue(None) map_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_') key = list(zip(*files)[0]) + [repr(kwargs), config.ASSETS_URL] key = map_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') 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') sprite_map = None if os.path.exists(asset_path): try: save_time, asset, sprite_map, sizes = pickle.load(open(cache_path)) sprite_maps[asset] = sprite_map except: pass if sprite_map: for file_, storage in files: if storage is not None: d_obj = storage.modified_time(file_) _time = time.mktime(d_obj.timetuple()) else: _time = os.path.getmtime(file_) 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: direction = StringValue(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value repeat = StringValue(kwargs.get('repeat', 'no-repeat')).value collapse = kwargs.get('collapse') or 0 if isinstance(collapse, List): collapse_x = int(NumberValue(collapse[0]).value) collapse_y = int(NumberValue(collapse[-1]).value) else: collapse_x = collapse_y = int(NumberValue(collapse).value) if 'collapse_x' in kwargs: collapse_x = int(NumberValue(kwargs['collapse_x']).value) if 'collapse_y' in kwargs: collapse_y = int(NumberValue(kwargs['collapse_y']).value) position = kwargs.get('position', 0) position = NumberValue(position) if position.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', 0)) padding = [int(NumberValue(v).value) for v in List.from_maybe(padding)] padding = (padding * 4)[:4] dst_colors = kwargs.get('dst_color') dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_colors) if v] src_colors = kwargs.get('src_color') src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) 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 = NumberValue(_position) if _position.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(NumberValue(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(ColorValue(v).value[:3]) for v in List.from_maybe(_dst_colors) if v] _src_colors = kwargs.get(name + '_src_color') if _src_colors is None: _src_colors = src_colors else: _src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) 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) try: new_image.save(asset_path) except IOError: log.exception("Error while saving image") filetime = int(now_time) url = '%s%s?_=%s' % (config.ASSETS_URL, asset_file, filetime) asset = 'url("%s") %s' % (escape(url), 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 cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT) pickle.dump((now_time, asset, sprite_map, zip(files, 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] = sprite_map for file_, size in sizes: _image_size_cache[file_] = size ret = StringValue(asset) return ret
def grad_color_stops(*args): args = List.from_maybe_starargs(args) color_stops = __color_stops(True, *args) ret = ', '.join( ['color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops]) return StringValue(ret)
def sprites(map): map = StringValue(map).value sprite_map = sprite_maps.get(map, {}) return List(list(sorted(s for s in sprite_map if not s.startswith('*'))))
def _grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False): if not Image: raise Exception("Images manipulation require PIL") if grid_color is None: grid_color = (120, 170, 250, 15) else: c = ColorValue(grid_color).value grid_color = (c[0], c[1], c[2], int(c[3] * 255.0)) if baseline_color is None: baseline_color = (120, 170, 250, 30) else: c = ColorValue(baseline_color).value baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0)) if background_color is None: background_color = (0, 0, 0, 0) else: c = ColorValue(background_color).value background_color = (c[0], c[1], c[2], int(c[3] * 255.0)) _height = int(height) if height >= 1 else int(height * 1000.0) _width = int(width) if width >= 1 else int(width * 1000.0) _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0) _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0) if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0: raise ValueError _full_width = (_left_gutter + _width + _right_gutter) new_image = Image.new( mode='RGBA', size=(_full_width * int(columns), _height), color=background_color ) draw = ImageDraw.Draw(new_image) for i in range(int(columns)): draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1), fill=grid_color) if _height > 1: draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1), fill=baseline_color) if not inline: grid_name = 'grid_' if left_gutter: grid_name += str(int(left_gutter)) + '+' grid_name += str(int(width)) if right_gutter: grid_name += '+' + str(int(right_gutter)) if height and height > 1: grid_name += 'x' + str(int(height)) key = (columns, grid_color, baseline_color, background_color) key = grid_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') asset_file = key + '.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 StringValue(inline)
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 = StringValue(path).value mime_type = inline and (StringValue(mime_type).value or mimetypes.guess_type(filepath)[0]) path = None if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] d_obj = _storage.modified_time(_file) filetime = int(time.mktime(d_obj.timetuple())) if inline or dst_color or spacing: path = _storage.open(_file) except: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): filetime = int(os.path.getmtime(_path)) if inline or dst_color or spacing: path = open(_path, 'rb') else: filetime = 'NA' BASE_URL = config.STATIC_URL if path: dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_color) if v] src_colors = src_color src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) 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 = [int(NumberValue(v).value) if v else 0 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 = int(os.path.getmtime(asset_path)) url = add_cache_buster(url, filetime) else: image = Image.open(path) 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 = int(os.path.getmtime(asset_path)) except IOError: log.exception("Error while saving image") inline = True # Retry inline version url = '%s%s' % (config.ASSETS_URL, asset_file) 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 = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if not only_path: url = 'url("%s")' % escape(url) return StringValue(url)
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 = StringValue(path).value mime_type = inline and (StringValue(mime_type).value or mimetypes.guess_type(filepath)[0]) path = None if callable(config.STATIC_ROOT): try: _file, _storage = list(config.STATIC_ROOT(filepath))[0] d_obj = _storage.modified_time(_file) filetime = int(time.mktime(d_obj.timetuple())) if inline or dst_color or spacing: path = _storage.open(_file) except: filetime = 'NA' else: _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) if os.path.exists(_path): filetime = int(os.path.getmtime(_path)) if inline or dst_color or spacing: path = open(_path, 'rb') else: filetime = 'NA' BASE_URL = config.STATIC_URL if path: dst_colors = [ list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_color) if v ] src_colors = src_color src_colors = [ tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) 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 = [ int(NumberValue(v).value) if v else 0 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 = int(os.path.getmtime(asset_path)) url = add_cache_buster(url, filetime) else: image = Image.open(path) 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 = int(os.path.getmtime(asset_path)) except IOError: log.exception("Error while saving image") inline = True # Retry inline version url = '%s%s' % (config.ASSETS_URL, asset_file) 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 = '%s%s' % (BASE_URL, filepath) if cache_buster: url = add_cache_buster(url, filetime) if not only_path: url = 'url("%s")' % escape(url) return StringValue(url)
# This isn't actually from Compass, but it's just a shortcut for enumerate(). @register('range', 1) @register('range', 2) def range_(frm, through=None): if through is None: through = frm frm = 1 return enumerate_(None, frm, through) # ------------------------------------------------------------------------------ # Working with CSS constants OPPOSITE_POSITIONS = { 'top': StringValue('bottom', quotes=None), 'bottom': StringValue('top', quotes=None), 'left': StringValue('right', quotes=None), 'right': StringValue('left', quotes=None), 'center': StringValue('center', quotes=None), } DEFAULT_POSITION = [ StringValue('center', quotes=None), StringValue('top', quotes=None) ] def _position(opposite, positions): if positions is None: positions = DEFAULT_POSITION else:
def elements_of_type(display): d = StringValue(display) ret = _elements_of_type.get(d.value, None) if ret is None: raise Exception("Elements of type '%s' not found!" % d.value) return List(ret, use_comma=True)