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
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")
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:
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))
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:
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)
naive: (0, 1)
scaled: (0, 255)
TIFF keeps: F
Mode P (palette, from GIFs and small PNGs)
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)
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)
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)
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:
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)
I;16 (0, 65535)
convert('L'): (0, 255)
scaled: (0, 255)
PNG keeps: I;16
Mode CMYK (print images) to PNG or WebP
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)
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.
Related
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.