#!/usr/bin/python2.5
import werkzeug
from werkzeug.exceptions import HTTPException
import wsgiref.handlers
from PIL import Image, ImageDraw, ImageFont, ImageEnhance
from cStringIO import StringIO
import random, itertools
if not hasattr(itertools, 'izip_longest') :
def izip_longest(*args, **kwds):
# izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
fillvalue = kwds.get('fillvalue')
def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
yield counter() # yields the fillvalue, or raises IndexError
fillers = itertools.repeat(fillvalue)
iters = [itertools.chain(it, sentinel(), fillers) for it in args]
try:
for tup in itertools.izip(*iters):
yield tup
except IndexError:
pass
itertools.izip_longest = izip_longest
class Defaults :
# settings
text = [
"aalto",
"unive",
"rsity"
]
chars = [ '"', '!', '?' ]
line_colors = [
"#0469af",
"#fbc614",
"#e1313b",
]
font_name = 'helvetica'
font_size = 30
background_color = "#ffffff"
line_spacing = -10
sharpness = 0.6
fonts = {
'dejavu-sans-bold': "/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf",
'helvetica': "HELR65W.TTF",
}
# enable debugging
debug = True
def randomize (seq) :
"""
Returns the given sequence in random order as a list
"""
# copy
l = list(seq)
# rearrange
random.shuffle(l)
return l
def build_data (text, chars, line_colors) :
"""
Returns a matrix of (text, color) tuples representing the data to render
[ [ (str, str) ] ]
text - list of lines
chars - list of random chars to interpse
line_colors - list of colors to draw the chars in
"""
data = []
for line, char, color in itertools.izip_longest(text, chars, line_colors, fillvalue=None) :
# pick position to place char
pos = random.randint(1, len(line) - 1)
if not color :
color = "#000000"
# split into three parts
if char :
data.append([
(line[:pos], "#000000"),
(char, color),
(line[pos:], "#000000"),
])
else :
data.append([
(line, "#000000"),
])
return data
def load_font (font_name, font_size) :
"""
Load a font by name
"""
# load font
font_path = fonts[font_name]
font = ImageFont.truetype(font_path, font_size)
return font
def render_img (data, font, background_color="#ffffff", line_spacing=0) :
"""
Render the data (as from build_data) as an image, using the given PIL.ImageFont, and return the PIL Image object
"""
img_width = img_height = 0
img_data = []
# compute image width/height
for segments in data :
line_width = line_height = 0
# build a new list of segments with additional info
line_segments = []
for seg_text, seg_color in segments :
# compute rendered text size
seg_width, seg_height = font.getsize(seg_text)
# update line_*
line_width += seg_width
line_height = max(line_height, seg_height)
# build the new segments list
line_segments.append((seg_text, seg_color, seg_width))
# update img_*
img_width = max(img_width, line_width)
img_height += line_height
img_data.append((line_segments, line_height))
# calculate height needed for line spacing
img_height += (len(img_data) - 1) * line_spacing
# create image
img = Image.new("RGB", (img_width, img_height), background_color)
draw = ImageDraw.Draw(img)
# draw text
img_y = 0
for segments, line_height in img_data :
img_x = 0
# draw each segment build above, incremeing along img_x
for seg_text, seg_color, seg_width in segments :
draw.text((img_x, img_y), seg_text, font=font, fill=seg_color)
img_x += seg_width
img_y += line_height + line_spacing
return img
def effect_sharpness (img, factor) :
"""
Sharpen the image by the given factor
"""
return ImageEnhance.Sharpness(img).enhance(factor)
def build_png (img) :
"""
Write the given PIL.Image as a string, returning the raw binary data
"""
# render PNG output
buf = StringIO()
img.save(buf, "png")
data = buf.getvalue()
return data
def arg_bool (val) :
if val.lower() in ('true', 't', '1', 'yes', 'y') :
return True
elif val.lower() in ('false', 'f', '0', 'no', 'n') :
return False
else :
raise ValueError(val)
def arg_color (val) :
if val.beginswith('#') :
int(val[1:], 16)
return val
else :
raise ValueError(val)
def handle_request (req) :
# parse args
if 'text' in req.args :
text = req.args.getlist('text', unicode)
else :
text = Defaults.text
if 'chars' in req.args :
chars = req.args.getlist('chars', unicode)
else :
chars = Defaults.chars
if 'colors' in req.args :
colors = req.args.getlist('colors', arg_color)
else :
colors = Defaults.line_colors
font_name = req.args.get('font', Defaults.font_name)
font_size = req.args.get('font-size', Defaults.font_size, int)
background_color = req.args.get('background-color', Defaults.background_color, arg_color)
line_spacing = req.args.get('line-spacing', Defaults.line_spacing, int)
sharpness = req.args.get('sharpness', Defaults.sharpness, float)
# put the chars in random order by default
if req.args.get('random-chars', True, arg_bool) :
chars = randomize(chars)
# load/prep resources
data = build_data(text, chars, colors)
font = load_font(font_name, font_size)
# render the image
img = render_img(data, font, background_color, line_spacing)
img = effect_sharpness(img, sharpness)
png_data = build_png(img)
# build the response
response = werkzeug.Response(png_data, mimetype='image/png')
return response
@werkzeug.Request.application
def wsgi_application (request) :
"""
Our werkzeug WSGI handler
"""
try :
# request -> response
response = handle_request(request)
return response
except HTTPException, e :
# return as HTTP response
return e
def main () :
handler = wsgiref.handlers.CGIHandler()
app = wsgi_application
if debug :
# enable debugging
app = werkzeug.DebuggedApplication(app, evalex=False)
handler.run(app)
if __name__ == '__main__' :
main()