index.cgi
author Tero Marttila <terom@fixme.fi>
Tue, 05 May 2009 17:23:40 +0300
changeset 5 6d0e03f2fef4
parent 4 c5bd0b75b59c
child 6 f61000aa264b
permissions -rwxr-xr-x
replace zip with itertools.izip_longest
#!/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()