|
| 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 | +} |
0 commit comments