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 and standalone programs 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.
<span>>>></span> <span>from</span> <span>PIL</span> <span>import</span> <span>Image</span><span>>>></span> <span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"lena.ppm"</span><span>)</span><span>>>></span> <span>from</span> <span>PIL</span> <span>import</span> <span>Image</span> <span>>>></span> <span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"lena.ppm"</span><span>)</span>>>> from PIL import Image >>> im = Image.open("lena.ppm")
Enter fullscreen mode Exit fullscreen mode
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 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.
<span>import</span> <span>os</span><span>,</span> <span>sys</span><span>from</span> <span>PIL</span> <span>import</span> <span>Image</span><span>size</span> <span>=</span> <span>(</span><span>128</span><span>,</span> <span>128</span><span>)</span><span>for</span> <span>infile</span> <span>in</span> <span>sys</span><span>.</span><span>argv</span><span>[</span><span>1</span><span>:]:</span><span>outfile</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>splitext</span><span>(</span><span>infile</span><span>)[</span><span>0</span><span>]</span> <span>+</span> <span>".thumbnail"</span><span>if</span> <span>infile</span> <span>!=</span> <span>outfile</span><span>:</span><span>try</span><span>:</span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>infile</span><span>)</span><span>im</span><span>.</span><span>thumbnail</span><span>(</span><span>size</span><span>)</span> <span># This creates the thumbnal. </span> <span>im</span><span>.</span><span>save</span><span>(</span><span>outfile</span><span>,</span> <span>"JPEG"</span><span>)</span><span>except</span> <span>IOError</span><span>:</span><span>print</span><span>(</span><span>"cannot create thumbnail for"</span><span>,</span> <span>infile</span><span>)</span><span>import</span> <span>os</span><span>,</span> <span>sys</span> <span>from</span> <span>PIL</span> <span>import</span> <span>Image</span> <span>size</span> <span>=</span> <span>(</span><span>128</span><span>,</span> <span>128</span><span>)</span> <span>for</span> <span>infile</span> <span>in</span> <span>sys</span><span>.</span><span>argv</span><span>[</span><span>1</span><span>:]:</span> <span>outfile</span> <span>=</span> <span>os</span><span>.</span><span>path</span><span>.</span><span>splitext</span><span>(</span><span>infile</span><span>)[</span><span>0</span><span>]</span> <span>+</span> <span>".thumbnail"</span> <span>if</span> <span>infile</span> <span>!=</span> <span>outfile</span><span>:</span> <span>try</span><span>:</span> <span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>infile</span><span>)</span> <span>im</span><span>.</span><span>thumbnail</span><span>(</span><span>size</span><span>)</span> <span># This creates the thumbnal. </span> <span>im</span><span>.</span><span>save</span><span>(</span><span>outfile</span><span>,</span> <span>"JPEG"</span><span>)</span> <span>except</span> <span>IOError</span><span>:</span> <span>print</span><span>(</span><span>"cannot create thumbnail for"</span><span>,</span> <span>infile</span><span>)</span>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)
Enter fullscreen mode Exit fullscreen mode
Cropping
The crop()
method also returns an Image object.
<span>box</span> <span>=</span> <span>(</span><span>100</span><span>,</span> <span>100</span><span>,</span> <span>400</span><span>,</span> <span>400</span><span>)</span><span>region</span> <span>=</span> <span>im</span><span>.</span><span>crop</span><span>(</span><span>box</span><span>)</span><span>box</span> <span>=</span> <span>(</span><span>100</span><span>,</span> <span>100</span><span>,</span> <span>400</span><span>,</span> <span>400</span><span>)</span> <span>region</span> <span>=</span> <span>im</span><span>.</span><span>crop</span><span>(</span><span>box</span><span>)</span>box = (100, 100, 400, 400) region = im.crop(box)
Enter fullscreen mode Exit fullscreen mode
Pasting
Likewise, it’s also possible to paste an Image object on another one using the paste()
method.
<span>im</span><span>.</span><span>paste</span><span>(</span><span>region</span><span>,</span> <span>box</span><span>)</span><span>im</span><span>.</span><span>paste</span><span>(</span><span>region</span><span>,</span> <span>box</span><span>)</span>im.paste(region, box)
Enter fullscreen mode Exit fullscreen mode
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.
<span>region</span> <span>=</span> <span>region</span><span>.</span><span>transpose</span><span>(</span><span>Image</span><span>.</span><span>ROTATE_180</span><span>)</span><span># Transforms it again </span><span>region</span> <span>=</span> <span>region</span><span>.</span><span>transpose</span><span>(</span><span>Image</span><span>.</span><span>FLIP_LEFT_RIGHT</span><span>)</span><span>region</span> <span>=</span> <span>region</span><span>.</span><span>transpose</span><span>(</span><span>Image</span><span>.</span><span>ROTATE_180</span><span>)</span> <span># Transforms it again </span><span>region</span> <span>=</span> <span>region</span><span>.</span><span>transpose</span><span>(</span><span>Image</span><span>.</span><span>FLIP_LEFT_RIGHT</span><span>)</span>region = region.transpose(Image.ROTATE_180) # Transforms it again region = region.transpose(Image.FLIP_LEFT_RIGHT)
Enter fullscreen mode Exit fullscreen mode
You can also use rotate()
to rotate the image at an arbitrary amount of degrees.
<span>out</span> <span>=</span> <span>im</span><span>.</span><span>rotate</span><span>(</span><span>45</span><span>)</span> <span># degrees counter-clockwise </span><span>out</span> <span>=</span> <span>im</span><span>.</span><span>rotate</span><span>(</span><span>45</span><span>)</span> <span># degrees counter-clockwise </span>out = im.rotate(45) # degrees counter-clockwise
Enter fullscreen mode Exit fullscreen mode
Resizing
Tuple is specified as (width, height).
<span>out</span> <span>=</span> <span>im</span><span>.</span><span>resize</span><span>((</span><span>128</span><span>,</span> <span>128</span><span>))</span><span>out</span> <span>=</span> <span>im</span><span>.</span><span>resize</span><span>((</span><span>128</span><span>,</span> <span>128</span><span>))</span>out = im.resize((128, 128))
Enter fullscreen mode Exit fullscreen mode
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.
<span>r</span><span>,</span> <span>g</span><span>,</span> <span>b</span> <span>=</span> <span>im</span><span>.</span><span>split</span><span>()</span><span># ... </span><span># Adjust the red values, the green values and blue values </span><span># ... </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>merge</span><span>(</span><span>"RGB"</span><span>,</span> <span>(</span><span>b</span><span>,</span> <span>g</span><span>,</span> <span>r</span><span>))</span><span>r</span><span>,</span> <span>g</span><span>,</span> <span>b</span> <span>=</span> <span>im</span><span>.</span><span>split</span><span>()</span> <span># ... </span> <span># Adjust the red values, the green values and blue values </span> <span># ... </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>merge</span><span>(</span><span>"RGB"</span><span>,</span> <span>(</span><span>b</span><span>,</span> <span>g</span><span>,</span> <span>r</span><span>))</span>r, g, b = im.split() # ... # Adjust the red values, the green values and blue values # ... im = Image.merge("RGB", (b, g, r))
Enter fullscreen mode Exit fullscreen mode
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.
<span># PPM is not a color mode, it's an image format (way the image is stored on disk) </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"lena.ppm"</span><span>)</span><span># Convert this PPM-file-format image to grayscale </span><span>im</span><span>.</span><span>convert</span><span>(</span><span>"L"</span><span>)</span><span># PPM is not a color mode, it's an image format (way the image is stored on disk) </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>open</span><span>(</span><span>"lena.ppm"</span><span>)</span> <span># Convert this PPM-file-format image to grayscale </span><span>im</span><span>.</span><span>convert</span><span>(</span><span>"L"</span><span>)</span># 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")
Enter fullscreen mode Exit fullscreen mode
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:
<span>from</span> <span>PIL</span> <span>import</span> <span>ImageFilter</span><span>im1</span> <span>=</span> <span>im</span><span>.</span><span>filter</span><span>(</span><span>ImageFilter</span><span>.</span><span>BLUR</span><span>)</span> <span># Blurs the image </span><span>from</span> <span>PIL</span> <span>import</span> <span>ImageFilter</span> <span>im1</span> <span>=</span> <span>im</span><span>.</span><span>filter</span><span>(</span><span>ImageFilter</span><span>.</span><span>BLUR</span><span>)</span> <span># Blurs the image </span>from PIL import ImageFilter im1 = im.filter(ImageFilter.BLUR) # Blurs the image
Enter fullscreen mode Exit fullscreen mode
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.
<span># multiply each pixel by 1.2 # This example indiscriminately adjusts the red, green and blue values. See below to modify them individually. </span><span>out</span> <span>=</span> <span>im</span><span>.</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span>*</span> <span>1.2</span><span>)</span><span># multiply each pixel by 1.2 # This example indiscriminately adjusts the red, green and blue values. See below to modify them individually. </span><span>out</span> <span>=</span> <span>im</span><span>.</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span>*</span> <span>1.2</span><span>)</span># 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)
Enter fullscreen mode Exit fullscreen mode
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:
<span># split the image into individual bands </span><span>source</span> <span>=</span> <span>im</span><span>.</span><span>split</span><span>()</span><span>R</span><span>,</span> <span>G</span><span>,</span> <span>B</span> <span>=</span> <span>0</span><span>,</span> <span>1</span><span>,</span> <span>2</span><span># select regions where red is less than 100 </span><span>mask</span> <span>=</span> <span>source</span><span>[</span><span>R</span><span>].</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span><</span> <span>100</span> <span>and</span> <span>255</span><span>)</span><span># process the green band </span><span>out</span> <span>=</span> <span>source</span><span>[</span><span>G</span><span>].</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span>*</span> <span>0.7</span><span>)</span><span># paste the processed band back, but only where red was < 100 </span><span>source</span><span>[</span><span>G</span><span>].</span><span>paste</span><span>(</span><span>out</span><span>,</span> <span>None</span><span>,</span> <span>mask</span><span>)</span><span># build a new multiband image </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>merge</span><span>(</span><span>im</span><span>.</span><span>mode</span><span>,</span> <span>source</span><span>)</span><span># split the image into individual bands </span><span>source</span> <span>=</span> <span>im</span><span>.</span><span>split</span><span>()</span> <span>R</span><span>,</span> <span>G</span><span>,</span> <span>B</span> <span>=</span> <span>0</span><span>,</span> <span>1</span><span>,</span> <span>2</span> <span># select regions where red is less than 100 </span><span>mask</span> <span>=</span> <span>source</span><span>[</span><span>R</span><span>].</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span><</span> <span>100</span> <span>and</span> <span>255</span><span>)</span> <span># process the green band </span><span>out</span> <span>=</span> <span>source</span><span>[</span><span>G</span><span>].</span><span>point</span><span>(</span><span>lambda</span> <span>i</span><span>:</span> <span>i</span> <span>*</span> <span>0.7</span><span>)</span> <span># paste the processed band back, but only where red was < 100 </span><span>source</span><span>[</span><span>G</span><span>].</span><span>paste</span><span>(</span><span>out</span><span>,</span> <span>None</span><span>,</span> <span>mask</span><span>)</span> <span># build a new multiband image </span><span>im</span> <span>=</span> <span>Image</span><span>.</span><span>merge</span><span>(</span><span>im</span><span>.</span><span>mode</span><span>,</span> <span>source</span><span>)</span># 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)
Enter fullscreen mode Exit fullscreen mode
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.
<span>from</span> <span>PIL</span> <span>import</span> <span>ImageEnhance</span><span>enh</span> <span>=</span> <span>ImageEnhance</span><span>.</span><span>Contrast</span><span>(</span><span>image</span><span>)</span><span>enh</span><span>.</span><span>enhance</span><span>(</span><span>1.3</span><span>).</span><span>show</span><span>(</span><span>"30% more contrast"</span><span>)</span><span>enhancer</span> <span>=</span> <span>ImageEnhance</span><span>.</span><span>Sharpness</span><span>(</span><span>image</span><span>)</span><span>for</span> <span>i</span> <span>in</span> <span>range</span><span>(</span><span>8</span><span>):</span><span>factor</span> <span>=</span> <span>i</span> <span>/</span> <span>4.0</span><span>enhancer</span><span>.</span><span>enhance</span><span>(</span><span>factor</span><span>).</span><span>show</span><span>(</span><span>"Sharpness %f"</span> <span>%</span> <span>factor</span><span>)</span><span>from</span> <span>PIL</span> <span>import</span> <span>ImageEnhance</span> <span>enh</span> <span>=</span> <span>ImageEnhance</span><span>.</span><span>Contrast</span><span>(</span><span>image</span><span>)</span> <span>enh</span><span>.</span><span>enhance</span><span>(</span><span>1.3</span><span>).</span><span>show</span><span>(</span><span>"30% more contrast"</span><span>)</span> <span>enhancer</span> <span>=</span> <span>ImageEnhance</span><span>.</span><span>Sharpness</span><span>(</span><span>image</span><span>)</span> <span>for</span> <span>i</span> <span>in</span> <span>range</span><span>(</span><span>8</span><span>):</span> <span>factor</span> <span>=</span> <span>i</span> <span>/</span> <span>4.0</span> <span>enhancer</span><span>.</span><span>enhance</span><span>(</span><span>factor</span><span>).</span><span>show</span><span>(</span><span>"Sharpness %f"</span> <span>%</span> <span>factor</span><span>)</span>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)
Enter fullscreen mode Exit fullscreen mode
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.
<span>def</span> <span>roll</span><span>(</span><span>image</span><span>,</span> <span>delta</span><span>):</span><span>"Roll an image sideways"</span><span>xsize</span><span>,</span> <span>ysize</span> <span>=</span> <span>image</span><span>.</span><span>size</span><span>delta</span> <span>=</span> <span>delta</span> <span>%</span> <span>xsize</span><span>if</span> <span>delta</span> <span>==</span> <span>0</span><span>:</span> <span>return</span> <span>image</span><span>part1</span> <span>=</span> <span>image</span><span>.</span><span>crop</span><span>((</span><span>0</span><span>,</span> <span>0</span><span>,</span> <span>delta</span><span>,</span> <span>ysize</span><span>))</span><span>part2</span> <span>=</span> <span>image</span><span>.</span><span>crop</span><span>((</span><span>delta</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>,</span> <span>ysize</span><span>))</span><span>image</span><span>.</span><span>paste</span><span>(</span><span>part2</span><span>,</span> <span>(</span><span>0</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>-</span><span>delta</span><span>,</span> <span>ysize</span><span>))</span><span>image</span><span>.</span><span>paste</span><span>(</span><span>part1</span><span>,</span> <span>(</span><span>xsize</span><span>-</span><span>delta</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>,</span> <span>ysize</span><span>))</span><span>return</span> <span>image</span><span>def</span> <span>roll</span><span>(</span><span>image</span><span>,</span> <span>delta</span><span>):</span> <span>"Roll an image sideways"</span> <span>xsize</span><span>,</span> <span>ysize</span> <span>=</span> <span>image</span><span>.</span><span>size</span> <span>delta</span> <span>=</span> <span>delta</span> <span>%</span> <span>xsize</span> <span>if</span> <span>delta</span> <span>==</span> <span>0</span><span>:</span> <span>return</span> <span>image</span> <span>part1</span> <span>=</span> <span>image</span><span>.</span><span>crop</span><span>((</span><span>0</span><span>,</span> <span>0</span><span>,</span> <span>delta</span><span>,</span> <span>ysize</span><span>))</span> <span>part2</span> <span>=</span> <span>image</span><span>.</span><span>crop</span><span>((</span><span>delta</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>,</span> <span>ysize</span><span>))</span> <span>image</span><span>.</span><span>paste</span><span>(</span><span>part2</span><span>,</span> <span>(</span><span>0</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>-</span><span>delta</span><span>,</span> <span>ysize</span><span>))</span> <span>image</span><span>.</span><span>paste</span><span>(</span><span>part1</span><span>,</span> <span>(</span><span>xsize</span><span>-</span><span>delta</span><span>,</span> <span>0</span><span>,</span> <span>xsize</span><span>,</span> <span>ysize</span><span>))</span> <span>return</span> <span>image</span>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
Enter fullscreen mode Exit fullscreen mode
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 PublicDomainPictures from Pixabay
暂无评论内容