changeset 23535:2aab625b502c

Add getframe function for opengl based toolkits (bug #48195) * graphics.in.h (graphics_toolkit::get_pixels, base_graphics_toolkit::get_pixels): new virtual methods * graphics.cc (F__get_frame__): new function. * Backend.h/cc (Backend::get_pixels): implement new method * ObjectProxy.h/cc (ObjectProxy::get_pixels): new method, invoke directly Figure object slotGetPixels in Qt::BlockingQueuedConnection mode * Figure.h/cc (Figure::slotGetPixels): new public slot * Canvas.h (Canvas::getPixels, Canvas::do_getPixels): new methods * GLCanvas.h/cc (GLCanvas::do_getPixels): reimplemented method, use opengl_renderer to draw object and get_pixels * __init_fltk__.cc (fltk_graphics_toolkit::get_pixels, figure_manager::get_pixels, figure_manager::do_get_pixels, plot_window::get_pixels, OpenGL_fltk::get_pixels): new methods * gl-renderer.h/cc (opengl_renderer::get_pixels): new method, read pixels in the current opengl scene * getframe.m: new function file * image.txi: add getframe doc * NEWS: announce new function
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Tue, 23 May 2017 15:01:27 +0200
parents b6498c088fca
children 94968aa621a8
files NEWS doc/interpreter/image.txi libgui/graphics/Backend.cc libgui/graphics/Backend.h libgui/graphics/Canvas.h libgui/graphics/Figure.cc libgui/graphics/Figure.h libgui/graphics/GLCanvas.cc libgui/graphics/GLCanvas.h libgui/graphics/ObjectProxy.cc libgui/graphics/ObjectProxy.h libinterp/corefcn/gl-render.cc libinterp/corefcn/gl-render.h libinterp/corefcn/graphics.cc libinterp/corefcn/graphics.in.h libinterp/dldfcn/__init_fltk__.cc scripts/image/frame2im.m scripts/image/getframe.m scripts/image/module.mk
diffstat 19 files changed, 383 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Sat May 27 09:59:32 2017 -0700
+++ b/NEWS	Tue May 23 15:01:27 2017 +0200
@@ -35,6 +35,7 @@
       corrcoef
       gsvd
       hgtransform
+      getframe
 
  ** Deprecated functions.
 
--- a/doc/interpreter/image.txi	Sat May 27 09:59:32 2017 -0700
+++ b/doc/interpreter/image.txi	Tue May 23 15:01:27 2017 +0200
@@ -152,6 +152,8 @@
 
 @DOCSTRING(ind2rgb)
 
+@DOCSTRING(getframe)
+
 @DOCSTRING(frame2im)
 
 @DOCSTRING(im2frame)
--- a/libgui/graphics/Backend.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/Backend.cc	Tue May 23 15:01:27 2017 +0200
@@ -195,6 +195,22 @@
       }
   }
 
+  uint8NDArray
+  Backend::get_pixels (const graphics_object& go) const
+  {
+    uint8NDArray retval;
+
+    if (go.get_properties ().is_visible () && go.isa ("figure"))
+      {
+        ObjectProxy *proxy = toolkitObjectProxy (go);
+
+        if (proxy)
+          retval = proxy->get_pixels ();
+      }
+
+    return retval;
+  }
+
   Object*
   Backend::toolkitObject (const graphics_object& go)
   {
--- a/libgui/graphics/Backend.h	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/Backend.h	Tue May 23 15:01:27 2017 +0200
@@ -59,6 +59,8 @@
                        const std::string& file_cmd,
                        const std::string& /*debug_file*/) const;
 
+    uint8NDArray get_pixels (const graphics_object& go) const;
+
     static Object * toolkitObject (const graphics_object& go);
 
     static ObjectProxy * toolkitObjectProxy (const graphics_object& go);
--- a/libgui/graphics/Canvas.h	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/Canvas.h	Tue May 23 15:01:27 2017 +0200
@@ -71,12 +71,15 @@
     virtual void toggleGrid (const graphics_handle& handle) = 0;
     virtual void autoAxes (const graphics_handle& handle) = 0;
 
+    virtual uint8NDArray getPixels (void) { return do_getPixels (m_handle); };
+
   protected:
     virtual void draw (const graphics_handle& handle) = 0;
     virtual void drawZoomBox (const QPoint& p1, const QPoint& p2) = 0;
     virtual void resize (int x, int y, int width, int height) = 0;
     virtual graphics_object selectFromAxes (const graphics_object& ax,
                                             const QPoint& pt) = 0;
