Online Pillow Compiler

Run Pillow code in your browser. Draw shapes and text, resize, crop and rotate images, apply filters and convert formats.

Python
from PIL import Image, ImageDraw

# Create a new image with RGB mode
image = Image.new("RGB", (200, 200), "blue")

# Draw a red rectangle on the image
draw = ImageDraw.Draw(image)
draw.rectangle([(50, 50), (150, 150)], fill="red")

# Show the image
image

Pillow opens, edits and saves images from Python. It reads and writes PNG, JPEG, GIF, WebP and other formats, and it can resize, crop, rotate, filter and draw on pictures. People use it to make thumbnails, convert files and put text on images. It is a fork of the older PIL library, which is why you import it as PIL. This page is an online Pillow compiler: the code runs in your browser, so you can try it without installing anything. Run the example first, then paste any snippet below into a new cell to try it.

What the example does

Image.new("RGB", (200, 200), "blue") creates a 200 by 200 pixel image filled with blue. Sizes are always (width, height), and a colour can be a name, a hex string such as "#ff8800" or an RGB tuple. ImageDraw.Draw(image) returns an object that draws directly onto that image. draw.rectangle() fills the area between two corners. Coordinates start at the top-left corner, y grows downwards, and both corners are included, so the red square is 101 pixels wide. Ending the cell with image makes the notebook show it.

Draw shapes and text

ImageDraw also draws ellipses, polygons, lines and text, and ImageFont.load_default(size=36) gives a font at any size without a font file. This snippet saves card.png for the next snippets:

from PIL import Image, ImageDraw, ImageFont

card = Image.new("RGB", (400, 200), "white")
draw = ImageDraw.Draw(card)
draw.ellipse([(20, 20), (180, 180)], fill="gold", outline="black", width=4)
draw.polygon([(60, 130), (100, 60), (140, 130)], fill="tomato")
draw.line([(200, 140), (380, 140)], fill="gray", width=3)
font = ImageFont.load_default(size=36)
draw.text((200, 80), "Hello", font=font, fill="black")
card.save("card.png")
card

The snippets below open card.png, so run this one first.

Resize, crop and rotate

crop() takes a box as (left, top, right, bottom). rotate() turns the image counterclockwise and keeps its original size, cutting off the corners, unless you pass expand=True. These methods return a new image and leave the original alone. thumbnail() is the exception: it shrinks the image in place to fit a box and keeps its proportions.

from PIL import Image

card = Image.open("card.png")
print(card.format, card.size, card.mode)

small = card.resize((200, 100))
left = card.crop((0, 0, 200, 200))
turned = card.rotate(30, expand=True, fillcolor="white")
print(small.size, left.size, turned.size)

card.thumbnail((100, 100))
print(card.size)
turned

To work on your own photo, click Mount folder in the sidebar's Data tab (Chrome or Edge on a computer), choose the folder it is in, and open it by name, as in Image.open("photo.jpg"). Add files does not take images.

Apply filters and adjust colours

ImageOps, ImageFilter and ImageEnhance hold the common edits. This makes four versions of the card and pastes them onto one sheet:

from PIL import Image, ImageEnhance, ImageFilter, ImageOps

card = Image.open("card.png")
versions = [
    ImageOps.grayscale(card),
    card.filter(ImageFilter.GaussianBlur(4)),
    ImageEnhance.Contrast(card).enhance(0.4),
    ImageOps.mirror(card),
]
sheet = Image.new("RGB", (400, 200), "white")
for i, version in enumerate(versions):
    sheet.paste(version.resize((200, 100)), ((i % 2) * 200, (i // 2) * 100))
sheet

Convert to another format

save() picks the format from the file extension. JPEG and WebP take a quality setting: lower values give smaller files with less detail.

import os
from PIL import Image

card = Image.open("card.png")
card.save("card.jpg", quality=85)
card.save("card.webp", quality=80)

for name in ["card.png", "card.jpg", "card.webp"]:
    with Image.open(name) as im:
        print(name, im.format, im.size, os.path.getsize(name), "bytes")

For a flat graphic like the card, the PNG is still the smallest of the three.

Good to know

  • JPEG cannot store transparency. Saving an RGBA image as .jpg raises OSError: cannot write mode RGBA as JPEG, so call image.convert("RGB") first.
  • image.size is (width, height), but np.array(image).shape is (height, width, 3), because NumPy arrays list rows first. Image.fromarray() turns an array back into an image.
  • There are no system fonts here, so ImageFont.truetype("arial.ttf") raises OSError. Use load_default(size=...), or put a .ttf file in a folder you mount and pass its name to truetype().