diff --git a/libgui/graphics/Figure.cc b/libgui/graphics/Figure.cc
--- a/libgui/graphics/Figure.cc
+++ b/libgui/graphics/Figure.cc
@@ -41,6 +41,8 @@ along with Octave; see the file COPYING.
 #include <QtDebug>
 #include <QTimer>
 #include <QToolBar>
+#include <QWindow>
+#include <QScreen>
 
 #include "Canvas.h"
 #include "Container.h"
@@ -133,6 +135,11 @@ namespace QtHandles
 
     figure::properties& fp = properties<figure> ();
 
+    // Register for the signal that indicates when a window has moved
+    // to a different screen
+    connect (win, SIGNAL (figureWindowShown ()),
+             this, SLOT (figureWindowShown ()));
+
     // Status bar
     m_statusBar = win->statusBar ();
     int boffset = 0;
@@ -1092,6 +1099,39 @@ namespace QtHandles
   }
 
   void
+  Figure::figureWindowShown ()
+  {
+#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
+    figure::properties& fp = properties<figure> ();
+    QMainWindow *win = qWidget<QMainWindow> ();
+    QWindow* window = win->windowHandle ();
+    QScreen* screen = window->screen ();
+    fp.set___device_pixel_ratio__ (screen->devicePixelRatio ());
+
+    // Make sure the figure gets redrawn after the devicePixelRatio is updated
+    graphics_object go = gh_manager::get_object (fp.get___myhandle__ ());
+    fp.get_toolkit ().redraw_figure (go);
+
+    connect (window, SIGNAL (screenChanged (QScreen*)),
+             this, SLOT (screenChanged (QScreen*)));
+#endif
+  }
+
+  void
+  Figure::screenChanged (QScreen* screen)
+  {
+#if defined (HAVE_QSCREEN_DEVICEPIXELRATIO)
+    figure::properties& fp = properties<figure> ();
+    float old_device_pixel_ratio = fp.get___device_pixel_ratio__ ();
+    fp.set___device_pixel_ratio__ (screen->devicePixelRatio ());
+
+    // Make sure the figure gets redrawn after the devicePixelRatio is updated
+    graphics_object go = gh_manager::get_object (fp.get___myhandle__ ());
+    fp.get_toolkit ().redraw_figure (go);
+#endif
+  }
+
+  void
   Figure::enableMouseTracking (void)
   {
     // Enable mouse tracking on every widgets
diff --git a/libgui/graphics/Figure.h b/libgui/graphics/Figure.h
--- a/libgui/graphics/Figure.h
+++ b/libgui/graphics/Figure.h
@@ -32,6 +32,7 @@ along with Octave; see the file COPYING.
 
 class QMainWindow;
 class QToolBar;
+class QScreen;
 
 namespace QtHandles
 {
@@ -129,6 +130,8 @@ namespace QtHandles
     void toggleAxes (void);
     void toggleGrid (void);
     void autoAxes (void);
+    void figureWindowShown ();
+    void screenChanged (QScreen*);
 
   public slots:
     uint8NDArray slotGetPixels (void);
diff --git a/libgui/graphics/FigureWindow.cc b/libgui/graphics/FigureWindow.cc
--- a/libgui/graphics/FigureWindow.cc
+++ b/libgui/graphics/FigureWindow.cc
@@ -48,4 +48,10 @@ namespace QtHandles
     return nullptr;
   }
 
+  void FigureWindow::showEvent (QShowEvent* ev)
+  {
+    QMainWindow::showEvent (ev);
+    emit figureWindowShown();
+  }
+
 }
diff --git a/libgui/graphics/FigureWindow.h b/libgui/graphics/FigureWindow.h
--- a/libgui/graphics/FigureWindow.h
+++ b/libgui/graphics/FigureWindow.h
@@ -37,10 +37,17 @@ namespace QtHandles
     Q_OBJECT
 
   public:
-    FigureWindow (QWidget *parent = nullptr);
+    explicit FigureWindow (QWidget *parent = nullptr);
     ~FigureWindow (void);
 
     QMenu * createPopupMenu (void);
+
+  protected:
+    void showEvent(QShowEvent *ev);
+
+  signals:
+    void figureWindowShown();
+
   };
 
 }
