diff --git a/src/Canvas.cc b/src/Canvas.cc index 2f7563a..f7cfa11 100644 --- a/src/Canvas.cc +++ b/src/Canvas.cc @@ -5,6 +5,7 @@ // #include "Canvas.h" +#include "PNG.h" #include "CanvasRenderingContext2d.h" #include #include @@ -128,31 +129,31 @@ NAN_SETTER(Canvas::SetHeight) { static cairo_status_t toBuffer(void *c, const uint8_t *data, unsigned len) { closure_t *closure = (closure_t *) c; - - // Olaf: grow buffer + if (closure->len + len > closure->max_len) { uint8_t *data; - unsigned max; - - // round to the nearest multiple of 1024 bytes - max = (closure->max_len + len + 1023) & ~1023; - + unsigned max = closure->max_len; + + do { + max *= 2; + } while (closure->len + len > max); + data = (uint8_t *) realloc(closure->data, max); if (!data) return CAIRO_STATUS_NO_MEMORY; closure->data = data; closure->max_len = max; } - + memcpy(closure->data + closure->len, data, len); closure->len += len; - + return CAIRO_STATUS_SUCCESS; } /* * EIO toBuffer callback. */ - + #if NODE_VERSION_AT_LEAST(0, 6, 0) void Canvas::ToBufferAsync(uv_work_t *req) { @@ -169,7 +170,7 @@ Canvas::EIO_ToBuffer(eio_req *req) { closure->canvas->surface() , toBuffer , closure); - + #if !NODE_VERSION_AT_LEAST(0, 5, 4) return 0; #endif @@ -209,14 +210,14 @@ Canvas::EIO_AfterToBuffer(eio_req *req) { delete closure->pfn; closure_destroy(closure); free(closure); - + #if !NODE_VERSION_AT_LEAST(0, 6, 0) return 0; #endif } /* - * Convert PNG data to a node::Buffer, async when a + * Convert PNG data to a node::Buffer, async when a * callback function is passed. */ @@ -249,7 +250,7 @@ NAN_METHOD(Canvas::ToBuffer) { // TODO: only one callback fn in closure canvas->Ref(); closure->pfn = new NanCallback(args[0].As()); - + #if NODE_VERSION_AT_LEAST(0, 6, 0) uv_work_t* req = new uv_work_t; req->data = closure; @@ -258,7 +259,7 @@ NAN_METHOD(Canvas::ToBuffer) { eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure); ev_ref(EV_DEFAULT_UC); #endif - + NanReturnUndefined(); // Sync } else { @@ -272,7 +273,7 @@ NAN_METHOD(Canvas::ToBuffer) { } TryCatch try_catch; - status = cairo_surface_write_to_png_stream(canvas->surface(), toBuffer, &closure); + status = canvas_write_to_png_stream(canvas->surface(), toBuffer, &closure); if (try_catch.HasCaught()) { closure_destroy(&closure); @@ -320,7 +321,9 @@ NAN_METHOD(Canvas::StreamPNGSync) { closure.fn = Handle::Cast(args[0]); TryCatch try_catch; - cairo_status_t status = cairo_surface_write_to_png_stream(canvas->surface(), streamPNG, &closure); + + //TODO: use libpng directly + cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure); if (try_catch.HasCaught()) { NanReturnValue(try_catch.ReThrow()); diff --git a/src/PNG.h b/src/PNG.h new file mode 100644 index 0000000..ac2a56a --- /dev/null +++ b/src/PNG.h @@ -0,0 +1,225 @@ +#ifndef _CANVAS_PNG_H +#define _CANVAS_PNG_H +#include +#include +#include +#include + +#pragma once + +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define likely(expr) (__builtin_expect (!!(expr), 1)) +#define unlikely(expr) (__builtin_expect (!!(expr), 0)) +#else +#define likely(expr) (expr) +#define unlikely(expr) (expr) +#endif + + +static void pngtest_flush(png_structp png_ptr) { + /* Do nothing; fflush() is said to be just a waste of energy. */ + (void) png_ptr; /* Stifle compiler warning */ +} + +/* Converts native endian xRGB => RGBx bytes */ +static void convert_data_to_bytes (png_structp png, png_row_infop row_info, png_bytep data) { + unsigned int i; + + for (i = 0; i < row_info->rowbytes; i += 4) { + uint8_t *b = &data[i]; + uint32_t pixel; + + memcpy(&pixel, b, sizeof (uint32_t)); + + b[0] = (pixel & 0xff0000) >> 16; + b[1] = (pixel & 0x00ff00) >> 8; + b[2] = (pixel & 0x0000ff) >> 0; + b[3] = 0; + } +} + +/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */ +static void unpremultiply_data(png_structp png, png_row_infop row_info, png_bytep data) { + unsigned int i; + + for (i = 0; i < row_info->rowbytes; i += 4) { + uint8_t *b = &data[i]; + uint32_t pixel; + uint8_t alpha; + + memcpy(&pixel, b, sizeof (uint32_t)); + alpha = (pixel & 0xff000000) >> 24; + if (alpha == 0) { + b[0] = b[1] = b[2] = b[3] = 0; + } else { + b[0] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + b[1] = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + b[2] = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha; + b[3] = alpha; + } + } +} + +static cairo_status_t canvas_write_png(cairo_surface_t *surface, png_rw_ptr write_func, void *closure) { + unsigned int i; + cairo_status_t status = CAIRO_STATUS_SUCCESS; + uint8_t *data; + png_structp png; + png_infop info; +// png_infop end_info; + png_bytep *volatile rows = NULL; + png_color_16 white; + int png_color_type; + int bpc; + unsigned int width = cairo_image_surface_get_width(surface); + unsigned int height = cairo_image_surface_get_height(surface); + + data = cairo_image_surface_get_data(surface); + if (data == NULL) { + status = CAIRO_STATUS_SURFACE_TYPE_MISMATCH; + return status; + } + cairo_surface_flush(surface); + + if (width == 0 || height == 0) { + status = CAIRO_STATUS_WRITE_ERROR; + return status; + } + + rows = (png_bytep *) malloc(height * sizeof (png_byte*)); + if (unlikely(rows == NULL)) { + status = CAIRO_STATUS_NO_MEMORY; + return status; + } + + for (i = 0; i < height; i++) { + rows[i] = (png_byte *) data + i * cairo_image_surface_get_stride(surface); + } + +#ifdef PNG_USER_MEM_SUPPORTED + png = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, NULL, NULL); +#else + png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); +#endif + + if (unlikely(png == NULL)) { + status = CAIRO_STATUS_NO_MEMORY; + free(rows); + return status; + } + + info = png_create_info_struct (png); + if (unlikely(info == NULL)) { + status = CAIRO_STATUS_NO_MEMORY; + png_destroy_write_struct(&png, &info); + free(rows); + return status; + + } + +#ifdef PNG_SETJMP_SUPPORTED + if (setjmp (png_jmpbuf (png))) { + png_destroy_write_struct(&png, &info); + free(rows); + return status; + } +#endif + + png_set_write_fn(png, closure, write_func, pngtest_flush); + + switch (cairo_image_surface_get_format(surface)) { + case CAIRO_FORMAT_ARGB32: + bpc = 8; + png_color_type = PNG_COLOR_TYPE_RGB_ALPHA; + break; + case CAIRO_FORMAT_RGB30: + bpc = 10; + png_color_type = PNG_COLOR_TYPE_RGB; + break; + case CAIRO_FORMAT_RGB24: + bpc = 8; + png_color_type = PNG_COLOR_TYPE_RGB; + break; + case CAIRO_FORMAT_A8: + bpc = 8; + png_color_type = PNG_COLOR_TYPE_GRAY; + break; + case CAIRO_FORMAT_A1: + bpc = 1; + png_color_type = PNG_COLOR_TYPE_GRAY; +#ifndef WORDS_BIGENDIAN + png_set_packswap(png); +#endif + break; + case CAIRO_FORMAT_INVALID: + case CAIRO_FORMAT_RGB16_565: + default: + status = CAIRO_STATUS_INVALID_FORMAT; + png_destroy_write_struct(&png, &info); + free(rows); + return status; + } + + png_set_IHDR(png, info, width, height, bpc, png_color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + white.gray = (1 << bpc) - 1; + white.red = white.blue = white.green = white.gray; + png_set_bKGD(png, info, &white); + + /* We have to call png_write_info() before setting up the write + * transformation, since it stores data internally in 'png' + * that is needed for the write transformation functions to work. + */ + png_write_info(png, info); + if (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + png_set_write_user_transform_fn(png, unpremultiply_data); + } else if (png_color_type == PNG_COLOR_TYPE_RGB) { + png_set_write_user_transform_fn(png, convert_data_to_bytes); + png_set_filler(png, 0, PNG_FILLER_AFTER); + } + + png_write_image (png, rows); + png_write_end (png, info); + + png_destroy_write_struct (&png, &info); + free(rows); + return status; +} + +struct png_write_closure_t { + cairo_write_func_t write_func; + void *closure; +}; + +static void stream_write_func(png_structp png, png_bytep data, png_size_t size) { + cairo_status_t status; + struct png_write_closure_t *png_closure; + + png_closure = (struct png_write_closure_t *) png_get_io_ptr(png); + status = png_closure->write_func(png_closure->closure, data, size); + if (unlikely(status)) { + cairo_status_t *error = (cairo_status_t *) png_get_error_ptr(png); + if (*error == CAIRO_STATUS_SUCCESS) { + *error = status; + } + png_error(png, NULL); + } +} + +static cairo_status_t canvas_write_to_png_stream(cairo_surface_t *surface, cairo_write_func_t write_func, void *closure) { + struct png_write_closure_t png_closure; + + if (cairo_surface_status(surface)) { + return cairo_surface_status(surface); + } + +// if (surface->finished) { +// return CAIRO_STATUS_SURFACE_FINISHED; +// } + + png_closure.write_func = write_func; + png_closure.closure = closure; + + return canvas_write_png(surface, stream_write_func, &png_closure); +} +#endif diff --git a/src/closure.h b/src/closure.h index 49ec8e2..a94b206 100644 --- a/src/closure.h +++ b/src/closure.h @@ -8,6 +8,14 @@ #ifndef __NODE_CLOSURE_H__ #define __NODE_CLOSURE_H__ +#ifdef __unix__ + #include +#endif + +#ifndef PAGE_SIZE + #define PAGE_SIZE 4096 +#endif + #include "nan.h" /* @@ -32,7 +40,7 @@ cairo_status_t closure_init(closure_t *closure, Canvas *canvas) { closure->len = 0; closure->canvas = canvas; - closure->data = (uint8_t *) malloc(closure->max_len = 1024); + closure->data = (uint8_t *) malloc(closure->max_len = PAGE_SIZE); if (!closure->data) return CAIRO_STATUS_NO_MEMORY; return CAIRO_STATUS_SUCCESS; } diff --git a/src/nan.h b/src/nan.h index ead7d65..fe37c76 100644 --- a/src/nan.h +++ b/src/nan.h @@ -1,12 +1,18 @@ /********************************************************************************** * NAN - Native Abstractions for Node.js * - * Copyright (c) 2013 Rod Vagg + * Copyright (c) 2013 NAN contributors: + * - Rod Vagg + * - King Koopa + * - Trevor Norris + * * MIT +no-false-attribs License * - * Version 0.1.0 (current Node unstable: 0.11.4) + * Version 0.2.0 (current Node unstable: 0.11.4) * * Changelog: + * * 0.2.0 .... TODO + * * * 0.1.0 Jul 21 2013 * - Added `NAN_GETTER`, `NAN_SETTER` * - Added `NanThrowError` with single Local argument @@ -77,32 +83,71 @@ static inline uint32_t NanUInt32OptionValue( static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); -# define NAN_METHOD(name) \ - void name(const v8::FunctionCallbackInfo& args) +# define _NAN_METHOD_ARGS const v8::FunctionCallbackInfo& args +# define NAN_METHOD(name) void name(_NAN_METHOD_ARGS) +# define _NAN_GETTER_ARGS const v8::PropertyCallbackInfo& args # define NAN_GETTER(name) \ - void name( \ - v8::Local property \ - , const v8::PropertyCallbackInfo& args) + void name(v8::Local property, _NAN_GETTER_ARGS) +# define _NAN_SETTER_ARGS const v8::PropertyCallbackInfo& args # define NAN_SETTER(name) \ void name( \ v8::Local property \ , v8::Local value \ - , const v8::PropertyCallbackInfo& args) + , _NAN_SETTER_ARGS) +# define _NAN_PROPERTY_GETTER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_GETTER(name) \ + void name(v8::Local property \ + , _NAN_PROPERTY_GETTER_ARGS) +# define _NAN_PROPERTY_SETTER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_SETTER(name) \ + void name(v8::Local property \ + , v8::Local value \ + , _NAN_PROPERTY_SETTER_ARGS) +# define _NAN_PROPERTY_ENUMERATOR_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_ENUMERATOR(name) \ + void name(_NAN_PROPERTY_ENUMERATOR_ARGS) +# define _NAN_PROPERTY_DELETER_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_DELETER(name) \ + void name( \ + v8::Local property \ + , _NAN_PROPERTY_DELETER_ARGS) +# define _NAN_PROPERTY_QUERY_ARGS \ + const v8::PropertyCallbackInfo& args +# define NAN_PROPERTY_QUERY(name) \ + void name(v8::Local property, _NAN_PROPERTY_QUERY_ARGS) +# define NanGetInternalFieldPointer(object, index) \ + object->GetAlignedPointerFromInternalField(index) +# define NanSetInternalFieldPointer(object, index, value) \ + object->SetAlignedPointerInInternalField(index, value) + +# define NAN_WEAK_CALLBACK(type, name) \ + void name( \ + v8::Isolate* isolate, \ + v8::Persistent* object, \ + type data) +# define NAN_WEAK_CALLBACK_OBJECT (*object) +# define NAN_WEAK_CALLBACK_DATA(type) ((type) data) # define NanScope() v8::HandleScope scope(nan_isolate) -# define NanReturnValue(value) return args.GetReturnValue().Set(value); -# define NanReturnUndefined() return; -# define NanAssignPersistent(type, handle, obj) handle.Reset(nan_isolate, obj); +# define NanReturnValue(value) return args.GetReturnValue().Set(value) +# define NanReturnUndefined() return +# define NanAssignPersistent(type, handle, obj) handle.Reset(nan_isolate, obj) # define NanObjectWrapHandle(obj) obj->handle() +# define NanMakeWeak(handle, parameter, callback) \ + handle.MakeWeak(nan_isolate, parameter, callback) -# define THROW_ERROR(fun, errmsg) \ +# define _NAN_THROW_ERROR(fun, errmsg) \ do { \ NanScope(); \ v8::ThrowException(fun(v8::String::New(errmsg))); \ } while (0); inline static void NanThrowError(const char* errmsg) { - THROW_ERROR(v8::Exception::Error, errmsg); + _NAN_THROW_ERROR(v8::Exception::Error, errmsg); } inline static void NanThrowError(v8::Local error) { @@ -111,17 +156,25 @@ static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); } inline static void NanThrowTypeError(const char* errmsg) { - THROW_ERROR(v8::Exception::TypeError, errmsg); + _NAN_THROW_ERROR(v8::Exception::TypeError, errmsg); } inline static void NanThrowRangeError(const char* errmsg) { - THROW_ERROR(v8::Exception::RangeError, errmsg); + _NAN_THROW_ERROR(v8::Exception::RangeError, errmsg); } - static inline void NanDispose(v8::Persistent &handle) { + template static inline void NanDispose(v8::Persistent &handle) { handle.Dispose(nan_isolate); } + static inline v8::Local NanNewBufferHandle ( + char *data, + size_t length, + node::smalloc::FreeCallback callback, + void *hint) { + return node::Buffer::New(data, length, callback, hint); + } + static inline v8::Local NanNewBufferHandle ( char *data, uint32_t size) { return node::Buffer::New(data, size); @@ -131,6 +184,10 @@ static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); return node::Buffer::New(size); } + static inline v8::Local NanBufferUse(char* data, uint32_t size) { + return node::Buffer::Use(data, size); + } + template inline v8::Local NanPersistentToLocal( const v8::Persistent& persistent) { @@ -148,36 +205,78 @@ static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); return NanPersistentToLocal(function_template)->HasInstance(value); } + static inline v8::Local NanNewContextHandle( + v8::ExtensionConfiguration* extensions = NULL, + v8::Handle g_template = v8::Handle(), + v8::Handle g_object = v8::Handle()) { + return v8::Local::New(nan_isolate, v8::Context::New( + nan_isolate, extensions, g_template, g_object)); + } + #else // Node 0.8 and 0.10 -# define NAN_METHOD(name) \ - v8::Handle name(const v8::Arguments& args) +# define _NAN_METHOD_ARGS const v8::Arguments& args +# define NAN_METHOD(name) v8::Handle name(_NAN_METHOD_ARGS) +# define _NAN_GETTER_ARGS const v8::AccessorInfo &args # define NAN_GETTER(name) \ - v8::Handle name( \ - v8::Local property \ - , const v8::AccessorInfo &args) + v8::Handle name(v8::Local property, _NAN_GETTER_ARGS) +# define _NAN_SETTER_ARGS const v8::AccessorInfo &args # define NAN_SETTER(name) \ void name( \ - v8::Local property \ - , v8::Local value \ - , const v8::AccessorInfo &args) + v8::Local property \ + , v8::Local value \ + , _NAN_SETTER_ARGS) +# define _NAN_PROPERTY_GETTER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_GETTER(name) \ + v8::Handle name(v8::Local property \ + , _NAN_PROPERTY_GETTER_ARGS) +# define _NAN_PROPERTY_SETTER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_SETTER(name) \ + v8::Handle name(v8::Local property \ + , v8::Local value \ + , _NAN_PROPERTY_SETTER_ARGS) +# define _NAN_PROPERTY_ENUMERATOR_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_ENUMERATOR(name) \ + v8::Handle name(_NAN_PROPERTY_ENUMERATOR_ARGS) +# define _NAN_PROPERTY_DELETER_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_DELETER(name) \ + v8::Handle name( \ + v8::Local property \ + , _NAN_PROPERTY_DELETER_ARGS) +# define _NAN_PROPERTY_QUERY_ARGS const v8::AccessorInfo& args +# define NAN_PROPERTY_QUERY(name) \ + v8::Handle name( \ + v8::Local property \ + , _NAN_PROPERTY_QUERY_ARGS) + +# define NanGetInternalFieldPointer(object, index) \ + object->GetPointerFromInternalField(index) +# define NanSetInternalFieldPointer(object, index, value) \ + object->SetPointerInInternalField(index, value) +# define NAN_WEAK_CALLBACK(type, name) void name( \ + v8::Persistent object, \ + void *data) +# define NAN_WEAK_CALLBACK_OBJECT object +# define NAN_WEAK_CALLBACK_DATA(type) ((type) data) # define NanScope() v8::HandleScope scope -# define NanReturnValue(value) return scope.Close(value); -# define NanReturnUndefined() return v8::Undefined(); +# define NanReturnValue(value) return scope.Close(value) +# define NanReturnUndefined() return v8::Undefined() # define NanAssignPersistent(type, handle, obj) \ - handle = v8::Persistent::New(obj); + handle = v8::Persistent::New(obj) # define NanObjectWrapHandle(obj) obj->handle_ +# define NanMakeWeak(handle, parameters, callback) \ + handle.MakeWeak(parameters, callback) -# define THROW_ERROR(fun, errmsg) \ +# define _NAN_THROW_ERROR(fun, errmsg) \ do { \ NanScope(); \ return v8::ThrowException(fun(v8::String::New(errmsg))); \ } while (0); inline static v8::Handle NanThrowError(const char* errmsg) { - THROW_ERROR(v8::Exception::Error, errmsg); + _NAN_THROW_ERROR(v8::Exception::Error, errmsg); } inline static v8::Handle NanThrowError( @@ -187,22 +286,43 @@ static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); } inline static v8::Handle NanThrowTypeError(const char* errmsg) { - THROW_ERROR(v8::Exception::TypeError, errmsg); + _NAN_THROW_ERROR(v8::Exception::TypeError, errmsg); } inline static v8::Handle NanThrowRangeError(const char* errmsg) { - THROW_ERROR(v8::Exception::RangeError, errmsg); + _NAN_THROW_ERROR(v8::Exception::RangeError, errmsg); } - static inline void NanDispose(v8::Persistent &handle) { + template static inline void NanDispose(v8::Persistent &handle) { handle.Dispose(); } + static inline v8::Local NanNewBufferHandle ( + char *data, + size_t length, + node::Buffer::free_callback callback, + void *hint) { + return v8::Local::New(node::Buffer::New(data, length, callback, hint)->handle_); + } + static inline v8::Local NanNewBufferHandle ( char *data, uint32_t size) { return v8::Local::New(node::Buffer::New(data, size)->handle_); } + static inline v8::Local NanNewBufferHandle (uint32_t size) { + return v8::Local::New(node::Buffer::New(size)->handle_); + } + + static inline void FreeData(char *data, void *hint) { + delete[] data; + } + + static inline v8::Local NanBufferUse(char* data, uint32_t size) { + return v8::Local::New( + node::Buffer::New(data, size, FreeData, NULL)->handle_); + } + template inline v8::Local NanPersistentToLocal( const v8::Persistent& persistent) { @@ -220,6 +340,16 @@ static v8::Isolate* nan_isolate = v8::Isolate::GetCurrent(); return function_template->HasInstance(value); } + static inline v8::Local NanNewContextHandle( + v8::ExtensionConfiguration* extensions = NULL, + v8::Handle g_template = v8::Handle(), + v8::Handle g_object = v8::Handle()) { + v8::Persistent ctx = v8::Context::New(extensions, g_template, g_object); + v8::Local lctx = v8::Local::New(ctx); + ctx.Dispose(); + return lctx; + } + #endif // node version class NanCallback {