From 8323bec404ee6eb6dbe07c6b9d168e2ab00a43a3 Mon Sep 17 00:00:00 2001 From: Mantas Kantautas Date: Thu, 22 Oct 2026 20:44:03 +0300 Subject: [PATCH 1/2] Correct byte order when resampling I;16B and I;16N images --- Tests/test_image_resize.py | 12 ++++++++++++ docs/releasenotes/13.1.0.rst | 7 +++++++ src/libImaging/Resample.c | 8 ++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 9dc24f47b68..f6d952b0291 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -154,6 +154,18 @@ def test_endianness( # as separately resized channel assert_image_equal(ch, references[channels[i]]) + @pytest.mark.parametrize("mode", ("I;16L", "I;16B", "I;16N")) + @pytest.mark.parametrize("size", ((20, 128), (128, 20))) + def test_16bit_byte_order(self, mode: str, size: tuple[int, int]) -> None: + # Use values with different high and low bytes, + # so that the byte order matters + im = hopper("I").point(lambda x: x * 251 + 7) + reference = self.resize(im.convert("I;16"), size, Image.Resampling.BICUBIC) + + resized = self.resize(im.convert(mode), size, Image.Resampling.BICUBIC) + assert resized.mode == mode + assert_image_equal(resized.convert("I"), reference.convert("I")) + @pytest.mark.parametrize( "resample", ( diff --git a/docs/releasenotes/13.1.0.rst b/docs/releasenotes/13.1.0.rst index 140bcbe3115..5edc0d28693 100644 --- a/docs/releasenotes/13.1.0.rst +++ b/docs/releasenotes/13.1.0.rst @@ -49,6 +49,13 @@ TODO Other changes ============= +Resizing I;16B and I;16N images +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On little-endian systems, :py:meth:`~PIL.Image.Image.resize` read and wrote +I;16B and I;16N pixel values in the wrong byte order when using a resampling +filter other than ``Resampling.NEAREST``. This has been fixed. + Blurring I;16 images ^^^^^^^^^^^^^^^^^^^^ diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index e079a7c8e70..5834ad15457 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -551,9 +551,9 @@ _ImagingResampleHorizontal_16bpc( int bigendian = 0; if ( - imIn->mode == IMAGING_MODE_I_16N + imIn->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN - || imIn->mode == IMAGING_MODE_I_16B + || imIn->mode == IMAGING_MODE_I_16N #endif ) { bigendian = 1; @@ -602,9 +602,9 @@ _ImagingResampleVertical_16bpc( int bigendian = 0; if ( - imIn->mode == IMAGING_MODE_I_16N + imIn->mode == IMAGING_MODE_I_16B #ifdef WORDS_BIGENDIAN - || imIn->mode == IMAGING_MODE_I_16B + || imIn->mode == IMAGING_MODE_I_16N #endif ) { bigendian = 1; From 1700e28abe3ac239ab82679c2ee30724dc956fd8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 23 Oct 2026 11:17:01 +1100 Subject: [PATCH 2/2] Simplify test --- Tests/test_image_resize.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index f6d952b0291..0d16d0710c2 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -154,17 +154,12 @@ def test_endianness( # as separately resized channel assert_image_equal(ch, references[channels[i]]) - @pytest.mark.parametrize("mode", ("I;16L", "I;16B", "I;16N")) - @pytest.mark.parametrize("size", ((20, 128), (128, 20))) - def test_16bit_byte_order(self, mode: str, size: tuple[int, int]) -> None: - # Use values with different high and low bytes, - # so that the byte order matters - im = hopper("I").point(lambda x: x * 251 + 7) - reference = self.resize(im.convert("I;16"), size, Image.Resampling.BICUBIC) - - resized = self.resize(im.convert(mode), size, Image.Resampling.BICUBIC) - assert resized.mode == mode - assert_image_equal(resized.convert("I"), reference.convert("I")) + @pytest.mark.parametrize("mode", ("I;16", "I;16L", "I;16B", "I;16N")) + def test_i16(self, mode: str) -> None: + im = Image.new(mode, (1, 2)) + im.putdata((0x0099, 0x0100)) + resized = self.resize(im, (1, 1), Image.Resampling.BICUBIC) + assert resized.getpixel((0, 0)) == 0x00CD @pytest.mark.parametrize( "resample",