From 297965ab0253dfa1b49c7eff2507266af366f424 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Tue, 28 Apr 2026 15:34:00 -0700 Subject: [PATCH 1/2] Added bit_depth attribute to PngImageFile --- Tests/test_file_png.py | 13 +++++++++++++ docs/handbook/image-file-formats.rst | 8 ++++++++ src/PIL/PngImagePlugin.py | 3 +++ 3 files changed, 24 insertions(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 8fe2a5eac68..4c7253b3824 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -222,6 +222,19 @@ def test_interlace(self) -> None: im.load() + @pytest.mark.parametrize( + "test_file, expected_bit_depth", + ( + ("Tests/images/hopper.png", 8), + ("Tests/images/hopper_bw_500.png", 1), + ("Tests/images/tiny.png", 4), + ("Tests/images/i_trns.png", 16), + ), + ) + def test_bit_depth(self, test_file: str, expected_bit_depth: int) -> None: + with Image.open(test_file) as im: + assert im.bit_depth == expected_bit_depth + def test_load_transparent_p(self) -> None: test_file = "Tests/images/pil123p.png" with Image.open(test_file) as im: diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index a9fd764e613..7449b28e892 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -895,6 +895,14 @@ decompression bombs. Additionally, the total size of all of the text chunks is limited to :data:`.PngImagePlugin.MAX_TEXT_MEMORY`, defaulting to 64MB. +The :py:class:`~PIL.PngImagePlugin.PngImageFile` class also exposes the +following attribute: + +**bit_depth** + The per-sample bit depth read from the PNG ``IHDR`` chunk. Valid values + depend on the PNG color type and are one of ``1``, ``2``, ``4``, ``8`` or + ``16``. + .. _png-saving: Saving diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 76a15bd0dc6..e2edafa0514 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -391,6 +391,7 @@ def __init__(self, fp: IO[bytes]) -> None: self.im_text: dict[str, str | iTXt] = {} self.im_size = (0, 0) self.im_mode = "" + self.im_bit_depth = 0 self.im_tile: list[ImageFile._Tile] = [] self.im_palette: tuple[str, bytes] | None = None self.im_custom_mimetype: str | None = None @@ -459,6 +460,7 @@ def chunk_IHDR(self, pos: int, length: int) -> bytes: msg = "Truncated IHDR chunk" raise ValueError(msg) self.im_size = i32(s, 0), i32(s, 4) + self.im_bit_depth = s[8] try: self.im_mode, self.im_rawmode = _MODES[(s[8], s[9])] except Exception: @@ -801,6 +803,7 @@ def _open(self) -> None: self._mode = self.png.im_mode self._size = self.png.im_size self.info = self.png.im_info + self.bit_depth = self.png.im_bit_depth self._text: dict[str, str | iTXt] | None = None self.tile = self.png.im_tile self.custom_mimetype = self.png.im_custom_mimetype From eddbdd254da876a1972dd3a23fef2701f9dd9ee1 Mon Sep 17 00:00:00 2001 From: AJ Slater Date: Fri, 1 May 2026 15:22:31 -0700 Subject: [PATCH 2/2] Narrow Image type for bit_depth assertions to fix mypy --- Tests/test_file_png.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 4c7253b3824..87493c75ca5 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -233,6 +233,7 @@ def test_interlace(self) -> None: ) def test_bit_depth(self, test_file: str, expected_bit_depth: int) -> None: with Image.open(test_file) as im: + assert isinstance(im, PngImagePlugin.PngImageFile) assert im.bit_depth == expected_bit_depth def test_load_transparent_p(self) -> None: