[Image-SIG] Proposed ImageText.py, please review

Pander pander at users.sourceforge.net
Fri Jun 27 20:04:24 CEST 2008


Hi allm

Please review ImageText.py which I would like to propose to be included
in the next version of PIL.

Regards,

Pander

contents of ImageText.py:
=========================

# -*- coding: utf-8 -*-
#
# The Python Imaging Library.
# $Id: ImageText.py 2008-06-26 0.1 Pander$
#
# PIL text as images
#
# History:
# 2008-06-26 0.1 Pander initial publication to be reviewed
#
# Inspired by http://plone.org/products/TextImage which is from the same
author.
#
# Todo:
# - Implement functionality to use background image.
# - Implement workaround for problem with invisible first character for
small
#   fonts, until this bug is fixed in ImageFont.
# - Implement unit test that can verify output images.
# - Implement returning result as a MIME string.
#
# See the README file for information on usage and redistribution.
#

import PIL.Image
import PIL.ImageDraw
import PIL.ImageFont
import os
from hashlib import md5

##
# The <b>ImageText</b> module defines a class with the same name.
# It offers static methods to create images displaying specified text by
using
# the ImageText and ImageDraw classes.
# <p>
# Either labels or wrapped paragraphs for UTF-8 text can be generated with a
# range of parameters like output file (name and directory), font
(align, path,
# size and color) and background (width, height, color, transparency)
and more.
# <p>
# Effort has been made in setting sane default values for all
parameters. See
# test cases.
#
# @see ImageDraw#ImageDraw.text
# @see ImageFont#ImageText.truetype
class ImageText:
    "PIL font wrapper"

##
# Get width and height for specified text if it would be rendered in
specified font.
#
# @param text Text for which width will be measured. Default is set to ' '.
# @param font Font to be used to measure with.
# @return Text width and height in a list of integers.
    def getWidthHeight(font, text=' '):
        # determine text width and height
        return PIL.ImageDraw.Draw(PIL.Image.new(mode='RGB', size=(0,
0))).textsize(text, font=font)
    getWidthHeight = staticmethod(getWidthHeight)

##
# Get width for specified text if it would be rendered in specified font.
#
# @param text Text for which width will be measured.
# @param font Font to be used to measure with.
# @return Text width as integer.
    def getWidth(font, text=' '):
        # determine text width
        return PIL.ImageDraw.Draw(PIL.Image.new(mode='RGB', size=(0,
0))).textsize(text, font=font)[0]
    getWidth = staticmethod(getWidth)

##
# Generate label with specified text rendered in it.
#
# @param text Text that will be rendered as a label, i.e. a single line
of text.
# @param filename Name of output file which will have to have '.png'
extension. Default is MD5 hash according to unique set of specified
parameters with '.png' extension.
# @param directory Directory to which output file will be writting.
Default is the current directory '.'.
# @param fontAlign Align text lef, center, centre or right. Default is left.
# @param fontPath Path to font used for redering. When set to '' or
None, a default font will be used.
# @param fontSize Font size used in rendering. Default is 16.
# @param fontColor Font color used in rendering Default is '#000000'
which is black.
# @param widht Width of the resulting background image. Default is -1,
which results in automatic setting.
# @param height Height of the resulting backgound image. Default is -1,
which results in automatic setting.
# @param color Color of the resulting backgroun image like '#FF0000' or
transparancy when set to 'transparent'.
# @param quality Image quality of output image file format. Default is
100 which means no data reduction.
# @param overwrite Should existing generated image be overwritten.
# @return String of path to generated image.
# @exception SyntaxError If align is not 'left', 'center', 'centre' or
'right'.
    def generateLabel(text='The quick brown fox jumps over the lazy dog.',
                 filename='',
                 directory='.',
                 fontAlign='left',
                 fontPath='',
                 fontSize=16,
                 fontColor='#000000',
                 width=-1,
                 height=-1,
                 color='transparent',
                 quality=100,
                 overwrite=False):
        # validate parameters
        fontAlign = fontAlign.lower()
        if fontAlign not in ('left', 'center', 'centre', 'right'):
            raise SyntaxError('Unsupported align ' + fontAlign)
        fontColor = fontColor.lower()
        color = color.lower()

        # configure font
        font = None
        if fontPath == '' or fontPath == None:
            font = PIL.ImageFont.load_default()
        else:
            font = PIL.ImageFont.truetype(fontPath, fontSize)

        # determine text width and height
        w, h = ImageText.getWidthHeight(font=font, text=text)
        if width == -1:
            width = w
        if height == -1:
            height = h

        # create filename
        if filename == '' or filename == None:
            hash = md5()
            hash.update(text)
            hash.update(fontAlign)
            hash.update(fontPath)
            hash.update(str(fontSize))
            hash.update(fontColor)
            hash.update(str(width))
            hash.update(str(height))
            hash.update(color)
            hash.update(str(quality))
            hash.update('Label')
            filename = str(hash.hexdigest()) + '.png'
        path = directory + '/' + filename
        if not overwrite and os.path.exists(path):
            return filename

        # align text
        x = 0
        if fontAlign == 'left':
            x = 0;
        elif fontAlign in ('center', 'centre'):
            x = (width - w) / 2
        elif fontAlign == 'right':
            x = width - w;
        y = (height - h) / 2

        image = None
        if color == 'transparent':
            image = PIL.Image.new(mode='RGBA', size=(width, height),
color=(0,0,0,0))
        else:
            image = PIL.Image.new(mode='RGB', size=(width, height),
color=color)
        image.info['quality'] = quality;
        draw = PIL.ImageDraw.Draw(image)
        draw.text((x, y), text.decode('utf-8'), font=font, fill=fontColor)
        image.save(path, quality=quality)

        return filename
    generateLabel = staticmethod(generateLabel)

##
# Generate paragraph with specified text wrapped and rendered in it.
#
# @param text Text that will be rendered as a label, i.e. a single line
of text.
# @param filename Name of output file which will have to have '.png'
extension. Default is MD5 hash according to unique set of specified
parameters with '.png' extension.
# @param directory Directory to which output file will be writting.
Default is the current directory '.'.
# @param fontAlign Align text lef, center, centre or right. Default is left.
# @param fontPath Path to font used for redering. When set to '' or
None, a default font will be used.
# @param fontSize Font size used in rendering. Default is 16.
# @param fontColor Font color used in rendering Default is '#000000'
which is black.
# @param widht Width of the resulting background image. Default is -1,
which results in automatic setting.
# @param height Height of the resulting backgound image. Default is -1,
which results in automatic setting.
# @param color Color of the resulting backgroun image like '#FF0000' or
transparancy when set to 'transparent'.
# @param quality Image quality of output image file format. Default is
100 which means no data reduction.
# @param overwrite Should existing generated image be overwritten.
# @return String of path to generated image.
# @exception SyntaxError If align is not 'left', 'center', 'centre' or
'right'.
# @exception SyntaxError If unable to wrap text because width is too small.
    def generateParagraph(text='Lorem ipsum dolor sit amet, consectetur
adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa
qui officia deserunt mollit anim id est laborum.',
                 filename='',
                 directory='.',
                 fontAlign='left',
                 fontPath='',
                 fontSize=16,
                 fontColor='#000000',
                 width=512,
                 height=-1,
                 color='transparent',
                 quality=100,
                 overwrite=False):
        # validate parameters
        if text.find(' ') == -1:
            return ImageText.generateLabel(text=text,
                 filename=filename,
                 directory=directory,
                 fontAlign=fontAlign,
                 fontPath=fontPath,
                 fontSize=fontSize,
                 fontColor=fontColor,
                 width=width,
                 height=height,
                 color=color,
                 quality=quality,
                 overwrite=overwrite)
        fontAlign = fontAlign.lower()
        if fontAlign not in ('left', 'center', 'centre', 'right'):
            raise SyntaxError('Unsupported align ' + fontAlign)
        fontColor = fontColor.lower()
        color = color.lower()

        # configure font
        font = None
        if fontPath == '' or fontPath == None:
            font = PIL.ImageFont.load_default()
        else:
            font = PIL.ImageFont.truetype(fontPath, fontSize)

        # determine lines
        words = text.split()
        lines = []
        first = True
        line = ''
        lineWidth = 0
        maxWidth = 0
        spaceWidth, h = ImageText.getWidthHeight(font=font)
        for word in words:
            wordWidth = ImageText.getWidth(font=font, text=word)
            if (wordWidth > width):
                raise SyntaxError('Unable to wrap text because width is
too small')
            if first:
                first = False
                line = word
                lineWidth = wordWidth
            else:
                if (lineWidth + spaceWidth + wordWidth > width):
                    lines.append((lineWidth, line))
                    if (lineWidth > maxWidth):
                        maxWidth = lineWidth
                    line = word
                    lineWidth = wordWidth
                else:
                    line = line + ' ' + word
                    lineWidth = lineWidth + spaceWidth + wordWidth
        if not first:
            lines.append((lineWidth, line))

        # determine text width and height
        w = maxWidth
        if height == -1:
            height = len(lines) * h

        # create filename
        if filename == '' or fileName == None:
            hash = md5()
            hash.update(text)
            hash.update(fontAlign)
            hash.update(fontPath)
            hash.update(str(fontSize))
            hash.update(fontColor)
            hash.update(str(width))
            hash.update(str(height))
            hash.update(color)
            hash.update(str(quality))
            hash.update('Paragraph')
            filename = str(hash.hexdigest()) + '.png'
        path = directory + '/' + filename
        if not overwrite and os.path.exists(path):
            return filename

        # align text
        x = 0
        y = (height - len(lines) * h) / 2

        # create image
        image = None
        if color == 'transparent':
            image = PIL.Image.new(mode='RGBA', size=(width, height),
color=(0,0,0,0))
        else:
            image = PIL.Image.new(mode='RGB', size=(width, height),
color=color)
        image.info['quality'] = quality;
        draw = PIL.ImageDraw.Draw(image)
        for line in lines:
            if fontAlign == 'left':
                x = 0;
            elif fontAlign in ('center', 'centre'):
                x = (width - line[0]) / 2
            elif fontAlign == 'right':
                x = width - line[0];
            draw.text((x, y), line[1].decode('utf-8'), font=font,
fill=fontColor)
            y = y + h
        image.save(path, quality=quality)

        return filename
    generateParagraph = staticmethod(generateParagraph)

##
# Test code in main.
#
if __name__ == '__main__':
    # tests for label

    # default
    print ImageText.generateLabel()
    # m- and x-width as well as total height with background color
    print ImageText.generateLabel(text='moooom', color='#ffffff')
    print ImageText.generateLabel(text='MbpqdM', color='#ffffff')
    # alignment
    print ImageText.generateLabel(text='left', color='#ffffff',
width=128, overwrite=True)
    print ImageText.generateLabel(text='center', color='#ffffff',
width=128, fontAlign='center', overwrite=True)
    print ImageText.generateLabel(text='right', color='#ffffff',
width=128, fontAlign='right', overwrite=True)
    # unicode
    print ImageText.generateLabel(text='èéëêøæ', filename='èéëêøæ.png',
color='#ffffff', fontPath='/usr/share/fonts/truetype/freefont/FreeSans.ttf')
    # sometimes first character of slim fonts are not displayed
    print ImageText.generateLabel(text='iiiiii', color='#ffffff')
    print ImageText.generateLabel(text=' iiiiii', color='#ffffff')
    print ImageText.generateLabel(text='iiiiii', color='#ffffff',
fontPath='/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Regular.ttf')
    print ImageText.generateLabel(text=' iiiiii', color='#ffffff',
fontPath='/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Regular.ttf')
    print ImageText.generateLabel(text='iiiiii=6*i without preceeding
space', color='#ffffff')
    print ImageText.generateLabel(text='iiiiii=6*i without preceeding
space', color='#ffffff',
fontPath='/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Regular.ttf')
    print ImageText.generateLabel(text=' iiiiii=6*i with spaceding
space', color='#ffffff',
fontPath='/usr/share/fonts/truetype/ttf-liberation/LiberationSerif-Regular.ttf')
    print ImageText.generateLabel(text=' iiiiii=6*i with spaceding
space', color='#ffffff')

    # tests for paragraph

    # default with overwriting already existing output
    print ImageText.generateParagraph(overwrite=True)
    # alignment
    print ImageText.generateParagraph(fontAlign='center', overwrite=True)
    print ImageText.generateParagraph(fontAlign='right', overwrite=True)
    print ImageText.generateParagraph(fontAlign='right', width=320,
height=320, overwrite=True)
    # unicode
    print ImageText.generateParagraph(text='èéëêøæü',
filename='èéëêøæü.png', color='#ffffff',
fontPath='/usr/share/fonts/truetype/freefont/FreeSans.ttf')

# old code
#    def getDefaultFontPath():
#        # most common sans font on Linux
#        fontPath='/usr/share/fonts/truetype/freefont/FreeSans.ttf'
#        if not os.path.exists(fontPath):
#            # most common sans font on OSX
#            fontPath='/Library/Fonts/Arial.ttf'
#        if not os.path.exists(fontPath):
#            # most common sans font on Windows
#            fontPath = 'C:/WINDOWS/Fonts/arial.ttf'
#        if not os.path.exists(fontPath):
#            raise SyntaxError('Cound not find default font')
#        return fontPath
#    getDefaultFontPath = staticmethod(getDefaultFontPath)













.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ImageText.py
Type: text/x-python
Size: 15253 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/image-sig/attachments/20080627/65aad54b/attachment-0001.py>


More information about the Image-SIG mailing list