Python Pillow Tutorial

Pillow is a Python image-processing library used to open, inspect, modify, create, and save raster images. It supports common formats such as PNG, JPEG, GIF, BMP, TIFF, and WebP, depending on the features available in the installed build.

In this Python Pillow tutorial, you will learn how to install Pillow, open and inspect images, display them, resize and rotate them, flip and crop them, change color modes, apply filters, draw on images, add text, work with transparency, and save the processed result.

Install Pillow for Python

We will start with installing pillow package using pip.

To install pillow, run the following pip command.

 pip install Pillow
C:\>pip install Pillow
Collecting Pillow
Installing collected packages: Pillow
Successfully installed Pillow-5.4.1

On systems with more than one Python installation, use the interpreter explicitly so that Pillow is installed for the intended environment.

</>
Copy
python -m pip install Pillow

On some systems, the interpreter command may be python3.

</>
Copy
python3 -m pip install Pillow

Verify the installation by importing Pillow through the PIL package name.

</>
Copy
from PIL import Image

print(Image.__version__)

The package is installed from PyPI as Pillow, but it is imported in Python as PIL. This name is retained for compatibility with the original Python Imaging Library.

Open an Image With Pillow Image.open()

You can read an image using Image.open() method of Python Pillow library.

Python Program

</>
Copy
from PIL import Image

im = Image.open("image1.png")

Image.open() returns Pillow Image object. The input image can be a binary image, greyscale image or color image.

Image.open() identifies the file format from its contents and opens the file lazily. Pixel data is generally read when an operation requires it. A context manager is useful when you want the underlying file handle closed as soon as processing finishes.

</>
Copy
from PIL import Image

with Image.open("image1.png") as image:
    print(image.format)
    print(image.size)
    print(image.mode)

Inspect Pillow Image Format, Size, and Mode

  • image.format identifies the detected file format, such as PNG or JPEG.
  • image.size returns a (width, height) tuple in pixels.
  • image.width and image.height provide the dimensions separately.
  • image.mode describes the pixel format, such as RGB, RGBA, L, or 1.

A missing or invalid path raises FileNotFoundError. A file that Pillow cannot identify as a supported image can raise PIL.UnidentifiedImageError.

</>
Copy
from PIL import Image, UnidentifiedImageError

try:
    with Image.open("image1.png") as image:
        print(image.size)
except FileNotFoundError:
    print("The image file was not found.")
except UnidentifiedImageError:
    print("The file is not a supported image.")

Show or Display an Image With Pillow

You can show the image read, using Image.show() method. The image is displayed in a window.

Python Program

</>
Copy
from PIL import Image

# read an image
im = Image.open("image1.png")

# show the image in a window
im.show()

Output

Run the program, and the default program in your OS to display the image will start to show the image.

Python Pillow Tutorial - Show Image

show() is mainly a debugging convenience. It usually writes a temporary file and asks the operating system to open it in an external image viewer. For notebooks, web applications, and desktop interfaces, use the display mechanism provided by that environment instead.

Resize an Image With Pillow

To resize image using PIL, follow these steps.

  1. Import Image from PIL package.
  2. Open image using Image.open() method. It returns an object.
  3. Call resize(size) method on the object. size is a tuple representing the target size.
  4. resize() method returns the resized image.

You can use this image object to save to persistent storage or show it like in the following program.

Python Program

</>
Copy
from PIL import Image

# read an image
im = Image.open("python-image.png")

#resize image
im = im.resize((100, 100))

im.show()

Output

Python Pillow Tutorial - Resize Image

Resize a Pillow Image With a Resampling Filter

When changing image dimensions, specify a resampling filter. Image.Resampling.LANCZOS is commonly used for high-quality downscaling, while BICUBIC and BILINEAR provide other quality and performance trade-offs.

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    resized = image.resize(
        (800, 600),
        Image.Resampling.LANCZOS,
    )
    resized.save("resized-image.png")

Preserve Aspect Ratio With Pillow thumbnail()

A fixed width and height can distort an image when the target ratio differs from the original. Use thumbnail() to fit the image within a bounding box while preserving its aspect ratio. Unlike many Pillow operations, thumbnail() modifies the image object in place.

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    preview = image.copy()
    preview.thumbnail((400, 400), Image.Resampling.LANCZOS)
    preview.save("thumbnail.png")

Rotate an Image With Pillow

rotate(angle) method rotates the image by given angle in degrees, and returns the resulting image.

Python Program

</>
Copy
from PIL import Image
 
# read an image
im = Image.open("python-image.png")
 
#rotate image
im = im.rotate(90)
 
im.show()

Output

Python Pillow Tutorial - Rotate Image

Positive angles rotate counterclockwise. By default, the output keeps the original canvas dimensions, which can clip corners. Set expand=True to enlarge the output canvas so that the complete rotated image fits.

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    rotated = image.rotate(
        45,
        resample=Image.Resampling.BICUBIC,
        expand=True,
    )
    rotated.save("rotated-image.png")

Flip an Image With Pillow transpose()

transpose(method) function transposes the image using the given transpose method. method can take these values.

  • PIL.Image.FLIP_LEFT_RIGHT
  • PIL.Image.FLIP_TOP_BOTTOM
  • PIL.Image.ROTATE_90
  • PIL.Image.ROTATE_180
  • PIL.Image.ROTATE_270
  • PIL.Image.TRANSPOSE or PIL.Image.TRANSVERSE.

Let us pass PIL.Image.FLIP_LEFT_RIGHT for method, and flip the image along vertical axis.

Python Program

</>
Copy
from PIL import Image

# read an image
im = Image.open("python-image.png")

#resize image
im = im.transpose(Image.FLIP_LEFT_RIGHT)

im.show()

Output

Python Pillow Tutorial - Flip Image

Current Pillow versions also expose transpose operations through Image.Transpose, which keeps the operation names grouped in one namespace.

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    horizontal = image.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
    vertical = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)

    horizontal.save("flipped-horizontal.png")
    vertical.save("flipped-vertical.png")

Crop an Image With Pillow Coordinates

Use crop() to extract a rectangular region. Pillow represents a crop box as (left, upper, right, lower). The left and upper edges are included, while the right and lower edges define the boundary.

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    cropped = image.crop((100, 50, 500, 350))
    cropped.save("cropped-image.png")

The example starts 100 pixels from the left and 50 pixels from the top, then creates a region ending at horizontal coordinate 500 and vertical coordinate 350.

Convert Pillow Image Modes

Use convert() to change the image mode. Common modes include RGB for red, green, and blue channels; RGBA for RGB plus transparency; L for grayscale; and 1 for a one-bit image.

Convert a Pillow Image to Grayscale

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    grayscale = image.convert("L")
    grayscale.save("grayscale-image.png")

Convert RGBA to RGB Before Saving as JPEG

JPEG does not support an alpha transparency channel. Convert an RGBA image to RGB before saving it as JPEG, or composite it onto a chosen background color when transparent areas must be preserved visually.

</>
Copy
from PIL import Image

with Image.open("transparent-image.png") as image:
    rgb_image = image.convert("RGB")
    rgb_image.save("converted-image.jpg", quality=90)

Apply Pillow Image Filters

The ImageFilter module provides predefined filters for blurring, sharpening, detecting edges, and other image-processing operations.

</>
Copy
from PIL import Image, ImageFilter

with Image.open("python-image.png") as image:
    blurred = image.filter(ImageFilter.GaussianBlur(radius=3))
    sharpened = image.filter(ImageFilter.SHARPEN)
    edges = image.filter(ImageFilter.FIND_EDGES)

    blurred.save("blurred-image.png")
    sharpened.save("sharpened-image.png")
    edges.save("edge-image.png")

Filtering returns a new image. The original object remains available unless you assign the result back to the same variable.

Adjust Pillow Image Brightness, Contrast, and Color

The ImageEnhance module adjusts visual properties with a numeric factor. A factor of 1.0 preserves the original appearance, values below 1.0 reduce the effect, and values above 1.0 increase it.

</>
Copy
from PIL import Image, ImageEnhance

with Image.open("python-image.png") as image:
    brighter = ImageEnhance.Brightness(image).enhance(1.3)
    higher_contrast = ImageEnhance.Contrast(image).enhance(1.5)
    more_color = ImageEnhance.Color(image).enhance(1.2)

    brighter.save("brighter-image.png")
    higher_contrast.save("contrast-image.png")
    more_color.save("color-image.png")

Draw Shapes and Text on a Pillow Image

Use ImageDraw.Draw to draw lines, rectangles, ellipses, polygons, and text. Coordinates are measured from the upper-left corner of the image.

</>
Copy
from PIL import Image, ImageDraw

image = Image.new("RGB", (600, 300), "white")
draw = ImageDraw.Draw(image)

draw.rectangle((40, 40, 240, 180), outline="black", width=4)
draw.ellipse((300, 40, 480, 220), fill="lightgray", outline="black")
draw.line((40, 250, 550, 250), fill="black", width=3)
draw.text((40, 200), "Created with Pillow", fill="black")

image.save("drawing.png")

Add Text With a TrueType Font in Pillow

Load a TrueType or OpenType font with ImageFont.truetype(). The font file must exist at the specified path.

</>
Copy
from PIL import Image, ImageDraw, ImageFont

with Image.open("python-image.png").convert("RGBA") as image:
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype("DejaVuSans.ttf", size=36)

    draw.text(
        (30, 30),
        "Python Pillow",
        font=font,
        fill=(255, 255, 255, 255),
        stroke_width=2,
        stroke_fill=(0, 0, 0, 255),
    )

    image.save("image-with-text.png")

Paste, Composite, and Preserve Transparency With Pillow

Pillow can place one image over another. For an overlay with transparency, convert both images to RGBA and use the overlay’s alpha channel as the mask.

</>
Copy
from PIL import Image

with Image.open("background.png").convert("RGBA") as background:
    with Image.open("logo.png").convert("RGBA") as logo:
        result = background.copy()
        result.paste(logo, (30, 30), logo)
        result.save("image-with-logo.png")

The third argument to paste() acts as a mask. Passing the RGBA logo itself uses its alpha channel, allowing partially transparent pixels to blend with the background.

Read and Modify Individual Pixels With Pillow

Use getpixel() and putpixel() for occasional pixel access. Coordinates use the form (x, y), where x increases from left to right and y increases from top to bottom.

</>
Copy
from PIL import Image

with Image.open("python-image.png").convert("RGB") as image:
    pixel = image.getpixel((10, 10))
    print("Original pixel:", pixel)

    image.putpixel((10, 10), (255, 0, 0))
    image.save("pixel-updated.png")

Calling Python methods once per pixel can be slow for large images. For substantial numerical processing, consider bulk-oriented APIs or a numerical array library rather than deeply nested Python loops.

Process Multiple Images With Pillow

The following example creates thumbnails for all PNG and JPEG files in an input directory. It keeps each source filename while writing the processed files to a separate directory.

</>
Copy
from pathlib import Path
from PIL import Image, UnidentifiedImageError

input_dir = Path("images")
output_dir = Path("thumbnails")
output_dir.mkdir(exist_ok=True)

for path in input_dir.iterdir():
    if path.suffix.lower() not in {".png", ".jpg", ".jpeg"}:
        continue

    try:
        with Image.open(path) as image:
            thumbnail = image.copy()
            thumbnail.thumbnail((300, 300), Image.Resampling.LANCZOS)
            thumbnail.save(output_dir / path.name)
    except UnidentifiedImageError:
        print(f"Skipped unsupported image: {path}")

When processing a directory, avoid overwriting source files until the output has been verified. Saving to a separate directory also makes failed or partial conversions easier to recover from.

Save an Image With Pillow

Image.save() method saves the image in specified format.

Python Program

</>
Copy
from PIL import Image

# read an image
im = Image.open("python-image.png")

#image transformations
im = im.transpose(Image.FLIP_LEFT_RIGHT)

#save image
im.save('result-image.png', 'PNG')

Run this program, and an image file with the name ‘result-image.png’ will be created in the current directory. You may specify the absolute path or relative path along with the file name to save it to a different location.

Pillow can usually infer the output format from the filename extension. You can also pass the format explicitly, as in the existing example. Format-specific options control compression, image quality, metadata, and related behavior.

Save a JPEG With Pillow Quality Options

</>
Copy
from PIL import Image

with Image.open("python-image.png") as image:
    image.convert("RGB").save(
        "result-image.jpg",
        format="JPEG",
        quality=90,
        optimize=True,
    )

Higher JPEG quality settings generally preserve more detail but produce larger files. PNG uses lossless compression and is often suitable for screenshots, diagrams, and images that require transparency.

Save a Pillow Image to Memory

Use io.BytesIO when an application needs encoded image bytes without first writing a file to disk.

</>
Copy
from io import BytesIO
from PIL import Image

buffer = BytesIO()

with Image.open("python-image.png") as image:
    image.save(buffer, format="PNG")

png_bytes = buffer.getvalue()
print(len(png_bytes))

Common Python Pillow Errors and Fixes

  • ModuleNotFoundError: No module named 'PIL': Install Pillow with the same Python interpreter used to run the script.
  • FileNotFoundError: Check the working directory, filename, extension, and path spelling.
  • UnidentifiedImageError: Confirm that the file is a valid supported image rather than a renamed or damaged non-image file.
  • cannot write mode RGBA as JPEG: Convert the image to RGB or composite transparency onto a background before saving.
  • Rotated corners are missing: Pass expand=True to rotate().
  • The resized image looks stretched: Preserve the aspect ratio with thumbnail() or calculate one target dimension from the other.
  • Text font cannot be opened: Supply a valid path to a readable TrueType or OpenType font file.
  • Source image stays unchanged: Store the object returned by methods such as resize(), rotate(), crop(), and filter().

Python Pillow Tutorial FAQs

What is Pillow in Python?

Pillow is a maintained Python library for working with raster images. It provides classes and functions for opening, inspecting, resizing, cropping, rotating, filtering, drawing, converting, and saving images.

How do I use Pillow in Python?

Install the package with python -m pip install Pillow, import required classes from PIL, open an image with Image.open(), apply an operation, and save the returned image object with save().

Why is Pillow installed but imported as PIL?

Pillow is the package distribution name, while PIL is the import namespace retained for compatibility with the original Python Imaging Library.

Does Pillow preserve image quality when resizing?

Resizing necessarily recalculates pixels, so quality depends on the source image, target dimensions, and resampling filter. For high-quality downscaling, Image.Resampling.LANCZOS is commonly appropriate. Avoid repeatedly resizing and re-saving a lossy image such as JPEG.

Can Pillow process images without saving them to disk?

Yes. Pillow can open file-like objects and save encoded images to an io.BytesIO buffer. This is useful for web responses, database storage, testing, and other in-memory workflows.

Python Pillow Editorial QA Checklist

  • Confirm that installation examples use the package name Pillow and Python imports use the namespace PIL.
  • Verify that each sample input filename matches the file referenced by its explanation.
  • Check that resize examples distinguish fixed-size resizing from aspect-ratio-preserving thumbnails.
  • Confirm that new examples use Image.Resampling and Image.Transpose where current enum syntax is being demonstrated.
  • Verify that RGBA images are converted or composited before being saved as JPEG.
  • Check that rotation examples mention expand=True when clipping would affect the expected result.
  • Confirm that crop boxes follow the order (left, upper, right, lower).
  • Run each complete Python example and verify that its output file is created in the stated location.

Python Pillow Image-Processing Summary

Pillow provides a direct API for common image-processing work in Python. A typical workflow is to open an image, inspect its size and mode, apply one or more transformations, and save the resulting image in a suitable format. Use context managers for file-backed images, preserve aspect ratio when required, choose a suitable resampling filter, and account for color mode and transparency before saving.

In this Python Tutorial, we learned how to use Python Pillow library.