From 6c692ff7ffa99d3ff30ffa281ad4ad7ee997f63a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 22 Jun 2026 02:29:37 +0300 Subject: [PATCH] gh-151890: Support more photo image options in tkinter.PhotoImage methods 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). The new option parameters are keyword-only. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XWevzas4XVpjzedzR9gKVo --- Doc/library/tkinter.rst | 42 +++++++++++-- Doc/whatsnew/3.16.rst | 7 +++ Lib/test/test_tkinter/test_images.py | 33 ++++++++++ Lib/tkinter/__init__.py | 63 ++++++++++++++++--- ...-06-22-02-29-02.gh-issue-151890.gT6JwI.rst | 6 ++ 5 files changed, 137 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-22-02-29-02.gh-issue-151890.gT6JwI.rst diff --git a/Doc/library/tkinter.rst b/Doc/library/tkinter.rst index 222a4d0128bd8d..04c0ce70d13066 100644 --- a/Doc/library/tkinter.rst +++ b/Doc/library/tkinter.rst @@ -5927,7 +5927,7 @@ Image classes .. method:: data(format=None, *, from_coords=None, background=None, \ - grayscale=False) + grayscale=False, metadata=None) Return the image data. @@ -5951,16 +5951,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 @@ -5973,13 +5984,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)``. @@ -6000,6 +6023,9 @@ Image classes .. versionadded:: 3.13 + .. versionchanged:: next + Added the *metadata* parameter. + .. method:: subsample(x, y='', *, from_coords=None) @@ -6032,7 +6058,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*. @@ -6054,9 +6080,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) diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index ec8e367d938ddb..3af253c0449df7 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -157,6 +157,13 @@ tkinter synchronization of the displayed view with the underlying text. (Contributed by Serhiy Storchaka in :gh:`151675`.) +* 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`.) + xml --- diff --git a/Lib/test/test_tkinter/test_images.py b/Lib/test/test_tkinter/test_images.py index f9b314da9e8a91..58c24ab80fc0ff 100644 --- a/Lib/test/test_tkinter/test_images.py +++ b/Lib/test/test_tkinter/test_images.py @@ -509,6 +509,16 @@ def test_put(self): 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)) @@ -519,6 +529,29 @@ def test_get(self): 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. diff --git a/Lib/tkinter/__init__.py b/Lib/tkinter/__init__.py index 8bdf7cc1e2d96b..ab127249b19566 100644 --- a/Lib/tkinter/__init__.py +++ b/Lib/tkinter/__init__.py @@ -4583,26 +4583,59 @@ def copy_replace(self, sourceImage, *, from_coords=None, to=None, shrink=False, 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 @@ -4622,6 +4655,8 @@ def read(self, filename, format=None, *, from_coords=None, to=None, shrink=False 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: @@ -4631,13 +4666,16 @@ def read(self, filename, format=None, *, from_coords=None, to=None, shrink=False 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, @@ -4656,6 +4694,8 @@ def write(self, filename, format=None, from_coords=None, *, 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: @@ -4665,9 +4705,12 @@ def write(self, filename, format=None, from_coords=None, *, 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 @@ -4694,6 +4737,8 @@ def data(self, format=None, *, from_coords=None, 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 index 00000000000000..3e064aeeba8131 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-22-02-29-02.gh-issue-151890.gT6JwI.rst @@ -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.