Skip to content

Commit 90ab23b

Browse files
committed
First pass at streaming encoder
1 parent 3aa4e56 commit 90ab23b

File tree

9 files changed

+195
-16
lines changed

9 files changed

+195
-16
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ BugReports: https://github.com/r-rust/gifski/issues
1515
SystemRequirements: Cargo (rustc package manager)
1616
Encoding: UTF-8
1717
LazyData: true
18-
RoxygenNote: 6.0.1
18+
RoxygenNote: 6.1.0
1919
Suggests:
2020
ggplot2,
2121
gapminder

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Generated by roxygen2: do not edit by hand
22

33
export(gifski)
4+
export(gifski_encoder_init)
45
export(save_gif)
6+
useDynLib(gifski,R_gifski_encoder_add_png)
7+
useDynLib(gifski,R_gifski_encoder_finalize)
8+
useDynLib(gifski,R_gifski_encoder_new)
59
useDynLib(gifski,R_png_to_gif)

R/gifski.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#'
66
#' @export
77
#' @rdname gifski
8+
#' @family gifski
89
#' @useDynLib gifski R_png_to_gif
910
#' @param png_files vector of png files
1011
#' @param gif_file output gif file

R/stream.R

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#' Streaming Gifski Encoder
2+
#'
3+
#' Starts a stateful gif encoder and returns a closure to add png frames
4+
#' to the gif. Upon completion, the caller has to invoke the closure one
5+
#' more time with `png_file = NULL` to finalize the output.
6+
#'
7+
#' @export
8+
#' @inheritParams gifski
9+
#' @family gifski
10+
#' @name streaming
11+
#' @useDynLib gifski R_gifski_encoder_new
12+
gifski_encoder_init <- function(gif_file = "animation.gif", width = 800, height = 600, loop = TRUE, delay = 1){
13+
gif_file <- normalizePath(gif_file, mustWork = FALSE)
14+
if(!file.exists(dirname(gif_file)))
15+
stop("Target directory does not exist:", dirname(gif_file))
16+
width <- as.integer(width)
17+
height <- as.integer(height)
18+
delay <- as.integer(delay * 100)
19+
loop <- as.logical(loop)
20+
ptr <- .Call(R_gifski_encoder_new, enc2utf8(gif_file), width, height, loop)
21+
function(png_file){
22+
if(is.character(png_file)){
23+
gifski_add_png(ptr, png_file, delay)
24+
} else if(is.null(png_file)){
25+
gifski_finalize(ptr)
26+
} else {
27+
stop("Invalid input: png_file must be a file path or NULL")
28+
}
29+
}
30+
}
31+
32+
#' @useDynLib gifski R_gifski_encoder_add_png
33+
gifski_add_png <- function(ptr, png_file, delay){
34+
png_file <- normalizePath(png_file, mustWork = TRUE)
35+
.Call(R_gifski_encoder_add_png, ptr, enc2utf8(png_file), delay)
36+
}
37+
38+
#' @useDynLib gifski R_gifski_encoder_finalize
39+
gifski_finalize <- function(ptr){
40+
.Call(R_gifski_encoder_finalize, ptr)
41+
}

