]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-151890: Support more photo image options in tkinter.PhotoImage methods (GH-151891)
authorSerhiy Storchaka <storchaka@gmail.com>
Mon, 22 Jun 2026 16:35:39 +0000 (19:35 +0300)
committerGitHub <noreply@github.com>
Mon, 22 Jun 2026 16:35:39 +0000 (16:35 +0000)
Add parameters mapping to Tk photo image options that previously had no
tkinter equivalent:

* the format parameter of PhotoImage.put() (Tk 8.6),
* the metadata parameter of PhotoImage.put(), read(), write() and data()
  (Tk 9.0),
* the withalpha parameter of PhotoImage.get() (Tk 9.0).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Doc/library/tkinter.rst
Doc/whatsnew/3.16.rst
Lib/test/test_tkinter/test_images.py
Lib/tkinter/__init__.py
Misc/NEWS.d/next/Library/2026-06-22-02-29-02.gh-issue-151890.gT6JwI.rst [new file with mode: 0644]

index db6834fbea53e4e060e95c3aa2130fd1ac6c31dc..0257de630670033903845704f0f9f8aac97c28eb 100644 (file)
@@ -6151,7 +6151,7 @@ Image classes
 
 
    .. method:: data(format=None, *, from_coords=None, background=None, \
-                    grayscale=False)
+                    grayscale=False, metadata=None)
 
       Return the image data.
 
@@ -6175,16 +6175,27 @@ Image classes
       If *grayscale* is true, the data does not contain color information; all
       pixel data is transformed into grayscale.
 
+      *metadata* is a dictionary passed to the image format driver.
+      It requires Tcl/Tk 9.0 or newer.
+
       .. versionadded:: 3.13
 
+      .. versionchanged:: next
+         Added the *metadata* parameter.
+
 
-   .. method:: get(x, y)
+   .. method:: get(x, y, *, withalpha=False)
 
       Return the color of the pixel at coordinates (*x*, *y*) as an
       ``(r, g, b)`` tuple of three integers between 0 and 255, representing the
       red, green and blue components respectively.
+      If *withalpha* is true, the returned tuple has a fourth element giving
+      the alpha (opacity) value of the pixel.
+
+      .. versionchanged:: next
+         Added the *withalpha* parameter, which requires Tcl/Tk 9.0 or newer.
 
-   .. method:: put(data, to=None)
+   .. method:: put(data, to=None, *, format=None, metadata=None)
 
       Set pixels of the image to the colors given in *data*, which must be a
       string or a nested sequence of horizontal rows of pixel colors (for
@@ -6197,13 +6208,25 @@ Image classes
       bottom-right corner, of the region.
       The default position is ``(0, 0)``.
 
+      *format* specifies the format of the image *data*, so that only image
+      file format handlers whose names begin with it are tried.
+
+      *metadata* is a dictionary passed to the image format driver.
+      It requires Tcl/Tk 9.0 or newer.
+
+      .. versionchanged:: next
+         Added the *format* and *metadata* parameters.
+
    .. method:: read(filename, format=None, *, from_coords=None, to=None, \
-                    shrink=False)
+                    shrink=False, metadata=None)
 
       Read image data from the file named *filename* into the image.
 
       *format* specifies the format of the image data in the file.
 
+      *metadata* is a dictionary passed to the image format driver.
+      It requires Tcl/Tk 9.0 or newer.
+
       *from_coords* specifies a rectangular sub-region of the image file data
       to be copied to the destination image.
       It must be a tuple or a list of 1 to 4 integers ``(x1, y1, x2, y2)``.
@@ -6224,6 +6247,9 @@ Image classes
 
       .. versionadded:: 3.13
 
+      .. versionchanged:: next
+         Added the *metadata* parameter.
+
 
    .. method:: subsample(x, y='', *, from_coords=None)
 
@@ -6256,7 +6282,7 @@ Image classes
 
 
    .. method:: write(filename, format=None, from_coords=None, *, \
-                     background=None, grayscale=False)
+                     background=None, grayscale=False, metadata=None)
 
       Write image data from the image to the file named *filename*.
 
@@ -6278,9 +6304,15 @@ Image classes
       If *grayscale* is true, the data does not contain color information; all
       pixel data is transformed into grayscale.
 
+      *metadata* is a dictionary passed to the image format driver.
+      It requires Tcl/Tk 9.0 or newer.
+
       .. versionchanged:: 3.13
          Added the *background* and *grayscale* parameters.
 
+      .. versionchanged:: next
+         Added the *metadata* parameter.
+
 
    .. method:: zoom(x, y='', *, from_coords=None)
 
index eb13c67ac6a5e852a28f0efed9d054176c239e05..b50a43f08b5dec8099d3fe606bdf66c9a2479aa8 100644 (file)
@@ -194,6 +194,13 @@ tkinter
   badge) and :meth:`~tkinter.Wm.wm_stackorder` (toplevel stacking order).
   (Contributed by Serhiy Storchaka in :gh:`151874`.)
 
+* Added support for more options in :class:`!tkinter.PhotoImage` methods: the
+  *format* parameter of :meth:`~tkinter.PhotoImage.put`, the *metadata*
+  parameter of :meth:`~tkinter.PhotoImage.put`, :meth:`~tkinter.PhotoImage.read`,
+  :meth:`~tkinter.PhotoImage.write` and :meth:`~tkinter.PhotoImage.data`, and
+  the *withalpha* parameter of :meth:`~tkinter.PhotoImage.get`.
+  (Contributed by Serhiy Storchaka in :gh:`151890`.)
+
 * Added the :meth:`~tkinter.PhotoImage.redither` method which recalculates the
   dithered image when its data was supplied in pieces.
   (Contributed by Serhiy Storchaka in :gh:`151888`.)
index 099996feb5654a3f2dac5749538dcccc90d5dce6..4ce66340620333943e44bab77cc17bdf358d2f97 100644 (file)
@@ -515,6 +515,16 @@ class PhotoImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
         self.assertEqual(image.get(0, 1), self.colorlist(0, 0, 255))
         self.assertEqual(image.get(1, 1), self.colorlist(255, 255, 0))
 
+    def test_put_format(self):
+        image = self.create()
+        with open(self.testfile, 'rb') as f:
+            data = f.read()
+        image2 = tkinter.PhotoImage(master=self.root)
+        image2.put(data, format='gif')
+        self.assertEqual(image2.width(), 16)
+        self.assertEqual(image2.height(), 16)
+        self.assertEqual(image2.get(4, 6), image.get(4, 6))
+
     def test_get(self):
         image = self.create()
         self.assertEqual(image.get(4, 6), self.colorlist(62, 116, 162))
@@ -525,6 +535,29 @@ class PhotoImageTest(BaseImageTest, AbstractTkTest, unittest.TestCase):
         self.assertRaises(tkinter.TclError, image.get, 16, 15)
         self.assertRaises(tkinter.TclError, image.get, 15, 16)
 
+    @requires_tk(9, 0)
+    def test_get_withalpha(self):
+        image = self.create()
+        rgb = image.get(4, 6)
+        rgba = image.get(4, 6, withalpha=True)
+        if self.wantobjects:
+            self.assertEqual(rgba[:3], rgb)
+            self.assertEqual(len(rgba), 4)
+            self.assertIn(rgba[3], (0, 255))  # GIF alpha is fully on or off
+        else:
+            self.assertTrue(rgba.startswith(rgb + ' '))
+
+    @requires_tk(9, 0)
+    def test_metadata(self):
+        image = self.create()
+        # The -metadata configuration option holds the image's metadata.
+        image.configure(metadata=('Comment', 'spam'))
+        self.assertIn('Comment', str(image.cget('metadata')))
+        # put() and data() accept a metadata dictionary, passed to the image
+        # format driver.  put() does not change the image's own metadata.
+        image.put('{red green} {blue yellow}', metadata={'Comment': 'spam'})
+        self.assertTrue(image.data(metadata={'Comment': 'spam'}))
+
     def test_read(self):
         # Due to the Tk bug https://core.tcl-lang.org/tk/tktview/1576528
         # the -from option does not work correctly for GIF and PNG files.
index bf1a0290088fd215489444b8626a38b6ef8a5ab2..dd7407176ddd8f984698858fb6c146c3ea7723b9 100644 (file)
@@ -4692,26 +4692,59 @@ class PhotoImage(Image):
             options.extend(('-compositingrule', compositingrule))
         self.tk.call(self.name, 'copy', sourceImage, *options)
 
-    def get(self, x, y):
-        """Return the color (red, green, blue) of the pixel at X,Y."""
-        return self.tk.call(self.name, 'get', x, y)
+    @staticmethod
+    def _metadata(metadata):
+        # A Tcl dict is a flat list of alternating keys and values.  A Python
+        # dict passed directly would expand to its keys only, so flatten it.
+        flat = ()
+        for key, value in metadata.items():
+            flat += (key, value)
+        return flat
+
+    def get(self, x, y, *, withalpha=False):
+        """Return the color of the pixel at X,Y as a tuple of its red, green
+        and blue components.
+
+        If WITHALPHA is true, the returned tuple has a fourth element giving
+        the alpha (opacity) value of the pixel.  This requires Tcl/Tk 9.0 or
+        newer.
+        """
+        args = (self.name, 'get', x, y)
+        if withalpha:
+            args += ('-withalpha',)
+        return self.tk.call(args)
 
