changeset 26728:f034b29320ad

Use OpenGl textures to render image objects (bug #55632). * gl-render.[h,cc] (opengl_renderer): Change the following data members to protected rather than private and allow easy access from gl2ps_renderer: xmin, xmax, ymin, ymax, zmin, zmax, m_devpixratio, xform. (opengl_renderer::draw_pixels): Remove virtual methods. (opengl_renderer::draw_image): Remove unnecessary manual clipping code. Make use of an opengl_texture to draw pixels rather than glDrawPixels. (opengl_texture::create): Add support for single and uint16 type pixel data. * gl2ps-print.cc (gl2ps_renderer::draw_image): New overload. Dump here all the manual clipping code, which is still necessary since gl2ps does not support textures only emulates glDrawPixels. * print.m: Set figure "__printing__" property first and reset last. Set figure "__modified__" to "off" after resetting all properties.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Fri, 08 Feb 2019 18:07:33 +0100
parents 88653f8d536d
children 75d79c39ac92
files libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/gl2ps-print.cc scripts/plot/util/print.m
diffstat 4 files changed, 365 insertions(+), 326 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/corefcn/gl-render.cc	Wed Feb 13 15:38:23 2019 +0000
+++ b/libinterp/corefcn/gl-render.cc	Fri Feb 08 18:07:33 2019 +0100
@@ -204,7 +204,7 @@
           {
             const NDArray xdata = data.array_value ();
 
-            OCTAVE_LOCAL_BUFFER (float, a, (3*tw*th));
+            OCTAVE_LOCAL_BUFFER (GLfloat, a, (3*tw*th));
 
             for (int i = 0; i < h; i++)
               {
@@ -216,13 +216,53 @@
                   }
               }
 
-            glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB, GL_FLOAT, a);
+            glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB,
+                                 GL_FLOAT, a);
+          }
+
+        else if (data.is_single_type ())
+          {
+            const FloatNDArray xdata = data.float_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLfloat, a, (3*tw*th));
+
+            for (int i = 0; i < h; i++)
+              {
+                for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
+                  {
+                    a[idx]   = xdata(i,j,0);
+                    a[idx+1] = xdata(i,j,1);
+                    a[idx+2] = xdata(i,j,2);
+                  }
+              }
+
+            glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0, GL_RGB,
+                                 GL_FLOAT, a);
+          }
+        else if (data.is_uint16_type ())
+          {
+            const uint16NDArray xdata = data.uint16_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLushort, a, (3*tw*th));
+
+            for (int i = 0; i < h; i++)
+              {
+                for (int j = 0, idx = i*tw*3; j < w; j++, idx += 3)
+                  {
+                    a[idx]   = xdata(i,j,0);
+                    a[idx+1] = xdata(i,j,1);
+                    a[idx+2] = xdata(i,j,2);
+                  }
+              }
+
+            glfcns.glTexImage2D (GL_TEXTURE_2D, 0, 3, tw, th, 0,
+                                 GL_RGB, GL_UNSIGNED_SHORT, a);
           }
         else if (data.is_uint8_type ())
           {
             const uint8NDArray xdata = data.uint8_array_value ();
 
-            OCTAVE_LOCAL_BUFFER (octave_uint8, a, (3*tw*th));
+            OCTAVE_LOCAL_BUFFER (GLubyte, a, (3*tw*th));
 
             for (int i = 0; i < h; i++)
               {
@@ -240,7 +280,7 @@
         else
           {
             ok = false;
-            warning ("opengl_texture::create: invalid texture data type (double or uint8 required)");
+            warning ("opengl_texture::create: invalid image data type, expected double, single, uint8, or uint16");
           }
 
         if (ok)
@@ -619,11 +659,11 @@
 #endif
 
   opengl_renderer::opengl_renderer (opengl_functions& glfcns)
-    : m_glfcns (glfcns), toolkit (), xform (), xmin (), xmax (), ymin (),
-      ymax (), zmin (), zmax (), xZ1 (), xZ2 (), marker_id (),
+    : m_glfcns (glfcns), xmin (), xmax (), ymin (), ymax (), zmin (), zmax (),
+      m_devpixratio (1.), xform (), toolkit (), 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_devpixratio (1.)
+      m_max_lights (0), selecting (false)
   {
     // 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
@@ -3833,250 +3873,49 @@
     dim_vector dv (cdata.dims ());
     int h = dv(0);
     int w = dv(1);
+    double x0, x1, y0, y1;
 
     Matrix x = props.get_xdata ().matrix_value ();
+    double dx = 1.0;
+    if (w > 1)
+      dx = (x(1) - x(0)) / (w - 1);
+
+    x0 = x(0)-dx/2;
+    x1 = x(1)+dx/2;
+
     Matrix y = props.get_ydata ().matrix_value ();
-
-    // Someone wants us to draw an empty image?  No way.
-    if (x.isempty () || y.isempty ())
-      return;
-
-    // Sort x/ydata and mark flipped dimensions
-    bool xflip = false;
-    if (x(0) > x(1))
-      {
-        std::swap (x(0), x(1));
-        xflip = true;
-      }
-    else if (w > 1 && x(1) == x(0))
-      x(1) = x(1) + (w-1);
-
-    bool yflip = false;
-    if (y(0) > y(1))
-      {
-        std::swap (y(0), y(1));
-        yflip = true;
-      }
-    else if (h > 1 && y(1) == y(0))
-      y(1) = y(1) + (h-1);
-
-    const ColumnVector p0 = xform.transform (x(0), y(0), 0);
-    const ColumnVector p1 = xform.transform (x(1), y(1), 0);
-
-    if (math::isnan (p0(0)) || math::isnan (p0(1))
-        || math::isnan (p1(0)) || math::isnan (p1(1)))
-      {
-        warning ("opengl_renderer: image X,Y data too large to draw");
-        return;
-      }
-
-    // image pixel size in screen pixel units
-    float pix_dx, pix_dy;
-    // image pixel size in normalized units
-    float nor_dx, nor_dy;
-
-    if (w > 1)
-      {
-        pix_dx = (p1(0) - p0(0)) / (w-1);
-        nor_dx = (x(1) - x(0)) / (w-1);
-      }
-    else
-      {
-        const ColumnVector p1w = xform.transform (x(1) + 1, y(1), 0);
-        pix_dx = p1w(0) - p0(0);
-        nor_dx = 1;
-      }
-
+    double dy = 1.0;
     if (h > 1)
-      {
-        pix_dy = (p1(1) - p0(1)) / (h-1);
-        nor_dy = (y(1) - y(0)) / (h-1);
-      }
-    else
-      {
-        const ColumnVector p1h = xform.transform (x(1), y(1) + 1, 0);
-        pix_dy = p1h(1) - p0(1);
-        nor_dy = 1;
-      }
-
-    // OpenGL won't draw any of the image if its origin is outside the
-    // viewport/clipping plane so we must do the clipping ourselves.
-
-    int j0, j1, jj, i0, i1, ii;
-    j0 = 0, j1 = w;
-    i0 = 0, i1 = h;
-
-    float im_xmin = x(0) - nor_dx/2;
-    float im_xmax = x(1) + nor_dx/2;
-    float im_ymin = y(0) - nor_dy/2;
-    float im_ymax = y(1) + nor_dy/2;
-
-    // Clip to axes or viewport
-    bool do_clip = props.is_clipping ();
-    Matrix vp = get_viewport_scaled ();
-
-    ColumnVector vp_lim_min =
-      xform.untransform (std::numeric_limits <float>::epsilon (),
-                         std::numeric_limits <float>::epsilon ());
-    ColumnVector vp_lim_max = xform.untransform (vp(2), vp(3));
-
-    if (vp_lim_min(0) > vp_lim_max(0))
-      std::swap (vp_lim_min(0), vp_lim_max(0));
-
-    if (vp_lim_min(1) > vp_lim_max(1))
-      std::swap (vp_lim_min(1), vp_lim_max(1));
-
-    float clip_xmin =
-      (do_clip ? (vp_lim_min(0) > xmin ? vp_lim_min(0) : xmin) : vp_lim_min(0));
-    float clip_ymin =
-      (do_clip ? (vp_lim_min(1) > ymin ? vp_lim_min(1) : ymin) : vp_lim_min(1));
-
-    float clip_xmax =
-      (do_clip ? (vp_lim_max(0) < xmax ? vp_lim_max(0) : xmax) : vp_lim_max(0));
-    float clip_ymax =
-      (do_clip ? (vp_lim_max(1) < ymax ? vp_lim_max(1) : ymax) : vp_lim_max(1));
-
-    if (im_xmin < clip_xmin)
-      j0 += (clip_xmin - im_xmin)/nor_dx + 1;
-    if (im_xmax > clip_xmax)
-      j1 -= (im_xmax - clip_xmax)/nor_dx;
-
-    if (im_ymin < clip_ymin)
-      i0 += (clip_ymin - im_ymin)/nor_dy + 1;
-    if (im_ymax > clip_ymax)
-      i1 -= (im_ymax - clip_ymax)/nor_dy;
-
-    if (i0 >= i1 || j0 >= j1)
-      return;
-
-    m_glfcns.glPixelZoom (m_devpixratio * pix_dx,
-                          - m_devpixratio * pix_dy);
-    m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);
-
-    // by default this is 4
-    m_glfcns.glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+      dy = (y(1) - y(0)) / (h - 1);
+
+    y0 = y(0)-dy/2;
+    y1 = y(1)+dy/2;
 
     // Expect RGB data
     if (dv.ndims () == 3 && dv(2) == 3)
       {
-        if (cdata.is_double_type ())
-          {
-            const NDArray xcdata = cdata.array_value ();
-
-            OCTAVE_LOCAL_BUFFER (GLfloat, a, 3*(j1-j0)*(i1-i0));
-
-            for (int i = i0; i < i1; i++)
-              {
-                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
-                  {
-                    if (! yflip)
-                      ii = i;
-                    else
-                      ii = h - i - 1;
-
-                    if (! xflip)
-                      jj = j;
-                    else
-                      jj = w - j - 1;
-
-                    a[idx]   = xcdata(ii,jj,0);
-                    a[idx+1] = xcdata(ii,jj,1);
-                    a[idx+2] = xcdata(ii,jj,2);
-                  }
-              }
-
-            draw_pixels (j1-j0, i1-i0, a);
-
-          }
-        else if (cdata.is_single_type ())
-          {
-            const FloatNDArray xcdata = cdata.float_array_value ();
-
-            OCTAVE_LOCAL_BUFFER (GLfloat, a, 3*(j1-j0)*(i1-i0));
-
-            for (int i = i0; i < i1; i++)
-              {
-                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
-                  {
-                    if (! yflip)
-                      ii = i;
-                    else
-                      ii = h - i - 1;
-
-                    if (! xflip)
-                      jj = j;
-                    else
-                      jj = w - j - 1;
-
-                    a[idx]   = xcdata(ii,jj,0);
-                    a[idx+1] = xcdata(ii,jj,1);
-                    a[idx+2] = xcdata(ii,jj,2);
-                  }
-              }
-
-            draw_pixels (j1-j0, i1-i0, a);
-
-          }
-        else if (cdata.is_uint8_type ())
-          {
-            const uint8NDArray xcdata = cdata.uint8_array_value ();
-
-            OCTAVE_LOCAL_BUFFER (GLubyte, a, 3*(j1-j0)*(i1-i0));
-
-            for (int i = i0; i < i1; i++)
-              {
-                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
-                  {
-                    if (! yflip)
-                      ii = i;
-                    else
-                      ii = h - i - 1;
-
-                    if (! xflip)
-                      jj = j;
-                    else
-                      jj = w - j - 1;
-
-                    a[idx]   = xcdata(ii,jj,0);
-                    a[idx+1] = xcdata(ii,jj,1);
-                    a[idx+2] = xcdata(ii,jj,2);
-                  }
-              }
-
-            draw_pixels (j1-j0, i1-i0, a);
-
-          }
-        else if (cdata.is_uint16_type ())
-          {
-            const uint16NDArray xcdata = cdata.uint16_array_value ();
-
-            OCTAVE_LOCAL_BUFFER (GLushort, a, 3*(j1-j0)*(i1-i0));
-
-            for (int i = i0; i < i1; i++)
-              {
-                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
-                  {
-                    if (! yflip)
-                      ii = i;
-                    else
-                      ii = h - i - 1;
-
-                    if (! xflip)
-                      jj = j;
-                    else
-                      jj = w - j - 1;
-
-                    a[idx]   = xcdata(ii,jj,0);
-                    a[idx+1] = xcdata(ii,jj,1);
-                    a[idx+2] = xcdata(ii,jj,2);
-                  }
-              }
-
-            draw_pixels (j1-j0, i1-i0, a);
-
-          }
-        else
-          warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)");
+        opengl_texture tex (m_glfcns);
+        tex = opengl_texture::create (m_glfcns, cdata);
+        m_glfcns.glColor4d (1.0, 1.0, 1.0, 1.0);
+
+        m_glfcns.glEnable (GL_TEXTURE_2D);
+
+        m_glfcns.glBegin (GL_QUADS);
+
+        tex.tex_coord (0.0, 0.0);
+        m_glfcns.glVertex3d (x0, y0, 0.0);
+
+        tex.tex_coord (1.0, 0.0);
+        m_glfcns.glVertex3d (x1, y0, 0.0);
+
+        tex.tex_coord (1.0, 1.0);
+        m_glfcns.glVertex3d (x1, y1, 0.0);
+
+        tex.tex_coord (0.0, 1.0);
+        m_glfcns.glVertex3d (x0, y1, 0.0);
+
+        m_glfcns.glEnd ();
+        m_glfcns.glDisable (GL_TEXTURE_2D);
       }
     else
       warning ("opengl_renderer: invalid image size (expected MxNx3 or MxN)");
@@ -4145,69 +3984,6 @@
   }
 
   void
