Files
CouchPotatoServer/libs/scss/functions/extra.py
T
2014-04-09 16:07:59 +02:00

472 lines
17 KiB
Python

"""Functions new to the pyScss library."""
from __future__ import absolute_import
import base64
import hashlib
import logging
import os.path
import random
import six
from six.moves import xrange
from scss import config
from scss.functions.library import FunctionLibrary
from scss.types import Color, Number, String, List
from scss.util import escape
try:
from PIL import Image, ImageDraw
except ImportError:
try:
import Image
import ImageDraw
except:
Image = None
log = logging.getLogger(__name__)
EXTRA_LIBRARY = FunctionLibrary()
register = EXTRA_LIBRARY.register
# ------------------------------------------------------------------------------
# Image stuff
def _image_noise(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, background=None):
if not density:
density = [0.8]
elif not isinstance(density, (tuple, list)):
density = [density]
if not intensity:
intensity = [0.5]
elif not isinstance(intensity, (tuple, list)):
intensity = [intensity]
if not color:
color = [(0, 0, 0, 0)]
elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)):
color = [color]
if not opacity:
opacity = [0.2]
elif not isinstance(opacity, (tuple, list)):
opacity = [opacity]
if not monochrome:
monochrome = [False]
elif not isinstance(monochrome, (tuple, list)):
monochrome = [monochrome]
pixels = {}
if background:
for y in xrange(size):
for x in xrange(size):
ca = float(background[3])
pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca)
loops = max(map(len, (density, intensity, color, opacity, monochrome)))
for l in range(loops):
_density = density[l % len(density)]
_intensity = intensity[l % len(intensity)]
_color = color[l % len(color)]
_opacity = opacity[l % len(opacity)]
_monochrome = monochrome[l % len(monochrome)]
_intensity = 1 - _intensity
if _intensity < 0.5:
cx = 255 * _intensity
cm = cx
else:
cx = 255 * (1 - _intensity)
cm = 255 * _intensity
xa = int(cm - cx)
xb = int(cm + cx)
if xa > 0:
xa &= 255
else:
xa = 0
if xb > 0:
xb &= 255
else:
xb = 0
r, g, b, a = _color
for i in xrange(int(round(_density * size ** 2))):
x = random.randint(1, size)
y = random.randint(1, size)
cc = random.randint(xa, xb)
cr = (cc) * (1 - a) + a * r
cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g
cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b
ca = random.random() * _opacity
ica = 1 - ca
pos = (x - 1, y - 1)
dst = pixels.get(pos, (0, 0, 0, 0))
src = (cr * ca, cg * ca, cb * ca, ca)
pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica)
for pos, col in pixels.items():
ca = col[3]
if ca:
pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255))
def _image_brushed(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, direction=None, spread=None, background=None):
if not density:
density = [0.8]
elif not isinstance(density, (tuple, list)):
density = [density]
if not intensity:
intensity = [0.5]
elif not isinstance(intensity, (tuple, list)):
intensity = [intensity]
if not color:
color = [(0, 0, 0, 0)]
elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)):
color = [color]
if not opacity:
opacity = [0.2]
elif not isinstance(opacity, (tuple, list)):
opacity = [opacity]
if not monochrome:
monochrome = [False]
elif not isinstance(monochrome, (tuple, list)):
monochrome = [monochrome]
if not direction:
direction = [0]
elif not isinstance(direction, (tuple, list)):
direction = [direction]
if not spread:
spread = [0]
elif not isinstance(spread, (tuple, list)):
spread = [spread]
def ppgen(d):
if d is None:
return
d = d % 4
if d == 0:
pp = lambda x, y, o: ((x - o) % size, y)
elif d == 1:
pp = lambda x, y, o: ((x - o) % size, (y + x - o) % size)
elif d == 2:
pp = lambda x, y, o: (y, (x - o) % size)
else:
pp = lambda x, y, o: ((x - o) % size, (y - x - o) % size)
return pp
pixels = {}
if background:
for y in xrange(size):
for x in xrange(size):
ca = float(background[3])
pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca)
loops = max(map(len, (density, intensity, color, opacity, monochrome, direction, spread)))
for l in range(loops):
_density = density[l % len(density)]
_intensity = intensity[l % len(intensity)]
_color = color[l % len(color)]
_opacity = opacity[l % len(opacity)]
_monochrome = monochrome[l % len(monochrome)]
_direction = direction[l % len(direction)]
_spread = spread[l % len(spread)]
_intensity = 1 - _intensity
if _intensity < 0.5:
cx = 255 * _intensity
cm = cx
else:
cx = 255 * (1 - _intensity)
cm = 255 * _intensity
xa = int(cm - cx)
xb = int(cm + cx)
if xa > 0:
xa &= 255
else:
xa = 0
if xb > 0:
xb &= 255
else:
xb = 0
r, g, b, a = _color
pp = ppgen(_direction)
if pp:
for y in xrange(size):
if _spread and (y + (l % 2)) % _spread:
continue
o = random.randint(1, size)
cc = random.randint(xa, xb)
cr = (cc) * (1 - a) + a * r
cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g
cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b
da = random.randint(0, 255) * _opacity
ip = round((size / 2.0 * _density) / int(1 / _density))
iq = round((size / 2.0 * (1 - _density)) / int(1 / _density))
if ip:
i = da / ip
aa = 0
else:
i = 0
aa = da
d = 0
p = ip
for x in xrange(size):
if d == 0:
if p > 0:
p -= 1
aa += i
else:
d = 1
q = iq
elif d == 1:
if q > 0:
q -= 1
else:
d = 2
p = ip
elif d == 2:
if p > 0:
p -= 1
aa -= i
else:
d = 3
q = iq
elif d == 3:
if q > 0:
q -= 1
else:
d = 0
p = ip
if aa > 0:
ca = aa / 255.0
else:
ca = 0.0
ica = 1 - ca
pos = pp(x, y, o)
dst = pixels.get(pos, (0, 0, 0, 0))
src = (cr * ca, cg * ca, cb * ca, ca)
pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica)
for pos, col in pixels.items():
ca = col[3]
if ca:
pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255))
@register('background-noise', 0)
@register('background-noise', 1)
@register('background-noise', 2)
@register('background-noise', 3)
@register('background-noise', 4)
@register('background-noise', 5)
@register('background-noise', 6)
@register('background-noise', 7)
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)
@register('background-brushed', 0)
@register('background-brushed', 1)
@register('background-brushed', 2)
@register('background-brushed', 3)
@register('background-brushed', 4)
@register('background-brushed', 5)
@register('background-brushed', 6)
@register('background-brushed', 7)
@register('background-brushed', 8)
@register('background-brushed', 9)
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 += '-' + 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)
@register('grid-image', 4)
@register('grid-image', 5)
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 = Color(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 = Color(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 = Color(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 String.unquoted(inline)
@register('image-color', 1)
@register('image-color', 2)
@register('image-color', 3)
def image_color(color, width=1, height=1):
if not Image:
raise Exception("Images manipulation require PIL")
w = int(Number(width).value)
h = int(Number(height).value)
if w <= 0 or h <= 0:
raise ValueError
new_image = Image.new(
mode='RGB' if color.alpha == 1 else 'RGBA',
size=(w, h),
color=color.rgba255,
)
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 String.unquoted(inline)