#!/usr/bin/env python

# Framer.py (version 1.0, Dec '09)
# Creates slide show pictures and composite picture
# for grandparent use in tracking grandchildren

# Requires:
#       Python (www.python.org)
#       Python Image Library (http://www.pythonware.com/products/pil/)
#       names.csv (firstname, middleName, lastName, birthday)
#       'photo' directory (picture names begin with firstName)
#       (idealy two photos per name, one landscape, one portrait)

# Ken Olsen, December 2009
# 648.ken@gmail.com
# bugled.blogspot.com


from __future__ import division
import Image, ImageDraw, sys, ImageFont, ImageOps, os, shutil

# quote for top & bottom of composite image (use '' to exclude)
textHeader = 'Grandchildren Mugshots - 2009'
textFooter = 'December 2009'

compName = 'mugshots.jpg' # name of composite file
debug = False # shows and saves troubleshooting bits
bgColors = ['white', 'black']
directories = [bgColors[0] + '/', bgColors[1] + '/', 'photos/']


def check():
    ''' Delete prior run, create directories '''
    for x in range(2):
        if os.path.exists(directories[x]):
            shutil.rmtree(directories[x])
        os.mkdir(directories[x])

    if not os.path.exists(directories[2]):
        print 'n\Photo directory not found.'
        print 'Should be in same directory as the script.\n'
        sys.exit()
    if os.path.exists('banners/'):
        shutil.rmtree('banners/')
    if debug: os.mkdir('banners/')

def loadNames():
    ''' Open names.csv and load into variable '''

    print '\nLoading "names.csv"'
    global kids
    kids = []
    try:
        data = open('names.csv', 'r')
    except:
        print "\nCouldn't find names.csv file!"
        print "Is it in same directory as python script?\n"
        sys.exit()
    for line in data:
        newLine = line.strip('\n')
        newLine2 = []
        for item in newLine.split(','):
            newLine2.append(item.strip(' '))
        if debug: print newLine2
        kids.append(newLine2)


def makeBanner(text, textColor, bgColor, width, height, maxFont = 100):
    '''Makes the top and bottome name banners'''
    im = Image.new('RGB', (width, height), bgColor)
    draw = ImageDraw.Draw(im)
    fontSize = 20
    found = False
    while not found:
        font = ImageFont.truetype('MTCORSVA.TTF', fontSize)
        textsize = draw.textsize(text, font=font)
        x,y = draw.textsize(text, font=font)
        if x < width - 20 and y < (height - 5) and fontSize < maxFont:
            fontSize = fontSize + 2
        else:
            fontSize = fontSize - 2
            found = True
    font = ImageFont.truetype('MTCORSVA.TTF', fontSize)
    x,y = draw.textsize(text, font=font)
    if debug:
        print "text: ", text, " fontSize: ", fontSize
    else:
        print '.',
    if textColor == 'white':
        xx = 10
    else:
        xx = int((width - x) / 2)
    yy = int((height - y) / 2)
    draw.text((xx,yy), text, fill=textColor, font=font)
    del draw
    return im


def revColor(color):
    '''Returns reverse color, i.e. black for white'''
    if color == 'white':
        revColor = 'black'
    else:
        revColor = 'white'
    return revColor


def getPhotoFileNames():
    """ Get names of image files in 'photo' dir """
    global photos
    print '\nFinding photos:'
    photos = []
    totalCount = 0
    allowableFormats = ['jpg', 'png']
    if debug: print os.listdir('photos')
    for fileName in os.listdir('photos'):
        if fileName[-3:].lower() in allowableFormats:
            if debug: print fileName
            photos.append(fileName)
    for kid in kids:
        try:
            firstName, middleName, lastName, birthDay, birthYear = kid
        except:
            print '\nTrouble getting data from "names.csv".\n' + \
                  'Check data for extra spaces or commas.\n'
            sys.exit()
        photoCount = 0
        for photo in photos:
            photoName = photo.capitalize()
            if photoName.find(firstName) == 0:
                photoCount = photoCount + 1
                totalCount = totalCount + 1
        print photoCount, 'photos for', firstName
    print totalCount, '/', len(os.listdir('photos')), 'files in "photo/" identified.'
    #todo: list files not identified?


def getPhotos(firstName, bgColor):
    '''find image files, determine landscape & portrait'''
    
    imageNames = []     # ['portrait', 'landscape']
    ratios = []         # aspect ratios
    images = []         # holds actual images
    for photo in photos:
        photoName = photo.capitalize()
        if photoName.find(firstName) == 0:
            imageNames.append(photoName)
            image = Image.open('photos/' + photoName)
            width, height = image.size
            ratio = width / height
            ratios.append(ratio)
    if len(imageNames) > 1: # got two, put portrait first
        if ratios[1] > ratios[0]:
            images.append(resize(imageNames[0], (360, 480), bgColor, 'right'))
            images.append(resize(imageNames[1], (420, 315), bgColor, 'left'))
        else:
            images.append(resize(imageNames[1], (360, 480), bgColor, 'right'))
            images.append(resize(imageNames[0], (420, 315), bgColor, 'left'))
    else: #only one image found
        if len(imageNames) > 0:
            images.append(resize(imageNames[0], (360, 480), bgColor))
    return images