-  opengl_renderer::draw_pixels (int width, int height, const float *data)
-  {
-#if defined (HAVE_OPENGL)
-
-    m_glfcns.glDrawPixels (width, height, GL_RGB, GL_FLOAT, data);
-
-#else
-
-    octave_unused_parameter (width);
-    octave_unused_parameter (height);
-    octave_unused_parameter (data);
-
-    // This shouldn't happen because construction of opengl_renderer
-    // objects is supposed to be impossible if OpenGL is not available.
-
-    panic_impossible ();
-
-#endif
-  }
-
-  void
-  opengl_renderer::draw_pixels (int width, int height, const uint8_t *data)
-  {
-#if defined (HAVE_OPENGL)
-
-    m_glfcns.glDrawPixels (width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
-
-#else
-
-    octave_unused_parameter (width);
-    octave_unused_parameter (height);
-    octave_unused_parameter (data);
-
-    // This shouldn't happen because construction of opengl_renderer
-    // objects is supposed to be impossible if OpenGL is not available.
-
-    panic_impossible ();
-
-#endif
-  }
-
-  void
-  opengl_renderer::draw_pixels (int width, int height, const uint16_t *data)
-  {
-#if defined (HAVE_OPENGL)
-
-    m_glfcns.glDrawPixels (width, height, GL_RGB, GL_UNSIGNED_SHORT, data);
-
-#else
-
-    octave_unused_parameter (width);
-    octave_unused_parameter (height);
-    octave_unused_parameter (data);
-
-    // This shouldn't happen because construction of opengl_renderer
-    // objects is supposed to be impossible if OpenGL is not available.
-
-    panic_impossible ();
-
-#endif
-  }
-
-  void
   opengl_renderer::set_color (const Matrix& c)
   {
 #if defined (HAVE_OPENGL)
--- a/libinterp/corefcn/gl-render.h	Wed Feb 13 15:38:23 2019 +0000
+++ b/libinterp/corefcn/gl-render.h	Fri Feb 08 18:07:33 2019 +0100
@@ -140,10 +140,6 @@
                                 double x, double y, double z,
                                 int halign, int valign, double rotation = 0.0);
 
-    virtual void draw_pixels (int w, int h, const float *data);
-    virtual void draw_pixels (int w, int h, const uint8_t *data);
-    virtual void draw_pixels (int w, int h, const uint16_t *data);
-
     virtual void render_grid (const double linewidth,
                               const std::string& gridstyle,
                               const Matrix& gridcolor, const double gridalpha,
@@ -212,19 +208,22 @@
 
     opengl_functions& m_glfcns;
 
+    // axis limits in model scaled coordinate
+    double xmin, xmax;
+    double ymin, ymax;
+    double zmin, zmax;
+    
+    // Factor used for translating Octave pixels to actual device pixels
+    double m_devpixratio;
+
+    // axes transformation data
+    graphics_xform xform;
+
   private:
 
     // The graphics toolkit associated with the figure being rendered.
     graphics_toolkit toolkit;
 
-    // axes transformation data
-    graphics_xform xform;
-
-    // axis limits in model scaled coordinate
-    double xmin, xmax;
-    double ymin, ymax;
-    double zmin, zmax;
-
     // Z projection limits in windows coordinate
     double xZ1, xZ2;
 
@@ -246,9 +245,6 @@
     // Indicate we are drawing for selection purpose
     bool selecting;
 
-    // Factor used for translating Octave pixels to actual device pixels
-    double m_devpixratio;
-
   private:
     class patch_tesselator;
   };
--- a/libinterp/corefcn/gl2ps-print.cc	Wed Feb 13 15:38:23 2019 +0000
+++ b/libinterp/corefcn/gl2ps-print.cc	Fri Feb 08 18:07:33 2019 +0100
@@ -175,6 +175,7 @@
 
     void draw_text (const text::properties& props);
 
+    void draw_image (const image::properties& props);
     void draw_pixels (int w, int h, const float *data);
     void draw_pixels (int w, int h, const uint8_t *data);
     void draw_pixels (int w, int h, const uint16_t *data);
@@ -1027,6 +1028,266 @@
   }
 
   void
