Skip to main content

Adventures in Pillow Part 2

From my last post where I made a slideshow promo video, I realised how powerful Pillow is. I still have a fair bit of automation to do, so another thing I do a lot of is motivational quotes for Twitter (I am aware they are fucking stupid, but they share well)



The breakdown of the components are a background with a filter, the quote, who said it and our logo.

Background and Filter

To start off I am going to create a folder called quote and add in an image and just name it background.jpg (sourced from Pixabay). Next I import PIL and open the file.

from PIL import Image
import os, errno

cwd = os.getcwd()
image_dir = os.path.join(cwd, 'quote')

def filterImage(imageFile):
    im = Image.open(imageFile)
    im.show()

filterImage(os.path.join(image_dir, 'background.jpg'))




Next I need to crop the image. I am going to take the centre portion out of the image to make square. Like so:

So I start off with getting the dimensions of the image with:

im.size

I get a tuple of (1920, 1080), so my square is going to by the height of my image squared, so I will add that as a variable.

cropDim = im.size[1]

Crop is a rectangular region from this image. The box is a 4-tuple defining the left, upper, right, and lower pixel coordinate. Upper is 0, and lower is my cropDim. To find the left and right I half the width (im.size[0] / 2) to give me the centre and then I minus half the cropDim for the left and add for the right.


def filterImage(imageFile):
    ...
    cropDim = im.size[1]
    left = (im.size[0] / 2) - (cropDim / 2)
    right = (im.size[0] / 2) + (cropDim / 2)
    im = im.crop((left, 0, right, cropDim))
    im.show()


Awesome, now to create the filter I am going to create a new image with an alpha mask 'RGBA', fill it with a colour and set a transparency. then paste onto the image. The image, the filter and then the paste in show in the screenshot below from left to right.


def filterImage(imageFile):
    ...

    filter = Image.new("RGBA", im.size, (62, 39, 35, 127))
    im.paste(filter, None, filter)


Apart from fiddling about with the filter, this looks good.

Quote

From here going to some text now. First going to import ImageDraw and ImageFont modules

from PIL import Image, ImageFont, ImageDraw

Next I am going pass a quote and author tuple as an argument in the function. Now going to import my font, which I have just downloaded from Google Fonts and saved into the directory, and create a draw object from the image. This will allow me to draw things on the image, in this case the quote. I position it 10px from the left and half way up, i.e. cropDim / 2, this is as x, y coordinates. Finally I use draw.text and pass my x, y, the text, the colour (in this case white) and then the font.



def filterImage(imageFile, quote):
    ...
    quoteText = quote[0]
    quoteAuthor = quote[1]
    font = ImageFont.truetype('Lato-Regular.ttf', 48)
    draw = ImageDraw.Draw(im)
    x, y = 10, cropDim / 2    draw.text((x, y), quoteText, 'white', font)

    im.show()

filterImage(os.path.join(image_dir, 'background.jpg'),             ('The world is a book and those who do not travel read only one page', 'Augustine of Hippo'))


So, problem. The text is obviously falling of the edge of the image therefore will have to wrap the string. For this I have import the python module textwrap. This will just give me the text as a list of string as X length passed in the variable. I change quoteText to the wrap function and then loop through run draw.text each time and adding to the y co-ordinate each time. In addition have made the font bigger and move the text up.



quoteText = textwrap.wrap(quote[0], width=20)
quoteAuthor = quote[1]

font = ImageFont.truetype('Lato-Regular.ttf', 96)
draw = ImageDraw.Draw(im)
x, y = 20, cropDim / 8
for text in quoteText:
    draw.text((x, y), text, 'white', font)
    y = y + 90


Still some niggles but will alter a bit later. Next I am going to add the same font but smaller and use the draw.text on the Author a bit further down and slightly indented.


fontAuthor = ImageFont.truetype('Lato-Regular.ttf', 30)
y = y + 100x = x + 30draw.text((x, y), quoteAuthor, 'white', fontAuthor)
im.show()

Logo

For the last bit I am going to add the logo, for this I will use the paste again.  I open my image which is in the same directory, then resize to 144 square. I then position as the height - height of the image - margin for vertical, and then half the width - half the logo width for the horizontal.



def filterImage(imageFile, quote):
    ...
    iconImage = Image.open('icon.png')
    iconDim = 100    iconImage.thumbnail((iconDim, iconDim))
    iconPositionTop = cropDim - iconDim - 100    iconPositionLeft = (cropDim / 2) - (iconDim / 2)
    im.paste(iconImage, (iconPositionLeft, iconPositionTop))

Finally one more draw.text for the username.



Alterations

A good first attempt but needs some alterations. First of is the fact the font size is explicit, so I am gonna change the larger font to 1/12 of the image size and the small to 1/30. Also the addition of the y axis in the textwrap loop is at a proportion of 1/10 also. This way when different size images come in then it will adjust accordingly.

Second, the filter I have changed to (82, 72, 63, 170). A slightly more sepia type tone. 

Also I have added an if statement that if the textwrap is more that 8 lines then to go for a font half the size.

