Fun hack of the day: picture postcards

You can now send any picture from my collection as a postcard. Here’s an example from the recent trip to Karwar.

Postcards are delivered as email attachments, not URLs, so they last forever. The font used is Comic Sans MS. If you have a suggestion for another free True Type font, tell me.

Here's the source code, available under a BSD-like license: part #1,
the Web form in DTML, part #2, small middle layer, and part #3, the code that generates the postcard:

# Import ImageFile even though unnecessary to prevent conflict with
# Zope's ImageFile module.
from PIL import ImageFile, Image, ImageFont, ImageDraw
from cStringIO import StringIO
import os.path

card_width = 620
outline_color = (0, 0, 0)
text_color = (0, 0, 0)

fontpath = '/Users/jace/Zope/Extensions'
fonts = {
    'Marker Felt': 'MarkerFeltWide.ttf',
    'Comic Sans': 'comic.ttf',
    }
fontsize = 16
linespacing = -3
padding = 7

class dummyRequest:
  class dummyRes:
    def setHeader(*args, **kw):
      pass
    def write(self, data):
      self.data  = data
    def __init__(self):
      self.data = ''
  def get_header(*args, **kw):
    pass
  def setHeader(*args, **kw):
    pass
  def __init__(self):
    self.RESPONSE = dummyRequest.dummyRes()

def makePostcard(imageob, message="Your message here.", fontface=''):
    """
    Returns a postcard with the message written on it.
    """

    # XXX: Utter ugliness thanks to gaps in the ExtFile API
    dreq = dummyRequest()
    imagedata = imageob.index_html(REQUEST=dreq, RESPONSE=dreq,
        display='postcard')
    if not imagedata: imagedata = dreq.RESPONSE.data

    infile = StringIO(imagedata)

    image = Image.open(infile)
    card = Image.new('RGB', (card_width, image.size[1]), (255, 255, 255))
    draw = ImageDraw.Draw(card)
    fontfile = os.path.join(fontpath, fonts.get(fontface, 'comic.ttf'))
    font = ImageFont.truetype(fontfile, fontsize)


    # Paste image
    if image.mode == 'RGBA':
        card.paste(image, (0, 0), image)
    else:
        card.paste(image, (0, 0))


    # Draw outline
    draw.rectangle((0,0,image.size[0]-1, card.size[1]-1),
        outline = outline_color)
    draw.rectangle((0,0,card_width-1, card.size[1]-1),
        outline = outline_color)


    # Write message -- tricky part

    message = message.strip()
    message = message.replace('\r\n', '\n')
    message = message.replace('\r', '\n')
    lines = message.split('\n')

    awidth = card.size[0] - image.size[0] - (padding*2)
    yoffset = 10

    segments = []
    for line in lines:
        line = line.replace('\t', ' ')
        lw = draw.textsize(line, font = font)[0]
        if lw <= awidth:
            segments.append(line)
        else:
            words = line.split(' ')
            while words:
                segment = ''
                wc = 0
                for word in words:
                    if draw.textsize((segment ' ' word).strip(),
                        font=font)[0] > awidth:
                        break
                    segment = (segment   ' '   word).strip()
                    wc  = 1
                for x in range(wc):
                    words.pop(0)
                # In case the word is too long:
                if segment == '':
                    segment = words.pop(0)
                segments.append(segment)

    for segment in segments:
        draw.text((image.size[0] padding, yoffset), segment,
            fill = text_color, font = font)
        yoffset  = draw.textsize(segment, font = font)[1]   linespacing

    outfile = StringIO()
    card.save(outfile, 'JPEG', quality=100)
    outfile.seek(0)
    return outfile.read()
  • Avatar

    bluesmoon — Nov 22, 2003 12:01:57 PM — #

    Hey, great idea. I think I'll do something similar for my pics.

    BTW, where can I get a good hosting service? Even if it's only for my pics and travelogs... those are the things I need permanent links for.
    • Avatar

      hserus — Nov 22, 2003 5:35:35 PM — #

      http://www.pair.com is really good.
      • Avatar

        satyap — Nov 22, 2003 7:16:48 PM — #

        My stuff's on csoft.net, so far so good. I looked at pair.com, can anyone say why it's better?
        • Avatar

          hserus — Nov 22, 2003 7:32:25 PM — #

          I've personally used pair.com and know other people who will recommend pair.com too. They have been around for years, have excellent customer service, and a really good antispam policy.
  • Avatar

    ravi — Nov 22, 2003 5:09:11 PM — #

    w00t!
    Excellent work, that! I'm gonna use it, sometime.

    How's Sparky doing? Damn, ages ... :
    • Avatar

      Kiran Jonnalagadda — Nov 23, 2003 3:21:09 AM — #

      Re: w00t!
      Sparky's several hundred kilometres away from where I am right now. But I'll be seeing him in a week. He's three months short of eleven years now, and still active as ever.
  • Avatar

    tariquesani — Nov 23, 2003 1:04:02 AM — #

    I see no antispam features to protect you and your website... (atleast some headers / disclaimers added)

    Also as usability goes I would prefer an option of send as link OR send as attachment
    • Avatar

      Kiran Jonnalagadda — Nov 23, 2003 2:52:11 AM — #

      There is an X-Sender-Ip-Address header. Sending as a link would require maintaining server side sessions, which is more work than I'm willing to put into this. :) Besides, I really hate greeting card services that send a link that expires in a few months. I'd much rather have a permanent copy of the card.

      But you're right about the disclaimer. I'll add that and a notice that the postcard is sent as a mail attachment, not a link.
  • Avatar

    Anonymous — Nov 23, 2003 2:29:17 PM — #

    That's nice -Premshree
  • Avatar

    evan — Nov 25, 2003 12:42:41 AM — #

    "bitstream vera" is a family of free truetype fonts that all the linuxers use these days.
    • Avatar

      Kiran Jonnalagadda — Nov 27, 2003 2:28:27 AM — #

      Thanks, Vera looks good, but I'm looking for casual fonts, something that can pass for handwriting.

      I've found a few interesting freeware and cheap shareware fonts. If PIL renders them decently enough, I'll add font selection support in a day or two.

Leave a Reply

You can respond with a photo by tagging it on Flickr with