Repository: python-pillow/Pillow

Problem statement:
Resize not allowing filter parameter on 16 bit image
When attempting to resize a 16 bit image as shown below, a value error appears and crashes the program: "ValueError: image has wrong mode" The image type is "I;16"

The primary problem is that this worked without error in early July and hasn't been touched in a month. When I opened the code today it gave the error even when reverting to a previously stable version. Below is the relevant code inside a custom cropping method. The coord_tup variable is just a tuple that is working as expected
```python
 new_im = im.crop(coord_tup)
 newSize = (640, 480)
 new_im = new_im.resize(newSize, resample=Image.Resampling.LANCZOS)
 npImg = np.asarray(new_im)
 return npImg
```
As stated this code used to work fine but when opened this week I get a value error on the line with the resize method, though it will technically work if I change the following
```python
 new_im = new_im.resize(newSize, resample=Image.Resampling.LANCZOS)
```
->
```python
 new_im = new_im.resize(newSize)
```
I understand through the API documentation that this should default to NEAREST but I could not find anything stating that LANCZOS would not work with 16 bit imaging

* OS: macOS Sonoma 14.6.1 (23G93
* Python: 3.12.3
* Pillow: 10.4.0






Hints:
Just to provide a self-contained code example, I agree that
```python
from PIL import Image
im = Image.new("I;16", (1, 1))
new_im = im.resize((640, 480), resample=Image.Resampling.LANCZOS)
```
is hitting
https://github.com/python-pillow/Pillow/blob/56e3147403d1785141b25f1d822a93e86c1deb98/src/libImaging/Resample.c#L567-L577

> The primary problem is that this worked without error in early July

Considering our last release was on July 1, this doesn't make much sense to me. I tested with 10.3.0, the release before that, and the same error occurs.
This was reported downstream as https://github.com/wagtail/Willow/issues/154

I tested a few Pillow versions and can report this started happening with Pillow 10.3.0. Pillow 10.2.0 is not affected and resizes without error.

Digging further, it appears that in Pillow 10.2.0 the sample image from https://github.com/wagtail/Willow/issues/154 has `mode='I'`. In 10.3.0 and later, it is opened as `I;16`

Looking at the changelog, https://github.com/python-pillow/Pillow/pull/7849 appears to be responsible for that change.

Is this considered a bug / regression? Or should we as end-users do something as a workaround? (use a different resample filter for example)
This is still an open issue, so it is something to be worked on.

The original post here doesn't mention PNGs, so you might be talking about a slightly different scenario. If your problem is #7849, then the simplest workaround would be to convert the image to I mode after opening - `im = im.convert("I")`.
Thanks for the response! I realize the original post doesn't mention PNGs, but assuming the original posters' code used to work before I'd say it is likely a similar scenario.

Given `I;16` mode support is limited according to the [documentation](https://pillow.readthedocs.io/en/latest/handbook/concepts.html), would you suggest always converting to `I` mode before performing image manipulation operations or is support going to be improved in the near future? Trying to gauge what the Willow maintainers should do here - perform the conversion workaround or wait for a new Pillow release.

Patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -2278,8 +2278,8 @@ def resize(
            :py:data:`Resampling.BILINEAR`, :py:data:`Resampling.HAMMING`,
            :py:data:`Resampling.BICUBIC` or :py:data:`Resampling.LANCZOS`.
            If the image has mode "1" or "P", it is always set to
-           :py:data:`Resampling.NEAREST`. If the image mode specifies a number
-           of bits, such as "I;16", then the default filter is
+           :py:data:`Resampling.NEAREST`. If the image mode is "BGR;15",
+           "BGR;16" or "BGR;24", then the default filter is
            :py:data:`Resampling.NEAREST`. Otherwise, the default filter is
            :py:data:`Resampling.BICUBIC`. See: :ref:`concept-filters`.
         :param box: An optional 4-tuple of floats providing
@@ -2302,8 +2302,8 @@ def resize(
         """
 
         if resample is None:
-            type_special = ";" in self.mode
-            resample = Resampling.NEAREST if type_special else Resampling.BICUBIC
+            bgr = self.mode.startswith("BGR;")
+            resample = Resampling.NEAREST if bgr else Resampling.BICUBIC
         elif resample not in (
             Resampling.NEAREST,
             Resampling.BILINEAR,


Test patch:
diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py
--- a/Tests/test_image_resize.py
+++ b/Tests/test_image_resize.py
@@ -44,9 +44,19 @@ def test_convolution_modes(self) -> None:
             self.resize(hopper("1"), (15, 12), Image.Resampling.BILINEAR)
         with pytest.raises(ValueError):
             self.resize(hopper("P"), (15, 12), Image.Resampling.BILINEAR)
-        with pytest.raises(ValueError):
-            self.resize(hopper("I;16"), (15, 12), Image.Resampling.BILINEAR)
-        for mode in ["L", "I", "F", "RGB", "RGBA", "CMYK", "YCbCr"]:
+        for mode in [
+            "L",
+            "I",
+            "I;16",
+            "I;16L",
+            "I;16B",
+            "I;16N",
+            "F",
+            "RGB",
+            "RGBA",
+            "CMYK",
+            "YCbCr",
+        ]:
             im = hopper(mode)
             r = self.resize(im, (15, 12), Image.Resampling.BILINEAR)
             assert r.mode == mode
@@ -305,14 +315,14 @@ def test_transposed(self) -> None:
             im = im.resize((64, 64))
             assert im.size == (64, 64)
 
-    @pytest.mark.parametrize("mode", ("L", "RGB", "I", "F"))
+    @pytest.mark.parametrize(
+        "mode", ("L", "RGB", "I", "I;16", "I;16L", "I;16B", "I;16N", "F")
+    )
     def test_default_filter_bicubic(self, mode: str) -> None:
         im = hopper(mode)
         assert im.resize((20, 20), Image.Resampling.BICUBIC) == im.resize((20, 20))
 
-    @pytest.mark.parametrize(
-        "mode", ("1", "P", "I;16", "I;16L", "I;16B", "BGR;15", "BGR;16")
-    )
+    @pytest.mark.parametrize("mode", ("1", "P", "BGR;15", "BGR;16"))
     def test_default_filter_nearest(self, mode: str) -> None:
         im = hopper(mode)
         assert im.resize((20, 20), Image.Resampling.NEAREST) == im.resize((20, 20))