Skip to content

Add retina screen support for TkAgg backend #10388

Open

Description

@evgenyneu

Bug report

The plots done with "TkAgg" backend look blurry on retina screen of my Mac Book Pro. The plots look good when I'm using the default "MaxOSX" backend. It would be nice to have retina-friendly output for "TkAgg" since I'm using the plots into a tkinter GUI app.

matplotlib plots: tkagg vs osx

Code for reproduction

import matplotlib
matplotlib.use('TkAgg') # Remove this to compare with MacOSX backend
import matplotlib.pyplot as plt

f = plt.figure(figsize=(6, 4))
a = f.add_subplot(111)
a.plot([1,2,3,4])
plt.show()

Actual outcome

The plots and toolbar look blurry on "TkAgg" backend.

Expected outcome

The plots and toolbar look sharp, just like on "MaxOSX" backend.

Matplotlib version

  • Operating system: MacOS 10.13.2
  • Matplotlib version: 2.1.2
  • tkinter.TkVersion: 8.5
  • Matplotlib backend (print(matplotlib.get_backend())): TkAgg
  • Python version: 3.6.4

I've installed python from https://www.python.org/downloads/mac-osx/, clicking on Download menu and selecting Python 3.6.4 for MacOS.

If I create a tkinter app, other elements like labels, menus are sharp as well, only the plot is blurry. This indicates that tkinter is capable of showing sharp graphics, the question is how to do it.