+  gl2ps_renderer::draw_image (const image::properties& props)
+  {
+    octave_value cdata = props.get_color_data ();
+    dim_vector dv (cdata.dims ());
+    int h = dv(0);
+    int w = dv(1);
+
+    Matrix x = props.get_xdata ().matrix_value ();
+    Matrix y = props.get_ydata ().matrix_value ();
+
+    // Someone wants us to draw an empty image?  No way.
+    if (x.isempty () || y.isempty ())
+      return;
+
+    // Sort x/ydata and mark flipped dimensions
+    bool xflip = false;
+    if (x(0) > x(1))
+      {
+        std::swap (x(0), x(1));
+        xflip = true;
+      }
+    else if (w > 1 && x(1) == x(0))
+      x(1) = x(1) + (w-1);
+
+    bool yflip = false;
+    if (y(0) > y(1))
+      {
+        std::swap (y(0), y(1));
+        yflip = true;
+      }
+    else if (h > 1 && y(1) == y(0))
+      y(1) = y(1) + (h-1);
+
+
+    const ColumnVector p0 = xform.transform (x(0), y(0), 0);
+    const ColumnVector p1 = xform.transform (x(1), y(1), 0);
+
+    if (math::isnan (p0(0)) || math::isnan (p0(1))
+        || math::isnan (p1(0)) || math::isnan (p1(1)))
+      {
+        warning ("opengl_renderer: image X,Y data too large to draw");
+        return;
+      }
+
+    // image pixel size in screen pixel units
+    float pix_dx, pix_dy;
+    // image pixel size in normalized units
+    float nor_dx, nor_dy;
+
+    if (w > 1)
+      {
+        pix_dx = (p1(0) - p0(0)) / (w-1);
+        nor_dx = (x(1) - x(0)) / (w-1);
+      }
+    else
+      {
+        const ColumnVector p1w = xform.transform (x(1) + 1, y(1), 0);
+        pix_dx = p1w(0) - p0(0);
+        nor_dx = 1;
+      }
+
+    if (h > 1)
+      {
+        pix_dy = (p1(1) - p0(1)) / (h-1);
+        nor_dy = (y(1) - y(0)) / (h-1);
+      }
+    else
+      {
+        const ColumnVector p1h = xform.transform (x(1), y(1) + 1, 0);
+        pix_dy = p1h(1) - p0(1);
+        nor_dy = 1;
+      }
+
+    // OpenGL won't draw any of the image if its origin is outside the
+    // viewport/clipping plane so we must do the clipping ourselves.
+
+    int j0, j1, jj, i0, i1, ii;
+    j0 = 0, j1 = w;
+    i0 = 0, i1 = h;
+
+    float im_xmin = x(0) - nor_dx/2;
+    float im_xmax = x(1) + nor_dx/2;
+    float im_ymin = y(0) - nor_dy/2;
+    float im_ymax = y(1) + nor_dy/2;
+
+    // Clip to axes or viewport
+    bool do_clip = props.is_clipping ();
+    Matrix vp = get_viewport_scaled ();
+
+    ColumnVector vp_lim_min =
+      xform.untransform (std::numeric_limits <float>::epsilon (),
+                         std::numeric_limits <float>::epsilon ());
+    ColumnVector vp_lim_max = xform.untransform (vp(2), vp(3));
+
+    if (vp_lim_min(0) > vp_lim_max(0))
+      std::swap (vp_lim_min(0), vp_lim_max(0));
+
+    if (vp_lim_min(1) > vp_lim_max(1))
+      std::swap (vp_lim_min(1), vp_lim_max(1));
+
+    float clip_xmin =
+      (do_clip ? (vp_lim_min(0) > xmin ? vp_lim_min(0) : xmin) : vp_lim_min(0));
+    float clip_ymin =
+      (do_clip ? (vp_lim_min(1) > ymin ? vp_lim_min(1) : ymin) : vp_lim_min(1));
+
+    float clip_xmax =
+      (do_clip ? (vp_lim_max(0) < xmax ? vp_lim_max(0) : xmax) : vp_lim_max(0));
+    float clip_ymax =
+      (do_clip ? (vp_lim_max(1) < ymax ? vp_lim_max(1) : ymax) : vp_lim_max(1));
+
+    if (im_xmin < clip_xmin)
+      j0 += (clip_xmin - im_xmin)/nor_dx + 1;
+    if (im_xmax > clip_xmax)
+      j1 -= (im_xmax - clip_xmax)/nor_dx;
+
+    if (im_ymin < clip_ymin)
+      i0 += (clip_ymin - im_ymin)/nor_dy + 1;
+    if (im_ymax > clip_ymax)
+      i1 -= (im_ymax - clip_ymax)/nor_dy;
+
+    if (i0 >= i1 || j0 >= j1)
+      return;
+
+    float zoom_x;
+    m_glfcns.glGetFloatv (GL_ZOOM_X, &zoom_x);
+    float zoom_y;
+    m_glfcns.glGetFloatv (GL_ZOOM_Y, &zoom_y);
+
+    m_glfcns.glPixelZoom (m_devpixratio * pix_dx,
+                          - m_devpixratio * pix_dy);
+    m_glfcns.glRasterPos3d (im_xmin + nor_dx*j0, im_ymin + nor_dy*i0, 0);
+
+    // Expect RGB data
+    if (dv.ndims () == 3 && dv(2) == 3)
+      {
+        if (cdata.is_double_type ())
+          {
+            const NDArray xcdata = cdata.array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLfloat, a, 3*(j1-j0)*(i1-i0));
+
+            for (int i = i0; i < i1; i++)
+              {
+                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
+                  {
+                    if (! yflip)
+                      ii = i;
+                    else
+                      ii = h - i - 1;
+
+                    if (! xflip)
+                      jj = j;
+                    else
+                      jj = w - j - 1;
+
+                    a[idx]   = xcdata(ii,jj,0);
+                    a[idx+1] = xcdata(ii,jj,1);
+                    a[idx+2] = xcdata(ii,jj,2);
+                  }
+              }
+
+            draw_pixels (j1-j0, i1-i0, a);
+
+          }
+        else if (cdata.is_single_type ())
+          {
+            const FloatNDArray xcdata = cdata.float_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLfloat, a, 3*(j1-j0)*(i1-i0));
+
+            for (int i = i0; i < i1; i++)
+              {
+                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
+                  {
+                    if (! yflip)
+                      ii = i;
+                    else
+                      ii = h - i - 1;
+
+                    if (! xflip)
+                      jj = j;
+                    else
+                      jj = w - j - 1;
+
+                    a[idx]   = xcdata(ii,jj,0);
+                    a[idx+1] = xcdata(ii,jj,1);
+                    a[idx+2] = xcdata(ii,jj,2);
+                  }
+              }
+
+            draw_pixels (j1-j0, i1-i0, a);
+
+          }
+        else if (cdata.is_uint8_type ())
+          {
+            const uint8NDArray xcdata = cdata.uint8_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLubyte, a, 3*(j1-j0)*(i1-i0));
+
+            for (int i = i0; i < i1; i++)
+              {
+                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
+                  {
+                    if (! yflip)
+                      ii = i;
+                    else
+                      ii = h - i - 1;
+
+                    if (! xflip)
+                      jj = j;
+                    else
+                      jj = w - j - 1;
+
+                    a[idx]   = xcdata(ii,jj,0);
+                    a[idx+1] = xcdata(ii,jj,1);
+                    a[idx+2] = xcdata(ii,jj,2);
+                  }
+              }
+
+            draw_pixels (j1-j0, i1-i0, a);
+
+          }
+        else if (cdata.is_uint16_type ())
+          {
+            const uint16NDArray xcdata = cdata.uint16_array_value ();
+
+            OCTAVE_LOCAL_BUFFER (GLushort, a, 3*(j1-j0)*(i1-i0));
+
+            for (int i = i0; i < i1; i++)
+              {
+                for (int j = j0, idx = (i-i0)*(j1-j0)*3; j < j1; j++, idx += 3)
+                  {
+                    if (! yflip)
+                      ii = i;
+                    else
+                      ii = h - i - 1;
+
+                    if (! xflip)
+                      jj = j;
+                    else
+                      jj = w - j - 1;
+
+                    a[idx]   = xcdata(ii,jj,0);
+                    a[idx+1] = xcdata(ii,jj,1);
+                    a[idx+2] = xcdata(ii,jj,2);
+                  }
+              }
+
+            draw_pixels (j1-j0, i1-i0, a);
+
+          }
+        else
+          warning ("opengl_renderer: invalid image data type (expected double, single, uint8, or uint16)");
+
+        m_glfcns.glPixelZoom (zoom_x, zoom_y);
+
+      }
+  }
+
+  void
   gl2ps_renderer::draw_pixels (int w, int h, const float *data)
   {
     // Clip data between 0 and 1 for float values
--- a/scripts/plot/util/print.m	Wed Feb 13 15:38:23 2019 +0000
+++ b/scripts/plot/util/print.m	Fri Feb 08 18:07:33 2019 +0100
@@ -466,6 +466,13 @@
 
     drawnow ();
 
+    ## Set the __printing__ property first
+    props(1).h = opts.figure;
+    props(1).name = "__printing__";
+    props(1).value = {"off"};
+    set (opts.figure, "__printing__", "on");
+    nfig += 1;
+
     ## print() requires children of axes to have units = "normalized", or "data"
     hobj = findall (opts.figure, "-not", "type", "figure", ...
                     "-not", "type", "axes", "-property", "units", ...
@@ -552,13 +559,10 @@
     ## graphics toolkit translates figure position to eps bbox (points)
     fpos = get (opts.figure, "position");
     props(end+1).h = opts.figure;
-    props(end).name = "__printing__";
-    props(end).value = {"off"};
-    props(end+1).h = opts.figure;
     props(end).name = "position";
     props(end).value = {fpos};
     fpos(3:4) = opts.canvas_size;
-    set (opts.figure, "__printing__", "on", "position", fpos);
+    set (opts.figure, "position", fpos);
     nfig += 1;
 
     ## Implement InvertHardCopy option
@@ -727,6 +731,9 @@
       endfor
     endif
 
+    ## Avoid a redraw since the figure should not have changed
+    set (gcf, "__modified__", "off");
+
     ## Unlink temporary files
     for n = 1:numel (opts.unlink)
       [status, output] = unlink (opts.unlink{n});
@@ -740,7 +747,6 @@
   if (isfigure (orig_figure))
     set (0, "currentfigure", orig_figure);
   endif
-
 endfunction
 
 function cmd = epstool (opts, filein, fileout)