楚新元 | All in R

Welcome to R Square

用 R 生成字符动画

楚新元 / 2021-08-18


加载相关 R 包

library(magick)
library(purrr)
library(gganimate)

获取动画

img = image_read("./data/ball.gif")
img

动画简单处理

倒放动画

img_rev = rev(img)
img_rev

局部放慢

img_slow = img[c(1:30, rep(31:44, each = 3))]
img_slow

从局部放慢动画里提取每帧图片

if (!dir.exists("result1")) dir.create("result1")
path1 = "./result1"
1:length(img_slow) %>% 
  map(
    \(x) image_write(
    img_slow[x],
    format = "png",
    paste0(path1, "/", x, ".png")
  )
)

每帧图片转为字符画

注:这个函数参考辉小宝同学知乎文章,详见:https://zhuanlan.zhihu.com/p/51153590

# 编写图片转为字符画的函数
convert_chars = \(
  input, outdir = NULL, quality = 80, 
  width = 100, is.color = FALSE, 
  chars = c('&', '#', 'i', '.', '*')
) {
  
    # input: 原始图片的路径,支持 magick 包支持的各种格式。
    # outdir:默认输出路径和输入图片路径相同,可自定义。
    # quality: 字符图片的质量,范围 0-100。
    # width: 字符文本的宽度,默认为 100,即一行 100 个字符。
    # is.color: 字符图片是否为彩色,默认为黑白字符图片。
    # chars: 字符集,可自定义。

    # 读入图片
    img = image_read(input)  
    gray = image_convert(img, colorspace = 'gray')  # 转为灰度图
    rgb = image_convert(img, colorspace = 'rgb')  # 转为 rgb 图

    # 修改图片尺寸
    gray = image_resize(gray, paste0(width, 'x'))
    rgb = image_resize(rgb, paste0(width, 'x'))

    # 获取图片灰度值矩阵,并将各像素值对应于相应字符
    gray = as.integer(image_data(gray))[, , 1]
    w = ncol(gray)   # 图片宽度
    h = nrow(gray)  # 图片高度
    index = findInterval(
      c(gray),
      seq(0, 255, length.out = length(chars) + 1),
      rightmost.closed = T
    )
    labels = chars[index]
    labels_mat = matrix(labels, ncol = w)

    # 确定 txt 文件输出路径
    input_ext = tools::file_ext(input)
    if (is.null(outdir)) {
      dest_txt = gsub(input_ext, "txt", input)
    } else {
      dest_txt = file.path(
        outdir, 
        paste0(gsub(".*/(.*?)\\..*", "\\1", input), ".txt")
      )
    }
    
    # 输出字符到 txt 文件内并保存到指定路径下
    write.table(
      labels_mat, dest_txt, quote = FALSE, 
      row.names = FALSE, col.names = FALSE
    )
   
    # 绘制字符图片,给相应字符着色,并保存成文件
    if (is.color) {
      rgb = as.integer(image_data(rgb))
      r = rgb[, , 1]  # red通道像素矩阵
      g = rgb[, , 2]  # green通道像素矩阵
      b = rgb[, , 3]  # blue通道像素矩阵
      cols = rgb(c(r), c(g), c(b), maxColorValue = 255) # 转化为颜色表示
    }
  
    if (is.null(outdir)) {
      dest_img = gsub(input_ext, "jpg", input)
    } else {
      dest_img = file.path(
        outdir, 
        paste0(gsub(".*/(.*?)\\..*", "\\1", input), ".jpg")
      )      
    }
    
    # 生成 .jpg 格式图片并保存到指定路径下
    jpeg(dest_img, width = 16 * w, height = 16 * h, quality = quality)
    op = par(mar = c(0, 0, 0, 0))
    plot(
      0, xlab = '', ylab = '', asp = 1,
      xlim = c(0, w), ylim = c(0, h),
      xaxs = "i", yaxs = "i",
      type = 'n', axes = FALSE
    )

    grid = expand.grid(y = h:1 - 0.5, x = 1:w - 0.5)  # 各字符位置
    if (is.color) {
      text(grid$x, grid$y, labels, cex = 1.5, col = cols)  # 绘制彩色字符
    } else {
      text(grid$x, grid$y, labels, cex = 1.5) # 绘制黑白字符
    }
  
    par(op)
    dev.off()
}

利用自定义函数实现图片转字符图片

if (!dir.exists("result2")) dir.create("result2")
path2 = "./result2"
path1 %>% 
  list.files(full.names = TRUE) %>% 
  walk(
    \(x) convert_chars(
      input = x, 
      outdir = path2, 
      width = 72, 
      is.color = TRUE, 
      chars = c("0", "1")
    )
  )

将字符图片转化为动画

# 生成动画
path2 %>% 
  list.files(
    pattern = "\\.jpg$", 
    full.names = TRUE
  ) %>% 
  image_read() %>% 
  image_animate() -> anim_obj
anim_obj

# 保存动画
anim_save(
  animation = anim_obj, 
  filename = "ball_chars.gif"
)