Integration Guide
Django
Django's ImageField depends on Pillow for image validation. For more advanced tasks like generating thumbnails or watermarking on upload, use Django signals or a dedicated library like django-imagekit.
from django.db import models
class Photo(models.Model):
image = models.ImageField(upload_to="photos/")
from django.db.models.signals import post_save
from PIL import Image
import os
def create_thumbnail(sender, instance, **kwargs):
if instance.image:
with Image.open(instance.image.path) as img:
img.thumbnail((300, 300), Image.Resampling.LANCZOS)
thumb_path = instance.image.path.replace("/photos/", "/thumbs/")
img.save(thumb_path)
post_save.connect(create_thumbnail, sender=Photo)
FastAPI
FastAPI's file upload handling works seamlessly with Pillow via UploadFile → BytesIO → Image.open().
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import StreamingResponse
from PIL import Image
import io
app = FastAPI()
@app.post("/resize")
async def resize_image(file: UploadFile = File(...)):
contents = await file.read()
img = Image.open(io.BytesIO(contents))
img.thumbnail((800, 800), Image.Resampling.LANCZOS)
buf = io.BytesIO()
img.save(buf, format="WEBP", quality=85)
buf.seek(0)
return StreamingResponse(buf, media_type="image/webp")
Flask
from flask import Flask, request, send_file
from PIL import Image
import io
app = Flask(__name__)
@app.post("/convert")
def convert():
file = request.files["image"]
img = Image.open(file.stream).convert("RGB")
buf = io.BytesIO()
img.save(buf, "JPEG", quality=90)
buf.seek(0)
return send_file(buf, mimetype="image/jpeg")
NumPy
Pillow and NumPy arrays can be converted freely. This allows you to apply arbitrary mathematical operations on pixel data and then render back to an image.
import numpy as np
from PIL import Image
img = Image.open("photo.jpg")
arr = np.array(img)
print(arr.shape)
inv = 255 - arr
result = Image.fromarray(inv.astype(np.uint8))
result.save("inverted.jpg")
PyTorch / torchvision
PyTorch uses Pillow as its default image loader. torchvision.transforms accepts PIL Images natively. When you load a dataset with torchvision.datasets.ImageFolder, Pillow does the heavy lifting under the hood.
from torchvision import transforms
from PIL import Image
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
)
])
pil_img = Image.open("photo.jpg")
tensor = preprocess(pil_img)
print(tensor.shape)
OpenCV
OpenCV and Pillow use different colour channel orders. OpenCV is BGR, Pillow is RGB. You must convert when passing data between libraries.
import cv2
import numpy as np
from PIL import Image
pil_img = Image.open("photo.jpg")
cv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
cv_img = cv2.imread("photo.jpg")
pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))
Tkinter
Tkinter's native PhotoImage only supports GIF and PGM. Use ImageTk.PhotoImage from Pillow to display any image format in a Tkinter window.
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
img = Image.open("photo.jpg").resize((400, 300))
photo = ImageTk.PhotoImage(img)
label = tk.Label(root, image=photo)
label.pack()
root.mainloop()
Qt (PyQt5 / PySide6)
Convert Pillow images to Qt's QImage via NumPy as an intermediate layer.
from PyQt5.QtGui import QImage, QPixmap
from PIL import Image
import numpy as np
img = Image.open("photo.jpg").convert("RGBA")
data = np.array(img)
h, w, ch = data.shape
qimage = QImage(data.tobytes(), w, h, ch * w, QImage.Format_RGBA8888)
pixmap = QPixmap.fromImage(qimage)