From 6bbed1add0fa7579fdc09d70d43ad3c1fcc41eee Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Sep 2023 20:10:44 +1000 Subject: [PATCH 1/6] Added has_transparency_data() --- Tests/test_image.py | 25 +++++++++++++++++++++++++ docs/reference/Image.rst | 1 + src/PIL/Image.py | 19 ++++++++++++++++++- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 7df1916ef81..487035a3ef9 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -906,6 +906,31 @@ def test_zero_tobytes(self, size): im = Image.new("RGB", size) assert im.tobytes() == b"" + def test_has_transparency_data(self): + for mode in ("1", "L", "P", "RGB"): + im = Image.new(mode, (1, 1)) + assert not im.has_transparency_data() + + for mode in ("LA", "La", "PA", "RGBA", "RGBa"): + im = Image.new(mode, (1, 1)) + assert im.has_transparency_data() + + # P mode with "transparency" info + with Image.open("Tests/images/first_frame_transparency.gif") as im: + assert "transparency" in im.info + assert im.has_transparency_data() + + # RGB mode with "transparency" info + with Image.open("Tests/images/rgb_trns.png") as im: + assert "transparency" in im.info + assert im.has_transparency_data() + + # P mode with RGBA palette + im = Image.new("RGBA", (1, 1)).convert("P") + assert im.mode == "P" + assert im.palette.mode == "RGBA" + assert im.has_transparency_data() + def test_apply_transparency(self): im = Image.new("P", (1, 1)) im.putpalette((0, 0, 0, 1, 1, 1)) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index 66e6b2a0c97..ae8f923cb7a 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -195,6 +195,7 @@ This helps to get the bounding box coordinates of the input image:: .. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel .. automethod:: PIL.Image.Image.getprojection +.. automethod:: PIL.Image.Image.has_transparency_data .. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.paste .. automethod:: PIL.Image.Image.point diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 2a6b4646bbd..96ce96d559a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -915,7 +915,7 @@ def convert( self.load() - has_transparency = self.info.get("transparency") is not None + has_transparency = "transparency" in self.info if not mode and self.mode == "P": # determine default mode if self.palette: @@ -1531,6 +1531,23 @@ def getpalette(self, rawmode="RGB"): rawmode = mode return list(self.im.getpalette(mode, rawmode)) + def has_transparency_data(self): + """ + Determine if an image has transparency data, whether in the form of an + alpha channel, a palette with an alpha channel, or a "transparency" key + in the info dictionary. + + Note the image might still appear solid, if all of the values shown + within are opaque. + + :returns: A boolean. + """ + return ( + self.mode in ("LA", "La", "PA", "RGBA", "RGBa") + or (self.mode == "P" and self.palette.mode == "RGBA") + or "transparency" in self.info + ) + def apply_transparency(self): """ If a P mode image has a "transparency" key in the info dictionary, From ad12caecda408aae101a9e59f0efd8b1be4e6744 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 25 Sep 2023 20:28:25 +1000 Subject: [PATCH 2/6] Convert RGBA palette to RGBA image when saving WebP --- Tests/test_file_webp.py | 10 ++++++++++ src/PIL/WebPImagePlugin.py | 7 +------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 3832441c02d..c8e4e6aece0 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -234,3 +234,13 @@ def test_duration(self, tmp_path): with Image.open(out_webp) as reloaded: assert reloaded.info["duration"] == 1000 + + def test_roundtrip_rgba_palette(self, tmp_path): + temp_file = str(tmp_path / "temp.webp") + im = Image.new("RGBA", (1, 1)).convert("P") + assert im.mode == "P" + assert im.palette.mode == "RGBA" + im.save(temp_file) + + with Image.open(temp_file) as im: + assert im.getpixel((0, 0)) == (0, 0, 0, 0) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index a6e1a2a00c5..86169b78085 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -332,12 +332,7 @@ def _save(im, fp, filename): exact = 1 if im.encoderinfo.get("exact") else 0 if im.mode not in _VALID_WEBP_LEGACY_MODES: - alpha = ( - "A" in im.mode - or "a" in im.mode - or (im.mode == "P" and "transparency" in im.info) - ) - im = im.convert("RGBA" if alpha else "RGB") + im = im.convert("RGBA" if im.has_transparency_data() else "RGB") data = _webp.WebPEncode( im.tobytes(), From faa66eaa6c1728b1fef23e516e7142f0c914f41d Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 26 Sep 2023 20:10:12 +1000 Subject: [PATCH 3/6] Added type hint Co-authored-by: Hugo van Kemenade --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 96ce96d559a..d0d5c18846c 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1531,7 +1531,7 @@ def getpalette(self, rawmode="RGB"): rawmode = mode return list(self.im.getpalette(mode, rawmode)) - def has_transparency_data(self): + def has_transparency_data(self) -> bool: """ Determine if an image has transparency data, whether in the form of an alpha channel, a palette with an alpha channel, or a "transparency" key From 1c30809245347efda6bf98ba71ace90240efb302 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Sep 2023 19:54:34 +1000 Subject: [PATCH 4/6] Allow for LA or PA in the future --- src/PIL/Image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index d0d5c18846c..842e5db56ea 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1544,7 +1544,7 @@ def has_transparency_data(self) -> bool: """ return ( self.mode in ("LA", "La", "PA", "RGBA", "RGBa") - or (self.mode == "P" and self.palette.mode == "RGBA") + or (self.mode == "P" and self.palette.mode.endswith("A")) or "transparency" in self.info ) From e27d7a6f8484d3fbbff121a7463960caccb1ff97 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 26 Sep 2023 20:14:22 +1000 Subject: [PATCH 5/6] Changed has_transparency_data() to property --- Tests/test_image.py | 10 +++++----- docs/reference/Image.rst | 2 +- src/PIL/Image.py | 1 + src/PIL/WebPImagePlugin.py | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Tests/test_image.py b/Tests/test_image.py index 487035a3ef9..b9c57770c70 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -909,27 +909,27 @@ def test_zero_tobytes(self, size): def test_has_transparency_data(self): for mode in ("1", "L", "P", "RGB"): im = Image.new(mode, (1, 1)) - assert not im.has_transparency_data() + assert not im.has_transparency_data for mode in ("LA", "La", "PA", "RGBA", "RGBa"): im = Image.new(mode, (1, 1)) - assert im.has_transparency_data() + assert im.has_transparency_data # P mode with "transparency" info with Image.open("Tests/images/first_frame_transparency.gif") as im: assert "transparency" in im.info - assert im.has_transparency_data() + assert im.has_transparency_data # RGB mode with "transparency" info with Image.open("Tests/images/rgb_trns.png") as im: assert "transparency" in im.info - assert im.has_transparency_data() + assert im.has_transparency_data # P mode with RGBA palette im = Image.new("RGBA", (1, 1)).convert("P") assert im.mode == "P" assert im.palette.mode == "RGBA" - assert im.has_transparency_data() + assert im.has_transparency_data def test_apply_transparency(self): im = Image.new("P", (1, 1)) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index ae8f923cb7a..e356469b673 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -195,7 +195,7 @@ This helps to get the bounding box coordinates of the input image:: .. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel .. automethod:: PIL.Image.Image.getprojection -.. automethod:: PIL.Image.Image.has_transparency_data +.. autoproperty:: PIL.Image.Image.has_transparency_data .. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.paste .. automethod:: PIL.Image.Image.point diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 842e5db56ea..244d2e43520 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1531,6 +1531,7 @@ def getpalette(self, rawmode="RGB"): rawmode = mode return list(self.im.getpalette(mode, rawmode)) + @property def has_transparency_data(self) -> bool: """ Determine if an image has transparency data, whether in the form of an diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 86169b78085..fa34769e77f 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -332,7 +332,7 @@ def _save(im, fp, filename): exact = 1 if im.encoderinfo.get("exact") else 0 if im.mode not in _VALID_WEBP_LEGACY_MODES: - im = im.convert("RGBA" if im.has_transparency_data() else "RGB") + im = im.convert("RGBA" if im.has_transparency_data else "RGB") data = _webp.WebPEncode( im.tobytes(), From 4142fc43abda6f793930df4b3dea1c2ed8b85ef2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Sep 2023 10:14:10 +1000 Subject: [PATCH 6/6] Added release notes --- docs/reference/Image.rst | 3 ++- docs/releasenotes/10.1.0.rst | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/reference/Image.rst b/docs/reference/Image.rst index e356469b673..4281b182ce7 100644 --- a/docs/reference/Image.rst +++ b/docs/reference/Image.rst @@ -195,7 +195,6 @@ This helps to get the bounding box coordinates of the input image:: .. automethod:: PIL.Image.Image.getpalette .. automethod:: PIL.Image.Image.getpixel .. automethod:: PIL.Image.Image.getprojection -.. autoproperty:: PIL.Image.Image.has_transparency_data .. automethod:: PIL.Image.Image.histogram .. automethod:: PIL.Image.Image.paste .. automethod:: PIL.Image.Image.point @@ -352,6 +351,8 @@ Instances of the :py:class:`Image` class have the following attributes: .. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell` +.. autoattribute:: PIL.Image.Image.has_transparency_data + Classes ------- diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst index da5153cceef..ad076c1f3be 100644 --- a/docs/releasenotes/10.1.0.rst +++ b/docs/releasenotes/10.1.0.rst @@ -32,10 +32,16 @@ TODO API Additions ============= -TODO -^^^^ +has_transparency_data +^^^^^^^^^^^^^^^^^^^^^ -TODO +Images now have :py:attr:`~PIL.Image.Image.has_transparency_data` to indicate +whether the image has transparency data, whether in the form of an alpha +channel, a palette with an alpha channel, or a "transparency" key in the +:py:attr:`~PIL.Image.Image.info` dictionary. + +Even if this attribute is true, the image might still appear solid, if all of +the values shown within are opaque. Security ========