Repository: python-pillow/Pillow

Issue:
PNG opens as I rather than I;16
### What did you do?
Open a 16-bit grayscale png and access its `mode` flag.

### What did you expect to happen?
`im.mode == 'I;16'`

### What actually happened?
`im.mode == 'I'`

### What are your OS, Python and Pillow versions?

* OS: Ubuntu 18.04.2 LTS (Linux 4.15.0-47)
* Python: 3.6.7
* Pillow: 6.0.0

```python
import numpy as np
from PIL import Image

ar = np.ones((32, 32), dtype=np.uint16)
im = Image.fromarray(ar)
im.save('foo.png')

im = Image.open('foo.png')
assert im.mode == 'I;16'
```

```sh
> file foo.png 
foo.png: PNG image data, 32 x 32, 16-bit grayscale, non-interlaced
```


Discussion:
I encountered this issue as well not very long ago. Is there any workaround in the meantime to "force" reading as `I;16`?
Note that this is a duplicate of #3041
> I encountered this issue as well not very long ago. Is there any workaround in the meantime to "force" reading as `I;16`?

Because Pillow doesn't scale images during conversion (#3159), you should just be able to convert the image.
```python
from PIL import Image
im = Image.open("Tests/images/16_bit_binary_pgm.png")
assert im.mode == "I"
assert im.getpixel((0, 0)) == 65535
assert im.convert("I;16").getpixel((0, 0)) == 65535
```
Just wanted to note that this is still an issue as of Pillow `10.1.0`. It can be make unit testing using Pillow harder because you can't assert that the original image _was_ 16-bit if that is part of what you are trying to test.
16-bit grayscale TIFF files are opened as `I;16`, but 16-bit grayscale PNG files for some reason aren't.
Individual plugins set the modes that images are opened in. If you're asking why the code is behaving this why, it is because of
https://github.com/python-pillow/Pillow/blob/2bd54260b614e3303f0f3b7d9586a011418c1f91/src/PIL/PngImagePlugin.py#L65

Resolving patch:
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -978,7 +978,7 @@ def convert_transparency(m, v):
         delete_trns = False
         # transparency handling
         if has_transparency:
