changeset 17855:bfbe5dcc9943

imwrite: implement DisposalMethod option. * __magick_read__.cc (F__magick_write__): set gifDisposeMethod() when writing an image to implement the DisposalMethod option. Set all the options in a single option rather than a loop per option. (init_reverse_disposal_methods): new method to return a reversed std::map of the disposal methods. (init_disposal_methods): move up, to be closer to the new init_reverse_disposal_methods() * private/__imwrite__.m: input check for the new option DisposalMethod and set its default. * imwrite.m: document new option.
author Carnë Draug <carandraug@octave.org>
date Tue, 05 Nov 2013 07:05:30 +0000
parents adb0ba0d0c13
children 97e49b588f5d c17a3e43956b
files libinterp/dldfcn/__magick_read__.cc scripts/image/imwrite.m scripts/image/private/__imwrite__.m
diffstat 3 files changed, 109 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/__magick_read__.cc	Tue Nov 05 05:48:56 2013 +0000
+++ b/libinterp/dldfcn/__magick_read__.cc	Tue Nov 05 07:05:30 2013 +0000
@@ -1254,6 +1254,49 @@
   return;
 }
 
+// Meant to be shared with both imfinfo and imwrite.
+static std::map<octave_idx_type, std::string>
+init_disposal_methods ()
+{
+  //  GIF Specifications:
+  //
+  // Disposal Method - Indicates the way in which the graphic is to
+  //                    be treated after being displayed.
+  //
+  //  0 -   No disposal specified. The decoder is
+  //        not required to take any action.
+  //  1 -   Do not dispose. The graphic is to be left
+  //        in place.
+  //  2 -   Restore to background color. The area used by the
+  //        graphic must be restored to the background color.
+  //  3 -   Restore to previous. The decoder is required to
+  //        restore the area overwritten by the graphic with
+  //        what was there prior to rendering the graphic.
+  //  4-7 - To be defined.
+  static std::map<octave_idx_type, std::string> methods;
+  if (methods.empty ())
+    {
+      methods[0] = "doNotSpecify";
+      methods[1] = "leaveInPlace";
+      methods[2] = "restoreBG";
+      methods[3] = "restorePrevious";
+    }
+  return methods;
+}
+static std::map<std::string, octave_idx_type>
+init_reverse_disposal_methods ()
+{
+  static std::map<std::string, octave_idx_type> methods;
+  if (methods.empty ())
+    {
+      methods["donotspecify"]     = 0;
+      methods["leaveinplace"]     = 1;
+      methods["restorebg"]        = 2;
+      methods["restoreprevious"]  = 3;
+    }
+  return methods;
+}
+
 void static
 write_file (const std::string& filename,
             const std::string& ext,
@@ -1384,16 +1427,20 @@
           return retval;
         }
     }
+  static std::map<std::string, octave_idx_type> disposal_methods
+    = init_reverse_disposal_methods ();
 
   const octave_idx_type nFrames = imvec.size ();
 
   const octave_idx_type quality = options.getfield ("quality").int_value ();
+  const ColumnVector delaytime = options.getfield ("delaytime").column_vector_value ();
+  const Array<std::string> disposalmethod = options.getfield ("disposalmethod").cellstr_value ();
   for (octave_idx_type i = 0; i < nFrames; i++)
-    imvec[i].quality (quality);
-
-  const ColumnVector delaytime = options.getfield ("delaytime").column_vector_value ();
-  for (octave_idx_type i = 0; i < nFrames; i++)
-    imvec[i].animationDelay (delaytime(i));
+    {
+      imvec[i].quality (quality);
+      imvec[i].animationDelay (delaytime(i));
+      imvec[i].gifDisposeMethod (disposal_methods[disposalmethod(i)]);
+    }
 
   // If writemode is set to append, read the image and append to it. Even
   // if set to append, make sure that something was read at all.
@@ -1411,31 +1458,31 @@
         }
     }
 
-      // FIXME - LoopCount or animationIterations
-      //  How it should work:
-      //
-      // This value is only set for the first image in the sequence. Trying
-      // to set this value with the append mode should have no effect, the
-      // value used with the first image is the one that counts (that would
-      // also be Matlab compatible). Thus, the right way to do this would be
-      // to have an else block on the condition above, and set this only
-      // when creating a new file. Since Matlab does not interpret a 4D
-      // matrix as sequence of images to write, its users need to use a for
-      // loop and set LoopCount only on the first iteration (it actually
-      // throws warnings otherwise)
-      //
-      //  Why is this not done the right way:
-      //
-      // When GM saves a single image, it discards the value if there is only
-      // a single image and sets it to "no loop".  Since our default is an
-      // infinite loop, if the user tries to do it the Matlab way (setting
-      // LoopCount only on the first image) that value will go nowhere.
-      // See https://sourceforge.net/p/graphicsmagick/bugs/248/
-      // Because of this, we document to set LoopCount on every iteration
-      // (in Matlab will cause a lot of warnings), or pass a 4D matrix with
-      // all frames (won't work in Matlab at all).
-      // Note that this only needs to be set on the first frame
-      imvec[0].animationIterations (options.getfield ("loopcount").uint_value ());
+  // FIXME - LoopCount or animationIterations
+  //  How it should work:
+  //
+  // This value is only set for the first image in the sequence. Trying
+  // to set this value with the append mode should have no effect, the
+  // value used with the first image is the one that counts (that would
+  // also be Matlab compatible). Thus, the right way to do this would be
+  // to have an else block on the condition above, and set this only
+  // when creating a new file. Since Matlab does not interpret a 4D
+  // matrix as sequence of images to write, its users need to use a for
+  // loop and set LoopCount only on the first iteration (it actually
+  // throws warnings otherwise)
+  //
+  //  Why is this not done the right way:
+  //
+  // When GM saves a single image, it discards the value if there is only
+  // a single image and sets it to "no loop".  Since our default is an
+  // infinite loop, if the user tries to do it the Matlab way (setting
+  // LoopCount only on the first image) that value will go nowhere.
+  // See https://sourceforge.net/p/graphicsmagick/bugs/248/
+  // Because of this, we document to set LoopCount on every iteration
+  // (in Matlab will cause a lot of warnings), or pass a 4D matrix with
+  // all frames (won't work in Matlab at all).
+  // Note that this only needs to be set on the first frame
+  imvec[0].animationIterations (options.getfield ("loopcount").uint_value ());
 
   write_file (filename, ext, imvec);
   if (error_state)
