# HG changeset patch # User Carnë Draug # Date 1383635130 0 # Node ID bfbe5dcc99435aa7f5295d09f91c3b7fe10f15c4 # Parent adb0ba0d0c1372e07dc55a8586c629d23cf6e298 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. diff -r adb0ba0d0c13 -r bfbe5dcc9943 libinterp/dldfcn/__magick_read__.cc --- 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 +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 methods; + if (methods.empty ()) + { + methods[0] = "doNotSpecify"; + methods[1] = "leaveInPlace"; + methods[2] = "restoreBG"; + methods[3] = "restorePrevious"; + } + return methods; +} +static std::map +init_reverse_disposal_methods () +{ + static std::map 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 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 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 -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 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) { diff -r adb0ba0d0c13 -r bfbe5dcc9943 scripts/image/imwrite.m --- 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), diff -r adb0ba0d0c13 -r bfbe5dcc9943 scripts/image/private/__imwrite__.m --- 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)