Pillow, the python image manipulation module

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

原文链接:Pillow, the python image manipulation module

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
When you procrastinate, you become a slave to yesterday.
拖延会让你成为昨天的奴隶
评论 抢沙发

请登录后发表评论

    暂无评论内容