Skip to content

Commit 78bc045

Browse files
authored
Merge branch 'main' into fromarray_mode
2 parents e6af31e + 13faa46 commit 78bc045

17 files changed

+235
-41
lines changed

.ci/requirements-mypy.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
mypy==1.16.0
1+
mypy==1.16.1
22
IceSpringPySideStubs-PyQt6
33
IceSpringPySideStubs-PySide6
44
ipython

.github/workflows/wheels-dependencies.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ ARCHIVE_SDIR=pillow-depends-main
3939
# Package versions for fresh source builds
4040
FREETYPE_VERSION=2.13.3
4141
HARFBUZZ_VERSION=11.2.1
42-
LIBPNG_VERSION=1.6.48
42+
LIBPNG_VERSION=1.6.49
4343
JPEGTURBO_VERSION=3.1.1
4444
OPENJPEG_VERSION=2.5.3
4545
XZ_VERSION=5.8.1

Tests/test_file_blp.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@
77
from PIL import BlpImagePlugin, Image
88

99
from .helper import (
10-
assert_image_equal,
1110
assert_image_equal_tofile,
12-
assert_image_similar,
11+
assert_image_similar_tofile,
1312
hopper,
1413
)
1514

@@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
5251
im = hopper("P")
5352
im.save(f, blp_version=version)
5453

55-
with Image.open(f) as reloaded:
56-
assert_image_equal(im.convert("RGB"), reloaded)
54+
assert_image_equal_tofile(im.convert("RGB"), f)
5755

5856
with Image.open("Tests/images/transparent.png") as im:
5957
f = tmp_path / "temp.blp"
6058
im.convert("P").save(f, blp_version=version)
6159

62-
with Image.open(f) as reloaded:
63-
assert_image_similar(im, reloaded, 8)
60+
assert_image_similar_tofile(im, f, 8)
6461

6562
im = hopper()
66-
with pytest.raises(ValueError):
63+
with pytest.raises(ValueError, match="Unsupported BLP image mode"):
6764
im.save(f)
6865

6966

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")

Tests/test_file_ppm.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,9 +294,10 @@ def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
294294
with open(path, "wb") as f:
295295
f.write(data)
296296

297-
with pytest.raises(ValueError, match="Token too long in file header: "):
297+
with pytest.raises(ValueError) as e:
298298
with Image.open(path):
299299
pass
300+
assert "Token too long in file header: " in repr(e)
300301

301302

302303
def test_truncated_file(tmp_path: Path) -> None:

Tests/test_file_qoi.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
from __future__ import annotations
22

3+
from pathlib import Path
4+
35
import pytest
46

57
from PIL import Image, QoiImagePlugin
68

7-
from .helper import assert_image_equal_tofile
9+
from .helper import assert_image_equal_tofile, hopper
810

911

1012
def test_sanity() -> None:
@@ -34,3 +36,22 @@ def test_op_index() -> None:
3436
# QOI_OP_INDEX as the first chunk
3537
with Image.open("Tests/images/op_index.qoi") as im:
3638
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
39+
40+
41+
def test_save(tmp_path: Path) -> None:
42+
f = tmp_path / "temp.qoi"
43+
44+
im = hopper()
45+
im.save(f, colorspace="sRGB")
46+
47+
assert_image_equal_tofile(im, f)
48+
49+
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
50+
with Image.open(path) as im:
51+
im.save(f)
52+
53+
assert_image_equal_tofile(im, f)
54+
55+
im = hopper("P")
56+
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
57+
im.save(f)

docs/deprecations.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,20 @@ Image.fromarray mode parameter
201201
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
202202
mode can be automatically determined from the object's shape and type instead.
203203

204+
Saving I mode images as PNG
205+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
206+
207+
.. deprecated:: 11.3.0
208+
209+
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
210+
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
211+
changing the data, this is now deprecated. Instead, the image can be converted to
212+
another mode before saving::
213+
214+
from PIL import Image
215+
im = Image.new("I", (1, 1))
216+
im.convert("I;16").save("out.png")
217+
204218
Removed features
205219
----------------
206220

docs/handbook/image-file-formats.rst

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
10821082

10831083
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
10841084

1085+
QOI
1086+
^^^
1087+
1088+
.. versionadded:: 9.5.0
1089+
1090+
Pillow reads and writes images in Quite OK Image format using a Python codec. If you
1091+
wish to write code specifically for this format, :pypi:`qoi` is an alternative library
1092+
that uses C to decode the image and interfaces with NumPy.
1093+
1094+
.. _qoi-saving:
1095+
1096+
Saving
1097+
~~~~~~
1098+
1099+
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
1100+
1101+
**colorspace**
1102+
If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
1103+
of all channels being linear.
1104+
10851105
SGI
10861106
^^^
10871107

@@ -1578,15 +1598,6 @@ PSD
15781598

15791599
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
15801600

1581-
QOI
1582-
^^^
1583-
1584-
.. versionadded:: 9.5.0
1585-
1586-
Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
1587-
write code specifically for this format, :pypi:`qoi` is an alternative library that
1588-
uses C to decode the image and interfaces with NumPy.
1589-
15901601
SUN
15911602
^^^
15921603

docs/releasenotes/11.3.0.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ Image.fromarray mode parameter
2929
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
3030
mode can be automatically determined from the object's shape and type instead.
3131

32+
Saving I mode images as PNG
33+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
35+
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
36+
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
37+
changing the data, this is now deprecated. Instead, the image can be converted to
38+
another mode before saving::
39+
40+
from PIL import Image
41+
im = Image.new("I", (1, 1))
42+
im.convert("I;16").save("out.png")
43+
3244
API changes
3345
===========
3446

@@ -48,6 +60,26 @@ TODO
4860
Other changes
4961
=============
5062

63+
Added QOI saving
64+
^^^^^^^^^^^^^^^^
65+
66+
Support has been added for saving QOI images. ``colorspace`` can be used to specify the
67+
colorspace as sRGB with linear alpha, e.g. ``im.save("out.qoi", colorspace="sRGB")``.
68+
By default, all channels will be linear.
69+
70+
Support using more screenshot utilities with ImageGrab on Linux
71+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
72+
73+
:py:meth:`~PIL.ImageGrab.grab` is now able to use GNOME Screenshot, grim or Spectacle
74+
on Linux in order to take a snapshot of the screen.
75+
76+
Do not build against libavif < 1
77+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
78+
79+
Pillow only supports libavif 1.0.0 or later. In order to prevent errors when building
80+
from source, if a user happens to have an earlier libavif on their system, Pillow will
81+
now ignore it.
82+
5183
Python 3.14 beta
5284
^^^^^^^^^^^^^^^^
5385

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

0 commit comments

Comments
 (0)