Skip to content

Add retina screen support for TkAgg backend #10388

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
evgenyneu opened this issue Feb 7, 2018 · 27 comments · Fixed by #28588
Open

Add retina screen support for TkAgg backend #10388

evgenyneu opened this issue Feb 7, 2018 · 27 comments · Fixed by #28588
Labels
GUI: tk keep Items to be ignored by the “Stale” Github Action New feature OS: Apple

Comments

@evgenyneu
Copy link

evgenyneu commented Feb 7, 2018

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.

@tacaswell
Copy link
Member

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

@evgenyneu
Copy link
Author

evgenyneu commented Feb 7, 2018

@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)

@tacaswell
Copy link
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.

@tacaswell tacaswell added this to the v3.0 milestone Feb 7, 2018
@evgenyneu
Copy link
Author

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

@evgenyneu
Copy link
Author

evgenyneu commented Feb 7, 2018

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()

@evgenyneu
Copy link
Author

evgenyneu commented Feb 7, 2018

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?

@efiring
Copy link
Member

efiring commented Feb 7, 2018

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.

@reaCodes
Copy link

reaCodes commented Feb 7, 2018

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.

@tacaswell
Copy link
Member

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

@reaCodes
Copy link

reaCodes commented Feb 8, 2018

@tacaswell
image

@ImportanceOfBeingErnest
Copy link
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.

@tacaswell
Copy link
Member

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

@dstansby dstansby modified the milestones: v3.0, needs sorting Jul 26, 2018
JDihlmann referenced this issue in cgtuebingen/low-poly-painter Aug 2, 2018
* Added Toolbar
  Contains buttons (Labels for better image quality) with icons
  Tkinter sadly does not have higher DPI support (icons look shitty)
  Buttons trigger canvas and application events

* Added Detail View
  Nothing here yet, but should contain info about current tool / mode
  For example: Color selection, Canny Parameter

* Resize Support
  UI changes when window is resized, canvas pins to center

* Icon Creation
  As mentioned Tkinter won't have high display resolution support
  For better Icon quality, export Icon as SVG and use following website
  https://www.online-convert.com/ to export SVG to GIF
  TKinter Photo only supports GIFs (Image for settings in comments)

 * Note (!)
   This is just for the first looks, UI can be fully changed :)
@thesilvjurado
Copy link

I see this has not been touched in a few years. Anyone have any ideas? I am running into the same issue. Graphics using TkAgg and/or GUI elements in Tkinter are all "blurry" in MacOS. Figures using Qt5Agg are sharp as expected, but I don't think you can run Tkinter GUIs using Qt5Agg.

@tacaswell
Copy link
Member

@thesilvjurado We are at the mercy of the underlying toolkits here. While tk has just added some support for windows I am not sure if they have added support for osx yet. As soon as that is done, we can support it the same way we support the other GUI toolkits (and because we have the windows support there already, it should "just" be a matter of removing some OS gating).


As we started to get higher and higher DPI screens, there was the problem that if you just naively use the same (pixel level) output to the screen you end up with UIs that are approximately half the physical size they used to be. Unfortunately many applications did not actually take into account the DPI, so what many of the windowing systems did was say "right, unless you tell us not to if we know you are on a high-dpi screen you are now drawing to 'virtual pixels' and we are going to up-scale what ever pixel map you sent to the screen by turning each 'virtual pixel' into a 2x2 set of 'screen pixels'". The (not unreasonable) judgement was that "blurry" is better than "too small to see" for the general consumer population. If your application (Matplotlib) is aware of the display DPI, you can assure the windowing system you have it handled, opt-out of the automatic scaling and then send pixel maps in 'screen pixels' to the screen (which basically means we rasterize the figure at 2x the notional DPI). The OSX backend (because it is using the native toolkit) supported this out of the gate on OSX and Qt supported it not long after on the rest of the platforms.

@thesilvjurado
Copy link

@tacaswell thanks for the detailed info. That makes sense. It seems Tk is not quite up to date / as maintained as Qt. I'll probably need to migrate my GUI to Qt. I appreciate the help!

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Jun 30, 2023
@QuLogic
Copy link
Member

QuLogic commented Jun 30, 2023

The TkAgg backend has some support for this internally since #19167, but uses some Windows-specific API to achieve the connection to the system. For macOS, some similar bit of code will have to be written, but I am not familiar with what that would be.

@QuLogic QuLogic added GUI: MacOSX and removed status: inactive Marked by the “Stale” Github Action labels Jun 30, 2023
@QuLogic QuLogic added keep Items to be ignored by the “Stale” Github Action OS: Apple and removed GUI: MacOSX labels Jun 30, 2023
@rnhmjoj
Copy link
Contributor

rnhmjoj commented Jun 10, 2024

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.

@tacaswell
Copy link
Member

@rnhmjoj Could you open a PR with that patch?

attn @richardsheridan

@rnhmjoj
Copy link
Contributor

rnhmjoj commented Jun 12, 2024

@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.

@tacaswell
Copy link
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?

@rnhmjoj
Copy link
Contributor

rnhmjoj commented Jun 12, 2024

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.

@tacaswell
Copy link
Member

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

@rnhmjoj
Copy link
Contributor

rnhmjoj commented Jul 14, 2024

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.

@tacaswell
Copy link
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.

@rnhmjoj
Copy link
Contributor

rnhmjoj commented Aug 22, 2024

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

@QuLogic
Copy link
Member

QuLogic commented Sep 21, 2024

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

@QuLogic QuLogic reopened this Sep 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GUI: tk keep Items to be ignored by the “Stale” Github Action New feature OS: Apple
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants