changeset 26242:2730917a7979

movie.m: Improve input validation and FPS accuracy (patch #9363). * movie.m: Improve input checking. Add corresponding BIST tests. Make use of a local timer rather than resetting the global timer. Call "tic" only once and compare "toc" to time table for better FPS accuracy. Tag target image so that movie can potentially reuse it for further movie plays in the same axes.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Sat, 15 Dec 2018 15:44:01 -0800
parents 2d80a065ce9a
children b22a2aa820e6
files scripts/image/movie.m
diffstat 1 files changed, 99 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- a/scripts/image/movie.m	Sat Dec 15 07:29:27 2018 -0800
+++ b/scripts/image/movie.m	Sat Dec 15 15:44:01 2018 -0800
@@ -1,5 +1,5 @@
 ## Copyright (C) 2017 Pantxo Diribarne
-## 
+##
 ## This file is part of Octave.
 ##
 ## Octave is free software: you can redistribute it and/or modify it
@@ -56,55 +56,60 @@
 
 function movie (varargin)
 
-  if (nargin == 0)
+  if (nargin == 0 || nargin > 4)
     print_usage ();
   endif
 
   ## Extract possible handle argument
   hax = [];
-  if (isaxes (varargin{1}))
-    hax = varargin{1};
-    varargin(1) = [];
-  elseif (isfigure (varargin{1}))
-    hax = get (varargin{1}, "currentaxes");
-    if (isempty (hax))
-      hax = axes ("parent", varargin{1});
+  if (ishghandle (varargin{1}))
+    typ = get (varargin{1}, "type");
+    if (strcmp (typ, "axes"))
+      hax = varargin{1};
+    elseif (strcmp (typ, "figure"))
+      figure (varargin{1});
+      hax = gca ();
+    else
+      error ("movie: H must be a handle to an axes or figure");
     endif
     varargin(1) = [];
+    nargin = nargin - 1;
+    if (nargin == 0)
+      print_usage ();
+    endif
   endif
 
   ## Extract other arguments
   n = 1;
   fps = 12;
   idx = [];
-  nargs = numel (varargin);
-  if (nargs == 0)
-    print_usage ();
-  elseif (nargs >= 1)
-    mov = varargin{1};
-    if (! isfield (mov, "cdata") || ! isfield (mov, "colormap"))
-      error ("movie: MOV must be a frame struct array");
+
+  mov = varargin{1};
+  if (! isfield (mov, "cdata") || ! isfield (mov, "colormap"))
+    error ("movie: MOV must be a frame struct array");
+  elseif (numel (mov) < 2)
+    error ("movie: MOV must contain at least two frames");
+  endif
+
+  if (nargin > 1)
+    n = varargin{2};
+    if (! isindex (abs (n(1))))
+      error ("movie: N must be a non-zero integer");
     endif
 
-    if (nargs >= 2)
-      n = varargin{2};
-      if (! isindex (abs (n(1))))
-        error ("movie: N must be a non-zero integer");
-      elseif (! isscalar (n))
-        idx = n(2:end)(:)';
-        n = n(1);
-        if (! isindex (idx, numel (mov)))
-          error (["movie: All elements N(2:end) must be valid indices ", ...
-                  "into the MOV struct array"]);
+    if (! isscalar (n))
+      idx = n(2:end)(:)';
+      n = n(1);
+      if (! isindex (idx, numel (mov)))
+        error (["movie: All elements N(2:end) must be valid indices ", ...
+                "into the MOV struct array"]);
+      endif
+    endif
 
-        endif
-      endif
-        
-      if (nargs >= 3)
-        fps = varargin{3};
-        if (! (isnumeric (fps) && isscalar (fps) && fps > 0))
-          error ("movie: FPS must be a numeric scalar > 0");
-        endif
+    if (nargin > 2)
+      fps = varargin{3};
+      if (! (isnumeric (fps) && isscalar (fps) && fps > 0))
+        error ("movie: FPS must be a numeric scalar > 0");
       endif
     endif
   endif
@@ -130,19 +135,40 @@
     endif
   endif
 
-  tau = 1/fps;
+  ## Initialize graphics objects
+  if (isempty (mov(1).cdata))
+    error ("movie: empty image data at frame 1");
+  endif
+
+  him = findobj (hax, "-depth", 1, "tag", "__movie_frame__");
+  if (isempty (him))
+    him = image ("parent", hax, "cdata", mov(1).cdata,
+                 "tag", "__movie_frame__");
+  else
+    set (him, "cdata", mov(1).cdata);
+  endif
+
   set (hax, "ydir", "reverse", "visible", "off");
-  tic ();
-  him = image ("parent", hax, "cdata", mov(1).cdata);
+
+  ## Initialize the timer
+  t = tau = 1/fps;
+  timerid = tic ();
+
   for ii = idx
-    pause (tau - toc ());
-    tic ();
+    cdata = mov(ii).cdata;
+    if (isempty (cdata))
+      error ("movie: empty image data at frame %d", ii);
+    endif
+    set (him, "cdata", cdata);
+
     if (! isempty (mov(ii).colormap))
       set (hax, "colormap", mov(ii).colormap)
     endif
-    set (him, "cdata", mov(ii).cdata);
+
+    pause (t - toc (timerid));
+    t += tau;
   endfor
-  
+
 endfunction
 
 
@@ -159,7 +185,7 @@
 %!   mov(ii).cdata = im;
 %! endfor
 %! clf ();
-%! title "Play movie forward 2 times"
+%! title ("Play movie forward 2 times");
 %! movie (mov, 2);
 
 %!demo
@@ -175,7 +201,7 @@
 %!   mov(ii).cdata = im;
 %! endfor
 %! clf ();
-%! title "Play movie forward and backward 5 times at 25 fps"
+%! title ("Play movie forward and backward 5 times at 25 fps");
 %! movie (mov, -5, 25);
 
 %!demo
@@ -191,7 +217,7 @@
 %!   mov(ii).cdata = im;
 %! endfor
 %! clf ();
-%! title "Play downsampled movie 5 times" 
+%! title ("Play downsampled movie 5 times");
 %! movie (mov, [5 1:3:nframes]);
 
 %!demo
@@ -206,12 +232,38 @@
 %!   mov(ii) = getframe ();
 %! endfor
 %! clf ();
-%! movie (mov, 5, 25);
+%! movie (mov, 3, 25);
 
 ## Test input validation
 %!error movie ()
 %!error <MOV must be a frame struct array> movie ({2})
+%!error <MOV must contain at least two frames>
+%! movie (struct ("cdata", [], "colormap", []));
 %!error <N must be a non-zero integer>
-%! movie (struct ("cdata", [], "colormap", []), 2.3);
+%! movie (struct ("cdata", {[], []}, "colormap", []), 2.3);
 %!error <N must be a non-zero integer>
-%! movie (struct ("cdata", [], "colormap", []), [2.3 -6]);
+%! movie (struct ("cdata", {[], []}, "colormap", []), [2.3 -6]);
+%!error <All elements N\(2:end\) must be valid indices into the MOV struct>
+%! movie (struct ("cdata", {[], []}, "colormap", []), [1 -1])
+%!error <All elements N\(2:end\) must be valid indices into the MOV struct>
+%! movie (struct ("cdata", {[], []}, "colormap", []), [1 5])
+%!error <FPS must be a numeric scalar . 0>
+%! movie (struct ("cdata", {[], []}, "colormap", []), 1, "a")
+%!error <FPS must be a numeric scalar . 0>
+%! movie (struct ("cdata", {[], []}, "colormap", []), 1, [1 1])
+%!error <FPS must be a numeric scalar . 0>
+%! movie (struct ("cdata", {[], []}, "colormap", []), 1, -1/12)
+%!error <empty image data at frame 1>
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   movie (hf, struct ("cdata", {[], []}, "colormap", []));
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect
+%!error <empty image data at frame 2>
+%! hf = figure ("visible", "off");
+%! unwind_protect
+%!   movie (struct ("cdata", {ones(2), []}, "colormap", []))
+%! unwind_protect_cleanup
+%!   close (hf);
+%! end_unwind_protect