Pillow, the python image manipulation module
Tags: python
Published: 2020-04-27 00:46:19

You know there are ways to edit images online. Maybe you just want to caption it, or crop or rescale it. Up until now you only knew of [online tools](ezgif.com) and [standalone programs](gimp.org) that were able to do this. Maybe you were relying on Photoshop for a long time to edit an image.

But anyone who has a bunch of images can tell you how difficult it is to find a program to edit a bunch of images at once, the so-called "batch editing". So instead of trying to find a tool that does that, it may be worthwhile to write a script in a programming language that you know.

In Python there is a package called Pillow that can manipulate several image types programmatically. Pillow is a fork of a now-abandoned package called PIL. Pillow runs on both Python 3 and 2, but given that Python 2 is unsupported now you probably should consider migrating.

## Installation

Usualy, `pip3 install Pillow` should do the trick. Although I have experienced some cases where you can't open any image file with Pillow because it throws an exception. If this happens it's probably because Pillow wasn't compiled with support for that image format, and you should compile pillow yourself. But this problem doesn't appear on Windows.

Operating system distribution-provided installations of Pillow should work out of the box, but I have not verified this.

I should also mention that you can't have PIL and Pillow installed in the same python environment, which could be a venv or a literal python installation. They are incompatible with each other. But you won't run into this problem on Python 3 because PIL was never ported to Python 3.

## Usage

Quick and dirty now, to open an image, you use the `Image.open()` function.

```python
>>> from PIL import Image
>>> im = Image.open("lena.ppm")
```

This returns a handle to an image. Inside `im.format` is a human-readable extension name for the file type, something like `PPM`. `im.size` is a tuple with width and height elements, like `(512, 512)`. `im.mode` contains the color system used in the image, such as `RGB` or `CMYK`.

To display the image, you could use `im.show()`. This uses the `xv` program to display the image on the screen so if you don't have that installed, then this method won't work. Admittingly, I haven't even heard of `xv` before this so this is as far as I will cover it in this article.

`show()` is mainly useful for testing and debugging purposes, but you should use the `im.save("pathfilename")` method to save the read image somewhere so that you can open it with any image viewer.

## Effects

A non-exhaustive list of operations that you can perform on Pillow images is:

* Creating thumbnails
* Cropping
* Pasting regions of image elsewhere
* Rotating regions of images
* Resizing
* Flipping regions of images
* General transformations
* Color mode conversion
* Per-pixel color manipulations
* Processing individual color bands (such as red)
* Enhancing image contrast and brightness
* Operating on each GIF frame independently

So you see, it's very powerful, if you can get the images to open that is. And as I said earlier, compiling from source usually solves these problems. Pillow has to link with a few C libraries and so that's where the compilation kicks in - it links the libraries with the main Pillow code written in C that has Python bindings.

