Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Animated Gifs are now async via AsyncAnimatedGif class

  • Loading branch information...
commit bceadfbc4ed40b528ed8d4ec3452efd135d2e99a 1 parent c466fca
Peteris Krumins authored
544 src/async_animated_gif.cpp
View
@@ -0,0 +1,544 @@
+#include <cerrno>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+
+#include "common.h"
+#include "gif_encoder.h"
+#include "async_animated_gif.h"
+
+#include "loki/ScopeGuard.h"
+
+using namespace v8;
+using namespace node;
+
+void
+AsyncAnimatedGif::Initialize(Handle<Object> target)
+{
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ NODE_SET_PROTOTYPE_METHOD(t, "push", Push);
+ NODE_SET_PROTOTYPE_METHOD(t, "endPush", EndPush);
+ NODE_SET_PROTOTYPE_METHOD(t, "encode", Encode);
+ NODE_SET_PROTOTYPE_METHOD(t, "setOutputFile", SetOutputFile);
+ NODE_SET_PROTOTYPE_METHOD(t, "setTmpDir", SetTmpDir);
+ target->Set(String::NewSymbol("AsyncAnimatedGif"), t->GetFunction());
+}
+
+AsyncAnimatedGif::AsyncAnimatedGif(int wwidth, int hheight, buffer_type bbuf_type) :
+ width(wwidth), height(hheight), buf_type(bbuf_type),
+ transparency_color(0xFF, 0xFF, 0xFE),
+ push_id(0), fragment_id(0) {}
+
+int
+file_size(const char *path)
+{
+ struct stat moo;
+ if (stat(path, &moo) == -1) return -1;
+ return moo.st_size;
+}
+
+bool
+is_dir(const char *path)
+{
+ struct stat moo;
+ if (stat(path, &moo) == -1) return false;
+ return S_ISDIR(moo.st_mode);
+}
+
+int
+AsyncAnimatedGif::EIO_Push(eio_req *req)
+{
+ push_request *push_req = (push_request *)req->data;
+
+ if (!is_dir(push_req->tmp_dir)) {
+ if (mkdir(push_req->tmp_dir, 0775) == -1) {
+ // there is no way to return this error to node as this call was
+ // async with no callback
+ fprintf(stderr, "Could not mkdir(%s).\n", push_req->tmp_dir);
+ return 0;
+ }
+ }
+
+ char fragment_dir[512];
+ snprintf(fragment_dir, 512, "%s/%d", push_req->tmp_dir, push_req->push_id);
+
+ if (!is_dir(fragment_dir)) {
+ if (mkdir(fragment_dir, 0775) == -1) {
+ fprintf(stderr, "Could not mkdir(%s).\n", fragment_dir);
+ return 0;
+ }
+ }
+
+ char filename[512];
+ snprintf(filename, 512, "%s/%d/rect-%d-%d-%d-%d-%d.dat",
+ push_req->tmp_dir, push_req->push_id, push_req->fragment_id,
+ push_req->x, push_req->y, push_req->w, push_req->h);
+ FILE *out = fopen(filename, "w+");
+ if (!out) {
+ fprintf(stderr, "Failed to open %s in AsyncAnimatedGif::EIO_Push.\n",
+ filename); // can't get errno either cause this stuff is async
+ return 0;
+ }
+ int written = fwrite(push_req->data, sizeof(unsigned char), push_req->data_size, out);
+ if (written != push_req->data_size) {
+ fprintf(stderr, "Failed to write all data to %s. Wrote only %d of %d.\n",
+ filename, written, push_req->data_size);
+ }
+ fclose(out);
+}
+
+int
+AsyncAnimatedGif::EIO_PushAfter(eio_req *req)
+{
+ ev_unref(EV_DEFAULT_UC);
+
+ push_request *push_req = (push_request *)req->data;
+ free(push_req->data);
+ free(push_req);
+
+ return 0;
+}
+
+Handle<Value>
+AsyncAnimatedGif::Push(unsigned char *data_buf, int x, int y, int w, int h)
+{
+ HandleScope scope;
+
+ if (tmp_dir.empty())
+ throw "Tmp dir is not set. Use .setTmpDir to set it before pushing.";
+
+ push_request *push_req = (push_request *)malloc(sizeof(*push_req));
+ if (!push_req)
+ throw "malloc in AsyncAnimatedGif::Push failed.";
+ push_req->data = (unsigned char *)malloc(sizeof(*push_req->data)*w*h*3);
+ if (!push_req->data) {
+ free(push_req);
+ throw "malloc in AsyncAnimatedGif::Push failed.";
+ }
+ memcpy(push_req->data, data_buf, w*h*3);
+ push_req->push_id = push_id;
+ push_req->fragment_id = fragment_id++;
+ push_req->tmp_dir = tmp_dir.c_str();
+ push_req->data_size = w*h*3;
+ push_req->x = x;
+ push_req->y = y;
+ push_req->w = w;
+ push_req->h = h;
+
+ eio_custom(EIO_Push, EIO_PRI_DEFAULT, EIO_PushAfter, push_req);
+ ev_ref(EV_DEFAULT_UC);
+
+ return Undefined();
+}
+
+void
+AsyncAnimatedGif::EndPush()
+{
+ push_id++;
+ fragment_id = 0;
+}
+
+Handle<Value>
+AsyncAnimatedGif::New(const Arguments &args)
+{
+ HandleScope scope;
+
+ if (args.Length() < 2)
+ return VException("At least two arguments required - width, height, [and input buffer type]");
+ if (!args[0]->IsInt32())
+ return VException("First argument must be integer width.");
+ if (!args[1]->IsInt32())
+ return VException("Second argument must be integer height.");
+
+ buffer_type buf_type = BUF_RGB;
+ if (args.Length() == 3) {
+ if (!args[2]->IsString())
+ return VException("Third argument must be 'rgb', 'bgr', 'rgba' or 'bgra'.");
+
+ String::AsciiValue bts(args[2]->ToString());
+ if (!(str_eq(*bts, "rgb") || str_eq(*bts, "bgr") ||
+ str_eq(*bts, "rgba") || str_eq(*bts, "bgra")))
+ {
+ return VException("Third argument must be 'rgb', 'bgr', 'rgba' or 'bgra'.");
+ }
+
+ if (str_eq(*bts, "rgb"))
+ buf_type = BUF_RGB;
+ else if (str_eq(*bts, "bgr"))
+ buf_type = BUF_BGR;
+ else if (str_eq(*bts, "rgba"))
+ buf_type = BUF_RGBA;
+ else if (str_eq(*bts, "bgra"))
+ buf_type = BUF_BGRA;
+ else
+ return VException("Third argument wasn't 'rgb', 'bgr', 'rgba' or 'bgra'.");
+ }
+
+ int w = args[0]->Int32Value();
+ int h = args[1]->Int32Value();
+
+ if (w < 0)
+ return VException("Width smaller than 0.");
+ if (h < 0)
+ return VException("Height smaller than 0.");
+
+ AsyncAnimatedGif *gif = new AsyncAnimatedGif(w, h, buf_type);
+ gif->Wrap(args.This());
+ return args.This();
+}
+
+Handle<Value>
+AsyncAnimatedGif::Push(const Arguments &args)
+{
+ HandleScope scope;
+
+ if (!Buffer::HasInstance(args[0]))
+ return VException("First argument must be Buffer.");
+ if (!args[1]->IsInt32())
+ return VException("Second argument must be integer x.");
+ if (!args[2]->IsInt32())
+ return VException("Third argument must be integer y.");
+ if (!args[3]->IsInt32())
+ return VException("Fourth argument must be integer w.");
+ if (!args[4]->IsInt32())
+ return VException("Fifth argument must be integer h.");
+
+ AsyncAnimatedGif *gif = ObjectWrap::Unwrap<AsyncAnimatedGif>(args.This());
+ Buffer *data_buf = ObjectWrap::Unwrap<Buffer>(args[0]->ToObject());
+ unsigned char *buf = (unsigned char *)data_buf->data();
+ int x = args[1]->Int32Value();
+ int y = args[2]->Int32Value();
+ int w = args[3]->Int32Value();
+ int h = args[4]->Int32Value();
+
+ if (x < 0)
+ return VException("Coordinate x smaller than 0.");
+ if (y < 0)
+ return VException("Coordinate y smaller than 0.");
+ if (w < 0)
+ return VException("Width smaller than 0.");
+ if (h < 0)
+ return VException("Height smaller than 0.");
+ if (x >= gif->width)
+ return VException("Coordinate x exceeds AsyncAnimatedGif's dimensions.");
+ if (y >= gif->height)
+ return VException("Coordinate y exceeds AsyncAnimatedGif's dimensions.");
+ if (x+w > gif->width)
+ return VException("Pushed fragment exceeds AsyncAnimatedGif's width.");
+ if (y+h > gif->height)
+ return VException("Pushed fragment exceeds AsyncAnimatedGif's height.");
+
+ try {
+ gif->Push(buf, x, y, w, h);
+ }
+ catch (const char *err) {
+ return VException(err);
+ }
+
+ return Undefined();
+}
+
+Handle<Value>
+AsyncAnimatedGif::EndPush(const Arguments &args)
+{
+ HandleScope scope;
+
+ AsyncAnimatedGif *gif = ObjectWrap::Unwrap<AsyncAnimatedGif>(args.This());
+ gif->EndPush();
+
+ return Undefined();
+}
+
+char **
+find_files(const char *path)
+{
+ char **files;
+ DIR *dp;
+ struct dirent *dirp;
+
+ if ((dp = opendir(path)) == NULL) {
+ return NULL;
+ }
+ int nfiles = 0;
+ while ((dirp = readdir(dp)) != NULL) {
+ if (str_eq(dirp->d_name, ".") || str_eq(dirp->d_name, ".."))
+ continue;
+ nfiles++;
+ }
+ files = (char **)malloc(sizeof(char *) * (nfiles + 1));
+ files[nfiles] = NULL;
+ int i = 0;
+ rewinddir(dp);
+ while ((dirp = readdir(dp)) != NULL) {
+ if (str_eq(dirp->d_name, ".") || str_eq(dirp->d_name, ".."))
+ continue;
+ int dir_name_len = strlen(dirp->d_name);
+ files[i] = (char *)malloc(sizeof(char) * (dir_name_len + 1));
+ strcpy(files[i], dirp->d_name);
+ i++;
+ }
+ closedir(dp);
+ return files;
+}
+
+void
+free_file_list(char **file_list)
+{
+ char **p = file_list;
+ while (*p)
+ free(*p++);
+ free(file_list);
+}
+
+int
+file_list_length(char **file_list)
+{
+ char **p = file_list;
+ int i = 0;
+ while (*p++)
+ i++;
+ return i;
+}
+
+int
+fragment_sort(const void *aa, const void *bb)
+{
+ const char *a = *(const char **)aa;
+ const char *b = *(const char **)bb;
+ int na, nb;
+ sscanf(a, "rect-%d", &na);
+ sscanf(b, "rect-%d", &nb);
+ return na > nb;
+}
+
+unsigned char *
+AsyncAnimatedGif::init_frame(int width, int height, Color &transparency_color)
+{
+ unsigned char *frame = (unsigned char *)malloc(sizeof(*frame)*width*height*3);
+ if (!frame) return NULL;
+
+ unsigned char *framgep = frame;
+ for (int i = 0; i < width*height; i++) {
+ *framgep++ = transparency_color.r;
+ *framgep++ = transparency_color.g;
+ *framgep++ = transparency_color.b;
+ }
+
+ return frame;
+}
+
+void
+AsyncAnimatedGif::push_fragment(unsigned char *frame, int width, int height,
+ buffer_type buf_type, unsigned char *fragment, int x, int y, int w, int h)
+{
+ int start = y*width*3 + x*3;
+ unsigned char *fragmentp = fragment;
+
+ switch (buf_type) {
+ case BUF_RGB:
+ for (int i = 0; i < h; i++) {
+ unsigned char *framep = &frame[start + i*width*3];
+ for (int j = 0; j < w; j++) {
+ *framep++ = *fragment++;
+ *framep++ = *fragment++;
+ *framep++ = *fragment++;
+ }
+ }
+ break;
+ case BUF_BGR:
+ for (int i = 0; i < h; i++) {
+ unsigned char *framep = &frame[start + i*width*3];
+ for (int j = 0; j < w; j++) {
+ *framep++ = *(fragment + 2);
+ *framep++ = *(fragment + 1);
+ *framep++ = *fragment;
+ framep += 3;
+ }
+ }
+ break;
+ }
+}
+
+Rect
+AsyncAnimatedGif::rect_dims(const char *fragment_name)
+{
+ int moo, x, y, w, h;
+ sscanf(fragment_name, "rect-%d-%d-%d-%d-%d", &moo, &x, &y, &w, &h);
+ return Rect(x, y, w, h);
+}
+
+int
+AsyncAnimatedGif::EIO_Encode(eio_req *req)
+{
+ async_encode_request *enc_req = (async_encode_request *)req->data;
+ AsyncAnimatedGif *gif = (AsyncAnimatedGif *)enc_req->gif_obj;
+
+ AnimatedGifEncoder encoder(gif->width, gif->height, BUF_RGB);
+ encoder.set_output_file(gif->output_file.c_str());
+ encoder.set_transparency_color(gif->transparency_color);
+
+ for (size_t push_id = 0; push_id < gif->push_id; push_id++) {
+ char fragment_path[512];
+ snprintf(fragment_path, 512, "%s/%d", gif->tmp_dir.c_str(), push_id);
+ if (!is_dir(fragment_path)) {
+ char error[600];
+ snprintf(error, 600, "Error in AsyncAnimatedGif::EIO_Encode %s is not a dir.",
+ fragment_path);
+ enc_req->error = strdup(error);
+ return 0;
+ }
+
+ char **fragments = find_files(fragment_path);
+ LOKI_ON_BLOCK_EXIT(free_file_list, fragments);
+ int nfragments = file_list_length(fragments);
+
+ qsort(fragments, nfragments, sizeof(char *), fragment_sort);
+
+ unsigned char *frame = init_frame(gif->width, gif->height, gif->transparency_color);
+ LOKI_ON_BLOCK_EXIT(free, frame);
+ if (!frame) {
+ enc_req->error = strdup("malloc failed in AsyncAnimatedGif::EIO_Encode.");
+ return 0;
+ }
+
+ for (int i = 0; i < nfragments; i++) {
+ snprintf(fragment_path, 512, "%s/%d/%s",
+ gif->tmp_dir.c_str(), push_id, fragments[i]);
+ FILE *in = fopen(fragment_path, "r");
+ if (!in) {
+ char error[600];
+ snprintf(error, 600, "Failed opening %s in AsyncAnimatedGif::EIO_Encode.",
+ fragment_path);
+ enc_req->error = strdup(error);
+ return 0;
+ }
+ LOKI_ON_BLOCK_EXIT(fclose, in);
+ int size = file_size(fragment_path);
+ unsigned char *data = (unsigned char *)malloc(sizeof(*data)*size);
+ LOKI_ON_BLOCK_EXIT(free, data);
+ int read = fread(data, sizeof *data, size, in);
+ if (read != size) {
+ char error[600];
+ snprintf(error, 600, "Error - should have read %d but read only %d from %s in AsyncAnimatedGif::EIO_Encode", size, read, fragment_path);
+ enc_req->error = strdup(error);
+ return 0;
+ }
+ Rect dims = rect_dims(fragments[i]);
+ push_fragment(frame, gif->width, gif->height, gif->buf_type,
+ data, dims.x, dims.y, dims.w, dims.h);
+ }
+ encoder.new_frame(frame);
+ }
+ encoder.finish();
+
+ return 0;
+}
+
+int
+AsyncAnimatedGif::EIO_EncodeAfter(eio_req *req)
+{
+ HandleScope scope;
+
+ ev_unref(EV_DEFAULT_UC);
+ async_encode_request *enc_req = (async_encode_request *)req->data;
+
+ Handle<Value> argv[2];
+
+ if (enc_req->error) {
+ argv[0] = False();
+ argv[1] = ErrorException(enc_req->error);
+ }
+ else {
+ argv[0] = True();
+ argv[1] = Undefined();
+ }
+
+ TryCatch try_catch; // don't quite see the necessity of this
+
+ enc_req->callback->Call(Context::GetCurrent()->Global(), 2, argv);
+
+ if (try_catch.HasCaught())
+ FatalException(try_catch);
+
+ enc_req->callback.Dispose();
+
+ enc_req->gif_obj->Unref();
+ free(enc_req);
+
+ return 0;
+}
+
+Handle<Value>
+AsyncAnimatedGif::Encode(const Arguments &args)
+{
+ HandleScope scope;
+
+ if (args.Length() != 1)
+ return VException("One argument required - callback function.");
+
+ if (!args[0]->IsFunction())
+ return VException("First argument must be a function.");
+
+ Local<Function> callback = Local<Function>::Cast(args[0]);
+ AsyncAnimatedGif *gif = ObjectWrap::Unwrap<AsyncAnimatedGif>(args.This());
+
+ async_encode_request *enc_req = (async_encode_request *)malloc(sizeof(*enc_req));
+ if (!enc_req)
+ return VException("malloc in AsyncAnimatedGif::Encode failed.");
+
+ enc_req->callback = Persistent<Function>::New(callback);
+ enc_req->gif_obj = gif;
+ enc_req->error = NULL;
+
+ eio_custom(EIO_Encode, EIO_PRI_DEFAULT, EIO_EncodeAfter, enc_req);
+
+ ev_ref(EV_DEFAULT_UC);
+ gif->Ref();
+
+ return Undefined();
+}
+
+Handle<Value>
+AsyncAnimatedGif::SetOutputFile(const Arguments &args)
+{
+ HandleScope scope;
+
+ if (args.Length() != 1)
+ return VException("One argument required - path to output file.");
+
+ if (!args[0]->IsString())
+ return VException("First argument must be string.");
+
+ String::AsciiValue file_name(args[0]->ToString());
+
+ AsyncAnimatedGif *gif = ObjectWrap::Unwrap<AsyncAnimatedGif>(args.This());
+ gif->output_file = *file_name;
+
+ return Undefined();
+}
+
+Handle<Value>
+AsyncAnimatedGif::SetTmpDir(const Arguments &args)
+{
+ HandleScope scope;
+
+ if (args.Length() != 1)
+ return VException("One argument required - path to tmp dir.");
+
+ if (!args[0]->IsString())
+ return VException("First argument must be string.");
+
+ String::AsciiValue tmp_dir(args[0]->ToString());
+
+ AsyncAnimatedGif *gif = ObjectWrap::Unwrap<AsyncAnimatedGif>(args.This());
+ gif->tmp_dir = *tmp_dir;
+
+ return Undefined();
+}
+
65 src/async_animated_gif.h
View
@@ -0,0 +1,65 @@
+#ifndef ASYNC_ANIMATED_GIF_H
+#define ASYNC_ANIMATED_GIF_H
+
+#include <string>
+
+#include <node.h>
+#include <node_buffer.h>
+
+#include "gif_encoder.h"
+#include "common.h"
+
+struct push_request {
+ unsigned int push_id;
+ unsigned int fragment_id;
+ const char *tmp_dir;
+ unsigned char *data;
+ int data_size;
+ int x, y, w, h;
+};
+
+class AsyncAnimatedGif;
+
+struct async_encode_request {
+ AsyncAnimatedGif *gif_obj;
+ v8::Persistent<v8::Function> callback;
+ char *error;
+};
+
+class AsyncAnimatedGif : public node::ObjectWrap {
+ int width, height;
+ buffer_type buf_type;
+
+ Color transparency_color;
+
+ unsigned int push_id, fragment_id;
+ std::string tmp_dir, output_file;
+
+ static int EIO_Push(eio_req *req);
+ static int EIO_PushAfter(eio_req *req);
+
+ static int EIO_Encode(eio_req *req);
+ static int EIO_EncodeAfter(eio_req *req);
+
+ static unsigned char *init_frame(int width, int height, Color &transparency_color);
+ static void push_fragment(unsigned char *frame, int width, int height, buffer_type buf_type,
+ unsigned char *fragment, int x, int y, int w, int h);
+ static Rect rect_dims(const char *fragment_name);
+
+public:
+ static void Initialize(v8::Handle<v8::Object> target);
+
+ AsyncAnimatedGif(int wwidth, int hheight, buffer_type bbuf_type);
+ v8::Handle<v8::Value> Push(unsigned char *data_buf, int x, int y, int w, int h);
+ void EndPush();
+
+ static v8::Handle<v8::Value> New(const v8::Arguments &args);
+ static v8::Handle<v8::Value> Push(const v8::Arguments &args);
+ static v8::Handle<v8::Value> Encode(const v8::Arguments &args);
+ static v8::Handle<v8::Value> EndPush(const v8::Arguments &args);
+ static v8::Handle<v8::Value> SetOutputFile(const v8::Arguments &args);
+ static v8::Handle<v8::Value> SetTmpDir(const v8::Arguments &args);
+};
+
+#endif
+
2  src/module.cpp
View
@@ -4,6 +4,7 @@
//#include "fixed_gif_stack.h"
#include "dynamic_gif_stack.h"
#include "animated_gif.h"
+#include "async_animated_gif.h"
using namespace v8;
@@ -15,5 +16,6 @@ init(Handle<Object> target)
//FixedGifStack::Initialize(target);
DynamicGifStack::Initialize(target);
AnimatedGif::Initialize(target);
+ AsyncAnimatedGif::Initialize(target);
}
52 tests/animated-gif/animated-gif-async.js
View
@@ -0,0 +1,52 @@
+var GifLib = require('gif');
+var Buffer = require('buffer').Buffer;
+var fs = require('fs');
+var sys = require('sys');
+
+var chunkDirs = fs.readdirSync('.').sort().filter(
+ function (f) {
+ //return /^\d+$/.test(f) && parseInt(f,10)<=2
+ return /^\d+$/.test(f)
+ }
+);
+
+function baseName(fileName) {
+ return fileName.slice(0, fileName.indexOf('.'));
+}
+
+function rectDim(fileName) {
+ var m = fileName.match(/^\d+-rgb-(\d+)-(\d+)-(\d+)-(\d+).dat$/);
+ var dim = [m[1], m[2], m[3], m[4]].map(function (n) {
+ return parseInt(n, 10);
+ });
+ return { x: dim[0], y: dim[1], w: dim[2], h: dim[3] }
+}
+
+var animatedGif = new GifLib.AsyncAnimatedGif(720,400);
+animatedGif.setOutputFile('animated-async.gif');
+animatedGif.setTmpDir('./moo');
+
+chunkDirs.forEach(function (dir) {
+ console.log(dir);
+ var chunkFiles = fs.readdirSync(dir).sort().filter(
+ function (f) {
+ return /^\d+-rgb-\d+-\d+-\d+-\d+.dat/.test(f);
+ }
+ );
+ chunkFiles.forEach(function (chunkFile) {
+ var dims = rectDim(chunkFile);
+ var rgb = fs.readFileSync(dir + '/' + chunkFile); // returns buffer
+ animatedGif.push(rgb, dims.x, dims.y, dims.w, dims.h);
+ });
+ animatedGif.endPush();
+});
+
+animatedGif.encode(function (status, error) {
+ if (status) {
+ console.log('animated gif successfully written to animated-async.gif');
+ }
+ else {
+ console.log('failed writing animated gif: ' + error);
+ }
+});
+
2  wscript
View
@@ -17,7 +17,7 @@ def configure(conf):
def build(bld):
obj = bld.new_task_gen("cxx", "shlib", "node_addon")
obj.target = "gif"
- obj.source = "src/common.cpp src/palette.cpp src/quantize.cpp src/gif_encoder.cpp src/gif.cpp src/dynamic_gif_stack.cpp src/animated_gif.cpp src/module.cpp"
+ obj.source = "src/common.cpp src/palette.cpp src/quantize.cpp src/gif_encoder.cpp src/gif.cpp src/dynamic_gif_stack.cpp src/animated_gif.cpp src/async_animated_gif.cpp src/module.cpp"
obj.uselib = "GIF"
obj.cxxflags = ["-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE"]
Please sign in to comment.
Something went wrong with that request. Please try again.