-            if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or (
+            if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or (
                 self.mode == "RGB" and mode == "RGBA"
             ):
                 # Use transparent conversion to promote from transparent
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -62,7 +62,7 @@
     (2, 0): ("L", "L;2"),
     (4, 0): ("L", "L;4"),
     (8, 0): ("L", "L"),
-    (16, 0): ("I", "I;16B"),
+    (16, 0): ("I;16", "I;16B"),
     # Truecolour
     (8, 2): ("RGB", "RGB"),
     (16, 2): ("RGB", "RGB;16B"),
@@ -467,7 +467,7 @@ def chunk_tRNS(self, pos, length):
                 # otherwise, we have a byte string with one alpha value
                 # for each palette entry
                 self.im_info["transparency"] = s
-        elif self.im_mode in ("1", "L", "I"):
+        elif self.im_mode in ("1", "L", "I;16"):
             self.im_info["transparency"] = i16(s)
         elif self.im_mode == "RGB":
             self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
@@ -1356,7 +1356,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
                 transparency = max(0, min(255, transparency))
                 alpha = b"\xFF" * transparency + b"\0"
                 chunk(fp, b"tRNS", alpha[:alpha_bytes])
-        elif im.mode in ("1", "L", "I"):
+        elif im.mode in ("1", "L", "I", "I;16"):
             transparency = max(0, min(65535, transparency))
             chunk(fp, b"tRNS", o16(transparency))
         elif im.mode == "RGB":


Test patch:
diff --git a/Tests/images/16_bit_binary_pgm.png b/Tests/images/16_bit_binary_pgm.png
deleted file mode 100644
Binary files a/Tests/images/16_bit_binary_pgm.png and /dev/null differ
diff --git a/Tests/images/16_bit_binary_pgm.tiff b/Tests/images/16_bit_binary_pgm.tiff
new file mode 100644
Binary files /dev/null and b/Tests/images/16_bit_binary_pgm.tiff differ
diff --git a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png
deleted file mode 100644
Binary files a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png and /dev/null differ
diff --git a/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff
new file mode 100644
Binary files /dev/null and b/Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff differ
diff --git a/Tests/images/hopper_emboss.bmp b/Tests/images/hopper_emboss.bmp
Binary files a/Tests/images/hopper_emboss.bmp and b/Tests/images/hopper_emboss.bmp differ
diff --git a/Tests/images/hopper_emboss_I.png b/Tests/images/hopper_emboss_I.png
deleted file mode 100644
Binary files a/Tests/images/hopper_emboss_I.png and /dev/null differ
diff --git a/Tests/images/hopper_emboss_more.bmp b/Tests/images/hopper_emboss_more.bmp
Binary files a/Tests/images/hopper_emboss_more.bmp and b/Tests/images/hopper_emboss_more.bmp differ
diff --git a/Tests/images/hopper_emboss_more_I.png b/Tests/images/hopper_emboss_more_I.png
deleted file mode 100644
Binary files a/Tests/images/hopper_emboss_more_I.png and /dev/null differ
diff --git a/Tests/images/imagedraw_rectangle_I.png b/Tests/images/imagedraw_rectangle_I.png
deleted file mode 100644
Binary files a/Tests/images/imagedraw_rectangle_I.png and /dev/null differ
diff --git a/Tests/images/imagedraw_rectangle_I.tiff b/Tests/images/imagedraw_rectangle_I.tiff
new file mode 100644
Binary files /dev/null and b/Tests/images/imagedraw_rectangle_I.tiff differ
diff --git a/Tests/test_file_mcidas.py b/Tests/test_file_mcidas.py
--- a/Tests/test_file_mcidas.py
+++ b/Tests/test_file_mcidas.py
@@ -19,7 +19,7 @@ def test_valid_file() -> None:
     # https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8
     # https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/
     test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara"
-    saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png"
+    saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff"
 
     # Act
     with Image.open(test_file) as im:
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -102,7 +102,7 @@ def test_sanity(self, tmp_path: Path) -> None:
             im = hopper(mode)
             im.save(test_file)
             with Image.open(test_file) as reloaded:
-                if mode in ("I;16", "I;16B"):
+                if mode in ("I", "I;16B"):
                     reloaded = reloaded.convert(mode)
                 assert_image_equal(reloaded, im)
 
@@ -304,8 +304,8 @@ def test_save_p_transparent_black(self, tmp_path: Path) -> None:
         assert im.getcolors() == [(100, (0, 0, 0, 0))]
 
     def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
-        for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
-            in_file = "Tests/images/" + mode.lower() + "_trns.png"
+        for mode, num_transparent in {"1": 1994, "L": 559, "I;16": 559}.items():
+            in_file = "Tests/images/" + mode.split(";")[0].lower() + "_trns.png"
             with Image.open(in_file) as im:
                 assert im.mode == mode
                 assert im.info["transparency"] == 255
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -88,7 +88,7 @@ def test_16bit_pgm() -> None:
         assert im.size == (20, 100)
         assert im.get_format_mimetype() == "image/x-portable-graymap"
 
-        assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png")
+        assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
 
 
 def test_16bit_pgm_write(tmp_path: Path) -> None:
diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py
--- a/Tests/test_image_filter.py
+++ b/Tests/test_image_filter.py
@@ -148,9 +148,7 @@ def test_kernel_not_enough_coefficients() -> None:
 @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
 def test_consistency_3x3(mode: str) -> None:
     with Image.open("Tests/images/hopper.bmp") as source:
-        reference_name = "hopper_emboss"
-        reference_name += "_I.png" if mode == "I" else ".bmp"
-        with Image.open("Tests/images/" + reference_name) as reference:
+        with Image.open("Tests/images/hopper_emboss.bmp") as reference:
             kernel = ImageFilter.Kernel(
                 (3, 3),
                 # fmt: off
@@ -160,23 +158,13 @@ def test_consistency_3x3(mode: str) -> None:
                 # fmt: on
                 0.3,
             )
-            source = source.split() * 2
-            reference = reference.split() * 2
-
-            if mode == "I":
-                source = source[0].convert(mode)
-            else:
-                source = Image.merge(mode, source[: len(mode)])
-            reference = Image.merge(mode, reference[: len(mode)])
             assert_image_equal(source.filter(kernel), reference)
 
 
 @pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
 def test_consistency_5x5(mode: str) -> None:
     with Image.open("Tests/images/hopper.bmp") as source:
-        reference_name = "hopper_emboss_more"
-        reference_name += "_I.png" if mode == "I" else ".bmp"
-        with Image.open("Tests/images/" + reference_name) as reference:
+        with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
             kernel = ImageFilter.Kernel(
                 (5, 5),
                 # fmt: off
@@ -188,14 +176,6 @@ def test_consistency_5x5(mode: str) -> None:
                 # fmt: on
                 0.3,
             )
-            source = source.split() * 2
-            reference = reference.split() * 2
-
-            if mode == "I":
-                source = source[0].convert(mode)
-            else:
-                source = Image.merge(mode, source[: len(mode)])
-            reference = Image.merge(mode, reference[: len(mode)])
             assert_image_equal(source.filter(kernel), reference)
 
 
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -753,7 +753,7 @@ def test_rectangle_I16(bbox: Coords) -> None:
     draw.rectangle(bbox, outline=0xFFFF)
 
     # Assert
-    assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
+    assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
 
 
 @pytest.mark.parametrize("bbox", BBOX)