Hi all, Here is a way to add text, see attachment. I've extended the test cases with capitols with diacritics. On the list a question regarding this arose, so it was good to double check this.
If possible, I would like to have this module included into PIL. Once I had it revied, but would like to resubmit it again since the PIL list has become more active again, especially regarding text overlays. Regards, Pander Stani wrote: > Op woensdag 14-01-2009 om 19:42 uur [tijdzone -0800], schreef Bernard > Rankin: >> >>> Phatch has already a text action which has done the hard work already as >>> well. Just start from that as a base and hack it to use a csv file as >>> input instead of a fixed text. If you have played with python and PIL >>> already, it should be not that hard. >>> >> Thanks for the help. >> >> I've taken a look at the tutorial and the existing text.py action. >> >> I am new to Python, and have never used PIL, but it does look do-able.. >> >> A couple of questions on the existing text action: >> >> 1) Is the the "cache" dict the only recommended way to maintain state >> between invocations? > Yes. >> 2) What is the "values" method of the Action class in the text.py file? > The values method convert relative values (given by the user) into > absolute values based on image properties (height, width, ...). For > example for a pixel value a user can enter absolute pixels, metrics (cm, > mm, ...) or a percentage. In the end we need absolute pixels to work > with PIL. So metrics are converted to pixels by multiplying with the > dpi. Percentage are calculated by multiplying with the appropriate image > property. For example for a horizontal offset of 5%, we take 5% of the > image width. The font size is based on the average of the image height > and width. > >> 3) What does the "global" statement in the init() method do, and why is it >> there? > Phatch uses a very simple plugin mechanism. It imports all the actions > at startup. In order not to load modules which might not be used and to > keep one action limited to one file, we need a lazy import for modules > which are only necessary when executing the action. Therefore they are > grouped in an init method and global is used to make them globally > available. > >> 4) What is line 38 trying to do, and why is it in the if clause? > If no (truetype) font is given, Phatch will use the default PIL font > which AFAIK can not handle unicode (e.g. chinese characters). Therefore > text needs to be converted to ascii. Phatch choses to replace a > character with a ? if it can't be encoded. >> Also, was this file based on the Watermark code? Many if the code comments >> would indicate so. > Indeed. Many actions are based on other ones. This is the fastest way to > write new actions. > >> (Running out of Ubuntu Intrepid repo) > If you start developing, please checkout phatch from the bazaar > repository (bzr lp:phatch) >> Where can I find further docs on the plug-in / action API? > I guess you've read already: > http://photobatch.wikidot.com/writing-actions > > The best is to study and understand the source of the other actions and > see them in action. >> Thank you > You're welcome! > > Stani > > _______________________________________________ > Image-SIG maillist - Image-SIG@python.org > http://mail.python.org/mailman/listinfo/image-sig
# -*- coding: utf-8 -*- # # The Python Imaging Library. # $Id: ImageText.py 2009-01-29 0.2 Pander$ # # PIL text as images # # History: # 2008-06-26 0.1 Pander initial publication to be reviewed # 2009-01-29 0.2 Pander extended unicode tests with capitals with diacritics # # 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='eèéëêøæüÃÃÃÃ', filename='eèéëêøæüÃÃÃÃ.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='eèéëêøæüÃÃÃÃ', filename='eèéëêøæüÃÃÃÃ.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)
_______________________________________________ Image-SIG maillist - Image-SIG@python.org http://mail.python.org/mailman/listinfo/image-sig