// // Canvas.cc // // Copyright (c) 2010 LearnBoost // #include "Canvas.h" #include "CanvasRenderingContext2d.h" #include "closure.h" #include #include #include #include #include Persistent Canvas::constructor; /* * Initialize Canvas. */ void Canvas::Initialize(Handle target) { HandleScope scope; // Constructor constructor = Persistent::New(FunctionTemplate::New(Canvas::New)); constructor->InstanceTemplate()->SetInternalFieldCount(1); constructor->SetClassName(String::NewSymbol("Canvas")); // Prototype Local proto = constructor->PrototypeTemplate(); NODE_SET_PROTOTYPE_METHOD(constructor, "toBuffer", ToBuffer); NODE_SET_PROTOTYPE_METHOD(constructor, "streamPNGSync", StreamPNGSync); proto->SetAccessor(String::NewSymbol("width"), GetWidth, SetWidth); proto->SetAccessor(String::NewSymbol("height"), GetHeight, SetHeight); target->Set(String::NewSymbol("Canvas"), constructor->GetFunction()); } /* * Initialize a Canvas with the given width and height. */ Handle Canvas::New(const Arguments &args) { HandleScope scope; int width = 0, height = 0; if (args[0]->IsNumber()) width = args[0]->Uint32Value(); if (args[1]->IsNumber()) height = args[1]->Uint32Value(); Canvas *canvas = new Canvas(width, height); canvas->Wrap(args.This()); return args.This(); } /* * Get width. */ Handle Canvas::GetWidth(Local prop, const AccessorInfo &info) { Canvas *canvas = ObjectWrap::Unwrap(info.This()); return Number::New(canvas->width); } /* * Set width. */ void Canvas::SetWidth(Local prop, Local val, const AccessorInfo &info) { if (val->IsNumber()) { Canvas *canvas = ObjectWrap::Unwrap(info.This()); canvas->width = val->Uint32Value(); canvas->resurface(info.This()); } } /* * Get height. */ Handle Canvas::GetHeight(Local prop, const AccessorInfo &info) { Canvas *canvas = ObjectWrap::Unwrap(info.This()); return Number::New(canvas->height); } /* * Set height. */ void Canvas::SetHeight(Local prop, Local val, const AccessorInfo &info) { if (val->IsNumber()) { Canvas *canvas = ObjectWrap::Unwrap(info.This()); canvas->height = val->Uint32Value(); canvas->resurface(info.This()); } } /* * Canvas::ToBuffer callback. */ static cairo_status_t toBuffer(void *c, const uint8_t *data, unsigned len) { closure_t *closure = (closure_t *) c; // Olaf (2011-02-21): Store more data, but don't call realloc() on every chunk. // Also, keep track of how much memory is used if (closure->len + len > closure->max_len) { uint8_t * new_data; unsigned new_max_len = closure->max_len; // Round up the buffer size to be a multiple of 1024 bytes. new_max_len = (closure->max_len + len + 1023) & ~1023; new_data = (uint8_t *) realloc(closure->data, new_max_len); if (new_data == NULL) return CAIRO_STATUS_NO_MEMORY; // Keep track of how much more memory we just allocated. V8::AdjustAmountOfExternalAllocatedMemory(new_max_len - closure->max_len); closure->data = new_data; closure->max_len = new_max_len; } memcpy(closure->data + closure->len, data, len); closure->len += len; return CAIRO_STATUS_SUCCESS; } /* * EIO toBuffer callback. */ int Canvas::EIO_ToBuffer(eio_req *req) { closure_t *closure = (closure_t *) req->data; closure->status = cairo_surface_write_to_png_stream( closure->canvas->surface() , toBuffer , closure); return 0; } /* * EIO after toBuffer callback. */ int Canvas::EIO_AfterToBuffer(eio_req *req) { HandleScope scope; closure_t *closure = (closure_t *) req->data; ev_unref(EV_DEFAULT_UC); if (closure->status) { Local argv[1] = { Canvas::Error(closure->status) }; closure->pfn->Call(Context::GetCurrent()->Global(), 1, argv); } else { Buffer *buf = Buffer::New(closure->len); memcpy(Buffer::Data(buf->handle_), closure->data, closure->len); Local argv[2] = { Local::New(Null()), Local::New(buf->handle_) }; closure->pfn->Call(Context::GetCurrent()->Global(), 2, argv); } closure->canvas->Unref(); closure->pfn.Dispose(); closure_destroy(closure); free(closure); return 0; } /* * Convert PNG data to a node::Buffer, async when a * callback function is passed. */ Handle Canvas::ToBuffer(const Arguments &args) { HandleScope scope; Canvas *canvas = ObjectWrap::Unwrap(args.This()); // Async if (args[0]->IsFunction()) { closure_t *closure = (closure_t *) malloc(sizeof(closure_t)); memset(closure, 0, sizeof(closure)); closure->len = 0; closure->canvas = canvas; // TODO: only one callback fn in closure canvas->Ref(); closure->pfn = Persistent::New(Handle::Cast(args[0])); eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure); ev_ref(EV_DEFAULT_UC); return Undefined(); } else { closure_t closure; // Olaf (2011-02-21): zero everything memset(&closure, 0, sizeof(closure)); TryCatch try_catch; cairo_status_t status = cairo_surface_write_to_png_stream(canvas->surface(), toBuffer, &closure); if (try_catch.HasCaught()) { closure_destroy(&closure); return try_catch.ReThrow(); } else if (status) { closure_destroy(&closure); return ThrowException(Canvas::Error(status)); } else { Buffer *buf = Buffer::New(closure.len); memcpy(Buffer::Data(buf->handle_), closure.data, closure.len); closure_destroy(&closure); return buf->handle_; } } } /* * Canvas::StreamPNG callback. */ static cairo_status_t streamPNG(void *c, const uint8_t *data, unsigned len) { HandleScope scope; closure_t *closure = (closure_t *) c; Local buf = Buffer::New(len); memcpy(Buffer::Data(buf->handle_), data, len); Local argv[3] = { Local::New(Null()) , Local::New(buf->handle_) , Integer::New(len) }; closure->fn->Call(Context::GetCurrent()->Global(), 3, argv); return CAIRO_STATUS_SUCCESS; } /* * Stream PNG data synchronously. */ Handle Canvas::StreamPNGSync(const Arguments &args) { HandleScope scope; // TODO: async as well if (!args[0]->IsFunction()) return ThrowException(Exception::TypeError(String::New("callback function required"))); Canvas *canvas = ObjectWrap::Unwrap(args.This()); closure_t closure; closure.fn = Handle::Cast(args[0]); TryCatch try_catch; cairo_status_t status = cairo_surface_write_to_png_stream(canvas->surface(), streamPNG, &closure); if (try_catch.HasCaught()) { return try_catch.ReThrow(); } else if (status) { Local argv[1] = { Canvas::Error(status) }; closure.fn->Call(Context::GetCurrent()->Global(), 1, argv); } else { Local argv[3] = { Local::New(Null()) , Local::New(Null()) , Integer::New(0) }; closure.fn->Call(Context::GetCurrent()->Global(), 3, argv); } return Undefined(); } /* * Initialize cairo surface. */ Canvas::Canvas(int w, int h): ObjectWrap() { width = w; height = h; _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); } /* * Destroy cairo surface. */ Canvas::~Canvas() { cairo_surface_destroy(_surface); } /* * Re-alloc the surface, destroying the previous. */ void Canvas::resurface(Handle canvas) { // Re-surface cairo_surface_destroy(_surface); _surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); // Reset context Handle context = canvas->Get(String::New("context")); if (!context->IsUndefined()) { Context2d *context2d = ObjectWrap::Unwrap(context->ToObject()); cairo_t *prev = context2d->context(); context2d->setContext(cairo_create(surface())); cairo_destroy(prev); } } /* * Construct an Error from the given cairo status. */ Local Canvas::Error(cairo_status_t status) { return Exception::Error(String::New(cairo_status_to_string(status))); }