Skip to content

Support setting PNG compression level. #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 115 additions & 21 deletions src/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//

#include "Canvas.h"
#include "PNG.h"
#include "CanvasRenderingContext2d.h"
#include <assert.h>
#include <stdlib.h>
Expand Down Expand Up @@ -44,6 +45,15 @@ Canvas::Initialize(Handle<Object> target) {
proto->SetAccessor(NanSymbol("type"), GetType);
proto->SetAccessor(NanSymbol("width"), GetWidth, SetWidth);
proto->SetAccessor(NanSymbol("height"), GetHeight, SetHeight);

proto->Set("PNG_NO_FILTERS", Uint32::New(PNG_NO_FILTERS));
proto->Set("PNG_FILTER_NONE", Uint32::New(PNG_FILTER_NONE));
proto->Set("PNG_FILTER_SUB", Uint32::New(PNG_FILTER_SUB));
proto->Set("PNG_FILTER_UP", Uint32::New(PNG_FILTER_UP));
proto->Set("PNG_FILTER_AVG", Uint32::New(PNG_FILTER_AVG));
proto->Set("PNG_FILTER_PAETH", Uint32::New(PNG_FILTER_PAETH));
proto->Set("PNG_ALL_FILTERS", Uint32::New(PNG_ALL_FILTERS));

target->Set(NanSymbol("Canvas"), ctor->GetFunction());
}

Expand Down Expand Up @@ -128,31 +138,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) {
Expand All @@ -165,11 +175,11 @@ Canvas::EIO_ToBuffer(eio_req *req) {
#endif
closure_t *closure = (closure_t *) req->data;

closure->status = cairo_surface_write_to_png_stream(
closure->status = canvas_write_to_png_stream(
closure->canvas->surface()
, toBuffer
, closure);

#if !NODE_VERSION_AT_LEAST(0, 5, 4)
return 0;
#endif
Expand Down Expand Up @@ -209,20 +219,22 @@ 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.
*/

NAN_METHOD(Canvas::ToBuffer) {
NanScope();
cairo_status_t status;
uint32_t compression_level = 6;
uint32_t filter = PNG_ALL_FILTERS;
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());

// TODO: async / move this out
Expand All @@ -234,10 +246,48 @@ NAN_METHOD(Canvas::ToBuffer) {
NanReturnValue(buf);
}

if (args.Length() > 1 && !(args[1]->StrictEquals(Undefined()) && args[2]->StrictEquals(Undefined()))) {
if (!args[1]->StrictEquals(Undefined())) {
bool good = true;
if (args[1]->IsNumber()) {
compression_level = args[1]->Uint32Value();
} else if (args[1]->IsString()) {
if (args[1]->StrictEquals(String::New("0"))) {
compression_level = 0;
} else {
uint32_t tmp = args[1]->Uint32Value();
if (tmp == 0) {
good = false;
} else {
compression_level = tmp;
}
}
} else {
good = false;
}

if (good) {
if (compression_level > 9) {
return NanThrowRangeError("Allowed compression levels lie in the range [0, 9].");
}
} else {
return NanThrowTypeError("Compression level must be a number.");
}
}

if (!args[2]->StrictEquals(Undefined())) {
if (args[2]->IsUint32()) {
filter = args[1]->Uint32Value();
} else {
return NanThrowTypeError("Invalid filter value.");
}
}
}

// Async
if (args[0]->IsFunction()) {
closure_t *closure = (closure_t *) malloc(sizeof(closure_t));
status = closure_init(closure, canvas);
status = closure_init(closure, canvas, compression_level, filter);

// ensure closure is ok
if (status) {
Expand All @@ -249,7 +299,7 @@ NAN_METHOD(Canvas::ToBuffer) {
// TODO: only one callback fn in closure
canvas->Ref();
closure->pfn = new NanCallback(args[0].As<Function>());

#if NODE_VERSION_AT_LEAST(0, 6, 0)
uv_work_t* req = new uv_work_t;
req->data = closure;
Expand All @@ -258,12 +308,12 @@ NAN_METHOD(Canvas::ToBuffer) {
eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure);
ev_ref(EV_DEFAULT_UC);
#endif

NanReturnUndefined();
// Sync
} else {
closure_t closure;
status = closure_init(&closure, canvas);
status = closure_init(&closure, canvas, compression_level, filter);

// ensure closure is ok
if (status) {
Expand All @@ -272,7 +322,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);
Expand Down Expand Up @@ -311,16 +361,60 @@ streamPNG(void *c, const uint8_t *data, unsigned len) {

NAN_METHOD(Canvas::StreamPNGSync) {
NanScope();
uint32_t compression_level = 6;
uint32_t filter = PNG_ALL_FILTERS;
// TODO: async as well
if (!args[0]->IsFunction())
return NanThrowTypeError("callback function required");

if (args.Length() > 1 && !(args[1]->StrictEquals(Undefined()) && args[2]->StrictEquals(Undefined()))) {
if (!args[1]->StrictEquals(Undefined())) {
bool good = true;
if (args[1]->IsNumber()) {
compression_level = args[1]->Uint32Value();
} else if (args[1]->IsString()) {
if (args[1]->StrictEquals(String::New("0"))) {
compression_level = 0;
} else {
uint32_t tmp = args[1]->Uint32Value();
if (tmp == 0) {
good = false;
} else {
compression_level = tmp;
}
}
} else {
good = false;
}

if (good) {
if (compression_level > 9) {
return NanThrowRangeError("Allowed compression levels lie in the range [0, 9].");
}
} else {
return NanThrowTypeError("Compression level must be a number.");
}
}

if (!args[2]->StrictEquals(Undefined())) {
if (args[2]->IsUint32()) {
filter = args[1]->Uint32Value();
} else {
return NanThrowTypeError("Invalid filter value.");
}
}
}


Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
closure_t closure;
closure.fn = Handle<Function>::Cast(args[0]);
closure.compression_level = compression_level;
closure.filter = filter;

TryCatch try_catch;
cairo_status_t status = cairo_surface_write_to_png_stream(canvas->surface(), streamPNG, &closure);

cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);

if (try_catch.HasCaught()) {
NanReturnValue(try_catch.ReThrow());
Expand Down Expand Up @@ -381,7 +475,7 @@ Canvas::Canvas(int w, int h, canvas_type_t t): ObjectWrap() {
if (CANVAS_TYPE_PDF == t) {
_closure = malloc(sizeof(closure_t));
assert(_closure);
cairo_status_t status = closure_init((closure_t *) _closure, this);
cairo_status_t status = closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
assert(status == CAIRO_STATUS_SUCCESS);
_surface = cairo_pdf_surface_create_for_stream(toBuffer, _closure, w, h);
} else {
Expand Down
Loading