index.cgi
author Tero Marttila <terom@fixme.fi>
Tue, 05 May 2009 17:18:04 +0300
changeset 4 c5bd0b75b59c
parent 3 ac063212bd67
child 5 6d0e03f2fef4
permissions -rwxr-xr-x
fix random-chars
#!/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


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 zip(text, chars, line_colors) :
        # pick position to place char
        pos = random.randint(1, len(line) - 1)
        
        # split into three parts
        data.append([
                (line[:pos], "#000000"),
                (char, color),
                (line[pos:], "#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()