def stitchImage(kid, bgColor, index):
    '''Add banners to picture and save'''

    firstName, middleName, lastName, birthDay, birthYear = kid
    order = str(index + 1)
    if index < 9: order = '0' + order
    textColor = revColor(bgColor)
    text = directories[2] + firstName + '_' + bgColor + '1.png'
    if debug: print 'Image.open(' + text + ')'
    images = getPhotos(firstName, bgColor)
    if bgColor == 'black': width = 430
    else: width = 360

    bannerTop = makeBanner(firstName + ' ' + middleName, textColor,
                           bgColor, width, 100, 76)
    bannerBottom = makeBanner(birthDay + ', ' + birthYear, textColor,
                              bgColor, width, 60, 55) 
    if debug:
        bannerTop.save('banners/' + firstName + '_' + bgColor + '1.png')
        bannerBottom.save('banners/' + firstName + '_' + bgColor + '2.png')
    if bgColor == 'black':
        canvas = Image.new('RGB', (800, 480), bgColor)
        canvas.paste(bannerTop,(370, 0))
        canvas.paste(bannerBottom,(370, 410))
        if len(images) > 0:
            canvas.paste(images[0], (0, 0))
            if len(images) > 1:
                canvas.paste(images[1], (380, 100))
    else:  # stitch white
        canvas = Image.new('RGB', (360, 640), bgColor)
        canvas.paste(bannerTop,(0, 0))
        if len(images) > 0:
            images[0] = ImageOps.crop(images[0], border=10)
            canvas.paste(images[0], (10, 100))
        canvas.paste(bannerBottom,(0, 580))        
        canvas = ImageOps.crop(canvas, border=2)            
        canvas = ImageOps.expand(canvas, border=2, fill='black')

    text = bgColor + '/' + order + '_' + firstName + '.jpg'
    canvas.save(text, quality=90)
    if debug: print 'im.save(' + text + ')'
    else: print '.',


def resize(fileName, targetSize, bgColor, align):
    '''Given a targetSize, resize image and paste onto background'''

    image = Image.open('photos/' + fileName)
    canvas = Image.new('RGB', targetSize, bgColor)  #blank canvas
    targetWidth, targetHeight = targetSize          #desired final size
    newWidth, newHeight = targetSize
    pasteWidth, pasteHeight = (0, 0)                #coords for paste
    width, height = image.size
    if width / height > newWidth / newHeight:       #add height
        newHeight = int(newWidth / width * height)
        if bgColor == 'white': pasteHeight = 0
        else: pasteHeight = int((targetHeight - newHeight) / 2)
    else: #add width
        newWidth = int(newHeight / height * width)
        if bgColor == 'white':
            pasteWidth =  int((targetWidth - newWidth) / 2)
        else:
            if align == 'right':
                pasteWidth = int(targetWidth - newWidth)
    image = image.resize((newWidth, newHeight))
    if debug:
        print ' in=(', width, ', ', height, ')',
        print ' target=(', targetWidth, ', ', targetHeight, ')',
        print ' resize=(', newWidth, ', ', newHeight, ')',
        print ' paste@(', pasteWidth, ', ', pasteHeight, ')'
    canvas.paste(image, (pasteWidth, pasteHeight))
    return canvas


def stitchComposite():
    '''Creates composite sheet of white background images'''

    index = 0
    photos = []
    allowableFormats = ['jpg', 'png']
    if debug: print os.listdir('white')
    for fileName in os.listdir('white'): #get filenames
        if fileName[-3:].lower() in allowableFormats:
            if debug: print fileName
            photos.append(fileName)
    numberPhotos = len(photos)
    print '\nStitching', numberPhotos, 'images into composite',           

    # figure out table size
    if numberPhotos <= 10: rows = 2
    else:
        if 10 < numberPhotos < 25: rows = 3
        else: rows = 4
    columns = int(round(numberPhotos / rows))
    print 'table of ', rows, 'rows by', columns, 'columns.'

    width = columns * 360
    height = rows * 640
    canvas = Image.new('RGB', (width, height), 'white')

    for y in range(rows):
        yy = y * 640
        for x in range(columns):
            xx = x * 360
            if debug:
                print 'xx: ', xx, ' yy: ', yy,
            else:
                print '.',
            if index < len(photos):
                if debug: print 'Image.open(' + photos[index] + ')'
                photo = Image.open('white/' + photos[index])
                canvas.paste(photo,(xx, yy))
            y = y + 1
            index = index + 1
        x = x + 1

    if textHeader <> '': # add quote to top
        height = height + 100
        bannerTop = makeBanner(textHeader, 'blue', 'white', width, 100, 80)
        newCanvas = Image.new('RGB', (width, height), 'white')
        newCanvas.paste(bannerTop, (0, 0))                  #top banner
        newCanvas.paste(canvas, (0, 100))                   #pictures
        canvas = newCanvas

    if textFooter <> '': # add date to bottom
        height = height + 100
        bannerBottom = makeBanner(textFooter, 'gray', 'white', width, 100, 60)
        newCanvas = Image.new('RGB', (width, height), 'white')
        newCanvas.paste(bannerBottom, (0, height - 100))    #bottom banner
        newCanvas.paste(canvas, (0, 0))                     #pictures
        canvas = newCanvas

    canvas = ImageOps.expand(newCanvas, border=50, fill='white') #add border
    canvas.save(compName, quality=90)
    print '\nComposite image saved as "' + compName + '".'


if __name__ == '__main__':

    check()                 # find or make directories
    loadNames()             # open names.csv and parse
    getPhotoFileNames()     # parse files in 'photo' dir
    print '\nSTITCHING IMAGES',
    for kid in kids:        # stitch banners & photos
        stitchImage(kid, 'white', kids.index(kid))
        stitchImage(kid, 'black', kids.index(kid))
        if not debug: print '.',
    stitchComposite()       # create composite picture 
    print '\nDONE!'
