R/im.convert.R

Defines functions magick.convert gm.convert im.convert

Documented in gm.convert im.convert

#' 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)
}
yihui/animation documentation built on March 27, 2023, 2:50 p.m.