diff --git a/libgui/graphics/GLCanvas.cc b/libgui/graphics/GLCanvas.cc
--- a/libgui/graphics/GLCanvas.cc
+++ b/libgui/graphics/GLCanvas.cc
@@ -72,7 +72,9 @@ namespace QtHandles
 
     if (go)
       {
-        m_renderer.set_viewport (width (), height ());
+        graphics_object fig = go.get_ancestor ("figure");
+        double dpr = fig.get ("__device_pixel_ratio__").double_value ();
+        m_renderer.set_viewport (dpr * width (), dpr * height ());
         m_renderer.draw (go);
       }
   }
@@ -175,7 +177,10 @@ namespace QtHandles
       {
         octave::opengl_selector s (m_glfcns);
 
-        s.set_viewport (width (), height ());
+        graphics_object fig = ax.get_ancestor ("figure");
+        double dpr = fig.get ("__device_pixel_ratio__").double_value ();
+
+        s.set_viewport (dpr * width (), dpr * height ());
         return s.select (ax, pt.x (), height () - pt.y (),
                          octave::select_ignore_hittest);
       }
diff --git a/libinterp/corefcn/gl-render.cc b/libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc
+++ b/libinterp/corefcn/gl-render.cc
@@ -617,7 +617,8 @@ namespace octave
       ymin (), ymax (), zmin (), zmax (), xZ1 (), xZ2 (),
       marker_id (), filled_marker_id (), camera_pos (), camera_dir (),
       view_vector (), interpreter ("none"), txt_renderer (),
-      m_current_light (0), m_max_lights (0), selecting (false)
+      m_current_light (0), m_max_lights (0), selecting (false),
+      m_devpixfactor (1.)
   {
     // This constructor will fail if we don't have OpenGL or if the data
     // types we assumed in our public interface aren't compatible with the
@@ -702,8 +703,11 @@ namespace octave
   void
   opengl_renderer::draw_figure (const figure::properties& props)
   {
+    // Current physical-pixel to toolkit-pixel factor
+    m_devpixfactor = props.get___device_pixel_ratio__ ();
+    
     // Initialize OpenGL context
-
+    
     init_gl_context (props.is_graphicssmoothing (), props.get_color_rgb ());
 
 #if defined (HAVE_OPENGL)
@@ -1222,6 +1226,11 @@ namespace octave
     m_glfcns.glMultMatrixd (x_mat1.data ());
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glLoadIdentity ();
+
+     // Use abstract Octave-pixels for transformation, not physical-pixels
+     vw[2] = octave::math::round (static_cast<float> (vw[2]) / m_devpixfactor);
+     vw[3] = octave::math::round (static_cast<float> (vw[3]) / m_devpixfactor);
+
     m_glfcns.glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
     m_glfcns.glMultMatrixd (x_mat2.data ());
     m_glfcns.glMatrixMode (GL_MODELVIEW);
@@ -3686,6 +3695,10 @@ namespace octave
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glPushMatrix ();
     m_glfcns.glLoadIdentity ();
+
+    vp[2] = octave::math::round (static_cast<float> (vp[2]) / m_devpixfactor);
+    vp[3] = octave::math::round (static_cast<float> (vp[3]) / m_devpixfactor);
+
     m_glfcns.glOrtho (0, vp[2], vp[3], 0, xZ1, xZ2);
     m_glfcns.glMatrixMode (GL_MODELVIEW);
     m_glfcns.glPushMatrix ();
@@ -3858,7 +3871,8 @@ namespace octave
     if (i0 >= i1 || j0 >= j1)
       return;
 
-    m_glfcns.glPixelZoom (pix_dx, -pix_dy);
+    m_glfcns.glPixelZoom (m_devpixfactor * pix_dx,
+                          - m_devpixfactor * pix_dy);
     m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);
 
     // by default this is 4
@@ -4111,7 +4125,7 @@ namespace octave
   {
 #if defined (HAVE_OPENGL)
 
-    m_glfcns.glLineWidth (w);
+    m_glfcns.glLineWidth (w * m_devpixfactor);
 
 #else
 
@@ -4261,6 +4275,10 @@ namespace octave
     m_glfcns.glMatrixMode (GL_PROJECTION);
     m_glfcns.glPushMatrix ();
     m_glfcns.glLoadIdentity ();
+
+    vw[2] = octave::math::round (static_cast<float> (vw[2]) / m_devpixfactor);
+    vw[3] = octave::math::round (static_cast<float> (vw[3]) / m_devpixfactor);
+
     m_glfcns.glOrtho (0, vw[2], vw[3], 0, xZ1, xZ2);
     m_glfcns.glMatrixMode (GL_MODELVIEW);
     m_glfcns.glPushMatrix ();
diff --git a/libinterp/corefcn/gl-render.h b/libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h
+++ b/libinterp/corefcn/gl-render.h
@@ -244,6 +244,9 @@ namespace octave
 
     // Indicate we are drawing for selection purpose
     bool selecting;
+
+    // Factor used for translating Octave pixels to actual device pixels
+    double m_devpixfactor;
   private:
     class patch_tesselator;
   };
diff --git a/libinterp/corefcn/graphics.cc b/libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc
+++ b/libinterp/corefcn/graphics.cc
@@ -870,6 +870,19 @@ screen_size_pixels (void)
                            sz.extract_n (0, 2, 1, 2)).extract_n (0, 2, 1, 2);
 }
 
