Automate the Boring Stuff with Python

Home > Other > Automate the Boring Stuff with Python > Page 42
Automate the Boring Stuff with Python Page 42

by Al Sweigart


  First, the os.makedirs() call creates a withLogo folder to store the finished images with logos, instead of overwriting the original image files. The exist_ok=True keyword argument will keep os.makedirs() from raising an exception if withLogo already exists. While looping through all the files in the working directory with os.listdir('.') ➊, the long if statement ➋ checks whether each filename doesn’t end with .png or .jpg. If so—or if the file is the logo image itself—then the loop should skip it and use continue ➌ to go to the next file. If filename does end with '.png' or '.jpg' (and isn’t the logo file), you can open it as an Image object ➍ and set width and height.

  Step 3: Resize the Images

  The program should resize the image only if the width or height is larger than SQUARE_FIT_SIZE (300 pixels, in this case), so put all of the resizing code inside an if statement that checks the width and height variables. Add the following code to your program:

  #! python3 # resizeAndAddLogo.py - Resizes all images in current working directory to fit # in a 300x300 square, and adds catlogo.png to the lower-right corner. import os from PIL import Image --snip-- # Check if image needs to be resized. if width > SQUARE_FIT_SIZE and height > SQUARE_FIT_SIZE: # Calculate the new width and height to resize to. if width > height: ➊ height = int((SQUARE_FIT_SIZE / width) * height) width = SQUARE_FIT_SIZE else: ➋ width = int((SQUARE_FIT_SIZE / height) * width) height = SQUARE_FIT_SIZE # Resize the image. print('Resizing %s...' % (filename)) ➌ im = im.resize((width, height)) --snip--

  If the image does need to be resized, you need to find out whether it is a wide or tall image. If width is greater than height, then the height should be reduced by the same proportion that the width would be reduced ➊. This proportion is the SQUARE_FIT_SIZE value divided by the current width. The new height value is this proportion multiplied by the current height value. Since the division operator returns a float value and resize() requires the dimensions to be integers, remember to convert the result to an integer with the int() function. Finally, the new width value will simply be set to SQUARE_FIT_SIZE.

  If the height is greater than or equal to the width (both cases are handled in the else clause), then the same calculation is done, except with the height and width variables swapped ➋.

  Once width and height contain the new image dimensions, pass them to the resize() method and store the returned Image object in im ➌.

  Step 4: Add the Logo and Save the Changes

  Whether or not the image was resized, the logo should still be pasted to the bottom-right corner. Where exactly the logo should be pasted depends on both the size of the image and the size of the logo. Figure 17-12 shows how to calculate the pasting position. The left coordinate for where to paste the logo will be the image width minus the logo width; the top coordinate for where to paste the logo will be the image height minus the logo height.

  Figure 17-12. The left and top coordinates for placing the logo in the bottom-right corner should be the image width/height minus the logo width/height.

  After your code pastes the logo into the image, it should save the modified Image object. Add the following to your program:

  #! python3 # resizeAndAddLogo.py - Resizes all images in current working directory to fit # in a 300x300 square, and adds catlogo.png to the lower-right corner. import os from PIL import Image --snip-- # Check if image needs to be resized. --snip-- # Add the logo. ➊ print('Adding logo to %s...' % (filename)) ➋ im.paste(logoIm, (width - logoWidth, height - logoHeight), logoIm) # Save changes. ➌ im.save(os.path.join('withLogo', filename))

  The new code prints a message telling the user that the logo is being added ➊, pastes logoIm onto im at the calculated coordinates ➋, and saves the changes to a filename in the withLogo directory ➌. When you run this program with the zophie.png file as the only image in the working directory, the output will look like this:

  Resizing zophie.png... Adding logo to zophie.png...

  The image zophie.png will be changed to a 225×300-pixel image that looks like Figure 17-13. Remember that the paste() method will not paste the transparency pixels if you do not pass the logoIm for the third argument as well. This program can automatically resize and “logo-ify” hundreds of images in just a couple minutes.

  Figure 17-13. The image zophie.png resized and the logo added (left). If you forget the third argument, the transparent pixels in the logo will be copied as solid white pixels (right).

  Ideas for Similar Programs

  Being able to composite images or modify image sizes in a batch can be useful in many applications. You could write similar programs to do the following:

  Add text or a website URL to images.

  Add timestamps to images.

  Copy or move images into different folders based on their sizes.

  Add a mostly transparent watermark to an image to prevent others from copying it.

  Drawing on Images

  If you need to draw lines, rectangles, circles, or other simple shapes on an image, use Pillow’s ImageDraw module. Enter the following into the interactive shell:

  >>> from PIL import Image, ImageDraw >>> im = Image.new('RGBA', (200, 200), 'white') >>> draw = ImageDraw.Draw(im)

  First, we import Image and ImageDraw. Then we create a new image, in this case, a 200×200 white image, and store the Image object in im. We pass the Image object to the ImageDraw.Draw() function to receive an ImageDraw object. This object has several methods for drawing shapes and text onto an Image object. Store the ImageDraw object in a variable like draw so you can use it easily in the following example.

  Drawing Shapes

  The following ImageDraw methods draw various kinds of shapes on the image. The fill and outline parameters for these methods are optional and will default to white if left unspecified.

  Points

  The point(xy, fill) method draws individual pixels. The xy argument represents a list of the points you want to draw. The list can be a list of x- and y-coordinate tuples, such as [(x, y), (x, y), ...], or a list of x- and y-coordinates without tuples, such as [x1, y1, x2, y2, ...]. The fill argument is the color of the points and is either an RGBA tuple or a string of a color name, such as 'red'. The fill argument is optional.

  Lines

  The line(xy, fill, width) method draws a line or series of lines. xy is either a list of tuples, such as [(x, y), (x, y), ...], or a list of integers, such as [x1, y1, x2, y2, ...]. Each point is one of the connecting points on the lines you’re drawing. The optional fill argument is the color of the lines, as an RGBA tuple or color name. The optional width argument is the width of the lines and defaults to 1 if left unspecified.

  Rectangles

  The rectangle(xy, fill, outline) method draws a rectangle. The xy argument is a box tuple of the form (left, top, right, bottom). The left and top values specify the x- and y-coordinates of the upper-left corner of the rectangle, while right and bottom specify the lower-right corner. The optional fill argument is the color that will fill the inside of the rectangle. The optional outline argument is the color of the rectangle’s outline.

  Ellipses

  The ellipse(xy, fill, outline) method draws an ellipse. If the width and height of the ellipse are identical, this method will draw a circle. The xy argument is a box tuple (left, top, right, bottom) that represents a box that precisely contains the ellipse. The optional fill argument is the color of the inside of the ellipse, and the optional outline argument is the color of the ellipse’s outline.

  Polygons

  The polygon(xy, fill, outline) method draws an arbitrary polygon. The xy argument is a list of tuples, such as [(x, y), (x, y), ...], or integers, such as [x1, y1, x2, y2, ...], representing the connecting points of the polygon’s sides. The last pair of coordinates will be automatically connected to the first pair. The optional fill argument is the color of the inside of the polygon, and the optional outline argument is the color of the polygon’s outline.

  Drawing Example

  Enter the following into the intera
ctive shell:

  >>> from PIL import Image, ImageDraw >>> im = Image.new('RGBA', (200, 200), 'white') >>> draw = ImageDraw.Draw(im) ➊ >>> draw.line([(0, 0), (199, 0), (199, 199), (0, 199), (0, 0)], fill='black') ➋ >>> draw.rectangle((20, 30, 60, 60), fill='blue') ➌ >>> draw.ellipse((120, 30, 160, 60), fill='red') ➍ >>> draw.polygon(((57, 87), (79, 62), (94, 85), (120, 90), (103, 113)), fill='brown') ➎ >>> for i in range(100, 200, 10): draw.line([(i, 0), (200, i - 100)], fill='green') >>> im.save('drawing.png')

  After making an Image object for a 200×200 white image, passing it to ImageDraw.Draw() to get an ImageDraw object, and storing the ImageDraw object in draw, you can call drawing methods on draw. Here we make a thin, black outline at the edges of the image ➊, a blue rectangle with its top-left corner at (20, 30) and bottom-right corner at (60, 60) ➋, a red ellipse defined by a box from (120, 30) to (160, 60) ➌, a brown polygon with five points ➍, and a pattern of green lines drawn with a for loop ➎. The resulting drawing.png file will look like Figure 17-14.

  Figure 17-14. The resulting drawing.png image

  There are several other shape-drawing methods for ImageDraw objects. The full documentation is available at http://pillow.readthedocs.org/en/latest/reference/ImageDraw.html.

  Drawing Text

  The ImageDraw object also has a text() method for drawing text onto an image. The text() method takes four arguments: xy, text, fill, and font.

  The xy argument is a two-integer tuple specifying the upper-left corner of the text box.

  The text argument is the string of text you want to write.

  The optional fill argument is the color of the text.

  The optional font argument is an ImageFont object, used to set the type-face and size of the text. This is described in more detail in the next section.

  Since it’s often hard to know in advance what size a block of text will be in a given font, the ImageDraw module also offers a textsize() method. Its first argument is the string of text you want to measure, and its second argument is an optional ImageFont object. The textsize() method will then return a two-integer tuple of the width and height that the text in the given font would be if it were written onto the image. You can use this width and height to help you calculate exactly where you want to put the text on your image.

  The first three arguments for text() are straightforward. Before we use text() to draw text onto an image, let’s look at the optional fourth argument, the ImageFont object.

  Both text() and textsize() take an optional ImageFont object as their final arguments. To create one of these objects, first run the following:

  >>> from PIL import ImageFont

  Now that you’ve imported Pillow’s ImageFont module, you can call the ImageFont.truetype() function, which takes two arguments. The first argument is a string for the font’s TrueType file—this is the actual font file that lives on your hard drive. A TrueType file has the .ttf file extension and can usually be found in the following folders:

  On Windows: C:WindowsFonts

  On OS X: /Library/Fonts and /System/Library/Fonts

  On Linux: /usr/share/fonts/truetype

  You don’t actually need to enter these paths as part of the TrueType file string because Python knows to automatically search for fonts in these directories. But Python will display an error if it is unable to find the font you specified.

  The second argument to ImageFont.truetype() is an integer for the font size in points (rather than, say, pixels). Keep in mind that Pillow creates PNG images that are 72 pixels per inch by default, and a point is 1/72 of an inch.

  Enter the following into the interactive shell, replacing FONT_FOLDER with the actual folder name your operating system uses:

  >>> from PIL import Image, ImageDraw, ImageFont >>> import os ➊ >>> im = Image.new('RGBA', (200, 200), 'white') ➋ >>> draw = ImageDraw.Draw(im) ➌ >>> draw.text((20, 150), 'Hello', fill='purple') >>> fontsFolder = 'FONT_FOLDER' # e.g. 'Library/Fonts' ➍ >>> arialFont = ImageFont.truetype(os.path.join(fontsFolder, 'arial.ttf'), 32) ➎ >>> draw.text((100, 150), 'Howdy', fill='gray', font=arialFont) >>> im.save('text.png')

  After importing Image, ImageDraw, ImageFont, and os, we make an Image object for a new 200×200 white image ➊ and make an ImageDraw object from the Image object ➋. We use text() to draw Hello at (20, 150) in purple ➌. We didn’t pass the optional fourth argument in this text() call, so the typeface and size of this text aren’t customized.

  To set a typeface and size, we first store the folder name (like /Library/Fonts) in fontsFolder. Then we call ImageFont.truetype(), passing it the .ttf file for the font we want, followed by an integer font size ➍. Store the Font object you get from ImageFont.truetype() in a variable like arialFont, and then pass the variable to text() in the final keyword argument. The text() call at ➎ draws Howdy at (100, 150) in gray in 32-point Arial.

  The resulting text.png file will look like Figure 17-15.

  Figure 17-15. The resulting text.png image

  Summary

  Images consist of a collection of pixels, and each pixel has an RGBA value for its color and its addressable by x- and y-coordinates. Two common image formats are JPEG and PNG. The pillow module can handle both of these image formats and others.

  When an image is loaded into an Image object, its width and height dimensions are stored as a two-integer tuple in the size attribute. Objects of the Image data type also have methods for common image manipulations: crop(), copy(), paste(), resize(), rotate(), and transpose(). To save the Image object to an image file, call the save() method.

  If you want your program to draw shapes onto an image, use ImageDraw methods to draw points, lines, rectangles, ellipses, and polygons. The module also provides methods for drawing text in a typeface and font size of your choosing.

  Although advanced (and expensive) applications such as Photoshop provide automatic batch processing features, you can use Python scripts to do many of the same modifications for free. In the previous chapters, you wrote Python programs to deal with plaintext files, spreadsheets, PDFs, and other formats. With the pillow module, you’ve extended your programming powers to processing images as well!

  Practice Questions

  Q:

  1. What is an RGBA value?

  Q:

  2. How can you get the RGBA value of 'CornflowerBlue' from the Pillow module?

  Q:

  3. What is a box tuple?

  Q:

  4. What function returns an Image object for, say, an image file named zophie.png?

  Q:

  5. How can you find out the width and height of an Image object’s image?

  Q:

  6. What method would you call to get Image object for a 100×100 image, excluding the lower left quarter of it?

  Q:

  7. After making changes to an Image object, how could you save it as an image file?

  Q:

  8. What module contains Pillow’s shape-drawing code?

  Q:

  9. Image objects do not have drawing methods. What kind of object does? How do you get this kind of object?

  Practice Projects

  For practice, write programs that do the following.

  Extending and Fixing the Chapter Project Programs

  The resizeAndAddLogo.py program in this chapter works with PNG and JPEG files, but Pillow supports many more formats than just these two. Extend resizeAndAddLogo.py to process GIF and BMP images as well.

  Another small issue is that the program modifies PNG and JPEG files only if their file extensions are set in lowercase. For example, it will process zophie.png but not zophie.PNG. Change the code so that the file extension check is case insensitive.

  Figure 17-16. When the image isn’t much larger than the logo, the results look ugly.

  Finally, the logo added to the bottom-right corner is meant to be just a small mark, but if the image is about the same size as the logo itself, the result will look like Figure 17-16. Modify resizeAndAddLogo.py so that the image
must be at least twice the width and height of the logo image before the logo is pasted. Other wise, it should skip adding the logo.

  Identifying Photo Folders on the Hard Drive

  I have a bad habit of transferring files from my digital camera to temporary folders somewhere on the hard drive and then forgetting about these folders. It would be nice to write a program that could scan the entire hard drive and find these leftover “photo folders.”

  Write a program that goes through every folder on your hard drive and finds potential photo folders. Of course, first you’ll have to define what you consider a “photo folder” to be; let’s say that it’s any folder where more than half of the files are photos. And how do you define what files are photos?

 

‹ Prev