Merge branch 'main' into pre-commit-clang

This commit is contained in:
Andrew Murray 2024-04-30 21:34:41 +10:00 committed by GitHub
commit 7d62c306cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 163 additions and 133 deletions

View File

@ -36,8 +36,8 @@ jobs:
docker: [ docker: [
# Run slower jobs first to give them a headstart and reduce waiting time # Run slower jobs first to give them a headstart and reduce waiting time
ubuntu-22.04-jammy-arm64v8, ubuntu-22.04-jammy-arm64v8,
ubuntu-22.04-jammy-ppc64le, ubuntu-24.04-noble-ppc64le,
ubuntu-22.04-jammy-s390x, ubuntu-24.04-noble-s390x,
# Then run the remainder # Then run the remainder
alpine, alpine,
amazon-2-amd64, amazon-2-amd64,
@ -52,14 +52,15 @@ jobs:
gentoo, gentoo,
ubuntu-20.04-focal-amd64, ubuntu-20.04-focal-amd64,
ubuntu-22.04-jammy-amd64, ubuntu-22.04-jammy-amd64,
ubuntu-24.04-noble-amd64,
] ]
dockerTag: [main] dockerTag: [main]
include: include:
- docker: "ubuntu-22.04-jammy-arm64v8" - docker: "ubuntu-22.04-jammy-arm64v8"
qemu-arch: "aarch64" qemu-arch: "aarch64"
- docker: "ubuntu-22.04-jammy-ppc64le" - docker: "ubuntu-24.04-noble-ppc64le"
qemu-arch: "ppc64le" qemu-arch: "ppc64le"
- docker: "ubuntu-22.04-jammy-s390x" - docker: "ubuntu-24.04-noble-s390x"
qemu-arch: "s390x" qemu-arch: "s390x"
name: ${{ matrix.docker }} name: ${{ matrix.docker }}
@ -81,8 +82,8 @@ jobs:
- name: Docker build - name: Docker build
run: | run: |
# The Pillow user in the docker container is UID 1000 # The Pillow user in the docker container is UID 1001
sudo chown -R 1000 $GITHUB_WORKSPACE sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -50,7 +50,7 @@ jobs:
- name: Build and Run Valgrind - name: Build and Run Valgrind
run: | run: |
# The Pillow user in the docker container is UID 1000 # The Pillow user in the docker container is UID 1001
sudo chown -R 1000 $GITHUB_WORKSPACE sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -5,6 +5,12 @@ Changelog (Pillow)
10.4.0 (unreleased) 10.4.0 (unreleased)
------------------- -------------------
- Deprecate BGR;15, BGR;16 and BGR;24 modes #7978
[radarhere, hugovk]
- Fix ImagingAccess for I;16N on big-endian #7921
[Yay295, radarhere]
- Support reading P mode TIFF images with padding #7996 - Support reading P mode TIFF images with padding #7996
[radarhere] [radarhere]

View File

@ -2,7 +2,6 @@
.PHONY: clean .PHONY: clean
clean: clean:
python3 setup.py clean
rm src/PIL/*.so || true rm src/PIL/*.so || true
rm -r build || true rm -r build || true
find . -name __pycache__ | xargs rm -r || true find . -name __pycache__ | xargs rm -r || true

View File

@ -29,6 +29,33 @@ elif "GITHUB_ACTIONS" in os.environ:
uploader = "github_actions" uploader = "github_actions"
modes = (
"1",
"L",
"LA",
"La",
"P",
"PA",
"F",
"I",
"I;16",
"I;16L",
"I;16B",
"I;16N",
"RGB",
"RGBA",
"RGBa",
"RGBX",
"BGR;15",
"BGR;16",
"BGR;24",
"CMYK",
"YCbCr",
"HSV",
"LAB",
)
def upload(a: Image.Image, b: Image.Image) -> str | None: def upload(a: Image.Image, b: Image.Image) -> str | None:
if uploader == "show": if uploader == "show":
# local img.show for errors. # local img.show for errors.
@ -273,7 +300,18 @@ def _cached_hopper(mode: str) -> Image.Image:
im = hopper("L") im = hopper("L")
else: else:
im = hopper() im = hopper()
return im.convert(mode) if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
im = im.convert(mode)
else:
try:
im = im.convert(mode)
except ImportError:
if mode == "LAB":
im = Image.open("Tests/images/hopper.Lab.tif")
else:
raise
return im
def djpeg_available() -> bool: def djpeg_available() -> bool:

View File

@ -28,45 +28,27 @@ from .helper import (
assert_image_similar_tofile, assert_image_similar_tofile,
assert_not_all_same, assert_not_all_same,
hopper, hopper,
is_big_endian,
is_win32, is_win32,
mark_if_feature_version, mark_if_feature_version,
modes,
skip_unless_feature, skip_unless_feature,
) )
# name, pixel size
image_modes = (
("1", 1),
("L", 1),
("LA", 4),
("La", 4),
("P", 1),
("PA", 4),
("F", 4),
("I", 4),
("I;16", 2),
("I;16L", 2),
("I;16B", 2),
("I;16N", 2),
("RGB", 4),
("RGBA", 4),
("RGBa", 4),
("RGBX", 4),
("BGR;15", 2),
("BGR;16", 2),
("BGR;24", 3),
("CMYK", 4),
("YCbCr", 4),
("HSV", 4),
("LAB", 4),
)
image_mode_names = [name for name, _ in image_modes] # Deprecation helper
def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image:
if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
return Image.new(mode, size)
else:
return Image.new(mode, size)
class TestImage: class TestImage:
@pytest.mark.parametrize("mode", image_mode_names) @pytest.mark.parametrize("mode", modes)
def test_image_modes_success(self, mode: str) -> None: def test_image_modes_success(self, mode: str) -> None:
Image.new(mode, (1, 1)) helper_image_new(mode, (1, 1))
@pytest.mark.parametrize("mode", ("", "bad", "very very long")) @pytest.mark.parametrize("mode", ("", "bad", "very very long"))
def test_image_modes_fail(self, mode: str) -> None: def test_image_modes_fail(self, mode: str) -> None:
@ -1045,30 +1027,33 @@ class TestImage:
class TestImageBytes: class TestImageBytes:
@pytest.mark.parametrize("mode", image_mode_names) @pytest.mark.parametrize("mode", modes)
def test_roundtrip_bytes_constructor(self, mode: str) -> None: def test_roundtrip_bytes_constructor(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
source_bytes = im.tobytes() source_bytes = im.tobytes()
reloaded = Image.frombytes(mode, im.size, source_bytes) if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
reloaded = Image.frombytes(mode, im.size, source_bytes)
else:
reloaded = Image.frombytes(mode, im.size, source_bytes)
assert reloaded.tobytes() == source_bytes assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize("mode", image_mode_names) @pytest.mark.parametrize("mode", modes)
def test_roundtrip_bytes_method(self, mode: str) -> None: def test_roundtrip_bytes_method(self, mode: str) -> None:
im = hopper(mode) im = hopper(mode)
source_bytes = im.tobytes() source_bytes = im.tobytes()
reloaded = Image.new(mode, im.size) reloaded = helper_image_new(mode, im.size)
reloaded.frombytes(source_bytes) reloaded.frombytes(source_bytes)
assert reloaded.tobytes() == source_bytes assert reloaded.tobytes() == source_bytes
@pytest.mark.parametrize(("mode", "pixelsize"), image_modes) @pytest.mark.parametrize("mode", modes)
def test_getdata_putdata(self, mode: str, pixelsize: int) -> None: def test_getdata_putdata(self, mode: str) -> None:
im = Image.new(mode, (2, 2)) if is_big_endian() and mode == "BGR;15":
source_bytes = bytes(range(im.width * im.height * pixelsize)) pytest.xfail("Known failure of BGR;15 on big-endian")
im.frombytes(source_bytes) im = hopper(mode)
reloaded = helper_image_new(mode, im.size)
reloaded = Image.new(mode, im.size)
reloaded.putdata(im.getdata()) reloaded.putdata(im.getdata())
assert_image_equal(im, reloaded) assert_image_equal(im, reloaded)

View File

@ -10,7 +10,7 @@ import pytest
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper, is_win32 from .helper import assert_image_equal, hopper, is_win32, modes
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
@ -33,7 +33,7 @@ except ImportError:
class AccessTest: class AccessTest:
# initial value # Initial value
_init_cffi_access = Image.USE_CFFI_ACCESS _init_cffi_access = Image.USE_CFFI_ACCESS
_need_cffi_access = False _need_cffi_access = False
@ -138,8 +138,8 @@ class TestImageGetPixel(AccessTest):
if bands == 1: if bands == 1:
return 1 return 1
if mode in ("BGR;15", "BGR;16"): if mode in ("BGR;15", "BGR;16"):
# These modes have less than 8 bits per band # These modes have less than 8 bits per band,
# So (1, 2, 3) cannot be roundtripped # so (1, 2, 3) cannot be roundtripped.
return (16, 32, 49) return (16, 32, 49)
return tuple(range(1, bands + 1)) return tuple(range(1, bands + 1))
@ -151,7 +151,7 @@ class TestImageGetPixel(AccessTest):
self.color(mode) if expected_color_int is None else expected_color_int self.color(mode) if expected_color_int is None else expected_color_int
) )
# check putpixel # Check putpixel
im = Image.new(mode, (1, 1), None) im = Image.new(mode, (1, 1), None)
im.putpixel((0, 0), expected_color) im.putpixel((0, 0), expected_color)
actual_color = im.getpixel((0, 0)) actual_color = im.getpixel((0, 0))
@ -160,7 +160,7 @@ class TestImageGetPixel(AccessTest):
f"expected {expected_color} got {actual_color}" f"expected {expected_color} got {actual_color}"
) )
# check putpixel negative index # Check putpixel negative index
im.putpixel((-1, -1), expected_color) im.putpixel((-1, -1), expected_color)
actual_color = im.getpixel((-1, -1)) actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, ( assert actual_color == expected_color, (
@ -168,22 +168,21 @@ class TestImageGetPixel(AccessTest):
f"expected {expected_color} got {actual_color}" f"expected {expected_color} got {actual_color}"
) )
# Check 0 # Check 0x0 image with None initial color
im = Image.new(mode, (0, 0), None) im = Image.new(mode, (0, 0), None)
assert im.load() is not None assert im.load() is not None
error = ValueError if self._need_cffi_access else IndexError error = ValueError if self._need_cffi_access else IndexError
with pytest.raises(error): with pytest.raises(error):
im.putpixel((0, 0), expected_color) im.putpixel((0, 0), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((0, 0)) im.getpixel((0, 0))
# Check 0 negative index # Check negative index
with pytest.raises(error): with pytest.raises(error):
im.putpixel((-1, -1), expected_color) im.putpixel((-1, -1), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((-1, -1)) im.getpixel((-1, -1))
# check initial color # Check initial color
im = Image.new(mode, (1, 1), expected_color) im = Image.new(mode, (1, 1), expected_color)
actual_color = im.getpixel((0, 0)) actual_color = im.getpixel((0, 0))
assert actual_color == expected_color, ( assert actual_color == expected_color, (
@ -191,45 +190,28 @@ class TestImageGetPixel(AccessTest):
f"expected {expected_color} got {actual_color}" f"expected {expected_color} got {actual_color}"
) )
# check initial color negative index # Check initial color negative index
actual_color = im.getpixel((-1, -1)) actual_color = im.getpixel((-1, -1))
assert actual_color == expected_color, ( assert actual_color == expected_color, (
f"initial color failed with negative index for mode {mode}, " f"initial color failed with negative index for mode {mode}, "
f"expected {expected_color} got {actual_color}" f"expected {expected_color} got {actual_color}"
) )
# Check 0 # Check 0x0 image with initial color
im = Image.new(mode, (0, 0), expected_color) im = Image.new(mode, (0, 0), expected_color)
with pytest.raises(error): with pytest.raises(error):
im.getpixel((0, 0)) im.getpixel((0, 0))
# Check 0 negative index # Check negative index
with pytest.raises(error): with pytest.raises(error):
im.getpixel((-1, -1)) im.getpixel((-1, -1))
@pytest.mark.parametrize( @pytest.mark.parametrize("mode", modes)
"mode",
(
"1",
"L",
"LA",
"I",
"I;16",
"I;16B",
"F",
"P",
"PA",
"BGR;15",
"BGR;16",
"BGR;24",
"RGB",
"RGBA",
"RGBX",
"CMYK",
"YCbCr",
),
)
def test_basic(self, mode: str) -> None: def test_basic(self, mode: str) -> None:
self.check(mode) if mode.startswith("BGR;"):
with pytest.warns(DeprecationWarning):
self.check(mode)
else:
self.check(mode)
def test_list(self) -> None: def test_list(self) -> None:
im = hopper() im = hopper()
@ -238,7 +220,7 @@ class TestImageGetPixel(AccessTest):
@pytest.mark.parametrize("mode", ("I;16", "I;16B")) @pytest.mark.parametrize("mode", ("I;16", "I;16B"))
@pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)) @pytest.mark.parametrize("expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1))
def test_signedness(self, mode: str, expected_color: int) -> None: def test_signedness(self, mode: str, expected_color: int) -> None:
# see https://github.com/python-pillow/Pillow/issues/452 # See https://github.com/python-pillow/Pillow/issues/452
# pixelaccess is using signed int* instead of uint* # pixelaccess is using signed int* instead of uint*
self.check(mode, expected_color) self.check(mode, expected_color)
@ -298,13 +280,6 @@ class TestCffi(AccessTest):
im = Image.new(mode, (10, 10), 40000) im = Image.new(mode, (10, 10), 40000)
self._test_get_access(im) self._test_get_access(im)
# These don't actually appear to be modes that I can actually make,
# as unpack sets them directly into the I mode.
# im = Image.new('I;32L', (10, 10), -2**10)
# self._test_get_access(im)
# im = Image.new('I;32B', (10, 10), 2**10)
# self._test_get_access(im)
def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None: def _test_set_access(self, im: Image.Image, color: tuple[int, ...] | float) -> None:
"""Are we writing the correct bits into the image? """Are we writing the correct bits into the image?
@ -336,23 +311,18 @@ class TestCffi(AccessTest):
self._test_set_access(hopper("LA"), (128, 128)) self._test_set_access(hopper("LA"), (128, 128))
self._test_set_access(hopper("1"), 255) self._test_set_access(hopper("1"), 255)
self._test_set_access(hopper("P"), 128) self._test_set_access(hopper("P"), 128)
# self._test_set_access(i, (128, 128)) #PA -- undone how to make self._test_set_access(hopper("PA"), (128, 128))
self._test_set_access(hopper("F"), 1024.0) self._test_set_access(hopper("F"), 1024.0)
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"): for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
im = Image.new(mode, (10, 10), 40000) im = Image.new(mode, (10, 10), 40000)
self._test_set_access(im, 45000) self._test_set_access(im, 45000)
# im = Image.new('I;32L', (10, 10), -(2**10))
# self._test_set_access(im, -(2**13)+1)
# im = Image.new('I;32B', (10, 10), 2**10)
# self._test_set_access(im, 2**13-1)
@pytest.mark.filterwarnings("ignore::DeprecationWarning") @pytest.mark.filterwarnings("ignore::DeprecationWarning")
def test_not_implemented(self) -> None: def test_not_implemented(self) -> None:
assert PyAccess.new(hopper("BGR;15")) is None assert PyAccess.new(hopper("BGR;15")) is None
# ref https://github.com/python-pillow/Pillow/pull/2009 # Ref https://github.com/python-pillow/Pillow/pull/2009
def test_reference_counting(self) -> None: def test_reference_counting(self) -> None:
size = 10 size = 10
@ -361,7 +331,7 @@ class TestCffi(AccessTest):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
px = Image.new("L", (size, 1), 0).load() px = Image.new("L", (size, 1), 0).load()
for i in range(size): for i in range(size):
# pixels can contain garbage if image is released # Pixels can contain garbage if image is released
assert px[i, 0] == 0 assert px[i, 0] == 0
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
@ -478,7 +448,7 @@ int main(int argc, char* argv[])
env = os.environ.copy() env = os.environ.copy()
env["PATH"] = sys.prefix + ";" + env["PATH"] env["PATH"] = sys.prefix + ";" + env["PATH"]
# do not display the Windows Error Reporting dialog # Do not display the Windows Error Reporting dialog
getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002) getattr(ctypes, "windll").kernel32.SetErrorMode(0x0002)
process = subprocess.Popen(["embed_pil.exe"], env=env) process = subprocess.Popen(["embed_pil.exe"], env=env)

View File

@ -81,7 +81,8 @@ def test_mode_F() -> None:
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
def test_mode_BGR(mode: str) -> None: def test_mode_BGR(mode: str) -> None:
data = [(16, 32, 49), (32, 32, 98)] data = [(16, 32, 49), (32, 32, 98)]
im = Image.new(mode, (1, 2)) with pytest.warns(DeprecationWarning):
im = Image.new(mode, (1, 2))
im.putdata(data) im.putdata(data)
assert list(im.getdata()) == data assert list(im.getdata()) == data

View File

@ -216,7 +216,10 @@ class TestLibPack:
) )
def test_I16(self) -> None: def test_I16(self) -> None:
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605) if sys.byteorder == "little":
self.assert_pack("I;16N", "I;16N", 2, 0x0201, 0x0403, 0x0605)
else:
self.assert_pack("I;16N", "I;16N", 2, 0x0102, 0x0304, 0x0506)
def test_F_float(self) -> None: def test_F_float(self) -> None:
self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34) self.assert_pack("F", "F;32F", 4, 1.539989614439558e-36, 4.063216068939723e-34)
@ -359,11 +362,14 @@ class TestLibUnpack:
) )
def test_BGR(self) -> None: def test_BGR(self) -> None:
self.assert_unpack("BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)) with pytest.warns(DeprecationWarning):
self.assert_unpack( self.assert_unpack(
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0) "BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8)
) )
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9)) self.assert_unpack(
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
)
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
def test_RGBA(self) -> None: def test_RGBA(self) -> None:
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6)) self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))

View File

@ -100,6 +100,13 @@ ImageMath eval()
``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or ``ImageMath.eval()`` has been deprecated. Use :py:meth:`~PIL.ImageMath.lambda_eval` or
:py:meth:`~PIL.ImageMath.unsafe_eval` instead. :py:meth:`~PIL.ImageMath.unsafe_eval` instead.
BGR;15, BGR 16 and BGR;24
^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 10.4.0
The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.
Support for LibTIFF earlier than 4 Support for LibTIFF earlier than 4
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -59,9 +59,6 @@ Pillow also provides limited support for a few additional modes, including:
* ``I;16L`` (16-bit little endian unsigned integer pixels) * ``I;16L`` (16-bit little endian unsigned integer pixels)
* ``I;16B`` (16-bit big endian unsigned integer pixels) * ``I;16B`` (16-bit big endian unsigned integer pixels)
* ``I;16N`` (16-bit native endian unsigned integer pixels) * ``I;16N`` (16-bit native endian unsigned integer pixels)
* ``BGR;15`` (15-bit reversed true colour)
* ``BGR;16`` (16-bit reversed true colour)
* ``BGR;24`` (24-bit reversed true colour)
Premultiplied alpha is where the values for each other channel have been Premultiplied alpha is where the values for each other channel have been
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)`` multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``

View File

@ -37,7 +37,7 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 | | Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 12 Monterey | 3.8, 3.9 | x86-64 | | macOS 13 Ventura | 3.8, 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 | | macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
| | PyPy3 | | | | PyPy3 | |
@ -47,7 +47,9 @@ These platforms are built and tested for every change.
| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 | | Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, 3.13, PyPy3 | | | | 3.12, 3.13, PyPy3 | |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.10 | arm64v8, ppc64le, | | | 3.10 | arm64v8 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, ppc64le, |
| | | s390x | | | | s390x |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Windows Server 2016 | 3.8 | x86-64 | | Windows Server 2016 | 3.8 | x86-64 |

View File

@ -23,6 +23,11 @@ TODO
Deprecations Deprecations
============ ============
BGR;15, BGR 16 and BGR;24
^^^^^^^^^^^^^^^^^^^^^^^^^
The experimental BGR;15, BGR;16 and BGR;24 modes have been deprecated.
Support for LibTIFF earlier than 4 Support for LibTIFF earlier than 4
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -346,7 +346,7 @@ class Interop(IntEnum):
InteropVersion = 2 InteropVersion = 2
RelatedImageFileFormat = 4096 RelatedImageFileFormat = 4096
RelatedImageWidth = 4097 RelatedImageWidth = 4097
RleatedImageHeight = 4098 RelatedImageHeight = 4098
class IFD(IntEnum): class IFD(IntEnum):

View File

@ -41,7 +41,7 @@ import warnings
from collections.abc import Callable, MutableMapping from collections.abc import Callable, MutableMapping
from enum import IntEnum from enum import IntEnum
from types import ModuleType from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, cast from typing import IO, TYPE_CHECKING, Any, Literal, Protocol, Sequence, cast
# VERSION was removed in Pillow 6.0.0. # VERSION was removed in Pillow 6.0.0.
# PILLOW_VERSION was removed in Pillow 9.0.0. # PILLOW_VERSION was removed in Pillow 9.0.0.
@ -55,6 +55,7 @@ from . import (
_plugins, _plugins,
) )
from ._binary import i32le, o32be, o32le from ._binary import i32le, o32be, o32le
from ._deprecate import deprecate
from ._typing import StrOrBytesPath, TypeGuard from ._typing import StrOrBytesPath, TypeGuard
from ._util import DeferredError, is_path from ._util import DeferredError, is_path
@ -876,7 +877,7 @@ class Image:
return self.pyaccess return self.pyaccess
return self.im.pixel_access(self.readonly) return self.im.pixel_access(self.readonly)
def verify(self): def verify(self) -> None:
""" """
Verifies the contents of a file. For data read from a file, this Verifies the contents of a file. For data read from a file, this
method attempts to determine if the file is broken, without method attempts to determine if the file is broken, without
@ -939,6 +940,9 @@ class Image:
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
if mode in ("BGR;15", "BGR;16", "BGR;24"):
deprecate(mode, 12)
self.load() self.load()
has_transparency = "transparency" in self.info has_transparency = "transparency" in self.info
@ -1263,7 +1267,9 @@ class Image:
return im.crop((x0, y0, x1, y1)) return im.crop((x0, y0, x1, y1))
def draft(self, mode, size): def draft(
self, mode: str, size: tuple[int, int]
) -> tuple[str, tuple[int, int, float, float]] | None:
""" """
Configures the image file loader so it returns a version of the Configures the image file loader so it returns a version of the
image that as closely as possible matches the given mode and image that as closely as possible matches the given mode and
@ -1286,7 +1292,7 @@ class Image:
""" """
pass pass
def _expand(self, xmargin, ymargin=None): def _expand(self, xmargin: int, ymargin: int | None = None) -> Image:
if ymargin is None: if ymargin is None:
ymargin = xmargin ymargin = xmargin
self.load() self.load()
@ -2956,6 +2962,9 @@ def new(
:returns: An :py:class:`~PIL.Image.Image` object. :returns: An :py:class:`~PIL.Image.Image` object.
""" """
if mode in ("BGR;15", "BGR;16", "BGR;24"):
deprecate(mode, 12)
_check_size(size) _check_size(size)
if color is None: if color is None:
@ -3443,7 +3452,7 @@ def eval(image, *args):
return image.point(args[0]) return image.point(args[0])
def merge(mode, bands): def merge(mode: str, bands: Sequence[Image]) -> Image:
""" """
Merge a set of single band images into a new multiband image. Merge a set of single band images into a new multiband image.

View File

@ -163,7 +163,7 @@ class ImageFile(Image.Image):
self.tile = [] self.tile = []
super().__setstate__(state) super().__setstate__(state)
def verify(self): def verify(self) -> None:
"""Check file integrity""" """Check file integrity"""
# raise exception if something's wrong. must be called # raise exception if something's wrong. must be called

View File

@ -18,6 +18,8 @@ import sys
from functools import lru_cache from functools import lru_cache
from typing import NamedTuple from typing import NamedTuple
from ._deprecate import deprecate
class ModeDescriptor(NamedTuple): class ModeDescriptor(NamedTuple):
"""Wrapper for mode strings.""" """Wrapper for mode strings."""
@ -63,6 +65,8 @@ def getmode(mode: str) -> ModeDescriptor:
"PA": ("RGB", "L", ("P", "A"), "|u1"), "PA": ("RGB", "L", ("P", "A"), "|u1"),
} }
if mode in modes: if mode in modes:
if mode in ("BGR;15", "BGR;16", "BGR;24"):
deprecate(mode, 12)
base_mode, base_type, bands, type_str = modes[mode] base_mode, base_type, bands, type_str = modes[mode]
return ModeDescriptor(mode, bands, base_mode, base_type, type_str) return ModeDescriptor(mode, bands, base_mode, base_type, type_str)

View File

@ -424,13 +424,15 @@ class JpegImageFile(ImageFile.ImageFile):
return s return s
def draft(self, mode, size): def draft(
self, mode: str, size: tuple[int, int]
) -> tuple[str, tuple[int, int, float, float]] | None:
if len(self.tile) != 1: if len(self.tile) != 1:
return return None
# Protect from second call # Protect from second call
if self.decoderconfig: if self.decoderconfig:
return return None
d, e, o, a = self.tile[0] d, e, o, a = self.tile[0]
scale = 1 scale = 1

View File

@ -783,7 +783,7 @@ class PngImageFile(ImageFile.ImageFile):
self.seek(frame) self.seek(frame)
return self._text return self._text
def verify(self): def verify(self) -> None:
"""Verify PNG file""" """Verify PNG file"""
if self.fp is None: if self.fp is None:

View File

@ -81,12 +81,6 @@ get_pixel_16B(Imaging im, int x, int y, void *color) {
#endif #endif
} }
static void
get_pixel_16(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image[y][x + x];
memcpy(color, in, sizeof(UINT16));
}
static void static void
get_pixel_BGR15(Imaging im, int x, int y, void *color) { get_pixel_BGR15(Imaging im, int x, int y, void *color) {
UINT8 *in = (UINT8 *)&im->image8[y][x * 2]; UINT8 *in = (UINT8 *)&im->image8[y][x * 2];
@ -207,7 +201,11 @@ ImagingAccessInit() {
ADD("I;16", get_pixel_16L, put_pixel_16L); ADD("I;16", get_pixel_16L, put_pixel_16L);
ADD("I;16L", get_pixel_16L, put_pixel_16L); ADD("I;16L", get_pixel_16L, put_pixel_16L);
ADD("I;16B", get_pixel_16B, put_pixel_16B); ADD("I;16B", get_pixel_16B, put_pixel_16B);
ADD("I;16N", get_pixel_16, put_pixel_16L); #ifdef WORDS_BIGENDIAN
ADD("I;16N", get_pixel_16B, put_pixel_16B);
#else
ADD("I;16N", get_pixel_16L, put_pixel_16L);
#endif
ADD("I;32L", get_pixel_32L, put_pixel_32L); ADD("I;32L", get_pixel_32L, put_pixel_32L);
ADD("I;32B", get_pixel_32B, put_pixel_32B); ADD("I;32B", get_pixel_32B, put_pixel_32B);
ADD("F", get_pixel_32, put_pixel_32); ADD("F", get_pixel_32, put_pixel_32);