@@ -1608,36 +1655,6 @@
     }
 }
 
-// Meant to be shared with both imfinfo and imwrite.
-static std::map<octave_idx_type, std::string>
-init_disposal_methods ()
-{
-  //  GIF Specifications:
-  //
-  // Disposal Method - Indicates the way in which the graphic is to
-  //                    be treated after being displayed.
-  //
-  //  0 -   No disposal specified. The decoder is
-  //        not required to take any action.
-  //  1 -   Do not dispose. The graphic is to be left
-  //        in place.
-  //  2 -   Restore to background color. The area used by the
-  //        graphic must be restored to the background color.
-  //  3 -   Restore to previous. The decoder is required to
-  //        restore the area overwritten by the graphic with
-  //        what was there prior to rendering the graphic.
-  //  4-7 - To be defined.
-  static std::map<octave_idx_type, std::string> methods;
-  if (methods.empty ())
-    {
-      methods[0] = "doNotSpecify";
-      methods[1] = "leaveInPlace";
-      methods[2] = "restoreBG";
-      methods[3] = "restorePrevious";
-    }
-  return methods;
-}
-
 static bool
 is_valid_exif (const std::string& val)
 {
--- a/scripts/image/imwrite.m	Tue Nov 05 05:48:56 2013 +0000
+++ b/scripts/image/imwrite.m	Tue Nov 05 07:05:30 2013 +0000
@@ -54,6 +54,13 @@
 ## equal to the number of frames in @var{im}.  The value is in seconds, must
 ## be between 0 and 655.35, and defaults to 0.5.
 ##
+## @item DisposalMethod
+## For formats that accept animations (such as GIF), controls what happens
+## to a frame before drawing the next one.  Its value can be one of the
+## following strings: "doNotSpecify" (default); "leaveInPlace"; "restoreBG";
+## and "restorePrevious", or a cell array of those string with length equal
+## to the number of frames in @var{img}.
+##
 ## @item LoopCount
 ## For formats that accept animations (such as GIF), controls how many times
 ## the sequence is repeated.  A value of Inf means an infinite loop (default),
--- a/scripts/image/private/__imwrite__.m	Tue Nov 05 05:48:56 2013 +0000
+++ b/scripts/image/private/__imwrite__.m	Tue Nov 05 07:05:30 2013 +0000
@@ -42,6 +42,7 @@
 
   ## set default for options
   options = struct ("writemode", "overwrite",
+                    "disposalmethod", {repmat({"doNotSpecify"}, 1, size (img, 4))},
                     "quality",   75,
                     "delaytime", ones (1, size (img, 4)) *500, # 0.5 seconds
                     "loopcount", 0, ## this is actually Inf
@@ -84,6 +85,30 @@
                  param_list{idx});
         endif
 
+      case "disposalmethod"
+        options.disposalmethod = param_list{idx+1};
+        if (! ischar (options.disposalmethod) &&
+            ! iscellstr (options.disposalmethod))
+          error ("imwrite: value for %s must be a string or cell array of strings",
+                 param_list{idx});
+        elseif (! iscell (options.disposalmethod))
+          options.disposalmethod = {options.disposalmethod};
+        endif
+        options.disposalmethod = tolower (options.disposalmethod);
+        matches = ismember (options.disposalmethod,
+                            {"donotspecify", "leaveinplace", "restorebg", "restoreprevious"});
+        if (any (! matches))
+          error ("imwrite: unknow method %s for option %s",
+                 options.disposalmethod{find (! matches, 1)},
+                 param_list{idx});
+        endif
+        if (isscalar (options.disposalmethod))
+          options.disposalmethod = repmat (options.disposalmethod, 1, size (img, 4));
+        elseif (numel (options.disposalmethod) != size (img, 4))
+          error ("imwrite: if value %s is a cell array must have same length as number of frames",
+                 param_list{idx});
+        endif
+
       case "loopcount"
         options.loopcount = param_list{idx+1};
         if (! isscalar (options.loopcount) || ! isnumeric (options.loopcount)