def percentage(value): value = NumberValue(value) value.units = {'%': _units_weights.get('%', 1), '_': '%'} return value
def hue(color): c = ColorValue(color).value h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0) ret = NumberValue(h * 360.0) ret.units = {'deg': _units_weights.get('deg', 1), '_': 'deg'} return ret
def lightness(color): c = ColorValue(color).value h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0) ret = NumberValue(l) ret.units = {'%': _units_weights.get('%', 1), '_': '%'} return ret
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. """ g = StringValue(g).value if not Image: raise Exception("Images manipulation require PIL") if g in sprite_maps: sprite_maps[glob]["*"] = datetime.datetime.now() elif ".." not in g: # Protect against going to prohibited places... vertical = kwargs.get("direction", "vertical") == "vertical" repeat = StringValue(kwargs.get("repeat", "no-repeat")) position = NumberValue(kwargs.get("position", 0)) collapse_x = NumberValue(kwargs.get("collapse_x", 0)) collapse_y = NumberValue(kwargs.get("collapse_y", 0)) if position and position > -1 and position < 1: position.units = {"%": _units_weights.get("%", 1), "_": "%"} dst_colors = kwargs.get("dst_color") if isinstance(dst_colors, ListValue): dst_colors = [list(ColorValue(v).value[:3]) for n, v in dst_colors.items() if v] else: dst_colors = [list(ColorValue(dst_colors).value[:3])] if dst_colors else [] src_colors = kwargs.get("src_color") if isinstance(src_colors, ListValue): src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for n, v in src_colors.items()] else: src_colors = [tuple(ColorValue(src_colors).value[:3]) if src_colors else (0, 0, 0)] 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 = kwargs.get("spacing", 0) if isinstance(spacing, ListValue): spacing = [int(NumberValue(v).value) for n, v in spacing.items()] else: spacing = [int(NumberValue(spacing).value)] spacing = (spacing * 4)[:4] 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 = [(f[len(config.STATIC_ROOT) :], s) for f, s in files] if not files: log.error("Nothing found at '%s'", glob_path) return StringValue(None) times = [] for file, storage in files: try: d_obj = storage.modified_time(file) times.append(int(time.mktime(d_obj.timetuple()))) except: times.append(int(os.path.getmtime(file))) map_name = os.path.normpath(os.path.dirname(g)).replace("\\", "_").replace("/", "_") key = list(zip(*files)[0]) + times + [repr(kwargs), config.ASSETS_URL] key = map_name + "-" + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip("=").replace("-", "_") asset_file = key + ".png" asset_path = os.path.join(config.ASSETS_ROOT, asset_file) try: asset, map, sizes = pickle.load(open(asset_path + ".cache")) sprite_maps[asset] = map except: def images(): for file, storage in files: yield Image.open(storage.open(file)) if storage is not None else Image.open(file) names = tuple(os.path.splitext(os.path.basename(file))[0] for file, storage in files) positions = [] spacings = [] tot_spacings = [] for name in names: name = name.replace("-", "_") _position = kwargs.get(name + "_position") if _position is None: _position = position else: _position = NumberValue(_position) if _position and _position > -1 and _position < 1: _position.units = {"%": _units_weights.get("%", 1), "_": "%"} positions.append(_position) _spacing = kwargs.get(name + "_spacing") if _spacing is None: _spacing = spacing else: if isinstance(_spacing, ListValue): _spacing = [int(NumberValue(v).value) for n, v in _spacing.items()] else: _spacing = [int(NumberValue(_spacing).value)] _spacing = (_spacing * 4)[:4] spacings.append(_spacing) if _position and _position.unit != "%": if vertical: if _position > 0: tot_spacings.append((_spacing[0], _spacing[1], _spacing[2], _spacing[3] + _position)) else: if _position > 0: tot_spacings.append((_spacing[0] + _position, _spacing[1], _spacing[2], _spacing[3])) else: tot_spacings.append(_spacing) sizes = tuple((collapse_x or image.size[0], collapse_y or image.size[1]) for image in images()) _spacings = zip(*tot_spacings) if vertical: width = max(zip(*sizes)[0]) + max(_spacings[1]) + max(_spacings[3]) height = sum(zip(*sizes)[1]) + sum(_spacings[0]) + sum(_spacings[2]) else: width = sum(zip(*sizes)[0]) + sum(_spacings[1]) + sum(_spacings[3]) height = max(zip(*sizes)[1]) + max(_spacings[0]) + max(_spacings[2]) new_image = Image.new(mode="RGBA", size=(width, height), color=(0, 0, 0, 0)) offsets_x = [] offsets_y = [] offset = 0 for i, image in enumerate(images()): spacing = spacings[i] position = positions[i] iwidth, iheight = image.size width, height = sizes[i] if vertical: if position and position.unit == "%": x = width * position.value - (spacing[3] + height + spacing[1]) elif position.value < 0: x = width + position.value - (spacing[3] + height + spacing[1]) else: x = position.value offset += spacing[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] ) 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(x + spacing[3]), offset), cropped_image) cx += width cy += height else: new_image.paste(image, (int(x + spacing[3]), offset)) offsets_x.append(x) offsets_y.append(offset - spacing[0]) offset += height + spacing[2] else: if position and position.unit == "%": y = height * position.value - (spacing[0] + height + spacing[2]) elif position.value < 0: y = height + position.value - (spacing[0] + height + spacing[2]) else: y = position.value offset += spacing[3] 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] ) 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, (offset, int(y + spacing[0])), cropped_image) cx += width cy += height else: new_image.paste(image, (offset, int(y + spacing[0]))) offsets_x.append(offset - spacing[3]) offsets_y.append(y) offset += width + spacing[1] try: new_image.save(asset_path) except IOError: log.exception("Error while saving image") filetime = int(time.mktime(datetime.datetime.now().timetuple())) url = "%s%s?_=%s" % (config.ASSETS_URL, asset_file, filetime) asset = 'url("%s") %s' % (escape(url), repeat) # Use the sorted list to remove older elements (keep only 500 objects): if len(sprite_maps) > 1000: for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]["*"], reverse=True)[500:]: del sprite_maps[a] # Add the new object: map = dict(zip(names, zip(sizes, rfiles, offsets_x, offsets_y))) map["*"] = datetime.datetime.now() map["*f*"] = asset_file map["*k*"] = key map["*n*"] = map_name map["*t*"] = filetime tmp_dir = config.ASSETS_ROOT cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=tmp_dir) pickle.dump((asset, map, zip(files, sizes)), cache_tmp) cache_tmp.close() os.rename(cache_tmp.name, asset_path + ".cache") sprite_maps[asset] = map for file, size in sizes: _image_size_cache[file] = size ret = StringValue(asset) return ret