Quick fix

Convert to a mode the format supports before saving: img.convert("RGB") for JPEG, but only for modes that already hold 0–255 colour (P, CMYK, RGBA after flattening). Float (F), 32-bit (I) and 16-bit (I;16) images must be scaled to 0–255 first, or saved as TIFF/PNG, which can store them.

What the error looks like

save_float.py
import numpy as np
from PIL import Image

depth = Image.fromarray(np.linspace(0, 1, 256 * 256, dtype=np.float32).reshape(256, 256))   # mode F
print(depth.mode)
depth.save("depth.jpg")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
F
Traceback (most recent call last):
  ...
OSError: cannot write mode F as JPEG

Which modes each format can store

We tried saving every common mode in every common format. "yes" means save() succeeded on Pillow 12.3.0:

what_saves_where.py
import io
import warnings
from PIL import Image

warnings.simplefilter("ignore", DeprecationWarning)   # see the note about mode I below

src = Image.open("photo.jpg")
modes = ["1", "L", "LA", "P", "RGB", "RGBA", "CMYK", "I", "I;16", "F"]
formats = ["JPEG", "PNG", "WEBP", "GIF", "TIFF", "BMP"]

print(f"{'mode':<6}" + "".join(f"{f:>6}" for f in formats))
for mode in modes:
    img = src.convert("I").convert("I;16") if mode == "I;16" else src.convert(mode)
    row = []
    for fmt in formats:
        try:
            img.save(io.BytesIO(), fmt)
            row.append("yes")
        except (OSError, ValueError, KeyError):
            row.append("-")
    print(f"{mode:<6}" + "".join(f"{r:>6}" for r in row))
Tested on ✓ Linux ✓ Windows ✓ macOS
Result
mode    JPEG   PNG  WEBP   GIF  TIFF   BMP
1        yes   yes   yes   yes   yes   yes
L        yes   yes   yes   yes   yes   yes
LA         -   yes   yes   yes   yes     -
P          -   yes   yes   yes   yes   yes
RGB      yes   yes   yes   yes   yes   yes
RGBA       -   yes   yes   yes   yes   yes
CMYK     yes     -   yes     -   yes     -
I          -   yes   yes   yes   yes     -
I;16       -   yes   yes   yes   yes     -
F          -     -   yes   yes   yes     -

A "yes" does not always mean the data survives unchanged; some formats convert internally. One more thing the test surfaced: saving mode I (32-bit integer) images as PNG prints DeprecationWarning: Saving I mode images as PNG is deprecated and will be removed in Pillow 13, so convert those to I;16 (if the values fit) or save them as TIFF. But a "-" always means you have to convert first. JPEG is the strictest: only L, RGB and CMYK (plus 1). For RGBA to JPEG, the RGBA to JPEG fixer has the full explanation.

Mode F (32-bit float): scale, don't just convert

Float images usually come from NumPy (depth maps, model output, scientific data) and often hold values in 0..1. convert("L") clips instead of scaling, so the result is nearly black:

save_float.py
import numpy as np
from PIL import Image

depth = Image.fromarray(np.linspace(0, 1, 256 * 256, dtype=np.float32).reshape(256, 256))

naive = depth.convert("L")                       # 0..1 floats become 0 or 1: almost black
print("naive:", naive.getextrema())

lo, hi = depth.getextrema()
scaled = depth.point(lambda v: (v - lo) * 255 / (hi - lo)).convert("L")
print("scaled:", scaled.getextrema())
scaled.save("depth.jpg")

depth.save("depth.tiff")                         # TIFF stores 32-bit float losslessly
print("TIFF keeps:", Image.open("depth.tiff").mode)
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
naive: (0, 1)
scaled: (0, 255)
TIFF keeps: F

Mode P (palette, from GIFs and small PNGs)

gif_to_jpeg.py
from PIL import Image

gif = Image.open("spinner.gif")                  # palette image (mode P)
print(gif.mode, "transparency" in gif.info)
gif.convert("RGB").save("frame.jpg", quality=90)
print("saved", Image.open("frame.jpg").mode)
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
P False
saved RGB

If the palette image has transparency, composite it onto a background first; otherwise transparent areas take whatever colour the palette entry happens to have.

Mode LA (greyscale with alpha)

save_la.py
from PIL import Image

la = Image.open("logo.png").convert("LA")        # greyscale + alpha
try:
    la.save("logo.jpg")
except OSError as e:
    print("error:", e)

bg = Image.new("LA", la.size, (255, 255))        # white, opaque
flat = Image.alpha_composite(bg.convert("RGBA"), la.convert("RGBA")).convert("L")
flat.save("logo.jpg")
print("saved", flat.mode)
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
error: cannot write mode LA as JPEG
saved L

Modes I;16 and I (16- and 32-bit integers)

16-bit images come from scientific cameras, medical scans and 16-bit PNGs. As with floats, convert("L") clips rather than rescales:

save_16bit.py
from PIL import Image

img16 = Image.open("photo.jpg").convert("I").point(lambda v: v * 257).convert("I;16")
print(img16.mode, img16.getextrema())

wrong = img16.convert("L")
print("convert('L'):", wrong.getextrema())        # clipped, not scaled

right = img16.point(lambda v: v / 257).convert("L")
print("scaled:", right.getextrema())
img16.save("photo16.png")                        # PNG stores 16-bit greyscale directly
print("PNG keeps:", Image.open("photo16.png").mode)
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
I;16 (0, 65535)
convert('L'): (0, 255)
scaled: (0, 255)
PNG keeps: I;16

Mode CMYK (print images) to PNG or WebP

cmyk_to_png.py
from PIL import Image

cmyk = Image.open("photo.jpg").convert("CMYK")   # e.g. a print-ready JPEG
try:
    cmyk.save("web.png")
except OSError as e:
    print("error:", e)
cmyk.convert("RGB").save("web.png")
print("saved", Image.open("web.png").mode)
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
error: cannot write mode CMYK as PNG
saved RGB

For accurate colour, convert with the image's ICC profile using ImageCms; plain convert("RGB") uses a simple formula that can shift colours noticeably on print-oriented files.

The outputs and images on this page are the real results of running every example with Pillow 12.3.0 on Python 3.13 (Linux) on 2026-10-15. Every example is also re-run automatically on macOS (Darwin 25.6.0, Pillow 12.3.0), Linux (Linux 6.17.0-1022-azure, Pillow 12.3.0), Windows (Windows 2025Server, Pillow 12.3.0); the line under each example shows the result per operating system. The DejaVu Sans font files sit next to each script so font examples behave the same everywhere. Sample photos: NASA/Sam Lott, public domain. Spotted something wrong? Tell us.