-    def put(self, data, to=None):
+    def put(self, data, to=None, *, format=None, metadata=None):
         """Put row formatted colors to image starting from
-        position TO, e.g. image.put("{red green} {blue yellow}", to=(4,6))"""
+        position TO, e.g. image.put("{red green} {blue yellow}", to=(4,6))
+
+        The FORMAT option specifies the format of the image DATA, so that only
+        image file format handlers whose names begin with it are tried.
+
+        The METADATA option, a dictionary passed to the image format driver,
+        requires Tcl/Tk 9.0 or newer.
+        """
         args = (self.name, 'put', data)
+        if format is not None:
+            args += ('-format', format)
+        if metadata is not None:
+            args += ('-metadata', self._metadata(metadata))
         if to:
             if to[0] == '-to':
                 to = to[1:]
-            args = args + ('-to',) + tuple(to)
+            args += ('-to',) + tuple(to)
         self.tk.call(args)
 
-    def read(self, filename, format=None, *, from_coords=None, to=None, shrink=False):
+    def read(self, filename, format=None, *, from_coords=None, to=None,
+             shrink=False, metadata=None):
         """Reads image data from the file named FILENAME into the image.
 
         The FORMAT option specifies the format of the image data in the
         file.
 
+        The METADATA option, a dictionary passed to the image format driver,
+        requires Tcl/Tk 9.0 or newer.
+
         The FROM_COORDS option specifies a rectangular sub-region of the image
         file data to be copied to the destination image.  It must be a tuple
         or a list of 1 to 4 integers (x1, y1, x2, y2).  (x1, y1) and
@@ -4731,6 +4764,8 @@ class PhotoImage(Image):
         options = ()
         if format is not None:
             options += ('-format', format)
+        if metadata is not None:
+            options += ('-metadata', self._metadata(metadata))
         if from_coords is not None:
             options += ('-from', *from_coords)
         if shrink:
@@ -4740,13 +4775,16 @@ class PhotoImage(Image):
         self.tk.call(self.name, 'read', filename, *options)
 
     def write(self, filename, format=None, from_coords=None, *,
-              background=None, grayscale=False):
+              background=None, grayscale=False, metadata=None):
         """Writes image data from the image to a file named FILENAME.
 
         The FORMAT option specifies the name of the image file format
         handler to be used to write the data to the file.  If this option
         is not given, the format is guessed from the file extension.
 
+        The METADATA option, a dictionary passed to the image format driver,
+        requires Tcl/Tk 9.0 or newer.
+
         The FROM_COORDS option specifies a rectangular region of the image
         to be written to the image file.  It must be a tuple or a list of 1
         to 4 integers (x1, y1, x2, y2).  If only x1 and y1 are specified,
@@ -4765,6 +4803,8 @@ class PhotoImage(Image):
         options = ()
         if format is not None:
             options += ('-format', format)
+        if metadata is not None:
+            options += ('-metadata', self._metadata(metadata))
         if from_coords is not None:
             options += ('-from', *from_coords)
         if grayscale:
@@ -4774,9 +4814,12 @@ class PhotoImage(Image):
         self.tk.call(self.name, 'write', filename, *options)
 
     def data(self, format=None, *, from_coords=None,
-             background=None, grayscale=False):
+             background=None, grayscale=False, metadata=None):
         """Returns image data.
 
+        The METADATA option, a dictionary passed to the image format driver,
+        requires Tcl/Tk 9.0 or newer.
+
         The FORMAT option specifies the name of the image file format
         handler to be used.  If this option is not given, this method uses
         a format that consists of a tuple (one element per row) of strings
@@ -4803,6 +4846,8 @@ class PhotoImage(Image):
         options = ()
         if format is not None:
             options += ('-format', format)
+        if metadata is not None:
+            options += ('-metadata', self._metadata(metadata))
         if from_coords is not None:
             options += ('-from', *from_coords)
         if grayscale:
diff --git a/Misc/NEWS.d/next/Library/2026-06-22-02-29-02.gh-issue-151890.gT6JwI.rst b/Misc/NEWS.d/next/Library/2026-06-22-02-29-02.gh-issue-151890.gT6JwI.rst
new file mode 100644 (file)
index 0000000..3e064ae
--- /dev/null
@@ -0,0 +1,6 @@
+Add the *format* parameter to the :meth:`tkinter.PhotoImage.put` method, the
+*metadata* parameter to the :meth:`~tkinter.PhotoImage.put`,
+:meth:`~tkinter.PhotoImage.read`, :meth:`~tkinter.PhotoImage.write` and
+:meth:`~tkinter.PhotoImage.data` methods, and the *withalpha* parameter to the
+:meth:`~tkinter.PhotoImage.get` method.  The *metadata* and *withalpha*
+parameters require Tcl/Tk 9.0 or newer.