man/gifski.Rd

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/streaming.Rd

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/init.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#define R_NO_REMAP
2+
#define STRICT_R_HEADERS
3+
#include <Rinternals.h>
4+
#include <R_ext/Rdynload.h>
5+
#include <R_ext/Visibility.h>
6+
7+
extern SEXP R_gifski_encoder_add_png(SEXP, SEXP);
8+
extern SEXP R_gifski_encoder_finalize(SEXP);
9+
extern SEXP R_gifski_encoder_new(SEXP, SEXP, SEXP, SEXP);
10+
extern SEXP R_png_to_gif(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
11+
12+
static const R_CallMethodDef CallEntries[] = {
13+
{"R_gifski_encoder_add_png", (DL_FUNC) &R_gifski_encoder_add_png, 2},
14+
{"R_gifski_encoder_finalize", (DL_FUNC) &R_gifski_encoder_finalize, 1},
15+
{"R_gifski_encoder_new", (DL_FUNC) &R_gifski_encoder_new, 4},
16+
{"R_png_to_gif", (DL_FUNC) &R_png_to_gif, 7},
17+
{NULL, NULL, 0}
18+
};
19+
20+
attribute_visible void R_init_gifski(DllInfo *dll) {
21+
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
22+
R_useDynamicSymbols(dll, FALSE);
23+
}

src/streamer.c

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#define R_NO_REMAP
2+
#define STRICT_R_HEADERS
3+
#include <Rinternals.h>
4+
5+
// Import C headers for rust API
6+
#include <pthread.h>
7+
#include <string.h>
8+
#include "myrustlib/gifski.h"
9+
10+
/* data to pass to encoder thread */
11+
typedef struct {
12+
int i;
13+
char *path;
14+
GifskiError err;
15+
gifski *g;
16+
GifskiSettings settings;
17+
pthread_t encoder_thread;
18+
} gifski_ptr_info;
19+
20+
/* gifski_write() blocks until main thread calls gifski_end_adding_frames() */
21+
static void * gifski_encoder_thread(void * data){
22+
gifski_ptr_info * info = data;
23+
info->err = gifski_write(info->g, info->path);
24+
return NULL;
25+
}
26+
27+
static void fin_gifski_encoder(SEXP ptr){
28+
gifski_ptr_info *info = (gifski_ptr_info*) R_ExternalPtrAddr(ptr);
29+
if(info == NULL)
30+
return;
31+
R_SetExternalPtrAddr(ptr, NULL);
32+
if(info->encoder_thread)
33+
pthread_cancel(info->encoder_thread);
34+
gifski_drop(info->g);
35+
free(info->path);
36+
free(info);
37+
}
38+
39+
static gifski_ptr_info *get_info(SEXP ptr){
40+
if(TYPEOF(ptr) != EXTPTRSXP || !Rf_inherits(ptr, "gifski_encoder"))
41+
Rf_error("pointer is not a gifski_encoder()");
42+
if(!R_ExternalPtrAddr(ptr))
43+
Rf_error("pointer is dead");
44+
return R_ExternalPtrAddr(ptr);
45+
}
46+
47+
SEXP R_gifski_encoder_new(SEXP gif_file, SEXP width, SEXP height, SEXP loop){
48+
gifski_ptr_info *info = malloc(sizeof(gifski_ptr_info));
49+
info->path = strdup(CHAR(STRING_ELT(gif_file, 0)));
50+
info->settings.height = Rf_asInteger(height);
51+
info->settings.width = Rf_asInteger(width);
52+
info->settings.quality = 100;
53+
info->settings.fast = false;
54+
info->settings.once = !Rf_asLogical(loop);
55+
info->err = GIFSKI_OK;
56+
info->i = 0;
57+
info->g = gifski_new(&info->settings);
58+
if(pthread_create(&info->encoder_thread, NULL, gifski_encoder_thread, info))
59+
Rf_error("Failed to create encoder thread");
60+
SEXP ptr = R_MakeExternalPtr(info, R_NilValue, R_NilValue);
61+
Rf_setAttrib(ptr, R_ClassSymbol, Rf_mkString("gifski_encoder"));
62+
R_RegisterCFinalizerEx(ptr, fin_gifski_encoder, TRUE);
63+
return ptr;
64+
}
65+
66+
SEXP R_gifski_encoder_add_png(SEXP ptr, SEXP png_file, SEXP delay){
67+
gifski_ptr_info *info = get_info(ptr);
68+
if(info->err != GIFSKI_OK)
69+
Rf_error("Gifski encoder is in bad state");
70+
const char *path = CHAR(STRING_ELT(png_file, 0));
71+
int d = Rf_asInteger(delay);
72+
if(gifski_add_frame_png_file(info->g, info->i, path, d) != GIFSKI_OK)
73+
Rf_error("Failed to add frame %d (%s)", info->i, path);
74+
info->i++;
75+
return Rf_ScalarInteger(info->i);
76+
}
77+
78+
SEXP R_gifski_encoder_finalize(SEXP ptr){
79+
gifski_ptr_info *info = get_info(ptr);
80+
if(info->err != GIFSKI_OK)
81+
Rf_error("Gifski encoder is in bad state");
82+
if(gifski_end_adding_frames(info->g) != GIFSKI_OK)
83+
Rf_error("Failed to finalizer encoder");
84+
pthread_join(info->encoder_thread, NULL);
85+
info->encoder_thread = NULL;
86+
SEXP path = Rf_mkString(info->path);
87+
fin_gifski_encoder(ptr);
88+
return path;
89+
}

src/wrapper.c

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#define R_NO_REMAP
22
#define STRICT_R_HEADERS
33
#include <Rinternals.h>
4-
#include <R_ext/Rdynload.h>
5-
#include <R_ext/Visibility.h>
64

75
// Import C headers for rust API
86
#include <pthread.h>
@@ -69,14 +67,3 @@ SEXP R_png_to_gif(SEXP png_files, SEXP gif_file, SEXP width, SEXP height, SEXP d
6967
Rf_error("Failure writing image %s", info.path);
7068
return gif_file;
7169
}
72-
73-
// Standard R package stuff
74-
static const R_CallMethodDef CallEntries[] = {
75-
{"R_png_to_gif", (DL_FUNC) &R_png_to_gif, 7},
76-
{NULL, NULL, 0}
77-
};
78-
79-
attribute_visible void R_init_gifski(DllInfo *dll) {
80-
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
81-
R_useDynamicSymbols(dll, FALSE);
82-
}

0 commit comments

Comments
 (0)