+static double
+device_pixel_ratio (graphics_handle h)
+{
+  double retval = 1.0;
+  
+  graphics_object fig = gh_manager::get_object (h).get_ancestor ("figure");
+  
+  if (fig.valid_object ())
+    retval = fig.get ("__device_pixel_ratio__").double_value ();
+  
+  return retval;
+}
+
 static void
 convert_cdata_2 (bool is_scaled, bool is_real, double clim_0, double clim_1,
                  const double *cmapv, double x, octave_idx_type lda,
@@ -6641,11 +6654,13 @@ axes::properties::update_font (std::stri
 
     }
 
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+  
   gh_manager::auto_lock guard;
   txt_renderer.set_font (get ("fontname").string_value (),
                          get ("fontweight").string_value (),
                          get ("fontangle").string_value (),
-                         get ("__fontsize_points__").double_value ());
+                         get ("__fontsize_points__").double_value () * dpr);
 }
 
 // The INTERNAL flag defines whether position or outerposition is used.
@@ -7714,6 +7729,7 @@ axes::properties::get_ticklabel_extents 
 {
   Matrix ext (1, 2, 0.0);
   double wmax, hmax;
+  double dpr = device_pixel_ratio (get___myhandle__ ());
   wmax = hmax = 0.0;
   int n = std::min (ticklabels.numel (), ticks.numel ());
   for (int i = 0; i < n; i++)
@@ -7731,8 +7747,8 @@ axes::properties::get_ticklabel_extents 
               ext = txt_renderer.get_extent (label, 0.0,
                                              get_ticklabelinterpreter ());
 
-              wmax = std::max (wmax, ext(0));
-              hmax = std::max (hmax, ext(1));
+              wmax = std::max (wmax, ext(0) / dpr);
+              hmax = std::max (hmax, ext(1) / dpr);
             }
           else
             {
@@ -8788,7 +8804,14 @@ text::properties::get_extent (void) cons
   m(0) += p(0);
   m(1) += p(1);
 
-  return convert_text_position (m, *this, "pixels", get_units ());
+  Matrix bbox = convert_text_position (m, *this, "pixels", get_units ());
+
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+
+  for (octave_idx_type ii = 0; ii < bbox.numel (); ii++)
+    bbox(ii) = bbox(ii) / dpr;
+  
+  return bbox;
 }
 
 void
@@ -8826,11 +8849,13 @@ text::properties::update_fontunits (cons
 void
 text::properties::update_font (void)
 {
+  double dpr = device_pixel_ratio (get___myhandle__ ());
+
   gh_manager::auto_lock guard;
   txt_renderer.set_font (get ("fontname").string_value (),
                          get ("fontweight").string_value (),
                          get ("fontangle").string_value (),
-                         get ("__fontsize_points__").double_value ());
+                         get ("__fontsize_points__").double_value () * dpr);
 
   txt_renderer.set_color (get_color_rgb ());
 }
@@ -8870,7 +8895,7 @@ text::properties::update_text_extent (vo
   // The bbox is relative to the text's position.  We'll leave it that
   // way, because get_position does not return valid results when the
   // text is first constructed.  Conversion to proper coordinates is
-  // performed in get_extent.
+  // performed in get_extent.  
   set_extent (bbox);
 
   if (__autopos_tag___is ("xlabel") || __autopos_tag___is ("ylabel")
@@ -8927,9 +8952,9 @@ text::properties::get___fontsize_points_
   double fontsz = get_fontsize ();
   double parent_height = box_pix_height;
 
+  graphics_object go (gh_manager::get_object (get___myhandle__ ()));
   if (fontunits_is ("normalized") && parent_height <= 0)
     {
-      graphics_object go (gh_manager::get_object (get___myhandle__ ()));
       graphics_object ax (go.get_ancestor ("axes"));
 
       parent_height = ax.get_properties ().get_boundingbox (true).elem (3);
diff --git a/libinterp/corefcn/graphics.in.h b/libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h
+++ b/libinterp/corefcn/graphics.in.h
@@ -3183,6 +3183,7 @@ public:
       any_property __plot_stream__ h , Matrix ()
       any_property __rotate_mode__ h , Matrix ()
       any_property __zoom_mode__ h , Matrix ()
+      double_property __device_pixel_ratio__ h , 1.0
 
       // Obsolete properties: doublebuffer, mincolormap, wvisual, wvisualmode,
       //                      xdisplay, xvisual, xvisualmode
diff --git a/m4/acinclude.m4 b/m4/acinclude.m4
--- a/m4/acinclude.m4
+++ b/m4/acinclude.m4
@@ -666,6 +666,36 @@ AC_DEFUN([OCTAVE_CHECK_FUNC_QOBJECT_FIND
   fi
 ])
 dnl
+dnl Check whether the Qt class QScreen has the devicePixelRatio member function.
+dnl This member function was introduced in Qt 5.5.
+dnl
+AC_DEFUN([OCTAVE_CHECK_FUNC_QSCREEN_DEVICEPIXELRATIO], [
+  AC_CACHE_CHECK([for QScreen::devicePixelRatio in <QScreen>],
+    [octave_cv_func_qscreen_devicepixelratio],
+    [AC_LANG_PUSH(C++)
+    ac_octave_save_CPPFLAGS="$CPPFLAGS"
+    ac_octave_save_CXXFLAGS="$CXXFLAGS"
+    CPPFLAGS="$QT_CPPFLAGS $CXXPICFLAG $CPPFLAGS"
+    CXXFLAGS="$CXXPICFLAG $CXXFLAGS"
+    AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+        #include <QApplication>
+        #include <QScreen>
+        ]], [[
+        QScreen *screen = QApplication::primaryScreen ();
+        qreal ratio = screen->devicePixelRatio ();
+        ]])],
+      octave_cv_func_qscreen_devicepixelratio=yes,
+      octave_cv_func_qscreen_devicepixelratio=no)
+    CPPFLAGS="$ac_octave_save_CPPFLAGS"
+    CXXFLAGS="$ac_octave_save_CXXFLAGS"
+    AC_LANG_POP(C++)
+  ])
+  if test $octave_cv_func_qscreen_devicepixelratio = yes; then
+    AC_DEFINE(HAVE_QSCREEN_DEVICEPIXELRATIO, 1,
+      [Define to 1 if you have the `QScreen::devicePixelRatio' member function.])
+  fi
+])
+dnl
 dnl Check whether the Qt class QTabWidget has the setMovable member function.
 dnl This member function was introduced in Qt 4.5.
 dnl
@@ -2105,6 +2135,7 @@ AC_DEFUN([OCTAVE_CHECK_QT_VERSION], [AC_
     OCTAVE_CHECK_FUNC_QHELPSEARCHQUERYWIDGET_SEARCHINPUT
     OCTAVE_CHECK_FUNC_QINSTALLMESSAGEHANDLER
     OCTAVE_CHECK_FUNC_QLINEEDIT_SETPLACEHOLDERTEXT
+    OCTAVE_CHECK_FUNC_QSCREEN_DEVICEPIXELRATIO
     OCTAVE_CHECK_FUNC_QMOUSEEVENT_LOCALPOS
     OCTAVE_CHECK_FUNC_QOBJECT_FINDCHILDREN_ACCEPTS_FINDCHILDOPTIONS
     OCTAVE_CHECK_FUNC_QTABWIDGET_SETMOVABLE
diff --git a/scripts/plot/util/print.m b/scripts/plot/util/print.m
--- a/scripts/plot/util/print.m
+++ b/scripts/plot/util/print.m
@@ -386,6 +386,15 @@ function print (varargin)
       nfig += 1;
     endfor
 
+    ## Don't account for the actual pixel density
+    if (strcmp (tk, "qt"))
+      props(end+1).h = opts.figure;
+      props(end).name = "__device_pixel_ratio__";
+      props(end).value = {get(opts.figure, "__device_pixel_ratio__")};
+      set (opts.figure, "__device_pixel_ratio__", 1);
+      nfig += 1;
+    endif
+
     ## print() requires axes units = "normalized"
     hax = findall (opts.figure, "-depth", 1, "type", "axes", ...
       "-not", "units", "normalized");