+    virtual uint8NDArray do_getPixels (const graphics_handle& handle) = 0;
 
   protected:
     Canvas (const graphics_handle& handle)
--- a/libgui/graphics/Figure.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/Figure.cc	Tue May 23 15:01:27 2017 +0200
@@ -395,6 +395,22 @@
       canvas->print (file_cmd, term);
   }
 
+  uint8NDArray
+  Figure::slotGetPixels (void)
+  {
+    uint8NDArray retval;
+    Canvas *canvas = m_container->canvas (m_handle);
+
+    if (canvas)
+      {
+        gh_manager::process_events ();
+        gh_manager::auto_lock lock;
+        retval = canvas->getPixels ();
+      }
+
+    return retval;
+  }
+
   void
   Figure::beingDeleted (void)
   {
--- a/libgui/graphics/Figure.h	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/Figure.h	Tue May 23 15:01:27 2017 +0200
@@ -128,6 +128,9 @@
     void toggleGrid (void);
     void autoAxes (void);
 
+  public slots:
+    uint8NDArray slotGetPixels (void);
+
   signals:
     void asyncUpdate (void);
 
--- a/libgui/graphics/GLCanvas.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/GLCanvas.cc	Tue May 23 15:01:27 2017 +0200
@@ -67,6 +67,24 @@
       }
   }
 
+  uint8NDArray
+  GLCanvas::do_getPixels (const graphics_handle& gh)
+  {
+    uint8NDArray retval;
+    graphics_object go = gh_manager::get_object (gh);
+
+    if (go)
+      {
+        octave::opengl_renderer r;
+
+        r.set_viewport (width (), height ());
+        r.draw (go);
+        retval = r.get_pixels (width (), height ());
+      }
+
+    return retval;
+  }
+
   void
   GLCanvas::toggleAxes (const graphics_handle& gh)
   {
--- a/libgui/graphics/GLCanvas.h	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/GLCanvas.h	Tue May 23 15:01:27 2017 +0200
@@ -37,6 +37,7 @@
     ~GLCanvas (void);
 
     void draw (const graphics_handle& handle);
+    uint8NDArray  do_getPixels (const graphics_handle& handle);
     void toggleAxes (const graphics_handle& handle);
     void toggleGrid (const graphics_handle& handle);
     void autoAxes (const graphics_handle& handle);
--- a/libgui/graphics/ObjectProxy.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/ObjectProxy.cc	Tue May 23 15:01:27 2017 +0200
@@ -24,7 +24,9 @@
 #  include "config.h"
 #endif
 
+#include <QCoreApplication>
 #include <QString>
+#include <QThread>
 
 #include "oct-mutex.h"
 
@@ -109,4 +111,25 @@
     emit sendPrint (file_cmd, term);
   }
 
+  uint8NDArray
+  ObjectProxy::get_pixels (void)
+  {
+    uint8NDArray retval;
+
+    // The ObjectProxy is generally ran from the interpreter thread while the 
+    // actual Figure (Object) lives in the gui thread. The following ensures 
+    // synchronous execution of the Figure method and allows retrieving a 
+    // return value.
+
+    Qt::ConnectionType t = Qt::BlockingQueuedConnection;
+
+    if (QThread::currentThread () == QCoreApplication::instance ()->thread ())
+      t = Qt::DirectConnection;
+
+    QMetaObject::invokeMethod (m_object, "slotGetPixels", t,
+                               Q_RETURN_ARG (uint8NDArray, retval));
+
+    return retval;
+  }
+
 };
--- a/libgui/graphics/ObjectProxy.h	Sat May 27 09:59:32 2017 -0700
+++ b/libgui/graphics/ObjectProxy.h	Tue May 23 15:01:27 2017 +0200
@@ -23,6 +23,8 @@
 #if ! defined (octave_ObjectProxy_h)
 #define octave_ObjectProxy_h 1
 
+#include "uint8NDArray.h"
+
 #include <QObject>
 
 class QString;
@@ -43,6 +45,7 @@
     void finalize (void);
     void redraw (void);
     void print (const QString& file_cmd, const QString& term);
+    uint8NDArray get_pixels (void);
 
     Object * object (void) { return m_object; }
     void setObject (Object *obj);
--- a/libinterp/corefcn/gl-render.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libinterp/corefcn/gl-render.cc	Tue May 23 15:01:27 2017 +0200
@@ -1073,6 +1073,39 @@
 #endif
   }
 
+  uint8NDArray
+  opengl_renderer::get_pixels (int width, int height)
+  {
+#if defined (HAVE_OPENGL)
+
+    glPixelStorei (GL_PACK_ALIGNMENT, 1);
+    uint8NDArray pix(dim_vector (3, width, height), 0);
+    glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, 
+                 pix.fortran_vec ());
+
+    // Permute and flip data      
+    Array<octave_idx_type> perm (dim_vector (3, 1));
+    perm(0) = 2;
+    perm(1) = 1;
+    perm(2) = 0;
+      
+    Array<idx_vector> idx (dim_vector (3, 1));
+    idx(0) = idx_vector::make_range (height - 1, -1, height);
+    idx(1) = idx_vector::colon;
+    idx(2) = idx_vector::colon;
+
+    return pix.permute (perm).index (idx);
+
+#else
+
+  // 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::finish (void)
   {
--- a/libinterp/corefcn/gl-render.h	Sat May 27 09:59:32 2017 -0700
+++ b/libinterp/corefcn/gl-render.h	Tue May 23 15:01:27 2017 +0200
@@ -63,6 +63,7 @@
 
     virtual void set_viewport (int w, int h);
     virtual graphics_xform get_transform (void) const { return xform; }
+    virtual uint8NDArray get_pixels (int width, int height);
 
     virtual void finish (void);
 
--- a/libinterp/corefcn/graphics.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libinterp/corefcn/graphics.cc	Tue May 23 15:01:27 2017 +0200
@@ -12095,3 +12095,22 @@
 
   return ovl ();
 }
+
+DEFUN (__get_frame__, args, ,
+       doc: /* -*- texinfo -*-
+@deftypefn {} {@var{cdata} = } __get_frame__ (@var{hfig})
+Internal function: returns the pixel cdata of figure hfig in the form of a
+height-by-width-by-3 uint8 array
+@end deftypefn */)
+{
+  if (args.length () != 1)
+    print_usage ();
+
+  double h = args(0).xdouble_value ("__get_frame__: argument is not a handle");
+
+  graphics_object go = gh_manager::get_object (h);
+  if (! go || ! go.isa ("figure"))
+    error ("__get_frame__: object is not a figure");
+
+  return ovl (go.get_toolkit ().get_pixels (go));
+}
--- a/libinterp/corefcn/graphics.in.h	Sat May 27 09:59:32 2017 -0700
+++ b/libinterp/corefcn/graphics.in.h	Tue May 23 15:01:27 2017 +0200
@@ -2198,6 +2198,12 @@
                              const std::string& = "") const
   { gripe_if_tkit_invalid ("print_figure"); }
 
+  virtual uint8NDArray get_pixels (const graphics_object&) const
+  { 
+    gripe_if_tkit_invalid ("get_pixels");
+    return uint8NDArray ();
+  }
+
   virtual Matrix get_canvas_size (const graphics_handle&) const
   {
     gripe_if_tkit_invalid ("get_canvas_size");
@@ -2312,6 +2318,9 @@
                      const std::string& debug_file = "") const
   { rep->print_figure (go, term, file, debug_file); }
 
+  uint8NDArray get_pixels (const graphics_object& go) const
+  { return rep->get_pixels (go); }
+
   Matrix get_canvas_size (const graphics_handle& fh) const
   { return rep->get_canvas_size (fh); }
 
--- a/libinterp/dldfcn/__init_fltk__.cc	Sat May 27 09:59:32 2017 -0700
+++ b/libinterp/dldfcn/__init_fltk__.cc	Tue May 23 15:01:27 2017 +0200
@@ -152,6 +152,12 @@
     gl2ps_print (gh_manager::get_object (m_number), cmd, term);
   }
 
+  uint8NDArray get_pixels (void)
+  {
+    m_renderer.draw (gh_manager::get_object (m_number));
+    return m_renderer.get_pixels (w (), h ());
+  }
+
   void resize (int xx, int yy, int ww, int hh)
   {
 #if defined (HAVE_OPENGL)
@@ -905,6 +911,11 @@
     m_canvas->print (cmd, term);
   }
 
+  uint8NDArray get_pixels ()
+  {
+    return m_canvas->get_pixels ();
+  }
+
   void show_menubar (void)
   {
     m_uimenu->show ();
@@ -1952,6 +1963,15 @@
       instance->do_print (hnd2idx (gh), cmd, term);
   }
 
+  static uint8NDArray get_pixels (const graphics_handle& gh)
+  {
+    uint8NDArray retval;
+    if (instance_ok ())
+      retval = instance->do_get_pixels (hnd2idx (gh));
+    
+    return retval;
+  }
+
   static void uimenu_update (const graphics_handle& figh,
                              const graphics_handle& uimenuh, int id)
   {
@@ -2124,6 +2144,17 @@
       win->second->print (cmd, term);
   }
 
+  uint8NDArray do_get_pixels (int idx)
+  {
+    uint8NDArray retval;
+    wm_iterator win = windows.find (idx);
+
+    if (win != windows.end ())
+      retval = win->second->get_pixels ();
+
+    return retval;
+  }
+
   void do_uimenu_update (int idx, const graphics_handle& gh, int id)
   {
     wm_iterator win = windows.find (idx);
@@ -2372,6 +2403,11 @@
     figure_manager::print (go.get_handle (), file_cmd, term);
   }
 
+  uint8NDArray get_pixels (const graphics_object& go) const
+  {
+    return figure_manager::get_pixels (go.get_handle ());
+  }
+
   Matrix get_canvas_size (const graphics_handle& fh) const
   {
     return figure_manager::get_size (fh);
--- a/scripts/image/frame2im.m	Sat May 27 09:59:32 2017 -0700
+++ b/scripts/image/frame2im.m	Tue May 23 15:01:27 2017 +0200
@@ -28,7 +28,7 @@
 ## for indexed and RGB movies respectively, with each frame concatenated
 ## along the 4th dimension.
 ##
-## @seealso{im2frame}
+## @seealso{im2frame, getframe}
 ## @end deftypefn
 
 ## Author: Carnë Draug <carandraug@octave.org>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/image/getframe.m	Tue May 23 15:01:27 2017 +0200
@@ -0,0 +1,195 @@
+## Copyright (C) 2017 Pantxo Diribarne
+## 
+## This program is free software; you can redistribute it and/or modify it
+## under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 3 of the License, or
+## (at your option) any later version.
+## 
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## 
+## You should have received a copy of the GNU General Public License
+## along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @deftypefn {} {@var{im} =} getframe ()
+## @deftypefnx {} {@var{im} =} getframe (@var{hax})
+## @deftypefnx {} {@var{im} =} getframe (@var{hfig})
+## @deftypefnx {} {@var{im} =} getframe (@dots{}, @var{rect})
+##
+## Capture a figure or axes pixels.
+##
+## Without any argument capture the current axes excluding ticklabels,
+## title and x/y/zlabels. The returned structure @var{im} has fields
+## "cdata", which contains the actual image data in the form of a
+## n-by-m-by-3 (rgb) uint8 matrix, and "colormap" which is provided for
+## matlab compatibility but is always empty.
+##
+## If a graphics handle @var{hax} to an axes object is provided, this
+## axes is captured instead of the currentaxes.
+##
+## If a graphics handle @var{hfig} to a figure object is provided, the whole
+## corresponding figure canvas is catured.
+##
+## Finally if a second argument @var{rect} is provided, it must be a
+## four element vector [left bottom width height], defining the region
+## inside the figure corresponding to @var{hfig} or the parent figure of
+## @var{hax} to be catured. Whatever the figure @qcode{"units"} property,
+## @var{rect} must be defined in @strong{pixels}.
+## 
+## @seealso{im2frame, frame2im}
+## @end deftypefn
+
+function im = getframe (h = [], rect = [])
+  hf = hax = [];
+  if (isempty (h))
+    hf = get (0, "currentfigure");
+    if (isempty (hf))
+      error ("getframe: no figure to capture")
+    endif
+    hax = get (hf, "currentaxes");
+    if (isempty (hax))
+      error ("getframe: no axes to capture")
+    endif
+  elseif (isfigure (h))
+    hf = h;
+  elseif (isaxes (h))
+    hf = ancestor (h, "figure");
+    hax = h;
+  else
+    error ("getframe: H must be a figure or axes handle")
+  endif
+
+  if (strcmp (get (hf, "__graphics_toolkit__"), "gnuplot"))
+    error ("getframe: not implemented for gnuplot graphics toolkit")
+  endif
+
+  unwind_protect
+    htmp = hax;
+    if (h == hf)
+      htmp = hf;
+    endif
+    units = get (htmp, "units");
+    set (htmp, "units", "pixels");
+    pos = get (htmp, "position");
+    if (h == hf)
+      pos(1:2) = 1;
+    endif
+  unwind_protect_cleanup
+    set (htmp, "units", units)
+  end_unwind_protect
+    
+  if (! isempty (rect))
+    xv = [pos(1); pos(1)+pos(3); pos(1)+pos(3); pos(1)];
+    yv = [pos(2); pos(2); pos(2)+pos(4); pos(2)+pos(4)];
+    x = [rect(1); rect(1)+rect(3); rect(1)+rect(3); rect(1)];
+    y = [rect(2); rect(2); rect(2)+rect(4); rect(2)+rect(4)];
+    in = inpolygon (x, y, xv, yv);
+    if (! all (in))
+      error ("getframe: RECT must define a region inside the figure");
+    endif
+    pos = rect;
+  endif
+
+  if (strcmp (get (hf, "visible"), "off"))
+    ## Use OpenGL offscreen rendering with OSMesa
+    try
+      cdata = __osmesa_print__ (hf);
+    catch
+      error ("getframe: couldn't render invisible figure. %s", lasterr ())
+    end_try_catch
+  else
+    cdata = __get_frame__ (hf);
+  endif
+  
+  i1 = max (floor (pos(1)), 1);
+  i2 = min (ceil (pos(1)+pos(3)-1), columns (cdata));
+  idxx = i1:i2;
+  i1 = max (floor (pos(2)), 1);
+  i2 = min (ceil (pos(2)+pos(4)-1), rows (cdata));
+  idxy = fliplr (rows (cdata) - (i1:i2) + 1);
+  
+  im = struct ("cdata", cdata(idxy,idxx,:), "colormap", []);
+  
+endfunction
+
+%!demo
+%! clf
+%! contourf (rand (5));
+%! drawnow ();
+%! im = getframe ();
+%! imshow (im.cdata);
+
+%!demo
+%! clf reset
+%! contourf (rand (5));
+%! im = getframe (gcf ());
+%! imshow (im.cdata);
+%! set (gca, 'position', [0 0 1 1]);
+
+%!demo
+%! clf
+%! hax1 = subplot (2,1,1);
+%! contourf (rand (5));
+%! title ('Original');
+%! im = getframe (hax1);
+%! hax2 = subplot (2,1,2);
+%! image (im.cdata);
+%! title ('Image');
+
+%!demo
+%! clf
+%! hax1 = subplot (2,1,1);
+%! contourf (rand (5));
+%! title ('Original');
+%!
+%! % Get the coordinates of the lower-left hand corner in pixels
+%! set (hax1, 'units', 'pixels');
+%! pos = get (hax1, 'position');
+%! set (hax1, 'units', 'normalized');
+%! rect = [pos(1:2) pos(3:4)/2];
+%!
+%! im = getframe (hax1, rect);
+%! hax2 = subplot (2,1,2);
+%! image (im.cdata);
+%! title ('Lower left hand corner');
+
+%!testif HAVE_OSMESA
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   pos = get (hf, "position");
+%!   assert (size (getframe (hf).cdata)(1:2), pos(4:-1:3))
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
+
+%!testif HAVE_OSMESA
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   hax = axes ("visible", "off", "position", [0 0 1 1]);
+%!   verts = [0 0; .5 0; 1 0; ...
+%!            0 .5; .5 .5; 1 .5; ...
+%!            0 1; .5 1; 1 1];
+%!   faces = [1 2 5 4; 2 3 6 5; 4 5 8 7; 5 6 9 8];
+%!   fvc = [1 0 0; 0 1 0; 0 0 1; 1 0 1];
+%!   patch ("vertices", verts, "faces", faces, "facevertexcdata", fvc, ...
+%!          "facecolor", "flat");
+%!   
+%!   kk = 1;
+%!   pos = get (hf, "position");
+%!   
+%!   for jj = [0.05 0.55]
+%!     for ii = [0.05 0.55]
+%!       rect = [ii jj .4 .4].*[pos(3:4) pos(3:4)];
+%!       im = getframe (hax, rect).cdata;
+%!       assert (im(:,:,1) == fvc(kk,1)*255)
+%!       assert (im(:,:,2) == fvc(kk,2)*255)
+%!       assert (im(:,:,3) == fvc(kk,3)*255)
+%!       kk++;
+%!     endfor
+%!   endfor
+%! unwind_protect_cleanup
+%!   close (hf)
+%! end_unwind_protect
--- a/scripts/image/module.mk	Sat May 27 09:59:32 2017 -0700
+++ b/scripts/image/module.mk	Tue May 23 15:01:27 2017 +0200
@@ -26,6 +26,7 @@
   %reldir%/cubehelix.m \
   %reldir%/flag.m \
   %reldir%/frame2im.m \
+  %reldir%/getframe.m \
   %reldir%/gray.m \
   %reldir%/gray2ind.m \
   %reldir%/hot.m \