1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
#' A wrapper for the `convert' utility of ImageMagick or GraphicsMagick
#'
#' The main purpose of these two functions is to create GIF animations.
#'
#' The function \code{im.convert} simply wraps the arguments of the
#' \command{convert} utility of ImageMagick to make it easier to call
#' ImageMagick in R;
#' @rdname convert
#' @param files either a character vector of file names, or a single string
#' containing wildcards (e.g. \file{Rplot*.png})
#' @param output the file name of the output (with proper extensions, e.g.
#' \code{gif})
#' @param convert the \command{convert} command; it must be \code{'magick'},
#' \code{'convert'} or \code{'gm convert'}; and it can be pre-specified as an
#' option in \code{\link{ani.options}('convert')}, e.g. (Windows users)
#' \code{ani.options(convert = 'c:/program
#' files/imagemagick/convert.exe')}, or (Mac users) \code{ani.options(convert
#' = '/opt/local/bin/convert')}; see the Note section for more details
#' @param cmd.fun a function to invoke the OS command; by default
#' \code{\link{system}}
#' @param extra.opts additional options to be passed to \command{convert} (or
#' \command{gm convert})
#' @param clean logical: delete the input \code{files} or not
#' @return The command for the conversion.
#'
#' If \code{ani.options('autobrowse') == TRUE}, this function will also try to
#' open the output automatically.
#' @note If \code{files} is a character vector, please make sure the order of
#' filenames is correct! The first animation frame will be \code{files[1]},
#' the second frame will be \code{files[2]}, ...
#'
#' Both ImageMagick and GraphicsMagick may have a limit on the number of
#' images to be converted. It is a known issue that this function can fail
#' with more than (approximately) 9000 images. The function
#' \code{\link{saveVideo}} is a better alternative in such a case.
#'
#' Most Windows users do not have read the boring notes below after they have
#' installed ImageMagick or GraphicsMagick. For the rest of Windows users:
#'
#' \describe{
#'
#' \item{\strong{ImageMagick users}}{Please install ImageMagick from
#' \url{http://www.imagemagick.org}, and make sure the the path to
#' \command{convert.exe} is in your \code{'PATH'} variable, in which case the
#' command \command{convert} can be called without the full path. Windows
#' users are often very confused about the ImageMagick and \code{'PATH'}
#' setting, so I'll try to search for ImageMagick in the Registry Hive by
#' \code{readRegistry('SOFTWARE\ImageMagick\Current')$BinPath}, thus you might
#' not really need to modify your \code{'PATH'} variable.
#'
#' For Windows users who have installed LyX, I will also try to find the
#' \command{convert} utility in the LyX installation directory, so they do not
#' really have to install ImageMagick if LyX exists in their system (of
#' course, the LyX should be installed with ImageMagick).
#'
#' Once the \command{convert} utility is found, the animation option
#' \code{'convert'} will be set (\code{ani.options(convert =
#' 'path/to/convert.exe')}); this can save time for searching for
#' \command{convert} in the operating system next time. }
#'
#' \item{\strong{GraphicsMagick users}}{During the installation of
#' GraphicsMagick, you will be asked if you allow it to change the PATH
#' variable; please do check the option. }
#'
#' }
#'
#' A reported problem is \code{cmd.fun = shell} might not work under Windows
#' but \code{cmd.fun = system} works fine. Try this option in case of
#' failures.
#' @author Yihui Xie
#' @family utilities
#' @references Examples at \url{https://yihui.org/animation/example/im-convert/}
#'
#' ImageMagick: \url{http://www.imagemagick.org/script/convert.php}
#'
#' GraphicsMagick: \url{http://www.graphicsmagick.org}
#' @export
im.convert = function(
files, output = 'animation.gif', convert = c('magick', 'convert', 'gm convert'),
cmd.fun = if (.Platform$OS.type == 'windows') shell else system, extra.opts = '', clean = FALSE
) {
movie.name = output
interval = head(ani.options('interval'), length(files))
loop = ifelse(isTRUE(ani.options('loop')), 0, ani.options('loop'))
convert = match.arg(convert, c('magick','convert', 'gm convert'))
if (convert == 'magick' && requireNamespace('magick', quietly = TRUE)) {
dispose_opt = regmatches(regexpr(pattern = "-dispose\\s+\\S+", text = extra.opts), x = extra.opts)
dispose = gsub("-dispose ", "", dispose_opt)
magick.convert(files = files, output = output, loop = loop, interval = interval, dispose = dispose)
cmd = 0
} else {
if (convert == 'convert' || convert == "magick") {
version = ''
if (!is.null(ani.options('convert'))) {
try(version <- cmd.fun(sprintf('%s --version', shQuote(ani.options('convert'))), intern = TRUE))
}
if (!length(grep('ImageMagick', version))) {
try(version <- cmd.fun(sprintf('%s --version', convert), intern = TRUE))
} else convert = ani.options('convert')
if (!length(grep('ImageMagick', version))) {
message('I cannot find ImageMagick with convert = ', shQuote(convert))
convert_switch = ifelse(convert == 'convert',"magick","convert")
try(version <- cmd.fun(sprintf('%s --version', convert_switch), intern = TRUE))
if (!length(grep('ImageMagick', version))) {
message('I also cannot find ImageMagick with convert = ', shQuote(convert_switch))
if (.Platform$OS.type != 'windows' || is.null(convert <- find_magic())) {
warning('Please install ImageMagick first or put its bin path into the system PATH variable')
return()
}
}else{
message('I find ImageMagick with convert = ', shQuote(convert_switch),". I will use " ,
shQuote(convert_switch)," instead of ", shQuote(convert),"!")
convert = convert_switch
}
}
} else {
## GraphicsMagick
version = ''
if (!is.null(ani.options('convert')))
try(version <- cmd.fun(sprintf('%s -version', shQuote(ani.options('convert'))), intern = TRUE))
if (!length(grep('GraphicsMagick', version))) {
try(version <- cmd.fun(sprintf('%s -version', convert), intern = TRUE))
if (!length(grep('GraphicsMagick', version))) {
warning('I cannot find GraphicsMagick with convert = ', shQuote(convert),
'; you may have to put the path of GraphicsMagick in the PATH variable.')
return()
}
} else convert = ani.options('convert')
}
convert = sprintf(
'%s -loop %s %s %s %s', convert, loop,
extra.opts, paste(
'-delay', interval * 100,
if (length(interval) == 1) paste(files, collapse = ' ') else files,
collapse = ' '),
shQuote(movie.name)
)
# there might be an error "the input line is too long", and we need to quote
# the command; see http://stackoverflow.com/q/682799/559676
if (.Platform$OS.type == 'windows') convert = sprintf('"%s"', convert)
message('Executing: ', strwrap(convert, exdent = 4, prefix = '\n'))
if (interactive()) flush.console()
cmd = cmd.fun(convert)
## if fails on Windows using shell(), try system() instead of shell()
if (cmd != 0 && .Platform$OS.type == 'windows' && identical(cmd.fun, shell)) {
cmd = system(convert)
}
}
if (cmd == 0) {
message('Output at: ', output)
if (clean) unlink(files)
if (file.exists(output)) auto_browse(output)
} else message('an error occurred in the conversion... see Notes in ?im.convert')
invisible(convert)
}
#' @details The function \code{gm.convert} is a wrapper for the command
#' \command{gm convert} of GraphicsMagick.
#' @rdname convert
#' @param ... arguments to be passed to \code{\link{im.convert}}
#' @export
gm.convert = function(..., convert = 'gm convert') {
im.convert(..., convert = convert)
}
magick.convert = function(files, output, interval = 1, loop = 0, dispose = NULL){
if (!length(dispose)) dispose = "background"
dispose = tolower(dispose)
img = magick::image_read(files, strip = TRUE)
anim = magick::image_animate(img, loop = loop, fps = 100 / as.integer(interval * 100), dispose = dispose)
magick::image_write(anim, path = output)
}
|