I changed the font to permanent marker in Google Fonts.

And most important added the save function.


from PIL import Image, ImageFont, ImageDraw
import os, errno
import textwrap

cwd = os.getcwd()
image_dir = os.path.join(cwd, 'quote')

def filterImage(imageFile, quote):
    # Open Image    im = Image.open(imageFile)
    # cropDim will be the standard unit over the process    cropDim = im.size[1]
    
    # Crop image    left = (im.size[0] / 2) - (cropDim / 2)
    right = (im.size[0] / 2) + (cropDim / 2)
    im = im.crop((left, 0, right, cropDim))
    
    # Add a filter of a transparent brown     filter = Image.new("RGBA", im.size, (82, 72, 63, 170))
    im.paste(filter, None, filter)

    # Get text as a list 20 characters long    quoteText = textwrap.wrap(quote[0], width=20)
    quoteAuthor = "-" + quote[1]

    # If quote is particularly long then use half the size font    if len(quoteText) > 8:
        font = ImageFont.truetype('PermanentMarker-Regular.ttf', cropDim / 24)
    else:
        font = ImageFont.truetype('PermanentMarker-Regular.ttf', cropDim / 12)
    
    # Draw Quote and author    draw = ImageDraw.Draw(im)
    x, y = cropDim / 54 , cropDim / 10    for text in quoteText:
        draw.text((x, y), text, 'white', font)
        y = y + cropDim / 10
    fontAuthor = ImageFont.truetype('Lato-Regular.ttf', cropDim / 30)
    y = y + 100    x = x + 30    draw.text((x, y), quoteAuthor, 'white', fontAuthor)

    # Place icon and username     iconImage = Image.open('icon.png')
    iconDim = 100    iconImage.thumbnail((iconDim, iconDim))
    iconPositionTop = cropDim - iconDim - 100    iconPositionLeft = (cropDim / 2) - (iconDim / 2)
    im.paste(iconImage, (iconPositionLeft, iconPositionTop))

    fontUser = ImageFont.truetype('Lato-Regular.ttf', 30)
    draw.text((iconPositionLeft - 40, cropDim - 70), '@travistaapp', 'white', fontUser)

    im.show()
    im.save(os.path.join(image_dir, 'quote_image.png'), 'PNG')

filterImage(os.path.join(image_dir, 'background.jpg'),('The world is a book and those who do not travel read only one page', 'Augustine of Hippo'))


Comments

Post a Comment

Popular posts from this blog

Raspberry Pi Download Machine Part 2

Well SSH is Boring I have got my RPi download machine up and running and having success, unfortunately though SSH is annoying and tedious, so I am gonna do something about it. But a disclaimer; this is my first time working with Aria2 in the most part. RPC  Remote Procedure Call is fancy way of saying 'make this thing make that thing do a thing'. So I ran the command to load the config file (and added to my init file) in the last post and ..... BOLLOCKS ALL. So ran the command again, and need to check the processes (like Task Manager). pi@raspberrypi : ~ $  ps aux | grep aria2c root       524  0.0  0.9  17028  9284 ?        Ss   21:21   0:00 aria2c --conf-path=/home/pi/.aria2/aria2.conf pi        1008  0.0  0.2   4272  1948 pts/0    S+   21:34   0:00 grep --color=auto aria2c There it is, that little scumbag. But still no dice. In the conf file I stated that the rpc-port to be 6800 so need to check that port.  A quick note on ports, every service

Raspberry Pi Download Machine Part 1

Yet Another Raspberry Pi Project Kodi builds, weird fucking robots and classic gaming always seems to be the theme of RPi projects. But a stat I would like to know is how many are just sat in a drawer somewhere. Anyway I am part of that number, until now. I manage to find a 4TB drive and managed to blag a caddy, so with this triangle of 'crap hanging around' I thought I'd build a downloader. Open Directories So I first had the idea from the sub-reddit open directories , here storage devices are opened up to the internet without a morsel of authentication. Google comes along and indexes them and you can go and find them. Decent ones find themselves on this sub of which I assume stays up until the user realises that there broadband is getting absolutely raped. So I wanted to be able to get a URL from a server and just throw it in a WebUI and get it to auto download and ping me when all is finished.  First Steps So to start off I downloaded Raspbian

Adventures with Pillow Part 1

I am in the middle of building an app and need to put together several promo videos for Twitter and Instagram, and man it is boring. So I thought 'how can I automate this process?', so welcome to this post. What I am going for is a selection of images, greyscaled flipping through with our logo on the front. So my approach was to use python, pillow and ffmpeg to build something. This could be disastrous. Images To start a few pre-flight checks, a variable for the test image and to check for/create a processed folder for the final images to go. import os , errno from PIL import Image , ImageFont , ImageDraw testImage = 'test.png' cwd = os.getcwd() image_dir = os.path.join(cwd , 'images' ) default_dir = os.path.join(cwd , 'processed' ) try : os.makedirs(default_dir) except OSError as e: if e.errno != errno.EEXIST: raise I am going to build a function that will do all of the processing, but first going to run on one image b