Skip to content
Merged
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
14 changes: 12 additions & 2 deletions Tests/test_file_png.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,11 @@ def test_sanity(self, tmp_path: Path) -> None:
assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png"

for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode)
im.save(test_file)
with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"):
if mode == "I;16B":
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)

Expand Down Expand Up @@ -801,6 +801,16 @@ def test_truncated_end_chunk(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png")

def test_deprecation(self, tmp_path: Path) -> None:
test_file = tmp_path / "out.png"

im = hopper("I")
with pytest.warns(DeprecationWarning):
im.save(test_file)

with Image.open(test_file) as reloaded:
assert_image_equal(im, reloaded.convert("I"))


@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib")
Expand Down
14 changes: 14 additions & 0 deletions docs/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ Image.Image.get_child_images()
method uses an image's file pointer, and so child images could only be retrieved from
an :py:class:`PIL.ImageFile.ImageFile` instance.

Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. deprecated:: 11.3.0

In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::

from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")

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

Expand Down
13 changes: 10 additions & 3 deletions docs/releasenotes/11.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ TODO
Deprecations
============

TODO
^^^^
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^

TODO
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::

from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")

API changes
===========
Expand Down
3 changes: 3 additions & 0 deletions src/PIL/PngImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from ._binary import o8
from ._binary import o16be as o16
from ._binary import o32be as o32
from ._deprecate import deprecate
from ._util import DeferredError

TYPE_CHECKING = False
Expand Down Expand Up @@ -1368,6 +1369,8 @@ def _save(
except KeyError as e:
msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e
if outmode == "I":
deprecate("Saving I mode images as PNG", 13, stacklevel=4)

#
# write minimal PNG file
Expand Down
3 changes: 2 additions & 1 deletion src/PIL/_deprecate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def deprecate(
*,
action: str | None = None,
plural: bool = False,
stacklevel: int = 3,
) -> None:
"""
Deprecations helper.
Expand Down Expand Up @@ -67,5 +68,5 @@ def deprecate(
warnings.warn(
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
DeprecationWarning,
stacklevel=3,
stacklevel=stacklevel,
)
Loading