# HG changeset patch
# User Pantxo Diribarne <pantxo.diribarne@gmail.com>
# Date 1495544487 -7200
#      Tue May 23 15:01:27 2017 +0200
# Node ID a6575560bcecf99e4b053289ad008171d5fa0111
# Parent  7463c8e91dba4277299592d1754574d0b56d2a85
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


diff -r 7463c8e91dba -r a6575560bcec NEWS
--- a/NEWS	Sun May 14 00:44:43 2017 +0200
+++ b/NEWS	Tue May 23 15:01:27 2017 +0200
@@ -35,6 +35,7 @@
       corrcoef
       gsvd
       hgtransform
+      getframe
 
  ** Deprecated functions.
 
diff -r 7463c8e91dba -r a6575560bcec doc/interpreter/image.txi
--- a/doc/interpreter/image.txi	Sun May 14 00:44:43 2017 +0200
+++ 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)
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/Backend.cc
--- a/libgui/graphics/Backend.cc	Sun May 14 00:44:43 2017 +0200
+++ 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)
   {
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/Backend.h
--- a/libgui/graphics/Backend.h	Sun May 14 00:44:43 2017 +0200
+++ 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);
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/Canvas.h
--- a/libgui/graphics/Canvas.h	Sun May 14 00:44:43 2017 +0200
+++ 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)
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/Figure.cc
--- a/libgui/graphics/Figure.cc	Sun May 14 00:44:43 2017 +0200
+++ 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)
   {
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/Figure.h
--- a/libgui/graphics/Figure.h	Sun May 14 00:44:43 2017 +0200
+++ 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);
 
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/GLCanvas.cc
--- a/libgui/graphics/GLCanvas.cc	Sun May 14 00:44:43 2017 +0200
+++ 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)
   {
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/GLCanvas.h
--- a/libgui/graphics/GLCanvas.h	Sun May 14 00:44:43 2017 +0200
+++ 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);
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/ObjectProxy.cc
--- a/libgui/graphics/ObjectProxy.cc	Sun May 14 00:44:43 2017 +0200
+++ 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;
+  }
+
 };
diff -r 7463c8e91dba -r a6575560bcec libgui/graphics/ObjectProxy.h
--- a/libgui/graphics/ObjectProxy.h	Sun May 14 00:44:43 2017 +0200
+++ 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);
diff -r 7463c8e91dba -r a6575560bcec libinterp/corefcn/gl-render.cc
--- a/libinterp/corefcn/gl-render.cc	Sun May 14 00:44:43 2017 +0200
+++ 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)
   {
diff -r 7463c8e91dba -r a6575560bcec libinterp/corefcn/gl-render.h
--- a/libinterp/corefcn/gl-render.h	Sun May 14 00:44:43 2017 +0200
+++ 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);
 
diff -r 7463c8e91dba -r a6575560bcec libinterp/corefcn/graphics.cc
--- a/libinterp/corefcn/graphics.cc	Sun May 14 00:44:43 2017 +0200
+++ b/libinterp/corefcn/graphics.cc	Tue May 23 15:01:27 2017 +0200
@@ -12085,3 +12085,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));
+}
diff -r 7463c8e91dba -r a6575560bcec libinterp/corefcn/graphics.in.h
--- a/libinterp/corefcn/graphics.in.h	Sun May 14 00:44:43 2017 +0200
+++ 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); }
 
diff -r 7463c8e91dba -r a6575560bcec libinterp/dldfcn/__init_fltk__.cc
--- a/libinterp/dldfcn/__init_fltk__.cc	Sun May 14 00:44:43 2017 +0200
+++ 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);
diff -r 7463c8e91dba -r a6575560bcec scripts/image/frame2im.m
--- a/scripts/image/frame2im.m	Sun May 14 00:44:43 2017 +0200
+++ 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>
diff -r 7463c8e91dba -r a6575560bcec scripts/image/getframe.m
--- /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,165 @@
+## 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.
+## 
+## @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
diff -r 7463c8e91dba -r a6575560bcec scripts/image/module.mk
--- a/scripts/image/module.mk	Sun May 14 00:44:43 2017 +0200
+++ b/scripts/image/module.mk	Tue May 23 15:01:27 2017 +0200
@@ -26,6 +26,7 @@
   scripts/image/cubehelix.m \
   scripts/image/flag.m \
   scripts/image/frame2im.m \
+  scripts/image/getframe.m \
   scripts/image/gray.m \
   scripts/image/gray2ind.m \
   scripts/image/hot.m \
