Mercurial > octave
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