Activity

  1. tacaswell commented on Feb 7, 2018

    @tacaswell
    Member

    @evgenyneu Does tk provide an API to opt into hi-dpi support?

  2. evgenyneu commented on Feb 7, 2018

    @evgenyneu
    Author

    @tacaswell good question, I have not found must information on this, apart from this option, not sure if it can help:

    root = Tk()
    root.tk.call('tk', 'scaling', 2.0)
    
  3. tacaswell commented on Feb 7, 2018

    @tacaswell
    Member

    That does look promising!

    Want to take a crack at this? Looking at how we do this in the Qt backend is probably a good starting place.

  4. added this to the milestone on Feb 7, 2018
  5. evgenyneu commented on Feb 7, 2018

    @evgenyneu
    Author

    Thanks, I'll see what I can do :)

  6. evgenyneu commented on Feb 7, 2018

    @evgenyneu
    Author

    I could not find a way to show an image that is appropriately scaled on retina screen. Here is a demo app shows shows a gif image. The code root.tk.call('tk', 'scaling', 2.0) does not seem to do anything.

    import tkinter
    
    class simpleapp_tk(tkinter.Tk):
        def __init__(self,parent):
            tkinter.Tk.__init__(self,parent)
            self.parent = parent
            self.initialize()
    
        def initialize(self):
            self.grid()
            self.img = tkinter.PhotoImage(file="image.gif")
    
            self.label = tkinter.Label(self,image=self.img, anchor='nw')
            self.label.grid(column=0,row=0,sticky='NSEW')
    
    if __name__ == "__main__":
        app = simpleapp_tk(None)
        app.geometry('{}x{}'.format(400, 200))
        app.title("Test App")
        app.mainloop()
  7. evgenyneu commented on Feb 7, 2018

    @evgenyneu
    Author

    It looks like a tkinter issue/feature. I could not find a way to detect current screen scaling (2x on my screen) and then apply this when showing an image. Does anyone know where can I find people who maintain tkinter to report this?

  8. efiring commented on Feb 7, 2018

    @efiring
    Member

    I did a little googling and came up with nothing promising. From my reading, the 'scaling' parameter has little or nothing to do with retina support. What is happening now is that our Tkagg backend is seeing the retina display only in its pixel-doubled compatibility mode.
    Gitk now looks nice and sharp on my retina display, though, including the history graph lines, which suggests that use of full resolution is not impossible. Gitk is using a Perl interface to Tk, I believe.
    Tkinter is part of the regular python installation, but I haven't found any sign of a dedicated maintainer for it. Even the link to the tkinter wiki is now dead.

  9. reaCodes commented on Feb 7, 2018

    @reaCodes

    My computer system is Windows 10, 1709 .
    The resolution is 2560*1440, DPI Zoom is 200%
    I encounter a similar situation.
    I encountered a similar situation
    image
    When I delete matplotlib.use('TkAgg')
    The display is still vague,
    image
    The UI looks hazy on high-resolution screens.

  10. tacaswell commented on Feb 7, 2018

    @tacaswell
    Member

    @reaCodes It looks like it is using 'tkagg' anyway (check matplotlib.get_backend())

  11. reaCodes commented on Feb 8, 2018

    @reaCodes
  12. ImportanceOfBeingErnest commented on Feb 8, 2018

    @ImportanceOfBeingErnest
    Member

    Googling led me to this question. Would it be possible to run python in

    <key>NSHighResolutionCapable</key>
    <true/>
    

    mode? If I understand correctly this should shrink the window by a factor of 2, but then produce the sharp image. If this succeeds, one may simply double the figure dpi in matplotlib and possibly use larger images for the buttons in the tk backend.

  13. tacaswell commented on Feb 8, 2018

    @tacaswell
    Member

    @ImportanceOfBeingErnest That is exactly how we do it for Qt (transparently double the DPI for rendering to the screen and using bigger icons).

  14. 12 remaining items

  15. rnhmjoj commented on Jun 10, 2024

    @rnhmjoj
    Contributor

    I have been using this patch for years on GNU/Linux with X.org to get correct DPI scaling:

    --- a/lib/matplotlib/backends/_backend_tk.py
    +++ b/lib/matplotlib/backends/_backend_tk.py
    @@ -212,6 +212,10 @@ class FigureCanvasTk(FigureCanvasBase):
     
             self._tkcanvas.focus_set()
     
    +        # set correct DPI scaling
    +        factor = self._tkcanvas.winfo_fpixels('1i') / 96
    +        self.figure.dpi *= factor
    +
         def _update_device_pixel_ratio(self, event=None):
             # Tk gives scaling with respect to 72 DPI, but Windows screens are
             # scaled vs 96 dpi, and pixel ratio settings are given in whole
    

    It would be good if matplotlib did this out-of-the-box, so I can avoid always recompiling the package.

  16. tacaswell commented on Jun 10, 2024

    @tacaswell
    Member

    @rnhmjoj Could you open a PR with that patch?

    attn @richardsheridan

  17. rnhmjoj commented on Jun 12, 2024

    @rnhmjoj
    Contributor

    @rnhmjoj Could you open a PR with that patch?

    I'm not sure if the matplotlib developers would consider this acceptable. I posted it here to receive some comments before making a serious attempt.

  18. tacaswell commented on Jun 12, 2024

    @tacaswell
    Member

    Why do you expect that this won't be acceptable? It seems reasonable, is there something I am missing? It looks like rather than expecting an API to give us the ratio, we ask tk how many pixels an inch is and compute it our selves?

  19. rnhmjoj commented on Jun 12, 2024

    @rnhmjoj
    Contributor

    Why do you expect that this won't be acceptable?

    Nothing in particular, I just don't know what is the intended behaviour regarding DPI scaling and if my change needs to respect some settings.
    As is, this will always rescale the figure and UI, probably overriding rcParams['figure.dpi'], but I haven't tested.

    It looks like rather than expecting an API to give us the ratio, we ask tk how many pixels an inch is and compute it our selves?

    Yes, at least that's how I understand it.

  20. tacaswell commented on Jun 12, 2024

    @tacaswell
    Member

    oh, I see I read too fast. We have a _dpi_scale attribute which is what should be set instead.

  21. rnhmjoj commented on Jul 14, 2024

    @rnhmjoj
    Contributor

    We have a _dpi_scale attribute

    Cam you point it to me? I can't find a reference to _dpi_scale anywhere in the code.

  22. tacaswell commented on Jul 16, 2024

    @tacaswell
    Member

    I'm not sure what code I was looking at / remembering. Sorry for pointing you in the wrong direction. There is some subtley with dealing with hidpi as we want to update the dpi when rendering to the screen but not when saving so just directly updating the dpi in __init__ is not correct.

    However, we already have

    def _update_device_pixel_ratio(self, event=None):
    # Tk gives scaling with respect to 72 DPI, but Windows screens are
    # scaled vs 96 dpi, and pixel ratio settings are given in whole
    # percentages, so round to 2 digits.
    ratio = round(self._tkcanvas.tk.call('tk', 'scaling') / (96 / 72), 2)
    if self._set_device_pixel_ratio(ratio):
    # The easiest way to resize the canvas is to resize the canvas
    # widget itself, since we implement all the logic for resizing the
    # canvas backing store on that event.
    w, h = self.get_width_height(physical=True)
    self._tkcanvas.configure(width=w, height=h)

    which get auto-called on windows via

    if sys.platform == 'win32':
    self._tkcanvas.bind("<Map>", self._update_device_pixel_ratio)

    I suspect the correct implementation is to always call _update_device_pixel_ratio and to put the non-win32 method of getting the scaling ratio in it.

  23. rnhmjoj commented on Aug 22, 2024

    @rnhmjoj
    Contributor

    If anyone feels like it, I made a PR to try to fix this: #28588

  24. QuLogic commented on Sep 21, 2024

    @QuLogic
    Member

    Unfortunately, #28588 only applied to Linux, so this is not yet fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions