From fbf5477c809a3b0990ee0139401b652ecce201c7 Mon Sep 17 00:00:00 2001
From: Thomas A Caswell <tcaswell@gmail.com>
Date: Wed, 20 Oct 2021 18:03:14 -0400
Subject: [PATCH] Backport PR #21212: Fix set_size_inches on HiDPI and also
 GTK4

---
 lib/matplotlib/backend_bases.py         |  9 +++++++--
 lib/matplotlib/backends/backend_gtk3.py |  2 ++
 lib/matplotlib/backends/backend_gtk4.py | 15 +++++----------
 lib/matplotlib/backends/backend_qt.py   |  6 ++++--
 lib/matplotlib/figure.py                |  3 +--
 5 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py
index 0f5949445775..312cb649dc81 100644
--- a/lib/matplotlib/backend_bases.py
+++ b/lib/matplotlib/backend_bases.py
@@ -1760,7 +1760,12 @@ def blit(self, bbox=None):
         """Blit the canvas in bbox (default entire canvas)."""
 
     def resize(self, w, h):
-        """Set the canvas size in pixels."""
+        """
+        UNUSED: Set the canvas size in pixels.
+
+        Certain backends may implement a similar method internally, but this is
+        not a requirement of, nor is it used by, Matplotlib itself.
+        """
 
     def draw_event(self, renderer):
         """Pass a `DrawEvent` to all functions connected to ``draw_event``."""
@@ -2813,7 +2818,7 @@ def full_screen_toggle(self):
         pass
 
     def resize(self, w, h):
-        """For GUI backends, resize the window (in pixels)."""
+        """For GUI backends, resize the window (in physical pixels)."""
 
     @_api.deprecated(
         "3.4", alternative="self.canvas.callbacks.process(event.name, event)")
diff --git a/lib/matplotlib/backends/backend_gtk3.py b/lib/matplotlib/backends/backend_gtk3.py
index 662d1028658e..e1d006436263 100644
--- a/lib/matplotlib/backends/backend_gtk3.py
+++ b/lib/matplotlib/backends/backend_gtk3.py
@@ -413,6 +413,8 @@ def set_window_title(self, title):
 
     def resize(self, width, height):
         """Set the canvas size in pixels."""
+        width = int(width / self.canvas.device_pixel_ratio)
+        height = int(height / self.canvas.device_pixel_ratio)
         if self.toolbar:
             toolbar_size = self.toolbar.size_request()
             height += toolbar_size.height
diff --git a/lib/matplotlib/backends/backend_gtk4.py b/lib/matplotlib/backends/backend_gtk4.py
index 2a589ee691a3..dcc97a1dec5c 100644
--- a/lib/matplotlib/backends/backend_gtk4.py
+++ b/lib/matplotlib/backends/backend_gtk4.py
@@ -374,18 +374,13 @@ def set_window_title(self, title):
 
     def resize(self, width, height):
         """Set the canvas size in pixels."""
+        width = int(width / self.canvas.device_pixel_ratio)
+        height = int(height / self.canvas.device_pixel_ratio)
         if self.toolbar:
-            toolbar_size = self.toolbar.size_request()
-            height += toolbar_size.height
+            min_size, nat_size = self.toolbar.get_preferred_size()
+            height += nat_size.height
         canvas_size = self.canvas.get_allocation()
-        if canvas_size.width == canvas_size.height == 1:
-            # A canvas size of (1, 1) cannot exist in most cases, because
-            # window decorations would prevent such a small window. This call
-            # must be before the window has been mapped and widgets have been
-            # sized, so just change the window's starting size.
-            self.window.set_default_size(width, height)
-        else:
-            self.window.resize(width, height)
+        self.window.set_default_size(width, height)
 
 
 class NavigationToolbar2GTK4(_NavigationToolbar2GTK, Gtk.Box):
diff --git a/lib/matplotlib/backends/backend_qt.py b/lib/matplotlib/backends/backend_qt.py
index 908e2affad31..d8c0caadd1fd 100644
--- a/lib/matplotlib/backends/backend_qt.py
+++ b/lib/matplotlib/backends/backend_qt.py
@@ -589,8 +589,10 @@ def _get_toolbar(self, canvas, parent):
         return toolbar
 
     def resize(self, width, height):
-        # these are Qt methods so they return sizes in 'virtual' pixels
-        # so we do not need to worry about dpi scaling here.
+        # The Qt methods return sizes in 'virtual' pixels so we do need to
+        # rescale from physical to logical pixels.
+        width = int(width / self.canvas.device_pixel_ratio)
+        height = int(height / self.canvas.device_pixel_ratio)
         extra_width = self.window.width() - self.canvas.width()
         extra_height = self.window.height() - self.canvas.height()
         self.canvas.resize(width, height)
diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py
index 418948eea5e0..2454776370d0 100644
--- a/lib/matplotlib/figure.py
+++ b/lib/matplotlib/figure.py
@@ -2657,10 +2657,9 @@ def set_size_inches(self, w, h=None, forward=True):
         if forward:
             canvas = getattr(self, 'canvas')
             if canvas is not None:
-                dpi_ratio = getattr(canvas, '_dpi_ratio', 1)
                 manager = getattr(canvas, 'manager', None)
                 if manager is not None:
-                    manager.resize(*(size * self.dpi / dpi_ratio).astype(int))
+                    manager.resize(*(size * self.dpi).astype(int))
         self.stale = True
 
     def get_size_inches(self):