I will go over the most common operations one by one. The [Pillow documentation](https://pillow.readthedocs.io/en/3.0.x/index.html) is an excellent place to learn the more advanced operations.

Also I'm more of the "learn by example" kind of person so you'll be seeing more examples here than explanations of what they do. Where things are unclear I will clarify them though.

All of these snippets assume you import Image first: `from PIL import Image`. If you are getting errors such as `Image is not defined`, you need to run the aforementioned command to import it.

### Creating thumbnails

Thumbnails are stored inside the image file, not separately. They are the small pictures you see in file managers.

```python
import os, sys
from PIL import Image

size = (128, 128)

for infile in sys.argv[1:]:
    outfile = os.path.splitext(infile)[0] + ".thumbnail"
    if infile != outfile:
        try:
            im = Image.open(infile)
            im.thumbnail(size)         # This creates the thumbnal.
            im.save(outfile, "JPEG")
        except IOError:
            print("cannot create thumbnail for", infile)
```

### Cropping

The `crop()` method also returns an Image object.

```python
box = (100, 100, 400, 400)
region = im.crop(box)
```

### Pasting

Likewise, it's also possible to paste an Image object on another one using the `paste()` method.

```python
im.paste(region, box)
```

### Rotating and flipping

As a general rule, methods do not modify the input image. They return the modified image as the output. And, rotating and flipping is accomplished by using the `transpose()` function. It may sound like an odd name considering the purpose of it, but you can pass a value such as `Image.ROTATE_180` to direct it to do the corresponding operation.

```python
region = region.transpose(Image.ROTATE_180)
# Transforms it again
region = region.transpose(Image.FLIP_LEFT_RIGHT)
```

You can also use `rotate()` to rotate the image at an arbitrary amount of degrees.

```python
out = im.rotate(45) # degrees counter-clockwise
```

### Resizing

Tuple is specified as (width, height).

```python
out = im.resize((128, 128))
```

### Getting the image bands

All images are composed of red, green and blue values. You can retrieve each of them separately from an image by calling the `split()` function. When you're done modifying them individually, you can call the `merge()` function to put them back together.

```python
r, g, b = im.split()
# ...

# Adjust the red values, the green values and blue values

# ...
im = Image.merge("RGB", (b, g, r))
```

### Color mode conversion

You can convert any color mode to RGB and vice versa. You can also convert any color mode to L (the grayscale mode) and vice versa.

```python
# PPM is not a color mode, it's an image format (way the image is stored on disk)
im = Image.open("lena.ppm")

# Convert this PPM-file-format image to grayscale
im.convert("L")
```

### Applying filters to images

A **filter** is a post-processing effect that is added to the image to make it look different. There are several filters in Pillow. All of them are in the ImageFilter module, *not the Image module*, so you need to import ImageFilter from PIL to use them.

* `BLUR`
* `CONTOUR`
* `DETAIL`
* `EDGE_ENHANCE`
* `EDGE_ENHANCE_MORE`
* `EMBOSS`
* `FIND_EDGES`
* `SMOOTH`
* `SMOOTH_MORE`
* `SHARPEN`

Use the filters like this:

```python
from PIL import ImageFilter
im1 = im.filter(ImageFilter.BLUR)  # Blurs the image
```

### Per-point manipulation

You can pass a function to the `point()` method that takes a single number as input and applies a math expression to it.

```python
# multiply each pixel by 1.2
# This example indiscriminately adjusts the red, green and blue values. See below to modify them individually.
out = im.point(lambda i: i * 1.2)
```

One of the benefits of having `split()` and `merge()` is they can be used in situations like this, where an image is expected by the `point()` function, but you can pass three different images with each color band so you can run those images/color-bands through three different functions. A function that does that would look something like this:

```python
# split the image into individual bands
source = im.split()

R, G, B = 0, 1, 2

# select regions where red is less than 100
mask = source[R].point(lambda i: i < 100 and 255)

# process the green band
out = source[G].point(lambda i: i * 0.7)

# paste the processed band back, but only where red was < 100
source[G].paste(out, None, mask)

# build a new multiband image
im = Image.merge(im.mode, source)
```


### Image enhancement

You can use the `ImageEnhancement` module to give your images more contrast, brightness, or sharpness, or change the color balance. It is not a substitute for careful and accurate drawing, however.

Each of the items in `ImageEnhancement` is a class that takes an image in the constructor, and contains a method to return a modified version of the image. Currently the classes are:

* PIL.ImageEnhance.Color(image)
* PIL.ImageEnhance.Contrast(image)
* PIL.ImageEnhance.Brightness(image)
* PIL.ImageEnhance.Sharpness(image)

They all have an `enhance()` method that takes a single number as its argument.

```python
from PIL import ImageEnhance

enh = ImageEnhance.Contrast(image)
enh.enhance(1.3).show("30% more contrast")

enhancer = ImageEnhance.Sharpness(image)

for i in range(8):
    factor = i / 4.0
    enhancer.enhance(factor).show("Sharpness %f" % factor)
```

## Neat example

This examples were taken from the documentation. It moves an image to the left, putting the end of the image on the right.

```python
def roll(image, delta):
    "Roll an image sideways"

    xsize, ysize = image.size

    delta = delta % xsize
    if delta == 0: return image

    part1 = image.crop((0, 0, delta, ysize))
    part2 = image.crop((delta, 0, xsize, ysize))
    image.paste(part2, (0, 0, xsize-delta, ysize))
    image.paste(part1, (xsize-delta, 0, xsize, ysize))

    return image
```


## And we're done

I'm never perfect, but in this particular article I'm dependent on what I see in the documentation, having failed to get Pillow running, which means there is a higher chance that errors have cropped up (no pun intended) in this article. so if you spot errors, let me know in the comments so I can correct them.

Image by <a href="https://pixabay.com/users/PublicDomainPictures-14/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=219958">PublicDomainPictures</a> from <a href="https://pixabay.com/?utm_source=link-attribution&amp;utm_medium=referral&amp;utm_campaign=image&amp;utm_content=219958">Pixabay</a>