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