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
86 changes: 52 additions & 34 deletions Tests/test_file_im.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,28 @@


def test_sanity() -> None:
with Image.open(TEST_IM) as im:
im.load()
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "IM"
with pytest.warns(DeprecationWarning, match="IM image format"):
im = Image.open(TEST_IM)
im.load()
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "IM"
im.close()


def test_name_limit(tmp_path: Path) -> None:
out = tmp_path / ("name_limit_test" * 7 + ".im")
with Image.open(TEST_IM) as im:
im.save(out)
with pytest.warns(DeprecationWarning, match="IM image format"):
with Image.open(TEST_IM) as im:
im.save(out)
assert filecmp.cmp(out, "Tests/images/hopper_long_name.im")


@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file() -> None:
def open_test_image() -> None:
im = Image.open(TEST_IM)
with pytest.warns(DeprecationWarning, match="IM image format"):
im = Image.open(TEST_IM)
im.load()

with pytest.warns(ResourceWarning):
Expand All @@ -41,54 +45,63 @@ def open_test_image() -> None:

def test_closed_file() -> None:
with warnings.catch_warnings(action="error"):
im = Image.open(TEST_IM)
with pytest.warns(DeprecationWarning, match="IM image format"):
im = Image.open(TEST_IM)
im.load()
im.close()


def test_context_manager() -> None:
with warnings.catch_warnings(action="error"):
with Image.open(TEST_IM) as im:
im.load()
with pytest.warns(DeprecationWarning, match="IM image format"):
with Image.open(TEST_IM) as im:
im.load()


def test_tell() -> None:
# Arrange
with Image.open(TEST_IM) as im:
# Act
frame = im.tell()
with pytest.warns(DeprecationWarning, match="IM image format"):
with Image.open(TEST_IM) as im:
# Act
frame = im.tell()

# Assert
assert frame == 0


def test_n_frames() -> None:
with Image.open(TEST_IM) as im:
assert isinstance(im, ImImagePlugin.ImImageFile)
assert im.n_frames == 1
assert not im.is_animated
with pytest.warns(DeprecationWarning, match="IM image format"):
im = Image.open(TEST_IM)
assert isinstance(im, ImImagePlugin.ImImageFile)
assert im.n_frames == 1
assert not im.is_animated
im.close()


def test_eoferror() -> None:
with Image.open(TEST_IM) as im:
assert isinstance(im, ImImagePlugin.ImImageFile)
n_frames = im.n_frames
with pytest.warns(DeprecationWarning, match="IM image format"):
im = Image.open(TEST_IM)
assert isinstance(im, ImImagePlugin.ImImageFile)
n_frames = im.n_frames

# Test seeking past the last frame
with pytest.raises(EOFError):
im.seek(n_frames)
assert im.tell() < n_frames
# Test seeking past the last frame
with pytest.raises(EOFError):
im.seek(n_frames)
assert im.tell() < n_frames

# Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1)
# Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1)
im.close()


@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
def test_roundtrip(mode: str, tmp_path: Path) -> None:
out = tmp_path / "temp.im"
im = hopper(mode)
im.save(out)
assert_image_equal_tofile(im, out)
with pytest.warns(DeprecationWarning, match="IM image format"):
im.save(out)
with pytest.warns(DeprecationWarning, match="IM image format"):
assert_image_equal_tofile(im, out)


def test_small_palette(tmp_path: Path) -> None:
Expand All @@ -97,17 +110,21 @@ def test_small_palette(tmp_path: Path) -> None:
im.putpalette(colors)

out = tmp_path / "temp.im"
im.save(out)
with pytest.warns(DeprecationWarning, match="IM image format"):
im.save(out)

with Image.open(out) as reloaded:
assert reloaded.getpalette() == colors + [0] * 765
with pytest.warns(DeprecationWarning, match="IM image format"):
reloaded = Image.open(out)
assert reloaded.getpalette() == colors + [0] * 765
reloaded.close()


