Skip to content

Commit

Permalink
First pass at streaming encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroen committed Sep 28, 2018
1 parent 3aa4e56 commit 90ab23b
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 16 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ BugReports: https://github.com/r-rust/gifski/issues
SystemRequirements: Cargo (rustc package manager)
Encoding: UTF-8
LazyData: true
RoxygenNote: 6.0.1
RoxygenNote: 6.1.0
Suggests:
ggplot2,
gapminder
Expand Down
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Generated by roxygen2: do not edit by hand

export(gifski)
export(gifski_encoder_init)
export(save_gif)
useDynLib(gifski,R_gifski_encoder_add_png)
useDynLib(gifski,R_gifski_encoder_finalize)
useDynLib(gifski,R_gifski_encoder_new)
useDynLib(gifski,R_png_to_gif)
1 change: 1 addition & 0 deletions R/gifski.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#'
#' @export
#' @rdname gifski
#' @family gifski
#' @useDynLib gifski R_png_to_gif
#' @param png_files vector of png files
#' @param gif_file output gif file
Expand Down
41 changes: 41 additions & 0 deletions R/stream.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#' Streaming Gifski Encoder
#'
#' Starts a stateful gif encoder and returns a closure to add png frames
#' to the gif. Upon completion, the caller has to invoke the closure one
#' more time with `png_file = NULL` to finalize the output.
#'
#' @export
#' @inheritParams gifski
#' @family gifski
#' @name streaming
#' @useDynLib gifski R_gifski_encoder_new
gifski_encoder_init <- function(gif_file = "animation.gif", width = 800, height = 600, loop = TRUE, delay = 1){
gif_file <- normalizePath(gif_file, mustWork = FALSE)
if(!file.exists(dirname(gif_file)))
stop("Target directory does not exist:", dirname(gif_file))
width <- as.integer(width)
height <- as.integer(height)
delay <- as.integer(delay * 100)
loop <- as.logical(loop)
ptr <- .Call(R_gifski_encoder_new, enc2utf8(gif_file), width, height, loop)
function(png_file){
if(is.character(png_file)){
gifski_add_png(ptr, png_file, delay)
} else if(is.null(png_file)){
gifski_finalize(ptr)
} else {
stop("Invalid input: png_file must be a file path or NULL")
}
}
}

#' @useDynLib gifski R_gifski_encoder_add_png
gifski_add_png <- function(ptr, png_file, delay){
png_file <- normalizePath(png_file, mustWork = TRUE)
.Call(R_gifski_encoder_add_png, ptr, enc2utf8(png_file), delay)
}

#' @useDynLib gifski R_gifski_encoder_finalize
gifski_finalize <- function(ptr){
.Call(R_gifski_encoder_finalize, ptr)
}
8 changes: 6 additions & 2 deletions man/gifski.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions man/streaming.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions src/init.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#define R_NO_REMAP
#define STRICT_R_HEADERS
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <R_ext/Visibility.h>

extern SEXP R_gifski_encoder_add_png(SEXP, SEXP);
extern SEXP R_gifski_encoder_finalize(SEXP);
extern SEXP R_gifski_encoder_new(SEXP, SEXP, SEXP, SEXP);
extern SEXP R_png_to_gif(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);

static const R_CallMethodDef CallEntries[] = {
{"R_gifski_encoder_add_png", (DL_FUNC) &R_gifski_encoder_add_png, 2},
{"R_gifski_encoder_finalize", (DL_FUNC) &R_gifski_encoder_finalize, 1},
{"R_gifski_encoder_new", (DL_FUNC) &R_gifski_encoder_new, 4},
{"R_png_to_gif", (DL_FUNC) &R_png_to_gif, 7},
{NULL, NULL, 0}
};

attribute_visible void R_init_gifski(DllInfo *dll) {
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}
89 changes: 89 additions & 0 deletions src/streamer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#define R_NO_REMAP
#define STRICT_R_HEADERS
#include <Rinternals.h>

// Import C headers for rust API
#include <pthread.h>
#include <string.h>
#include "myrustlib/gifski.h"

