Skip to content

Commit 13faa46

Browse files
authored
Deprecate saving I mode images as PNG (#9023)
2 parents ef0bab0 + 79e0b0b commit 13faa46

File tree

5 files changed

+41
-6
lines changed

5 files changed

+41
-6
lines changed

Tests/test_file_png.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ def test_sanity(self, tmp_path: Path) -> None:
100100
assert im.format == "PNG"
101101
assert im.get_format_mimetype() == "image/png"
102102

103-
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
103+
for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
104104
im = hopper(mode)
105105
im.save(test_file)
106106
with Image.open(test_file) as reloaded:
107-
if mode in ("I", "I;16B"):
107+
if mode == "I;16B":
108108
reloaded = reloaded.convert(mode)
109109
assert_image_equal(reloaded, im)
110110

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

804+
def test_deprecation(self, tmp_path: Path) -> None:
805+
test_file = tmp_path / "out.png"
806+
807+
im = hopper("I")
808+
with pytest.warns(DeprecationWarning):
809+
im.save(test_file)
810+
811+
with Image.open(test_file) as reloaded:
812+
assert_image_equal(im, reloaded.convert("I"))
813+
804814

805815
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
806816
@skip_unless_feature("zlib")

docs/deprecations.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,20 @@ Image.Image.get_child_images()
193193
method uses an image's file pointer, and so child images could only be retrieved from
194194
an :py:class:`PIL.ImageFile.ImageFile` instance.
195195

196+
Saving I mode images as PNG
197+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
198+
199+
.. deprecated:: 11.3.0
200+
201+
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
202+
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
203+
changing the data, this is now deprecated. Instead, the image can be converted to
204+
another mode before saving::
205+
206+
from PIL import Image
207+
im = Image.new("I", (1, 1))
208+
im.convert("I;16").save("out.png")
209+
196210
Removed features
197211
----------------
198212

docs/releasenotes/11.3.0.rst

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,17 @@ TODO
2323
Deprecations
2424
============
2525

26-
TODO
27-
^^^^
26+
Saving I mode images as PNG
27+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
2828

29-
TODO
29+
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
30+
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
31+
changing the data, this is now deprecated. Instead, the image can be converted to
32+
another mode before saving::
33+
34+
from PIL import Image
35+
im = Image.new("I", (1, 1))
36+
im.convert("I;16").save("out.png")
3037

3138
API changes
3239
===========

src/PIL/PngImagePlugin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from ._binary import o8
4949
from ._binary import o16be as o16
5050
from ._binary import o32be as o32
51+
from ._deprecate import deprecate
5152
from ._util import DeferredError
5253

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

13721375
#
13731376
# write minimal PNG file

src/PIL/_deprecate.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def deprecate(
1212
*,
1313
action: str | None = None,
1414
plural: bool = False,
15+
stacklevel: int = 3,
1516
) -> None:
1617
"""
1718
Deprecations helper.
@@ -67,5 +68,5 @@ def deprecate(
6768
warnings.warn(
6869
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
6970
DeprecationWarning,
70-
stacklevel=3,
71+
stacklevel=stacklevel,
7172
)

0 commit comments

Comments
 (0)