def test_save_unsupported_mode(tmp_path: Path) -> None:
out = tmp_path / "temp.im"
im = hopper("HSV")
with pytest.raises(ValueError):
im.save(out)
with pytest.warns(DeprecationWarning, match="IM image format"):
im.save(out)


def test_invalid_file() -> None:
Expand All @@ -118,4 +135,5 @@ def test_invalid_file() -> None:


def test_number() -> None:
assert ImImagePlugin.number("1.2") == 1.2
with pytest.warns(DeprecationWarning, match="IM image format"):
assert ImImagePlugin.number("1.2") == 1.2
3 changes: 2 additions & 1 deletion Tests/test_imagefile.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def roundtrip(format: str) -> tuple[Image.Image, Image.Image]:
assert_image_equal(*roundtrip("BMP"))
im1, im2 = roundtrip("GIF")
assert_image_similar(im1.convert("P"), im2, 1)
assert_image_equal(*roundtrip("IM"))
with pytest.warns(DeprecationWarning, match="IM image format"):
assert_image_equal(*roundtrip("IM"))
assert_image_equal(*roundtrip("MSP"))
if features.check("zlib"):
# force multiple blocks in PNG driver
Expand Down
2 changes: 1 addition & 1 deletion Tests/test_mode_i16.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_basic(tmp_path: Path, mode: str) -> None:
im_out = im_in.transform((w, h), Image.Transform.EXTENT, (0, 0, w, h))
verify(im_out) # transform

filename = tmp_path / "temp.im"
filename = tmp_path / "temp.tiff"
im_in.save(filename)

with Image.open(filename) as im_out:
Expand Down
9 changes: 8 additions & 1 deletion docs/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ ExifTags.IFD.Makernote
``ExifTags.IFD.MakerNote``.

Image getdata()
~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^

.. deprecated:: 12.1.0

Expand All @@ -30,6 +30,13 @@ Image getdata()
identical, except that it returns a tuple of pixel values, instead of an internal
Pillow data type.

IM image format
^^^^^^^^^^^^^^^

.. deprecated:: 13.0.0

The IM image format has been deprecated.

Removed features
----------------

Expand Down
2 changes: 2 additions & 0 deletions docs/handbook/image-file-formats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
IM
^^

.. deprecated:: 13.0.0

IM is a format used by LabEye and other applications based on the IFUNC image
processing library. The library reads and writes most uncompressed interchange
versions of this format.
Expand Down
9 changes: 6 additions & 3 deletions docs/releasenotes/13.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ ImageCms.ImageCmsProfile.product_name and .product_info
Deprecations
============

TODO
^^^^
IM image format
^^^^^^^^^^^^^^^

TODO
.. deprecated:: 13.0.0

The IM image format has been deprecated. If you are using this format and would like
to continue doing so, please provide a report about what other software uses it.

API changes
===========
Expand Down
5 changes: 5 additions & 0 deletions src/PIL/ImImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from typing import IO, Any

from . import Image, ImageFile, ImagePalette
from ._deprecate import deprecate
from ._util import DeferredError

# --------------------------------------------------------------------
Expand Down Expand Up @@ -106,6 +107,7 @@


def number(s: Any) -> float:
deprecate("IM image format", 15)
try:
return int(s)
except ValueError:
Expand Down Expand Up @@ -206,6 +208,8 @@ def _open(self) -> None:
msg = "Not an IM file"
raise SyntaxError(msg)

deprecate("IM image format", 15)

# Basic attributes
self._size = self.info[SIZE]
self._mode = self.info[MODE]
Expand Down Expand Up @@ -341,6 +345,7 @@ def tell(self) -> int:


def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
deprecate("IM image format", 15)
try:
image_type, rawmode = SAVE[im.mode]
except KeyError as e:
Expand Down
2 changes: 2 additions & 0 deletions src/PIL/_deprecate.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def deprecate(
raise RuntimeError(msg)
elif when == 14:
removed = "Pillow 14 (2027-10-15)"
elif when == 15:
removed = "Pillow 15 (2028-10-15)"
else:
msg = f"Unknown removal version: {when}. Update {__name__}?"
raise ValueError(msg)
Expand Down