Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,6 @@ pillow-test-images.zip

# pyinstaller
*.spec

# mypy
.mypy_cache
3 changes: 1 addition & 2 deletions Tests/test_file_blp.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ def test_save(tmp_path: Path) -> None:
assert_image_similar_tofile(im, f, 8)

im = hopper()
with pytest.raises(ValueError, match="Unsupported BLP image mode"):
im.save(f)
im.save(f)


@pytest.mark.parametrize(
Expand Down
7 changes: 0 additions & 7 deletions Tests/test_file_dds.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,13 +411,6 @@ def test_not_implemented(test_file: str, message: str) -> None:
pass


def test_save_unsupported_mode(tmp_path: Path) -> None:
out = tmp_path / "temp.dds"
im = hopper("HSV")
with pytest.raises(OSError, match="cannot write mode HSV as DDS"):
im.save(out)


@pytest.mark.parametrize(
"mode, test_file",
[
Expand Down
8 changes: 0 additions & 8 deletions Tests/test_file_eps.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
hopper,
is_win32,
mark_if_feature_version,
skip_unless_feature,
Expand Down Expand Up @@ -285,13 +284,6 @@ def test_1(filename: str) -> None:
assert_image_equal_tofile(im, "Tests/images/eps/1.bmp")


def test_image_mode_not_supported(tmp_path: Path) -> None:
im = hopper("RGBA")
tmpfile = tmp_path / "temp.eps"
with pytest.raises(ValueError):
im.save(tmpfile)


@pytest.mark.skipif(not HAS_GHOSTSCRIPT, reason="Ghostscript not available")
@skip_unless_feature("zlib")
def test_render_scale1() -> None:
Expand Down
7 changes: 0 additions & 7 deletions Tests/test_file_im.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,6 @@ def test_small_palette(tmp_path: Path) -> None:
assert reloaded.getpalette() == colors + [0] * 765


def test_save_unsupported_mode(tmp_path: Path) -> None:
out = tmp_path / "temp.im"
im = hopper("HSV")
with pytest.raises(ValueError):
im.save(out)


def test_invalid_file() -> None:
invalid_file = "Tests/images/flower.jpg"

Expand Down
13 changes: 0 additions & 13 deletions Tests/test_file_jpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,14 +772,6 @@ def test_save_correct_modes(self, mode: str) -> None:
img = Image.new(mode, (20, 20))
img.save(out, "JPEG")

@pytest.mark.parametrize("mode", ("LA", "La", "RGBA", "RGBa", "P"))
def test_save_wrong_modes(self, mode: str) -> None:
# ref https://github.com/python-pillow/Pillow/issues/2005
out = BytesIO()
img = Image.new(mode, (20, 20))
with pytest.raises(OSError):
img.save(out, "JPEG")

def test_save_tiff_with_dpi(self, tmp_path: Path) -> None:
# Arrange
outfile = tmp_path / "temp.tif"
Expand Down Expand Up @@ -1107,11 +1099,6 @@ def test_repr_jpeg(self) -> None:
assert repr_jpeg.format == "JPEG"
assert_image_similar(im, repr_jpeg, 17)

def test_repr_jpeg_error_returns_none(self) -> None:
im = hopper("F")

assert im._repr_jpeg_() is None


@pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg")
Expand Down
10 changes: 0 additions & 10 deletions Tests/test_file_msp.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,3 @@ def test_msp_v2() -> None:
continue
path = os.path.join(YA_EXTRA_DIR, f)
_assert_file_image_equal(path, path.replace(".MSP", ".png"))


def test_cannot_save_wrong_mode(tmp_path: Path) -> None:
# Arrange
im = hopper()
filename = tmp_path / "temp.msp"

# Act/Assert
with pytest.raises(OSError):
im.save(filename)
8 changes: 0 additions & 8 deletions Tests/test_file_palm.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import subprocess
from pathlib import Path

import pytest

from PIL import Image

from .helper import assert_image_equal, hopper, magick_command
Expand Down Expand Up @@ -67,9 +65,3 @@ def test_p_mode(tmp_path: Path) -> None:
# Act / Assert
helper_save_as_palm(tmp_path, mode)
roundtrip(tmp_path, mode)


@pytest.mark.parametrize("mode", ("L", "RGB"))
def test_oserror(tmp_path: Path, mode: str) -> None:
with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode)
9 changes: 2 additions & 7 deletions Tests/test_file_pcx.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import io
import struct
from pathlib import Path

import pytest
Expand Down Expand Up @@ -30,18 +31,12 @@ def test_sanity(tmp_path: Path) -> None:
im.putpalette((255, 0, 0))
_roundtrip(tmp_path, im)

# Test an unsupported mode
f = tmp_path / "temp.pcx"
im = hopper("RGBA")
with pytest.raises(ValueError):
im.save(f)


@pytest.mark.parametrize("size", ((0, 1), (1, 0), (0, 0)))
def test_save_zero(size: tuple[int, int]) -> None:
b = io.BytesIO()
im = Image.new("1", size)
with pytest.raises(ValueError):
with pytest.raises(struct.error):
im.save(b, "PCX")


Expand Down
8 changes: 0 additions & 8 deletions Tests/test_file_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,6 @@ def test_monochrome(tmp_path: Path) -> None:
assert os.path.getsize(outfile) < (5000 if features.check("libtiff") else 15000)


def test_unsupported_mode(tmp_path: Path) -> None:
im = hopper("PA")
outfile = tmp_path / "temp_PA.pdf"

with pytest.raises(ValueError):
im.save(outfile)


def test_resolution(tmp_path: Path) -> None:
im = hopper()

Expand Down
5 changes: 0 additions & 5 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,11 +564,6 @@ def test_repr_png(self) -> None:
assert repr_png.format == "PNG"
assert_image_equal(im, repr_png)

def test_repr_png_error_returns_none(self) -> None:
im = hopper("F")

assert im._repr_png_() is None

def test_chunk_order(self, tmp_path: Path) -> None:
with Image.open("Tests/images/icc_profile.png") as im:
test_file = tmp_path / "temp.png"
Expand Down
6 changes: 0 additions & 6 deletions Tests/test_file_ppm.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,6 @@ def test_pfm_big_endian(tmp_path: Path) -> None:
assert_image_equal_tofile(im, filename)


def test_save_unsupported_mode(tmp_path: Path) -> None:
im = hopper("P")
with pytest.raises(OSError, match="cannot write mode P as PPM"):
im.save(tmp_path / "out.ppm")


@pytest.mark.parametrize(
"data",
[
Expand Down
4 changes: 0 additions & 4 deletions Tests/test_file_qoi.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,3 @@ def test_save(tmp_path: Path) -> None:
im.save(f)

assert_image_equal_tofile(im, f)

im = hopper("P")
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
im.save(f)
8 changes: 0 additions & 8 deletions Tests/test_file_sgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,6 @@ def test_write16(tmp_path: Path) -> None:
assert_image_equal_tofile(im, out)


def test_unsupported_mode(tmp_path: Path) -> None:
im = hopper("LA")
out = tmp_path / "temp.sgi"

with pytest.raises(ValueError):
im.save(out, format="sgi")


def test_unsupported_number_of_bytes_per_pixel(tmp_path: Path) -> None:
im = hopper()
out = tmp_path / "temp.sgi"
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_file_spider.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def test_save(tmp_path: Path) -> None:
def test_save_zero(size: tuple[int, int]) -> None:
b = BytesIO()
im = Image.new("1", size)
with pytest.raises(SystemError):
with pytest.raises((SystemError, ZeroDivisionError)):
im.save(b, "SPIDER")


Expand Down
10 changes: 1 addition & 9 deletions Tests/test_file_tga.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from PIL import Image, UnidentifiedImageError

from .helper import assert_image_equal, assert_image_equal_tofile, hopper
from .helper import assert_image_equal, assert_image_equal_tofile

_TGA_DIR = os.path.join("Tests", "images", "tga")
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
Expand Down Expand Up @@ -157,14 +157,6 @@ def test_missing_palette() -> None:
assert im.mode == "L"


def test_save_wrong_mode(tmp_path: Path) -> None:
im = hopper("PA")
out = tmp_path / "temp.tga"

with pytest.raises(OSError):
im.save(out)


def test_save_mapdepth() -> None:
# This image has been manually hexedited from 200x32_p_bl_raw.tga
# to include an origin
Expand Down
6 changes: 0 additions & 6 deletions Tests/test_file_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,6 @@ def test_save_rgba(self, tmp_path: Path) -> None:
outfile = tmp_path / "temp.tif"
im.save(outfile)

def test_save_unsupported_mode(self, tmp_path: Path) -> None:
im = hopper("HSV")
outfile = tmp_path / "temp.tif"
with pytest.raises(OSError):
im.save(outfile)

def test_8bit_s(self) -> None:
with Image.open("Tests/images/8bit.s.tif") as im:
im.load()
Expand Down
8 changes: 0 additions & 8 deletions Tests/test_file_xbm.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,6 @@ def test_invalid_file() -> None:
XbmImagePlugin.XbmImageFile(invalid_file)


def test_save_wrong_mode(tmp_path: Path) -> None:
im = hopper()
out = tmp_path / "temp.xbm"

with pytest.raises(OSError):
im.save(out)


def test_hotspot(tmp_path: Path) -> None:
im = hopper("1")
out = tmp_path / "temp.xbm"
Expand Down
32 changes: 32 additions & 0 deletions Tests/test_image_save.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from io import BytesIO

from PIL import Image


def test_image_save() -> None:
# Some extensions specify the mode, and not all file objects are named.
im = Image.new("RGBA", (1, 1))
out = BytesIO()
im.save(out, format=".bw")
im = Image.open(out)
assert im.mode == "L"

# This works:
Image.new("LAB", (1, 1)).convert("RGBA").convert("PA")
# But this fails on internal convert to RGBA:
# Image.new('LAB', (1,1)).convert('PA')

for format in Image.SAVE.keys():
for mode in Image.MODES:
im = Image.new(mode, (1, 1))
out = BytesIO()
try:
im.save(out, format=format)
except Exception as ex:
msg = f"Mode {mode} to format {format}: {ex}"
if "handler not installed" in str(ex):
print(msg)
else:
raise Exception(msg)
7 changes: 4 additions & 3 deletions src/PIL/BlpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,11 +463,12 @@ def encode(self, bufsize: int) -> tuple[int, int, bytes]:


def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo
# PA is possible according to https://wowwiki-archive.fandom.com/wiki/BLP_file
if im.mode != "P":
msg = "Unsupported BLP image mode"
raise ValueError(msg)
im = im.convert("RGBA").convert("P")

magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
magic = b"BLP1" if encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
fp.write(magic)

assert im.palette is not None
Expand Down
9 changes: 3 additions & 6 deletions src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,11 @@ def _dib_save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
def _save(
im: Image.Image, fp: IO[bytes], filename: str | bytes, bitmap_header: bool = True
) -> None:
try:
rawmode, bits, colors = SAVE[im.mode]
except KeyError as e:
msg = f"cannot write mode {im.mode} as BMP"
raise OSError(msg) from e

info = im.encoderinfo
if im.mode not in SAVE:
im = im.convert("RGBA")

rawmode, bits, colors = SAVE[im.mode]
dpi = info.get("dpi", (96, 96))

# 1 meter == 39.3701 inches
Expand Down
6 changes: 3 additions & 3 deletions src/PIL/DdsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,13 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int


def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
encoderinfo = im.encoderinfo
if im.mode not in ("RGB", "RGBA", "L", "LA"):
msg = f"cannot write mode {im.mode} as DDS"
raise OSError(msg)
im = im.convert("RGBA")

flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
bitcount = len(im.getbands()) * 8
pixel_format = im.encoderinfo.get("pixel_format")
pixel_format = encoderinfo.get("pixel_format")
args: tuple[int] | str
if pixel_format:
codec_name = "bcn"
Expand Down
6 changes: 6 additions & 0 deletions src/PIL/EpsImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -
# make sure image data is available
im.load()

if im.mode not in ("L", "RGB", "CMYK"):
if im.mode == "LAB":
im = im.convert("RGBA").convert("CMYK")
else:
im = im.convert("CMYK")
Comment on lines +431 to +433
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
im = im.convert("RGBA").convert("CMYK")
else:
im = im.convert("CMYK")
im = im.convert("RGBA")
im.convert("CMYK")


# determine PostScript image mode
if im.mode == "L":
operator = (8, 1, b"image")
Expand Down
2 changes: 2 additions & 0 deletions src/PIL/GifImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ def _normalize_mode(im: Image.Image) -> Image.Image:
if im.mode in RAWMODE:
im.load()
return im
if im.mode in ("CMYK", "HSV", "LAB", "PA", "RGBa", "RGBX", "YCbCr"):
im = im.convert("RGBA")
if Image.getmodebase(im.mode) == "RGB":
im = im.convert("P", palette=Image.Palette.ADAPTIVE)
assert im.palette is not None
Expand Down
Loading
Loading