Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First pass at streaming encoder #7

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: gifski
Type: Package
Title: Highest Quality GIF Encoder
Version: 0.8.6
Version: 0.8.6.1
Authors@R: c(
person("Jeroen", "Ooms", ,"[email protected]", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-4035-0289")),
Expand All @@ -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)
}
7 changes: 7 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ if [ $? -eq 0 ]; then
VERSION=$($CARGO --version)
echo "Using $CARGO ($VERSION)"
sed -e "s|@cargobin@|$CARGO|" src/Makevars.in > src/Makevars

# CRAN forbids using $HOME during CMD check; try to override CARGO_HOME
if [ "$CARGO" == "/usr/bin/cargo" ] && [ "$_R_CHECK_SIZE_OF_TARBALL_" ]; then
if [ -z "CARGO_HOME" ] && [ ! -e "$HOME/.cargo" ]; then
sed -i.bak "s|#export|export|" src/Makevars
fi
fi
exit 0
fi

Expand Down
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.

5 changes: 4 additions & 1 deletion src/Makevars.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ PKG_CFLAGS = -pthread $(C_VISIBILITY)
PKG_LIBS = -L$(LIBDIR) -lmyrustlib -lresolv -pthread
CARGO = @cargobin@

#all: clean
# CRAN policy forbids using $HOME
#export CARGO_HOME=$TMPDIR/.cargo

all: clean

$(SHLIB): $(STATLIB)

Expand Down
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);
}