/* data to pass to encoder thread */
typedef struct {
int i;
char *path;
GifskiError err;
gifski *g;
GifskiSettings settings;
pthread_t encoder_thread;
} gifski_ptr_info;

/* gifski_write() blocks until main thread calls gifski_end_adding_frames() */
static void * gifski_encoder_thread(void * data){
gifski_ptr_info * info = data;
info->err = gifski_write(info->g, info->path);
return NULL;
}

static void fin_gifski_encoder(SEXP ptr){
gifski_ptr_info *info = (gifski_ptr_info*) R_ExternalPtrAddr(ptr);
if(info == NULL)
return;
R_SetExternalPtrAddr(ptr, NULL);
if(info->encoder_thread)
pthread_cancel(info->encoder_thread);
gifski_drop(info->g);
free(info->path);
free(info);
}

static gifski_ptr_info *get_info(SEXP ptr){
if(TYPEOF(ptr) != EXTPTRSXP || !Rf_inherits(ptr, "gifski_encoder"))
Rf_error("pointer is not a gifski_encoder()");
if(!R_ExternalPtrAddr(ptr))
Rf_error("pointer is dead");
return R_ExternalPtrAddr(ptr);
}

SEXP R_gifski_encoder_new(SEXP gif_file, SEXP width, SEXP height, SEXP loop){
gifski_ptr_info *info = malloc(sizeof(gifski_ptr_info));
info->path = strdup(CHAR(STRING_ELT(gif_file, 0)));
info->settings.height = Rf_asInteger(height);
info->settings.width = Rf_asInteger(width);
info->settings.quality = 100;
info->settings.fast = false;
info->settings.once = !Rf_asLogical(loop);
info->err = GIFSKI_OK;
info->i = 0;
info->g = gifski_new(&info->settings);
if(pthread_create(&info->encoder_thread, NULL, gifski_encoder_thread, info))
Rf_error("Failed to create encoder thread");
SEXP ptr = R_MakeExternalPtr(info, R_NilValue, R_NilValue);
Rf_setAttrib(ptr, R_ClassSymbol, Rf_mkString("gifski_encoder"));
R_RegisterCFinalizerEx(ptr, fin_gifski_encoder, TRUE);
return ptr;
}

SEXP R_gifski_encoder_add_png(SEXP ptr, SEXP png_file, SEXP delay){
gifski_ptr_info *info = get_info(ptr);
if(info->err != GIFSKI_OK)
Rf_error("Gifski encoder is in bad state");
const char *path = CHAR(STRING_ELT(png_file, 0));
int d = Rf_asInteger(delay);
if(gifski_add_frame_png_file(info->g, info->i, path, d) != GIFSKI_OK)
Rf_error("Failed to add frame %d (%s)", info->i, path);
info->i++;
return Rf_ScalarInteger(info->i);
}

SEXP R_gifski_encoder_finalize(SEXP ptr){
gifski_ptr_info *info = get_info(ptr);
if(info->err != GIFSKI_OK)
Rf_error("Gifski encoder is in bad state");
if(gifski_end_adding_frames(info->g) != GIFSKI_OK)
Rf_error("Failed to finalizer encoder");
pthread_join(info->encoder_thread, NULL);
info->encoder_thread = NULL;
SEXP path = Rf_mkString(info->path);
fin_gifski_encoder(ptr);
return path;
}
13 changes: 0 additions & 13 deletions src/wrapper.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#define R_NO_REMAP
#define STRICT_R_HEADERS
#include <Rinternals.h>
#include <R_ext/Rdynload.h>
#include <R_ext/Visibility.h>

// Import C headers for rust API
#include <pthread.h>
Expand Down Expand Up @@ -69,14 +67,3 @@ SEXP R_png_to_gif(SEXP png_files, SEXP gif_file, SEXP width, SEXP height, SEXP d
Rf_error("Failure writing image %s", info.path);
return gif_file;
}

// Standard R package stuff
static const R_CallMethodDef CallEntries[] = {
{"R_png_to_gif", (DL_FUNC) &R_png_to_gif, 7},
{NULL, NULL, 0}
};

attribute_visible void R_init_gifski(DllInfo *dll) {
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
}

0 comments on commit 90ab23b

Please sign in to comment.