Initial commit
This commit is contained in:
+673
@@ -0,0 +1,673 @@
|
||||
//
|
||||
// Canvas.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "PNG.h"
|
||||
#include "CanvasRenderingContext2d.h"
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <node_buffer.h>
|
||||
#include <node_version.h>
|
||||
#include <cairo-pdf.h>
|
||||
#include <cairo-svg.h>
|
||||
#include "closure.h"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
#include "JPEGStream.h"
|
||||
#endif
|
||||
|
||||
Nan::Persistent<FunctionTemplate> Canvas::constructor;
|
||||
|
||||
/*
|
||||
* Initialize Canvas.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// Constructor
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Canvas::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("Canvas").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
|
||||
Nan::SetPrototypeMethod(ctor, "toBuffer", ToBuffer);
|
||||
Nan::SetPrototypeMethod(ctor, "streamPNGSync", StreamPNGSync);
|
||||
Nan::SetPrototypeMethod(ctor, "streamPDFSync", StreamPDFSync);
|
||||
#ifdef HAVE_JPEG
|
||||
Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
|
||||
#endif
|
||||
Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
|
||||
Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride);
|
||||
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
|
||||
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
|
||||
|
||||
Nan::SetTemplate(proto, "PNG_NO_FILTERS", Nan::New<Uint32>(PNG_NO_FILTERS));
|
||||
Nan::SetTemplate(proto, "PNG_FILTER_NONE", Nan::New<Uint32>(PNG_FILTER_NONE));
|
||||
Nan::SetTemplate(proto, "PNG_FILTER_SUB", Nan::New<Uint32>(PNG_FILTER_SUB));
|
||||
Nan::SetTemplate(proto, "PNG_FILTER_UP", Nan::New<Uint32>(PNG_FILTER_UP));
|
||||
Nan::SetTemplate(proto, "PNG_FILTER_AVG", Nan::New<Uint32>(PNG_FILTER_AVG));
|
||||
Nan::SetTemplate(proto, "PNG_FILTER_PAETH", Nan::New<Uint32>(PNG_FILTER_PAETH));
|
||||
Nan::SetTemplate(proto, "PNG_ALL_FILTERS", Nan::New<Uint32>(PNG_ALL_FILTERS));
|
||||
|
||||
Nan::Set(target, Nan::New("Canvas").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a Canvas with the given width and height.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Canvas::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
int width = 0, height = 0;
|
||||
canvas_type_t type = CANVAS_TYPE_IMAGE;
|
||||
if (info[0]->IsNumber()) width = info[0]->Uint32Value();
|
||||
if (info[1]->IsNumber()) height = info[1]->Uint32Value();
|
||||
if (info[2]->IsString()) type = !strcmp("pdf", *String::Utf8Value(info[2]))
|
||||
? CANVAS_TYPE_PDF
|
||||
: !strcmp("svg", *String::Utf8Value(info[2]))
|
||||
? CANVAS_TYPE_SVG
|
||||
: CANVAS_TYPE_IMAGE;
|
||||
Canvas *canvas = new Canvas(width, height, type);
|
||||
canvas->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get type string.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Canvas::GetType) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<String>(canvas->isPDF() ? "pdf" : canvas->isSVG() ? "svg" : "image").ToLocalChecked());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get stride.
|
||||
*/
|
||||
NAN_GETTER(Canvas::GetStride) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Canvas::GetWidth) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(canvas->width));
|
||||
}
|
||||
|
||||
/*
|
||||
* Set width.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Canvas::SetWidth) {
|
||||
if (value->IsNumber()) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
canvas->width = value->Uint32Value();
|
||||
canvas->resurface(info.This());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Canvas::GetHeight) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(canvas->height));
|
||||
}
|
||||
|
||||
/*
|
||||
* Set height.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Canvas::SetHeight) {
|
||||
if (value->IsNumber()) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
canvas->height = value->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;
|
||||
|
||||
if (closure->len + len > closure->max_len) {
|
||||
uint8_t *data;
|
||||
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) {
|
||||
#elif NODE_VERSION_AT_LEAST(0, 5, 4)
|
||||
void
|
||||
Canvas::EIO_ToBuffer(eio_req *req) {
|
||||
#else
|
||||
int
|
||||
Canvas::EIO_ToBuffer(eio_req *req) {
|
||||
#endif
|
||||
closure_t *closure = (closure_t *) req->data;
|
||||
|
||||
closure->status = canvas_write_to_png_stream(
|
||||
closure->canvas->surface()
|
||||
, toBuffer
|
||||
, closure);
|
||||
|
||||
#if !NODE_VERSION_AT_LEAST(0, 5, 4)
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* EIO after toBuffer callback.
|
||||
*/
|
||||
|
||||
#if NODE_VERSION_AT_LEAST(0, 6, 0)
|
||||
void
|
||||
Canvas::ToBufferAsyncAfter(uv_work_t *req) {
|
||||
#else
|
||||
int
|
||||
Canvas::EIO_AfterToBuffer(eio_req *req) {
|
||||
#endif
|
||||
|
||||
Nan::HandleScope scope;
|
||||
closure_t *closure = (closure_t *) req->data;
|
||||
#if NODE_VERSION_AT_LEAST(0, 6, 0)
|
||||
delete req;
|
||||
#else
|
||||
ev_unref(EV_DEFAULT_UC);
|
||||
#endif
|
||||
|
||||
if (closure->status) {
|
||||
Local<Value> argv[1] = { Canvas::Error(closure->status) };
|
||||
closure->pfn->Call(1, argv);
|
||||
} else {
|
||||
Local<Object> buf = Nan::CopyBuffer((char*)closure->data, closure->len).ToLocalChecked();
|
||||
memcpy(Buffer::Data(buf), closure->data, closure->len);
|
||||
Local<Value> argv[2] = { Nan::Null(), buf };
|
||||
closure->pfn->Call(2, argv);
|
||||
}
|
||||
|
||||
closure->canvas->Unref();
|
||||
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
|
||||
* callback function is passed.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Canvas::ToBuffer) {
|
||||
cairo_status_t status;
|
||||
uint32_t compression_level = 6;
|
||||
uint32_t filter = PNG_ALL_FILTERS;
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
|
||||
// TODO: async / move this out
|
||||
if (canvas->isPDF() || canvas->isSVG()) {
|
||||
cairo_surface_finish(canvas->surface());
|
||||
closure_t *closure = (closure_t *) canvas->closure();
|
||||
|
||||
Local<Object> buf = Nan::CopyBuffer((char*) closure->data, closure->len).ToLocalChecked();
|
||||
info.GetReturnValue().Set(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.Length() >= 1 && info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
|
||||
// Return raw ARGB data -- just a memcpy()
|
||||
cairo_surface_t *surface = canvas->surface();
|
||||
cairo_surface_flush(surface);
|
||||
const unsigned char *data = cairo_image_surface_get_data(surface);
|
||||
Local<Object> buf = Nan::CopyBuffer(reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
|
||||
info.GetReturnValue().Set(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
|
||||
if (!info[1]->IsUndefined()) {
|
||||
bool good = true;
|
||||
if (info[1]->IsNumber()) {
|
||||
compression_level = info[1]->Uint32Value();
|
||||
} else if (info[1]->IsString()) {
|
||||
if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
|
||||
compression_level = 0;
|
||||
} else {
|
||||
uint32_t tmp = info[1]->Uint32Value();
|
||||
if (tmp == 0) {
|
||||
good = false;
|
||||
} else {
|
||||
compression_level = tmp;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
good = false;
|
||||
}
|
||||
|
||||
if (good) {
|
||||
if (compression_level > 9) {
|
||||
return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
|
||||
}
|
||||
} else {
|
||||
return Nan::ThrowTypeError("Compression level must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!info[2]->IsUndefined()) {
|
||||
if (info[2]->IsUint32()) {
|
||||
filter = info[2]->Uint32Value();
|
||||
} else {
|
||||
return Nan::ThrowTypeError("Invalid filter value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Async
|
||||
if (info[0]->IsFunction()) {
|
||||
closure_t *closure = (closure_t *) malloc(sizeof(closure_t));
|
||||
status = closure_init(closure, canvas, compression_level, filter);
|
||||
|
||||
// ensure closure is ok
|
||||
if (status) {
|
||||
closure_destroy(closure);
|
||||
free(closure);
|
||||
return Nan::ThrowError(Canvas::Error(status));
|
||||
}
|
||||
|
||||
// TODO: only one callback fn in closure
|
||||
canvas->Ref();
|
||||
closure->pfn = new Nan::Callback(info[0].As<Function>());
|
||||
|
||||
#if NODE_VERSION_AT_LEAST(0, 6, 0)
|
||||
uv_work_t* req = new uv_work_t;
|
||||
req->data = closure;
|
||||
uv_queue_work(uv_default_loop(), req, ToBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
|
||||
#else
|
||||
eio_custom(EIO_ToBuffer, EIO_PRI_DEFAULT, EIO_AfterToBuffer, closure);
|
||||
ev_ref(EV_DEFAULT_UC);
|
||||
#endif
|
||||
|
||||
return;
|
||||
// Sync
|
||||
} else {
|
||||
closure_t closure;
|
||||
status = closure_init(&closure, canvas, compression_level, filter);
|
||||
|
||||
// ensure closure is ok
|
||||
if (status) {
|
||||
closure_destroy(&closure);
|
||||
return Nan::ThrowError(Canvas::Error(status));
|
||||
}
|
||||
|
||||
Nan::TryCatch try_catch;
|
||||
status = canvas_write_to_png_stream(canvas->surface(), toBuffer, &closure);
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
closure_destroy(&closure);
|
||||
try_catch.ReThrow();
|
||||
return;
|
||||
} else if (status) {
|
||||
closure_destroy(&closure);
|
||||
return Nan::ThrowError(Canvas::Error(status));
|
||||
} else {
|
||||
Local<Object> buf = Nan::CopyBuffer((char *)closure.data, closure.len).ToLocalChecked();
|
||||
closure_destroy(&closure);
|
||||
info.GetReturnValue().Set(buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Canvas::StreamPNG callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPNG(void *c, const uint8_t *data, unsigned len) {
|
||||
Nan::HandleScope scope;
|
||||
closure_t *closure = (closure_t *) c;
|
||||
Local<Object> buf = Nan::CopyBuffer((char *)data, len).ToLocalChecked();
|
||||
Local<Value> argv[3] = {
|
||||
Nan::Null()
|
||||
, buf
|
||||
, Nan::New<Number>(len) };
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure->fn, 3, argv);
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream PNG data synchronously.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Canvas::StreamPNGSync) {
|
||||
uint32_t compression_level = 6;
|
||||
uint32_t filter = PNG_ALL_FILTERS;
|
||||
// TODO: async as well
|
||||
if (!info[0]->IsFunction())
|
||||
return Nan::ThrowTypeError("callback function required");
|
||||
|
||||
if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
|
||||
if (!info[1]->IsUndefined()) {
|
||||
bool good = true;
|
||||
if (info[1]->IsNumber()) {
|
||||
compression_level = info[1]->Uint32Value();
|
||||
} else if (info[1]->IsString()) {
|
||||
if (info[1]->StrictEquals(Nan::New<String>("0").ToLocalChecked())) {
|
||||
compression_level = 0;
|
||||
} else {
|
||||
uint32_t tmp = info[1]->Uint32Value();
|
||||
if (tmp == 0) {
|
||||
good = false;
|
||||
} else {
|
||||
compression_level = tmp;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
good = false;
|
||||
}
|
||||
|
||||
if (good) {
|
||||
if (compression_level > 9) {
|
||||
return Nan::ThrowRangeError("Allowed compression levels lie in the range [0, 9].");
|
||||
}
|
||||
} else {
|
||||
return Nan::ThrowTypeError("Compression level must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!info[2]->IsUndefined()) {
|
||||
if (info[2]->IsUint32()) {
|
||||
filter = info[2]->Uint32Value();
|
||||
} else {
|
||||
return Nan::ThrowTypeError("Invalid filter value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
closure_t closure;
|
||||
closure.fn = Local<Function>::Cast(info[0]);
|
||||
closure.compression_level = compression_level;
|
||||
closure.filter = filter;
|
||||
|
||||
Nan::TryCatch try_catch;
|
||||
|
||||
cairo_status_t status = canvas_write_to_png_stream(canvas->surface(), streamPNG, &closure);
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
try_catch.ReThrow();
|
||||
return;
|
||||
} else if (status) {
|
||||
Local<Value> argv[1] = { Canvas::Error(status) };
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
|
||||
} else {
|
||||
Local<Value> argv[3] = {
|
||||
Nan::Null()
|
||||
, Nan::Null()
|
||||
, Nan::New<Uint32>(0) };
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)closure.fn, 1, argv);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Canvas::StreamPDF FreeCallback
|
||||
*/
|
||||
|
||||
void stream_pdf_free(char *, void *) {}
|
||||
|
||||
/*
|
||||
* Canvas::StreamPDF callback.
|
||||
*/
|
||||
|
||||
static cairo_status_t
|
||||
streamPDF(void *c, const uint8_t *data, unsigned len) {
|
||||
Nan::HandleScope scope;
|
||||
closure_t *closure = static_cast<closure_t *>(c);
|
||||
Local<Object> buf = Nan::NewBuffer(const_cast<char *>(reinterpret_cast<const char *>(data)), len, stream_pdf_free, 0).ToLocalChecked();
|
||||
Local<Value> argv[3] = {
|
||||
Nan::Null()
|
||||
, buf
|
||||
, Nan::New<Number>(len) };
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), closure->fn, 3, argv);
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
cairo_status_t canvas_write_to_pdf_stream(cairo_surface_t *surface, cairo_write_func_t write_func, void *closure) {
|
||||
closure_t *pdf_closure = static_cast<closure_t *>(closure);
|
||||
size_t whole_chunks = pdf_closure->len / PAGE_SIZE;
|
||||
size_t remainder = pdf_closure->len - whole_chunks * PAGE_SIZE;
|
||||
|
||||
for (size_t i = 0; i < whole_chunks; ++i) {
|
||||
write_func(pdf_closure, &pdf_closure->data[i * PAGE_SIZE], PAGE_SIZE);
|
||||
}
|
||||
|
||||
if (remainder) {
|
||||
write_func(pdf_closure, &pdf_closure->data[whole_chunks * PAGE_SIZE], remainder);
|
||||
}
|
||||
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream PDF data synchronously.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Canvas::StreamPDFSync) {
|
||||
if (!info[0]->IsFunction())
|
||||
return Nan::ThrowTypeError("callback function required");
|
||||
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.Holder());
|
||||
|
||||
if (!canvas->isPDF())
|
||||
return Nan::ThrowTypeError("wrong canvas type");
|
||||
|
||||
cairo_surface_finish(canvas->surface());
|
||||
|
||||
closure_t closure;
|
||||
closure.data = static_cast<closure_t *>(canvas->closure())->data;
|
||||
closure.len = static_cast<closure_t *>(canvas->closure())->len;
|
||||
closure.fn = info[0].As<Function>();
|
||||
|
||||
Nan::TryCatch try_catch;
|
||||
|
||||
cairo_status_t status = canvas_write_to_pdf_stream(canvas->surface(), streamPDF, &closure);
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
try_catch.ReThrow();
|
||||
} else if (status) {
|
||||
Local<Value> error = Canvas::Error(status);
|
||||
Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 1, &error);
|
||||
} else {
|
||||
Local<Value> argv[3] = {
|
||||
Nan::Null()
|
||||
, Nan::Null()
|
||||
, Nan::New<Uint32>(0) };
|
||||
Nan::Call(closure.fn, Nan::GetCurrentContext()->Global(), 3, argv);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stream JPEG data synchronously.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
NAN_METHOD(Canvas::StreamJPEGSync) {
|
||||
// TODO: async as well
|
||||
if (!info[0]->IsNumber())
|
||||
return Nan::ThrowTypeError("buffer size required");
|
||||
if (!info[1]->IsNumber())
|
||||
return Nan::ThrowTypeError("quality setting required");
|
||||
if (!info[2]->IsBoolean())
|
||||
return Nan::ThrowTypeError("progressive setting required");
|
||||
if (!info[3]->IsFunction())
|
||||
return Nan::ThrowTypeError("callback function required");
|
||||
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
|
||||
closure_t closure;
|
||||
closure.fn = Local<Function>::Cast(info[3]);
|
||||
|
||||
Nan::TryCatch try_catch;
|
||||
write_to_jpeg_stream(canvas->surface(), info[0]->NumberValue(), info[1]->NumberValue(), info[2]->BooleanValue(), &closure);
|
||||
|
||||
if (try_catch.HasCaught()) {
|
||||
try_catch.ReThrow();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Initialize cairo surface.
|
||||
*/
|
||||
|
||||
Canvas::Canvas(int w, int h, canvas_type_t t): Nan::ObjectWrap() {
|
||||
type = t;
|
||||
width = w;
|
||||
height = h;
|
||||
_surface = NULL;
|
||||
_closure = NULL;
|
||||
|
||||
if (CANVAS_TYPE_PDF == t) {
|
||||
_closure = malloc(sizeof(closure_t));
|
||||
assert(_closure);
|
||||
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 if (CANVAS_TYPE_SVG == t) {
|
||||
_closure = malloc(sizeof(closure_t));
|
||||
assert(_closure);
|
||||
cairo_status_t status = closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
|
||||
assert(status == CAIRO_STATUS_SUCCESS);
|
||||
_surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, w, h);
|
||||
} else {
|
||||
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
|
||||
assert(_surface);
|
||||
Nan::AdjustExternalMemory(nBytes());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy cairo surface.
|
||||
*/
|
||||
|
||||
Canvas::~Canvas() {
|
||||
switch (type) {
|
||||
case CANVAS_TYPE_PDF:
|
||||
case CANVAS_TYPE_SVG:
|
||||
cairo_surface_finish(_surface);
|
||||
closure_destroy((closure_t *) _closure);
|
||||
free(_closure);
|
||||
cairo_surface_destroy(_surface);
|
||||
break;
|
||||
case CANVAS_TYPE_IMAGE:
|
||||
int oldNBytes = nBytes();
|
||||
cairo_surface_destroy(_surface);
|
||||
Nan::AdjustExternalMemory(-oldNBytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-alloc the surface, destroying the previous.
|
||||
*/
|
||||
|
||||
void
|
||||
Canvas::resurface(Local<Object> canvas) {
|
||||
Nan::HandleScope scope;
|
||||
Local<Value> context;
|
||||
switch (type) {
|
||||
case CANVAS_TYPE_PDF:
|
||||
cairo_pdf_surface_set_size(_surface, width, height);
|
||||
break;
|
||||
case CANVAS_TYPE_SVG:
|
||||
// Re-surface
|
||||
cairo_surface_finish(_surface);
|
||||
closure_destroy((closure_t *) _closure);
|
||||
cairo_surface_destroy(_surface);
|
||||
closure_init((closure_t *) _closure, this, 0, PNG_NO_FILTERS);
|
||||
_surface = cairo_svg_surface_create_for_stream(toBuffer, _closure, width, height);
|
||||
|
||||
// Reset context
|
||||
context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
|
||||
if (!context->IsUndefined()) {
|
||||
Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
|
||||
cairo_t *prev = context2d->context();
|
||||
context2d->setContext(cairo_create(surface()));
|
||||
cairo_destroy(prev);
|
||||
}
|
||||
break;
|
||||
case CANVAS_TYPE_IMAGE:
|
||||
// Re-surface
|
||||
size_t oldNBytes = nBytes();
|
||||
cairo_surface_destroy(_surface);
|
||||
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
||||
Nan::AdjustExternalMemory(nBytes() - oldNBytes);
|
||||
|
||||
// Reset context
|
||||
context = canvas->Get(Nan::New<String>("context").ToLocalChecked());
|
||||
if (!context->IsUndefined()) {
|
||||
Context2d *context2d = Nan::ObjectWrap::Unwrap<Context2d>(context->ToObject());
|
||||
cairo_t *prev = context2d->context();
|
||||
context2d->setContext(cairo_create(surface()));
|
||||
cairo_destroy(prev);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct an Error from the given cairo status.
|
||||
*/
|
||||
|
||||
Local<Value>
|
||||
Canvas::Error(cairo_status_t status) {
|
||||
return Exception::Error(Nan::New<String>(cairo_status_to_string(status)).ToLocalChecked());
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
|
||||
//
|
||||
// Canvas.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_CANVAS_H__
|
||||
#define __NODE_CANVAS_H__
|
||||
|
||||
#include <v8.h>
|
||||
#include <node.h>
|
||||
#include <node_object_wrap.h>
|
||||
#include <node_version.h>
|
||||
|
||||
#if HAVE_PANGO
|
||||
#include <pango/pangocairo.h>
|
||||
#else
|
||||
#include <cairo.h>
|
||||
#endif
|
||||
|
||||
#include <nan.h>
|
||||
|
||||
using namespace v8;
|
||||
using namespace node;
|
||||
|
||||
/*
|
||||
* Maxmimum states per context.
|
||||
* TODO: remove/resize
|
||||
*/
|
||||
|
||||
#ifndef CANVAS_MAX_STATES
|
||||
#define CANVAS_MAX_STATES 64
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Canvas types.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
CANVAS_TYPE_IMAGE,
|
||||
CANVAS_TYPE_PDF,
|
||||
CANVAS_TYPE_SVG
|
||||
} canvas_type_t;
|
||||
|
||||
/*
|
||||
* Canvas.
|
||||
*/
|
||||
|
||||
class Canvas: public Nan::ObjectWrap {
|
||||
public:
|
||||
int width;
|
||||
int height;
|
||||
canvas_type_t type;
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(ToBuffer);
|
||||
static NAN_GETTER(GetType);
|
||||
static NAN_GETTER(GetStride);
|
||||
static NAN_GETTER(GetWidth);
|
||||
static NAN_GETTER(GetHeight);
|
||||
static NAN_SETTER(SetWidth);
|
||||
static NAN_SETTER(SetHeight);
|
||||
static NAN_METHOD(StreamPNGSync);
|
||||
static NAN_METHOD(StreamPDFSync);
|
||||
static NAN_METHOD(StreamJPEGSync);
|
||||
static Local<Value> Error(cairo_status_t status);
|
||||
#if NODE_VERSION_AT_LEAST(0, 6, 0)
|
||||
static void ToBufferAsync(uv_work_t *req);
|
||||
static void ToBufferAsyncAfter(uv_work_t *req);
|
||||
#else
|
||||
static
|
||||
#if NODE_VERSION_AT_LEAST(0, 5, 4)
|
||||
void
|
||||
#else
|
||||
int
|
||||
#endif
|
||||
EIO_ToBuffer(eio_req *req);
|
||||
static int EIO_AfterToBuffer(eio_req *req);
|
||||
#endif
|
||||
|
||||
inline bool isPDF(){ return CANVAS_TYPE_PDF == type; }
|
||||
inline bool isSVG(){ return CANVAS_TYPE_SVG == type; }
|
||||
inline cairo_surface_t *surface(){ return _surface; }
|
||||
inline void *closure(){ return _closure; }
|
||||
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
|
||||
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
|
||||
inline int nBytes(){ return height * stride(); }
|
||||
Canvas(int width, int height, canvas_type_t type);
|
||||
void resurface(Local<Object> canvas);
|
||||
|
||||
private:
|
||||
~Canvas();
|
||||
cairo_surface_t *_surface;
|
||||
void *_closure;
|
||||
};
|
||||
|
||||
#endif
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
|
||||
//
|
||||
// Gradient.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "color.h"
|
||||
#include "Canvas.h"
|
||||
#include "CanvasGradient.h"
|
||||
|
||||
Nan::Persistent<FunctionTemplate> Gradient::constructor;
|
||||
|
||||
/*
|
||||
* Initialize CanvasGradient.
|
||||
*/
|
||||
|
||||
void
|
||||
Gradient::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// Constructor
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Gradient::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("CanvasGradient").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Nan::SetPrototypeMethod(ctor, "addColorStop", AddColorStop);
|
||||
Nan::Set(target, Nan::New("CanvasGradient").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasGradient.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Gradient::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
// Linear
|
||||
if (4 == info.Length()) {
|
||||
Gradient *grad = new Gradient(
|
||||
info[0]->NumberValue()
|
||||
, info[1]->NumberValue()
|
||||
, info[2]->NumberValue()
|
||||
, info[3]->NumberValue());
|
||||
grad->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
return;
|
||||
}
|
||||
|
||||
// Radial
|
||||
if (6 == info.Length()) {
|
||||
Gradient *grad = new Gradient(
|
||||
info[0]->NumberValue()
|
||||
, info[1]->NumberValue()
|
||||
, info[2]->NumberValue()
|
||||
, info[3]->NumberValue()
|
||||
, info[4]->NumberValue()
|
||||
, info[5]->NumberValue());
|
||||
grad->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
return;
|
||||
}
|
||||
|
||||
return Nan::ThrowTypeError("invalid arguments");
|
||||
}
|
||||
|
||||
/*
|
||||
* Add color stop.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Gradient::AddColorStop) {
|
||||
if (!info[0]->IsNumber())
|
||||
return Nan::ThrowTypeError("offset required");
|
||||
if (!info[1]->IsString())
|
||||
return Nan::ThrowTypeError("color string required");
|
||||
|
||||
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(info.This());
|
||||
short ok;
|
||||
String::Utf8Value str(info[1]);
|
||||
uint32_t rgba = rgba_from_string(*str, &ok);
|
||||
|
||||
if (ok) {
|
||||
rgba_t color = rgba_create(rgba);
|
||||
cairo_pattern_add_color_stop_rgba(
|
||||
grad->pattern()
|
||||
, info[0]->NumberValue()
|
||||
, color.r
|
||||
, color.g
|
||||
, color.b
|
||||
, color.a);
|
||||
} else {
|
||||
return Nan::ThrowTypeError("parse color failed");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize linear gradient.
|
||||
*/
|
||||
|
||||
Gradient::Gradient(double x0, double y0, double x1, double y1) {
|
||||
_pattern = cairo_pattern_create_linear(x0, y0, x1, y1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize radial gradient.
|
||||
*/
|
||||
|
||||
Gradient::Gradient(double x0, double y0, double r0, double x1, double y1, double r1) {
|
||||
_pattern = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the pattern.
|
||||
*/
|
||||
|
||||
Gradient::~Gradient() {
|
||||
cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
|
||||
//
|
||||
// CanvasGradient.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_GRADIENT_H__
|
||||
#define __NODE_GRADIENT_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
|
||||
class Gradient: public Nan::ObjectWrap {
|
||||
public:
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(AddColorStop);
|
||||
Gradient(double x0, double y0, double x1, double y1);
|
||||
Gradient(double x0, double y0, double r0, double x1, double y1, double r1);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
|
||||
private:
|
||||
~Gradient();
|
||||
cairo_pattern_t *_pattern;
|
||||
};
|
||||
|
||||
#endif
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
|
||||
//
|
||||
// Pattern.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "Image.h"
|
||||
#include "CanvasPattern.h"
|
||||
|
||||
Nan::Persistent<FunctionTemplate> Pattern::constructor;
|
||||
|
||||
/*
|
||||
* Initialize CanvasPattern.
|
||||
*/
|
||||
|
||||
void
|
||||
Pattern::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// Constructor
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Pattern::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
|
||||
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("CanvasPattern").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Nan::Set(target, Nan::New("CanvasPattern").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new CanvasPattern.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Pattern::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
cairo_surface_t *surface;
|
||||
|
||||
Local<Object> obj = info[0]->ToObject();
|
||||
|
||||
// Image
|
||||
if (Nan::New(Image::constructor)->HasInstance(obj)) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(obj);
|
||||
if (!img->isComplete()) {
|
||||
return Nan::ThrowError("Image given has not completed loading");
|
||||
}
|
||||
surface = img->surface();
|
||||
|
||||
// Canvas
|
||||
} else if (Nan::New(Canvas::constructor)->HasInstance(obj)) {
|
||||
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(obj);
|
||||
surface = canvas->surface();
|
||||
|
||||
// Invalid
|
||||
} else {
|
||||
return Nan::ThrowTypeError("Image or Canvas expected");
|
||||
}
|
||||
|
||||
Pattern *pattern = new Pattern(surface);
|
||||
pattern->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize linear gradient.
|
||||
*/
|
||||
|
||||
Pattern::Pattern(cairo_surface_t *surface) {
|
||||
_pattern = cairo_pattern_create_for_surface(surface);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy the pattern.
|
||||
*/
|
||||
|
||||
Pattern::~Pattern() {
|
||||
cairo_pattern_destroy(_pattern);
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
|
||||
//
|
||||
// CanvasPattern.h
|
||||
//
|
||||
// Copyright (c) 2011 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_PATTERN_H__
|
||||
#define __NODE_PATTERN_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
|
||||
class Pattern: public Nan::ObjectWrap {
|
||||
public:
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
Pattern(cairo_surface_t *surface);
|
||||
inline cairo_pattern_t *pattern(){ return _pattern; }
|
||||
|
||||
private:
|
||||
~Pattern();
|
||||
// TODO REPEAT/REPEAT_X/REPEAT_Y
|
||||
cairo_pattern_t *_pattern;
|
||||
};
|
||||
|
||||
#endif
|
||||
+2475
File diff suppressed because it is too large
Load Diff
+185
@@ -0,0 +1,185 @@
|
||||
|
||||
//
|
||||
// CanvasRenderingContext2d.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_CONTEXT2D_H__
|
||||
#define __NODE_CONTEXT2D_H__
|
||||
|
||||
#include "color.h"
|
||||
#include "Canvas.h"
|
||||
#include "CanvasGradient.h"
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include <ft2build.h>
|
||||
#include <cairo-ft.h>
|
||||
#include FT_FREETYPE_H
|
||||
#endif
|
||||
|
||||
#include <vector>
|
||||
using namespace std;
|
||||
|
||||
typedef enum {
|
||||
TEXT_DRAW_PATHS,
|
||||
TEXT_DRAW_GLYPHS
|
||||
} canvas_draw_mode_t;
|
||||
|
||||
/*
|
||||
* State struct.
|
||||
*
|
||||
* Used in conjunction with Save() / Restore() since
|
||||
* cairo's gstate maintains only a single source pattern at a time.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
rgba_t fill;
|
||||
rgba_t stroke;
|
||||
cairo_filter_t patternQuality;
|
||||
cairo_pattern_t *fillPattern;
|
||||
cairo_pattern_t *strokePattern;
|
||||
cairo_pattern_t *fillGradient;
|
||||
cairo_pattern_t *strokeGradient;
|
||||
float globalAlpha;
|
||||
short textAlignment;
|
||||
short textBaseline;
|
||||
rgba_t shadow;
|
||||
int shadowBlur;
|
||||
double shadowOffsetX;
|
||||
double shadowOffsetY;
|
||||
canvas_draw_mode_t textDrawingMode;
|
||||
|
||||
#if HAVE_PANGO
|
||||
PangoWeight fontWeight;
|
||||
PangoStyle fontStyle;
|
||||
double fontSize;
|
||||
char *fontFamily;
|
||||
#endif
|
||||
|
||||
} canvas_state_t;
|
||||
|
||||
#if HAVE_PANGO
|
||||
void state_assign_fontFamily(canvas_state_t *state, const char *str);
|
||||
#endif
|
||||
|
||||
class Context2d: public Nan::ObjectWrap {
|
||||
public:
|
||||
short stateno;
|
||||
canvas_state_t *states[CANVAS_MAX_STATES];
|
||||
canvas_state_t *state;
|
||||
Context2d(Canvas *canvas);
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_METHOD(DrawImage);
|
||||
static NAN_METHOD(PutImageData);
|
||||
static NAN_METHOD(Save);
|
||||
static NAN_METHOD(Restore);
|
||||
static NAN_METHOD(Rotate);
|
||||
static NAN_METHOD(Translate);
|
||||
static NAN_METHOD(Scale);
|
||||
static NAN_METHOD(Transform);
|
||||
static NAN_METHOD(ResetTransform);
|
||||
static NAN_METHOD(IsPointInPath);
|
||||
static NAN_METHOD(BeginPath);
|
||||
static NAN_METHOD(ClosePath);
|
||||
static NAN_METHOD(AddPage);
|
||||
static NAN_METHOD(Clip);
|
||||
static NAN_METHOD(Fill);
|
||||
static NAN_METHOD(Stroke);
|
||||
static NAN_METHOD(FillText);
|
||||
static NAN_METHOD(StrokeText);
|
||||
static NAN_METHOD(SetFont);
|
||||
#ifdef HAVE_FREETYPE
|
||||
static NAN_METHOD(SetFontFace);
|
||||
#endif
|
||||
static NAN_METHOD(SetFillColor);
|
||||
static NAN_METHOD(SetStrokeColor);
|
||||
static NAN_METHOD(SetFillPattern);
|
||||
static NAN_METHOD(SetStrokePattern);
|
||||
static NAN_METHOD(SetTextBaseline);
|
||||
static NAN_METHOD(SetTextAlignment);
|
||||
static NAN_METHOD(SetLineDash);
|
||||
static NAN_METHOD(GetLineDash);
|
||||
static NAN_METHOD(MeasureText);
|
||||
static NAN_METHOD(BezierCurveTo);
|
||||
static NAN_METHOD(QuadraticCurveTo);
|
||||
static NAN_METHOD(LineTo);
|
||||
static NAN_METHOD(MoveTo);
|
||||
static NAN_METHOD(FillRect);
|
||||
static NAN_METHOD(StrokeRect);
|
||||
static NAN_METHOD(ClearRect);
|
||||
static NAN_METHOD(Rect);
|
||||
static NAN_METHOD(Arc);
|
||||
static NAN_METHOD(ArcTo);
|
||||
static NAN_METHOD(GetImageData);
|
||||
static NAN_GETTER(GetPatternQuality);
|
||||
static NAN_GETTER(GetGlobalCompositeOperation);
|
||||
static NAN_GETTER(GetGlobalAlpha);
|
||||
static NAN_GETTER(GetShadowColor);
|
||||
static NAN_GETTER(GetFillColor);
|
||||
static NAN_GETTER(GetStrokeColor);
|
||||
static NAN_GETTER(GetMiterLimit);
|
||||
static NAN_GETTER(GetLineCap);
|
||||
static NAN_GETTER(GetLineJoin);
|
||||
static NAN_GETTER(GetLineWidth);
|
||||
static NAN_GETTER(GetLineDashOffset);
|
||||
static NAN_GETTER(GetShadowOffsetX);
|
||||
static NAN_GETTER(GetShadowOffsetY);
|
||||
static NAN_GETTER(GetShadowBlur);
|
||||
static NAN_GETTER(GetAntiAlias);
|
||||
static NAN_GETTER(GetTextDrawingMode);
|
||||
static NAN_GETTER(GetFilter);
|
||||
static NAN_SETTER(SetPatternQuality);
|
||||
static NAN_SETTER(SetGlobalCompositeOperation);
|
||||
static NAN_SETTER(SetGlobalAlpha);
|
||||
static NAN_SETTER(SetShadowColor);
|
||||
static NAN_SETTER(SetMiterLimit);
|
||||
static NAN_SETTER(SetLineCap);
|
||||
static NAN_SETTER(SetLineJoin);
|
||||
static NAN_SETTER(SetLineWidth);
|
||||
static NAN_SETTER(SetLineDashOffset);
|
||||
static NAN_SETTER(SetShadowOffsetX);
|
||||
static NAN_SETTER(SetShadowOffsetY);
|
||||
static NAN_SETTER(SetShadowBlur);
|
||||
static NAN_SETTER(SetAntiAlias);
|
||||
static NAN_SETTER(SetTextDrawingMode);
|
||||
static NAN_SETTER(SetFilter);
|
||||
inline void setContext(cairo_t *ctx) { _context = ctx; }
|
||||
inline cairo_t *context(){ return _context; }
|
||||
inline Canvas *canvas(){ return _canvas; }
|
||||
inline bool hasShadow();
|
||||
void inline setSourceRGBA(rgba_t color);
|
||||
void inline setSourceRGBA(cairo_t *ctx, rgba_t color);
|
||||
void setTextPath(const char *str, double x, double y);
|
||||
void blur(cairo_surface_t *surface, int radius);
|
||||
void shadow(void (fn)(cairo_t *cr));
|
||||
void shadowStart();
|
||||
void shadowApply();
|
||||
void savePath();
|
||||
void restorePath();
|
||||
void saveState();
|
||||
void restoreState();
|
||||
void inline setFillRule(v8::Local<v8::Value> value);
|
||||
void fill(bool preserve = false);
|
||||
void stroke(bool preserve = false);
|
||||
void save();
|
||||
void restore();
|
||||
|
||||
#if HAVE_PANGO
|
||||
void setFontFromState();
|
||||
inline PangoLayout *layout(){ return _layout; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
~Context2d();
|
||||
Canvas *_canvas;
|
||||
cairo_t *_context;
|
||||
cairo_path_t *_path;
|
||||
#if HAVE_PANGO
|
||||
PangoLayout *_layout;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// FontFace.cc
|
||||
//
|
||||
// Copyright (c) 2012 Julian Viereck <julian.viereck@gmail.com>
|
||||
//
|
||||
|
||||
#include "FontFace.h"
|
||||
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
Nan::Persistent<FunctionTemplate> FontFace::constructor;
|
||||
|
||||
/*
|
||||
* Destroy ft_face.
|
||||
*/
|
||||
|
||||
FontFace::~FontFace() {
|
||||
// Decrement extra reference count added in ::New(...).
|
||||
// Once there is no reference left to crFace, cairo will release the
|
||||
// free type font face as well.
|
||||
cairo_font_face_destroy(_crFace);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize FontFace.
|
||||
*/
|
||||
|
||||
void
|
||||
FontFace::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// Constructor
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(FontFace::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("FontFace").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Nan::Set(target, Nan::New("FontFace").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new FontFace object.
|
||||
*/
|
||||
|
||||
FT_Library library; /* handle to library */
|
||||
|
||||
bool FontFace::_initLibrary = true;
|
||||
static cairo_user_data_key_t key;
|
||||
|
||||
/*
|
||||
* Initialize a new FontFace.
|
||||
*/
|
||||
|
||||
NAN_METHOD(FontFace::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
if (!info[0]->IsString()
|
||||
|| !info[1]->IsNumber()) {
|
||||
return Nan::ThrowError("Wrong argument types passed to FontFace constructor");
|
||||
}
|
||||
|
||||
String::Utf8Value filePath(info[0]);
|
||||
int faceIdx = int(info[1]->NumberValue());
|
||||
|
||||
FT_Face ftFace;
|
||||
FT_Error ftError;
|
||||
cairo_font_face_t *crFace;
|
||||
|
||||
if (_initLibrary) {
|
||||
_initLibrary = false;
|
||||
ftError = FT_Init_FreeType(&library);
|
||||
if (ftError) {
|
||||
return Nan::ThrowError("Could not load library");
|
||||
}
|
||||
}
|
||||
|
||||
// Create new freetype font face.
|
||||
ftError = FT_New_Face(library, *filePath, faceIdx, &ftFace);
|
||||
if (ftError) {
|
||||
return Nan::ThrowError("Could not load font file");
|
||||
}
|
||||
|
||||
#if HAVE_PANGO
|
||||
// Load the font file in fontconfig
|
||||
FcBool ok = FcConfigAppFontAddFile(FcConfigGetCurrent(), (FcChar8 *)(*filePath));
|
||||
if (!ok) {
|
||||
return Nan::ThrowError("Could not load font in FontConfig");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Create new cairo font face.
|
||||
crFace = cairo_ft_font_face_create_for_ft_face(ftFace, 0);
|
||||
|
||||
// If the cairo font face is released, release the FreeType font face as well.
|
||||
int status = cairo_font_face_set_user_data (crFace, &key,
|
||||
ftFace, (cairo_destroy_func_t) FT_Done_Face);
|
||||
if (status) {
|
||||
cairo_font_face_destroy (crFace);
|
||||
FT_Done_Face (ftFace);
|
||||
return Nan::ThrowError("Failed to setup cairo font face user data");
|
||||
}
|
||||
|
||||
// Explicit reference count the cairo font face. Otherwise the font face might
|
||||
// get released by cairo although the JS font face object is still alive.
|
||||
cairo_font_face_reference(crFace);
|
||||
|
||||
FontFace *face = new FontFace(crFace);
|
||||
face->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// FontFace.h
|
||||
//
|
||||
// Copyright (c) 2012 Julian Viereck <julian.viereck@gmail.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_TRUE_TYPE_FONT_FACE_H__
|
||||
#define __NODE_TRUE_TYPE_FONT_FACE_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include <cairo-ft.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
class FontFace: public Nan::ObjectWrap {
|
||||
public:
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
FontFace(cairo_font_face_t *crFace)
|
||||
:_crFace(crFace) {}
|
||||
|
||||
inline cairo_font_face_t *cairoFace(){ return _crFace; }
|
||||
private:
|
||||
~FontFace();
|
||||
cairo_font_face_t *_crFace;
|
||||
static bool _initLibrary;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+974
@@ -0,0 +1,974 @@
|
||||
//
|
||||
// Image.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "Canvas.h"
|
||||
#include "Image.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <node_buffer.h>
|
||||
|
||||
#ifdef HAVE_GIF
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
unsigned len;
|
||||
unsigned pos;
|
||||
} gif_data_t;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Read closure used by loadFromBuffer.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
unsigned len;
|
||||
uint8_t *buf;
|
||||
} read_closure_t;
|
||||
|
||||
Nan::Persistent<FunctionTemplate> Image::constructor;
|
||||
|
||||
/*
|
||||
* Initialize Image.
|
||||
*/
|
||||
|
||||
void
|
||||
Image::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(Image::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("Image").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
|
||||
Nan::SetAccessor(proto, Nan::New("source").ToLocalChecked(), GetSource, SetSource);
|
||||
Nan::SetAccessor(proto, Nan::New("complete").ToLocalChecked(), GetComplete);
|
||||
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
|
||||
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
|
||||
Nan::SetAccessor(proto, Nan::New("onload").ToLocalChecked(), GetOnload, SetOnload);
|
||||
Nan::SetAccessor(proto, Nan::New("onerror").ToLocalChecked(), GetOnerror, SetOnerror);
|
||||
#if CAIRO_VERSION_MINOR >= 10
|
||||
Nan::SetAccessor(proto, Nan::New("dataMode").ToLocalChecked(), GetDataMode, SetDataMode);
|
||||
ctor->Set(Nan::New("MODE_IMAGE").ToLocalChecked(), Nan::New<Number>(DATA_IMAGE));
|
||||
ctor->Set(Nan::New("MODE_MIME").ToLocalChecked(), Nan::New<Number>(DATA_MIME));
|
||||
#endif
|
||||
Nan::Set(target, Nan::New("Image").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new Image.
|
||||
*/
|
||||
|
||||
NAN_METHOD(Image::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
Image *img = new Image;
|
||||
img->data_mode = DATA_IMAGE;
|
||||
img->Wrap(info.This());
|
||||
info.GetReturnValue().Set(info.This());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get complete boolean.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetComplete) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Boolean>(Image::COMPLETE == img->state));
|
||||
}
|
||||
|
||||
#if CAIRO_VERSION_MINOR >= 10
|
||||
|
||||
/*
|
||||
* Get dataMode.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetDataMode) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(img->data_mode));
|
||||
}
|
||||
|
||||
/*
|
||||
* Set dataMode.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Image::SetDataMode) {
|
||||
if (value->IsNumber()) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
int mode = value->Uint32Value();
|
||||
img->data_mode = (data_mode_t) mode;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetWidth) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(img->width));
|
||||
}
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetHeight) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(img->height));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get src path.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetSource) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<String>(img->filename ? img->filename : "").ToLocalChecked());
|
||||
}
|
||||
|
||||
/*
|
||||
* Clean up assets and variables.
|
||||
*/
|
||||
|
||||
void
|
||||
Image::clearData() {
|
||||
if (_surface) {
|
||||
cairo_surface_destroy(_surface);
|
||||
Nan::AdjustExternalMemory(-_data_len);
|
||||
_data_len = 0;
|
||||
_surface = NULL;
|
||||
}
|
||||
|
||||
free(_data);
|
||||
_data = NULL;
|
||||
|
||||
free(filename);
|
||||
filename = NULL;
|
||||
|
||||
width = height = 0;
|
||||
state = DEFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set src path.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Image::SetSource) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
cairo_status_t status = CAIRO_STATUS_READ_ERROR;
|
||||
|
||||
img->clearData();
|
||||
|
||||
// url string
|
||||
if (value->IsString()) {
|
||||
String::Utf8Value src(value);
|
||||
if (img->filename) free(img->filename);
|
||||
img->filename = strdup(*src);
|
||||
status = img->load();
|
||||
// Buffer
|
||||
} else if (Buffer::HasInstance(value)) {
|
||||
uint8_t *buf = (uint8_t *) Buffer::Data(value->ToObject());
|
||||
unsigned len = Buffer::Length(value->ToObject());
|
||||
status = img->loadFromBuffer(buf, len);
|
||||
}
|
||||
|
||||
// check status
|
||||
if (status) {
|
||||
img->error(Canvas::Error(status));
|
||||
} else {
|
||||
img->loaded();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load image data from `buf` by sniffing
|
||||
* the bytes to determine format.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadFromBuffer(uint8_t *buf, unsigned len) {
|
||||
uint8_t data[4] = {0};
|
||||
memcpy(data, buf, (len < 4 ? len : 4) * sizeof(uint8_t));
|
||||
|
||||
if (isPNG(data)) return loadPNGFromBuffer(buf);
|
||||
#ifdef HAVE_GIF
|
||||
if (isGIF(data)) return loadGIFFromBuffer(buf, len);
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
#if CAIRO_VERSION_MINOR < 10
|
||||
if (isJPEG(data)) return loadJPEGFromBuffer(buf, len);
|
||||
#else
|
||||
if (isJPEG(data)) {
|
||||
if (DATA_IMAGE == data_mode) return loadJPEGFromBuffer(buf, len);
|
||||
if (DATA_MIME == data_mode) return decodeJPEGBufferIntoMimeSurface(buf, len);
|
||||
if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
||||
cairo_status_t status;
|
||||
status = loadJPEGFromBuffer(buf, len);
|
||||
if (status) return status;
|
||||
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load PNG data from `buf`.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadPNGFromBuffer(uint8_t *buf) {
|
||||
read_closure_t closure;
|
||||
closure.len = 0;
|
||||
closure.buf = buf;
|
||||
_surface = cairo_image_surface_create_from_png_stream(readPNG, &closure);
|
||||
cairo_status_t status = cairo_surface_status(_surface);
|
||||
if (status) return status;
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read PNG data.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::readPNG(void *c, uint8_t *data, unsigned int len) {
|
||||
read_closure_t *closure = (read_closure_t *) c;
|
||||
memcpy(data, closure->buf + closure->len, len);
|
||||
closure->len += len;
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get onload callback.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetOnload) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
if (img->onload) {
|
||||
info.GetReturnValue().Set(img->onload->GetFunction());
|
||||
} else {
|
||||
info.GetReturnValue().SetNull();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set onload callback.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Image::SetOnload) {
|
||||
if (value->IsFunction()) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
img->onload = new Nan::Callback(value.As<Function>());
|
||||
} else if (value->IsNull()) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
if (img->onload) {
|
||||
delete img->onload;
|
||||
}
|
||||
img->onload = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get onerror callback.
|
||||
*/
|
||||
|
||||
NAN_GETTER(Image::GetOnerror) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
if (img->onerror) {
|
||||
info.GetReturnValue().Set(img->onerror->GetFunction());
|
||||
} else {
|
||||
info.GetReturnValue().SetNull();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set onerror callback.
|
||||
*/
|
||||
|
||||
NAN_SETTER(Image::SetOnerror) {
|
||||
if (value->IsFunction()) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
img->onerror = new Nan::Callback(value.As<Function>());
|
||||
} else if (value->IsNull()) {
|
||||
Image *img = Nan::ObjectWrap::Unwrap<Image>(info.This());
|
||||
if (img->onerror) {
|
||||
delete img->onerror;
|
||||
}
|
||||
img->onerror = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new Image.
|
||||
*/
|
||||
|
||||
Image::Image() {
|
||||
filename = NULL;
|
||||
_data = NULL;
|
||||
_data_len = 0;
|
||||
_surface = NULL;
|
||||
width = height = 0;
|
||||
state = DEFAULT;
|
||||
onload = NULL;
|
||||
onerror = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroy image and associated surface.
|
||||
*/
|
||||
|
||||
Image::~Image() {
|
||||
clearData();
|
||||
|
||||
if (onerror) {
|
||||
delete onerror;
|
||||
onerror = NULL;
|
||||
}
|
||||
|
||||
if (onload) {
|
||||
delete onload;
|
||||
onload = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiate image loading.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::load() {
|
||||
if (LOADING != state) {
|
||||
state = LOADING;
|
||||
return loadSurface();
|
||||
}
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke onload (when assigned) and assign dimensions.
|
||||
*/
|
||||
|
||||
void
|
||||
Image::loaded() {
|
||||
Nan::HandleScope scope;
|
||||
state = COMPLETE;
|
||||
|
||||
width = cairo_image_surface_get_width(_surface);
|
||||
height = cairo_image_surface_get_height(_surface);
|
||||
_data_len = height * cairo_image_surface_get_stride(_surface);
|
||||
Nan::AdjustExternalMemory(_data_len);
|
||||
|
||||
if (onload != NULL) {
|
||||
onload->Call(0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke onerror (when assigned) with the given err.
|
||||
*/
|
||||
|
||||
void
|
||||
Image::error(Local<Value> err) {
|
||||
Nan::HandleScope scope;
|
||||
if (onerror != NULL) {
|
||||
Local<Value> argv[1] = { err };
|
||||
onerror->Call(1, argv);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Load cairo surface from the image src.
|
||||
*
|
||||
* TODO: support more formats
|
||||
* TODO: use node IO or at least thread pool
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadSurface() {
|
||||
FILE *stream = fopen(filename, "rb");
|
||||
if (!stream) return CAIRO_STATUS_READ_ERROR;
|
||||
uint8_t buf[5];
|
||||
if (1 != fread(&buf, 5, 1, stream)) {
|
||||
fclose(stream);
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
fseek(stream, 0, SEEK_SET);
|
||||
|
||||
// png
|
||||
if (isPNG(buf)) {
|
||||
fclose(stream);
|
||||
return loadPNG();
|
||||
}
|
||||
|
||||
// gif
|
||||
#ifdef HAVE_GIF
|
||||
if (isGIF(buf)) return loadGIF(stream);
|
||||
#endif
|
||||
|
||||
// jpeg
|
||||
#ifdef HAVE_JPEG
|
||||
if (isJPEG(buf)) return loadJPEG(stream);
|
||||
#endif
|
||||
|
||||
fclose(stream);
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load PNG.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadPNG() {
|
||||
_surface = cairo_image_surface_create_from_png(filename);
|
||||
return cairo_surface_status(_surface);
|
||||
}
|
||||
|
||||
// GIF support
|
||||
|
||||
#ifdef HAVE_GIF
|
||||
|
||||
/*
|
||||
* Return the alpha color for `gif` at `frame`, or -1.
|
||||
*/
|
||||
|
||||
int
|
||||
get_gif_transparent_color(GifFileType *gif, int frame) {
|
||||
ExtensionBlock *ext = gif->SavedImages[frame].ExtensionBlocks;
|
||||
int len = gif->SavedImages[frame].ExtensionBlockCount;
|
||||
for (int x = 0; x < len; ++x, ++ext) {
|
||||
if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) && (ext->Bytes[0] & 1)) {
|
||||
return ext->Bytes[3] == 0 ? 0 : (uint8_t) ext->Bytes[3];
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Memory GIF reader callback.
|
||||
*/
|
||||
|
||||
int
|
||||
read_gif_from_memory(GifFileType *gif, GifByteType *buf, int len) {
|
||||
gif_data_t *data = (gif_data_t *) gif->UserData;
|
||||
if ((data->pos + len) > data->len) len = data->len - data->pos;
|
||||
memcpy(buf, data->pos + data->buf, len);
|
||||
data->pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load GIF.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadGIF(FILE *stream) {
|
||||
struct stat s;
|
||||
int fd = fileno(stream);
|
||||
|
||||
// stat
|
||||
if (fstat(fd, &s) < 0) {
|
||||
fclose(stream);
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
uint8_t *buf = (uint8_t *) malloc(s.st_size);
|
||||
|
||||
if (!buf) {
|
||||
fclose(stream);
|
||||
return CAIRO_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
size_t read = fread(buf, s.st_size, 1, stream);
|
||||
fclose(stream);
|
||||
|
||||
cairo_status_t result = CAIRO_STATUS_READ_ERROR;
|
||||
if (1 == read) result = loadGIFFromBuffer(buf, s.st_size);
|
||||
free(buf);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load give from `buf` and the given `len`.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) {
|
||||
int i = 0;
|
||||
GifFileType* gif;
|
||||
|
||||
gif_data_t gifd = { buf, len, 0 };
|
||||
|
||||
#if GIFLIB_MAJOR >= 5
|
||||
int errorcode;
|
||||
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory, &errorcode)) == NULL)
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
#else
|
||||
if ((gif = DGifOpen((void*) &gifd, read_gif_from_memory)) == NULL)
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
#endif
|
||||
|
||||
if (GIF_OK != DGifSlurp(gif)) {
|
||||
GIF_CLOSE_FILE(gif);
|
||||
return CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
width = gif->SWidth;
|
||||
height = gif->SHeight;
|
||||
|
||||
uint8_t *data = (uint8_t *) malloc(width * height * 4);
|
||||
if (!data) {
|
||||
GIF_CLOSE_FILE(gif);
|
||||
return CAIRO_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
GifImageDesc *img = &gif->SavedImages[i].ImageDesc;
|
||||
|
||||
// local colormap takes precedence over global
|
||||
ColorMapObject *colormap = img->ColorMap
|
||||
? img->ColorMap
|
||||
: gif->SColorMap;
|
||||
|
||||
int bgColor = 0;
|
||||
int alphaColor = get_gif_transparent_color(gif, i);
|
||||
if (gif->SColorMap) bgColor = (uint8_t) gif->SBackGroundColor;
|
||||
else if(alphaColor >= 0) bgColor = alphaColor;
|
||||
|
||||
uint8_t *src_data = (uint8_t*) gif->SavedImages[i].RasterBits;
|
||||
uint32_t *dst_data = (uint32_t*) data;
|
||||
|
||||
if (!gif->Image.Interlace) {
|
||||
if (width == img->Width && height == img->Height) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
||||
| colormap->Colors[*src_data].Red << 16
|
||||
| colormap->Colors[*src_data].Green << 8
|
||||
| colormap->Colors[*src_data].Blue;
|
||||
|
||||
dst_data++;
|
||||
src_data++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Image does not take up whole "screen" so we need to fill-in the background
|
||||
int bottom = img->Top + img->Height;
|
||||
int right = img->Left + img->Width;
|
||||
|
||||
for (int y = 0; y < height; ++y) {
|
||||
for (int x = 0; x < width; ++x) {
|
||||
if (y < img->Top || y >= bottom || x < img->Left || x >= right) {
|
||||
*dst_data = ((bgColor == alphaColor) ? 0 : 255) << 24
|
||||
| colormap->Colors[bgColor].Red << 16
|
||||
| colormap->Colors[bgColor].Green << 8
|
||||
| colormap->Colors[bgColor].Blue;
|
||||
} else {
|
||||
*dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24
|
||||
| colormap->Colors[*src_data].Red << 16
|
||||
| colormap->Colors[*src_data].Green << 8
|
||||
| colormap->Colors[*src_data].Blue;
|
||||
}
|
||||
|
||||
dst_data++;
|
||||
src_data++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Image is interlaced so that it streams nice over 14.4k and 28.8k modems :)
|
||||
// We first load in 1/8 of the image, followed by another 1/8, followed by
|
||||
// 1/4 and finally the remaining 1/2.
|
||||
int ioffs[] = { 0, 4, 2, 1 };
|
||||
int ijumps[] = { 8, 8, 4, 2 };
|
||||
|
||||
uint8_t *src_ptr = src_data;
|
||||
uint32_t *dst_ptr;
|
||||
|
||||
for(int z = 0; z < 4; z++) {
|
||||
for(int y = ioffs[z]; y < height; y += ijumps[z]) {
|
||||
dst_ptr = dst_data + width * y;
|
||||
for(int x = 0; x < width; ++x) {
|
||||
*dst_ptr = ((*src_ptr == alphaColor) ? 0 : 255) << 24
|
||||
| (colormap->Colors[*src_ptr].Red) << 16
|
||||
| (colormap->Colors[*src_ptr].Green) << 8
|
||||
| (colormap->Colors[*src_ptr].Blue);
|
||||
|
||||
dst_ptr++;
|
||||
src_ptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GIF_CLOSE_FILE(gif);
|
||||
|
||||
// New image surface
|
||||
_surface = cairo_image_surface_create_for_data(
|
||||
data
|
||||
, CAIRO_FORMAT_ARGB32
|
||||
, width
|
||||
, height
|
||||
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
|
||||
|
||||
cairo_status_t status = cairo_surface_status(_surface);
|
||||
|
||||
if (status) {
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
_data = data;
|
||||
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
#endif /* HAVE_GIF */
|
||||
|
||||
// JPEG support
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
// libjpeg 6.2 does not have jpeg_mem_src; define it ourselves here unless
|
||||
// libjpeg 8 is installed.
|
||||
#if JPEG_LIB_VERSION < 80
|
||||
|
||||
/* Read JPEG image from a memory segment */
|
||||
static void
|
||||
init_source(j_decompress_ptr cinfo) {}
|
||||
|
||||
static boolean
|
||||
fill_input_buffer(j_decompress_ptr cinfo) {
|
||||
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
||||
return TRUE;
|
||||
}
|
||||
static void
|
||||
skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
|
||||
struct jpeg_source_mgr* src = (struct jpeg_source_mgr*) cinfo->src;
|
||||
if (num_bytes > 0) {
|
||||
src->next_input_byte += (size_t) num_bytes;
|
||||
src->bytes_in_buffer -= (size_t) num_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
static void term_source (j_decompress_ptr cinfo) {}
|
||||
static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
|
||||
struct jpeg_source_mgr* src;
|
||||
|
||||
if (cinfo->src == NULL) {
|
||||
cinfo->src = (struct jpeg_source_mgr *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
||||
sizeof(struct jpeg_source_mgr));
|
||||
}
|
||||
|
||||
src = (struct jpeg_source_mgr*) cinfo->src;
|
||||
src->init_source = init_source;
|
||||
src->fill_input_buffer = fill_input_buffer;
|
||||
src->skip_input_data = skip_input_data;
|
||||
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
|
||||
src->term_source = term_source;
|
||||
src->bytes_in_buffer = nbytes;
|
||||
src->next_input_byte = (JOCTET*)buffer;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Takes an initialised jpeg_decompress_struct and decodes the
|
||||
* data into _surface.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::decodeJPEGIntoSurface(jpeg_decompress_struct *args) {
|
||||
int stride = width * 4;
|
||||
cairo_status_t status;
|
||||
|
||||
uint8_t *data = (uint8_t *) malloc(width * height * 4);
|
||||
if (!data) {
|
||||
jpeg_abort_decompress(args);
|
||||
jpeg_destroy_decompress(args);
|
||||
return CAIRO_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
uint8_t *src = (uint8_t *) malloc(width * args->output_components);
|
||||
if (!src) {
|
||||
free(data);
|
||||
jpeg_abort_decompress(args);
|
||||
jpeg_destroy_decompress(args);
|
||||
return CAIRO_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
for (int y = 0; y < height; ++y) {
|
||||
jpeg_read_scanlines(args, &src, 1);
|
||||
uint32_t *row = (uint32_t *)(data + stride * y);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
if (args->jpeg_color_space == 1) {
|
||||
uint32_t *pixel = row + x;
|
||||
*pixel = 255 << 24
|
||||
| src[x] << 16
|
||||
| src[x] << 8
|
||||
| src[x];
|
||||
} else {
|
||||
int bx = 3 * x;
|
||||
uint32_t *pixel = row + x;
|
||||
*pixel = 255 << 24
|
||||
| src[bx + 0] << 16
|
||||
| src[bx + 1] << 8
|
||||
| src[bx + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_surface = cairo_image_surface_create_for_data(
|
||||
data
|
||||
, CAIRO_FORMAT_ARGB32
|
||||
, width
|
||||
, height
|
||||
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
|
||||
|
||||
jpeg_finish_decompress(args);
|
||||
jpeg_destroy_decompress(args);
|
||||
status = cairo_surface_status(_surface);
|
||||
|
||||
if (status) {
|
||||
free(data);
|
||||
free(src);
|
||||
return status;
|
||||
}
|
||||
|
||||
free(src);
|
||||
|
||||
_data = data;
|
||||
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
#if CAIRO_VERSION_MINOR >= 10
|
||||
|
||||
/*
|
||||
* Takes a jpeg data buffer and assigns it as mime data to a
|
||||
* dummy surface
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
|
||||
// TODO: remove this duplicate logic
|
||||
// JPEG setup
|
||||
struct jpeg_decompress_struct args;
|
||||
struct jpeg_error_mgr err;
|
||||
args.err = jpeg_std_error(&err);
|
||||
jpeg_create_decompress(&args);
|
||||
|
||||
jpeg_mem_src(&args, buf, len);
|
||||
|
||||
jpeg_read_header(&args, 1);
|
||||
jpeg_start_decompress(&args);
|
||||
width = args.output_width;
|
||||
height = args.output_height;
|
||||
|
||||
// Data alloc
|
||||
// 8 pixels per byte using Alpha Channel format to reduce memory requirement.
|
||||
int buf_size = height * cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
|
||||
uint8_t *data = (uint8_t *) malloc(buf_size);
|
||||
if (!data) return CAIRO_STATUS_NO_MEMORY;
|
||||
|
||||
// New image surface
|
||||
_surface = cairo_image_surface_create_for_data(
|
||||
data
|
||||
, CAIRO_FORMAT_A1
|
||||
, width
|
||||
, height
|
||||
, cairo_format_stride_for_width(CAIRO_FORMAT_A1, width));
|
||||
|
||||
// Cleanup
|
||||
jpeg_abort_decompress(&args);
|
||||
jpeg_destroy_decompress(&args);
|
||||
cairo_status_t status = cairo_surface_status(_surface);
|
||||
|
||||
if (status) {
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
_data = data;
|
||||
|
||||
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for disposing of a mime data closure.
|
||||
*/
|
||||
|
||||
void
|
||||
clearMimeData(void *closure) {
|
||||
Nan::AdjustExternalMemory(-((read_closure_t *)closure)->len);
|
||||
free(((read_closure_t *) closure)->buf);
|
||||
free(closure);
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a given buffer as mime data against the surface.
|
||||
* The provided buffer will be copied, and the copy will
|
||||
* be automatically freed when the surface is destroyed.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
|
||||
uint8_t *mime_data = (uint8_t *) malloc(len);
|
||||
if (!mime_data) return CAIRO_STATUS_NO_MEMORY;
|
||||
|
||||
read_closure_t *mime_closure = (read_closure_t *) malloc(sizeof(read_closure_t));
|
||||
if (!mime_closure) {
|
||||
free(mime_data);
|
||||
return CAIRO_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(mime_data, data, len);
|
||||
|
||||
mime_closure->buf = mime_data;
|
||||
mime_closure->len = len;
|
||||
|
||||
Nan::AdjustExternalMemory(len);
|
||||
|
||||
return cairo_surface_set_mime_data(_surface
|
||||
, mime_type
|
||||
, mime_data
|
||||
, len
|
||||
, clearMimeData
|
||||
, mime_closure);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Load jpeg from buffer.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
|
||||
// TODO: remove this duplicate logic
|
||||
// JPEG setup
|
||||
struct jpeg_decompress_struct args;
|
||||
struct jpeg_error_mgr err;
|
||||
args.err = jpeg_std_error(&err);
|
||||
jpeg_create_decompress(&args);
|
||||
|
||||
jpeg_mem_src(&args, buf, len);
|
||||
|
||||
jpeg_read_header(&args, 1);
|
||||
jpeg_start_decompress(&args);
|
||||
width = args.output_width;
|
||||
height = args.output_height;
|
||||
|
||||
return decodeJPEGIntoSurface(&args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Load JPEG, convert RGB to ARGB.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
Image::loadJPEG(FILE *stream) {
|
||||
cairo_status_t status;
|
||||
|
||||
if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG.
|
||||
// JPEG setup
|
||||
struct jpeg_decompress_struct args;
|
||||
struct jpeg_error_mgr err;
|
||||
args.err = jpeg_std_error(&err);
|
||||
jpeg_create_decompress(&args);
|
||||
|
||||
jpeg_stdio_src(&args, stream);
|
||||
|
||||
jpeg_read_header(&args, 1);
|
||||
jpeg_start_decompress(&args);
|
||||
width = args.output_width;
|
||||
height = args.output_height;
|
||||
|
||||
status = decodeJPEGIntoSurface(&args);
|
||||
fclose(stream);
|
||||
} else { // We'll need the actual source jpeg data, so read fully.
|
||||
#if CAIRO_VERSION_MINOR >= 10
|
||||
uint8_t *buf;
|
||||
unsigned len;
|
||||
|
||||
fseek(stream, 0, SEEK_END);
|
||||
len = ftell(stream);
|
||||
fseek(stream, 0, SEEK_SET);
|
||||
|
||||
buf = (uint8_t *) malloc(len);
|
||||
if (!buf) return CAIRO_STATUS_NO_MEMORY;
|
||||
|
||||
if (fread(buf, len, 1, stream) != 1) {
|
||||
status = CAIRO_STATUS_READ_ERROR;
|
||||
} else if ((DATA_IMAGE | DATA_MIME) == data_mode) {
|
||||
status = loadJPEGFromBuffer(buf, len);
|
||||
if (!status) status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
|
||||
} else if (DATA_MIME == data_mode) {
|
||||
status = decodeJPEGBufferIntoMimeSurface(buf, len);
|
||||
} else {
|
||||
status = CAIRO_STATUS_READ_ERROR;
|
||||
}
|
||||
|
||||
fclose(stream);
|
||||
free(buf);
|
||||
#else
|
||||
status = CAIRO_STATUS_READ_ERROR;
|
||||
#endif
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif /* HAVE_JPEG */
|
||||
|
||||
/*
|
||||
* Return UNKNOWN, JPEG, or PNG based on the filename.
|
||||
*/
|
||||
|
||||
Image::type
|
||||
Image::extension(const char *filename) {
|
||||
size_t len = strlen(filename);
|
||||
filename += len;
|
||||
if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG;
|
||||
if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF;
|
||||
if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG;
|
||||
if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG;
|
||||
return Image::UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sniff bytes 0..1 for JPEG's magic number ff d8.
|
||||
*/
|
||||
|
||||
int
|
||||
Image::isJPEG(uint8_t *data) {
|
||||
return 0xff == data[0] && 0xd8 == data[1];
|
||||
}
|
||||
|
||||
/*
|
||||
* Sniff bytes 0..2 for "GIF".
|
||||
*/
|
||||
|
||||
int
|
||||
Image::isGIF(uint8_t *data) {
|
||||
return 'G' == data[0] && 'I' == data[1] && 'F' == data[2];
|
||||
}
|
||||
|
||||
/*
|
||||
* Sniff bytes 1..3 for "PNG".
|
||||
*/
|
||||
|
||||
int
|
||||
Image::isPNG(uint8_t *data) {
|
||||
return 'P' == data[1] && 'N' == data[2] && 'G' == data[3];
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
|
||||
//
|
||||
// Image.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_IMAGE_H__
|
||||
#define __NODE_IMAGE_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
|
||||
#ifdef HAVE_JPEG
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GIF
|
||||
#include <gif_lib.h>
|
||||
|
||||
#if GIFLIB_MAJOR > 5 || GIFLIB_MAJOR == 5 && GIFLIB_MINOR >= 1
|
||||
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif, NULL)
|
||||
#else
|
||||
#define GIF_CLOSE_FILE(gif) DGifCloseFile(gif)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
class Image: public Nan::ObjectWrap {
|
||||
public:
|
||||
char *filename;
|
||||
int width, height;
|
||||
Nan::Callback *onload;
|
||||
Nan::Callback *onerror;
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_GETTER(GetSource);
|
||||
static NAN_GETTER(GetOnload);
|
||||
static NAN_GETTER(GetOnerror);
|
||||
static NAN_GETTER(GetComplete);
|
||||
static NAN_GETTER(GetWidth);
|
||||
static NAN_GETTER(GetHeight);
|
||||
static NAN_GETTER(GetDataMode);
|
||||
static NAN_SETTER(SetSource);
|
||||
static NAN_SETTER(SetOnload);
|
||||
static NAN_SETTER(SetOnerror);
|
||||
static NAN_SETTER(SetDataMode);
|
||||
inline cairo_surface_t *surface(){ return _surface; }
|
||||
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
|
||||
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
|
||||
static int isPNG(uint8_t *data);
|
||||
static int isJPEG(uint8_t *data);
|
||||
static int isGIF(uint8_t *data);
|
||||
static cairo_status_t readPNG(void *closure, unsigned char *data, unsigned len);
|
||||
inline int isComplete(){ return COMPLETE == state; }
|
||||
cairo_status_t loadSurface();
|
||||
cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadPNGFromBuffer(uint8_t *buf);
|
||||
cairo_status_t loadPNG();
|
||||
void clearData();
|
||||
#ifdef HAVE_GIF
|
||||
cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadGIF(FILE *stream);
|
||||
#endif
|
||||
#ifdef HAVE_JPEG
|
||||
cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
|
||||
cairo_status_t loadJPEG(FILE *stream);
|
||||
cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info);
|
||||
#if CAIRO_VERSION_MINOR >= 10
|
||||
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
|
||||
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
|
||||
#endif
|
||||
#endif
|
||||
void error(Local<Value> error);
|
||||
void loaded();
|
||||
cairo_status_t load();
|
||||
Image();
|
||||
|
||||
enum {
|
||||
DEFAULT
|
||||
, LOADING
|
||||
, COMPLETE
|
||||
} state;
|
||||
|
||||
enum data_mode_t {
|
||||
DATA_IMAGE = 1
|
||||
, DATA_MIME = 2
|
||||
} data_mode;
|
||||
|
||||
typedef enum {
|
||||
UNKNOWN
|
||||
, GIF
|
||||
, JPEG
|
||||
, PNG
|
||||
} type;
|
||||
|
||||
static type extension(const char *filename);
|
||||
|
||||
private:
|
||||
cairo_surface_t *_surface;
|
||||
uint8_t *_data;
|
||||
int _data_len;
|
||||
~Image();
|
||||
};
|
||||
|
||||
#endif
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
|
||||
//
|
||||
// ImageData.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "ImageData.h"
|
||||
|
||||
Nan::Persistent<FunctionTemplate> ImageData::constructor;
|
||||
|
||||
/*
|
||||
* Initialize ImageData.
|
||||
*/
|
||||
|
||||
void
|
||||
ImageData::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
|
||||
Nan::HandleScope scope;
|
||||
|
||||
// Constructor
|
||||
Local<FunctionTemplate> ctor = Nan::New<FunctionTemplate>(ImageData::New);
|
||||
constructor.Reset(ctor);
|
||||
ctor->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
ctor->SetClassName(Nan::New("ImageData").ToLocalChecked());
|
||||
|
||||
// Prototype
|
||||
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
|
||||
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth);
|
||||
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight);
|
||||
Nan::Set(target, Nan::New("ImageData").ToLocalChecked(), ctor->GetFunction());
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize a new ImageData object.
|
||||
*/
|
||||
|
||||
NAN_METHOD(ImageData::New) {
|
||||
if (!info.IsConstructCall()) {
|
||||
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
|
||||
}
|
||||
|
||||
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
|
||||
Local<v8::Object> clampedArray;
|
||||
Local<Object> global = Context::GetCurrent()->Global();
|
||||
#else
|
||||
Local<Uint8ClampedArray> clampedArray;
|
||||
#endif
|
||||
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int length;
|
||||
|
||||
if (info[0]->IsUint32() && info[1]->IsUint32()) {
|
||||
width = info[0]->Uint32Value();
|
||||
if (width == 0) {
|
||||
Nan::ThrowRangeError("The source width is zero.");
|
||||
return;
|
||||
}
|
||||
height = info[1]->Uint32Value();
|
||||
if (height == 0) {
|
||||
Nan::ThrowRangeError("The source height is zero.");
|
||||
return;
|
||||
}
|
||||
length = width * height * 4;
|
||||
|
||||
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
|
||||
Local<Int32> sizeHandle = Nan::New(length);
|
||||
Local<Value> caargv[] = { sizeHandle };
|
||||
clampedArray = global->Get(Nan::New("Uint8ClampedArray").ToLocalChecked()).As<Function>()->NewInstance(1, caargv);
|
||||
#else
|
||||
clampedArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
|
||||
#endif
|
||||
|
||||
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
|
||||
} else if (info[0]->ToObject()->GetIndexedPropertiesExternalArrayDataType() == kExternalPixelArray && info[1]->IsUint32()) {
|
||||
clampedArray = info[0]->ToObject();
|
||||
length = clampedArray->GetIndexedPropertiesExternalArrayDataLength();
|
||||
#else
|
||||
} else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) {
|
||||
clampedArray = info[0].As<Uint8ClampedArray>();
|
||||
length = clampedArray->Length();
|
||||
#endif
|
||||
if (length == 0) {
|
||||
Nan::ThrowRangeError("The input data has a zero byte length.");
|
||||
return;
|
||||
}
|
||||
if (length % 4 != 0) {
|
||||
Nan::ThrowRangeError("The input data byte length is not a multiple of 4.");
|
||||
return;
|
||||
}
|
||||
width = info[1]->Uint32Value();
|
||||
int size = length / 4;
|
||||
if (width == 0) {
|
||||
Nan::ThrowRangeError("The source width is zero.");
|
||||
return;
|
||||
}
|
||||
if (size % width != 0) {
|
||||
Nan::ThrowRangeError("The input data byte length is not a multiple of (4 * width).");
|
||||
return;
|
||||
}
|
||||
height = size / width;
|
||||
if (info[2]->IsUint32() && info[2]->Uint32Value() != height) {
|
||||
Nan::ThrowRangeError("The input data byte length is not equal to (4 * width * height).");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]) or (width, height)");
|
||||
return;
|
||||
}
|
||||
|
||||
Nan::TypedArrayContents<uint8_t> dataPtr(clampedArray);
|
||||
|
||||
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(*dataPtr), width, height);
|
||||
imageData->Wrap(info.This());
|
||||
info.This()->Set(Nan::New("data").ToLocalChecked(), clampedArray);
|
||||
info.GetReturnValue().Set(info.This());
|
||||
}
|
||||
|
||||
/*
|
||||
* Get width.
|
||||
*/
|
||||
|
||||
NAN_GETTER(ImageData::GetWidth) {
|
||||
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(imageData->width()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Get height.
|
||||
*/
|
||||
|
||||
NAN_GETTER(ImageData::GetHeight) {
|
||||
ImageData *imageData = Nan::ObjectWrap::Unwrap<ImageData>(info.This());
|
||||
info.GetReturnValue().Set(Nan::New<Number>(imageData->height()));
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
//
|
||||
// ImageData.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_IMAGE_DATA_H__
|
||||
#define __NODE_IMAGE_DATA_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
#include <stdlib.h>
|
||||
#include "v8.h"
|
||||
|
||||
class ImageData: public Nan::ObjectWrap {
|
||||
public:
|
||||
static Nan::Persistent<FunctionTemplate> constructor;
|
||||
static void Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target);
|
||||
static NAN_METHOD(New);
|
||||
static NAN_GETTER(GetWidth);
|
||||
static NAN_GETTER(GetHeight);
|
||||
|
||||
inline int width() { return _width; }
|
||||
inline int height() { return _height; }
|
||||
inline uint8_t *data() { return _data; }
|
||||
inline int stride() { return _width * 4; }
|
||||
ImageData(uint8_t *data, int width, int height) : _width(width), _height(height), _data(data) {}
|
||||
|
||||
private:
|
||||
int _width;
|
||||
int _height;
|
||||
uint8_t *_data;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
|
||||
//
|
||||
// JPEGStream.h
|
||||
//
|
||||
|
||||
#ifndef __NODE_JPEG_STREAM_H__
|
||||
#define __NODE_JPEG_STREAM_H__
|
||||
|
||||
#include "Canvas.h"
|
||||
#include <jpeglib.h>
|
||||
#include <jerror.h>
|
||||
|
||||
/*
|
||||
* Expanded data destination object for closure output,
|
||||
* inspired by IJG's jdatadst.c
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
struct jpeg_destination_mgr pub;
|
||||
closure_t *closure;
|
||||
JOCTET *buffer;
|
||||
int bufsize;
|
||||
} closure_destination_mgr;
|
||||
|
||||
void
|
||||
init_closure_destination(j_compress_ptr cinfo){
|
||||
// we really don't have to do anything here
|
||||
}
|
||||
|
||||
boolean
|
||||
empty_closure_output_buffer(j_compress_ptr cinfo){
|
||||
Nan::HandleScope scope;
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
|
||||
Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize).ToLocalChecked();
|
||||
|
||||
// emit "data"
|
||||
Local<Value> argv[2] = {
|
||||
Nan::Null()
|
||||
, buf
|
||||
};
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, argv);
|
||||
|
||||
dest->buffer = (JOCTET *)malloc(dest->bufsize);
|
||||
cinfo->dest->next_output_byte = dest->buffer;
|
||||
cinfo->dest->free_in_buffer = dest->bufsize;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
term_closure_destination(j_compress_ptr cinfo){
|
||||
Nan::HandleScope scope;
|
||||
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
|
||||
|
||||
/* emit remaining data */
|
||||
Local<Object> buf = Nan::NewBuffer((char *)dest->buffer, dest->bufsize - dest->pub.free_in_buffer).ToLocalChecked();
|
||||
|
||||
Local<Value> data_argv[2] = {
|
||||
Nan::Null()
|
||||
, buf
|
||||
};
|
||||
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, data_argv);
|
||||
|
||||
// emit "end"
|
||||
Local<Value> end_argv[2] = {
|
||||
Nan::Null()
|
||||
, Nan::Null()
|
||||
};
|
||||
|
||||
Nan::MakeCallback(Nan::GetCurrentContext()->Global(), (v8::Local<v8::Function>)dest->closure->fn, 2, end_argv);
|
||||
}
|
||||
|
||||
void
|
||||
jpeg_closure_dest(j_compress_ptr cinfo, closure_t * closure, int bufsize){
|
||||
closure_destination_mgr * dest;
|
||||
|
||||
/* The destination object is made permanent so that multiple JPEG images
|
||||
* can be written to the same buffer without re-executing jpeg_mem_dest.
|
||||
*/
|
||||
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
|
||||
cinfo->dest = (struct jpeg_destination_mgr *)
|
||||
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
|
||||
sizeof(closure_destination_mgr));
|
||||
}
|
||||
|
||||
dest = (closure_destination_mgr *) cinfo->dest;
|
||||
|
||||
cinfo->dest->init_destination = &init_closure_destination;
|
||||
cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
|
||||
cinfo->dest->term_destination = &term_closure_destination;
|
||||
|
||||
dest->closure = closure;
|
||||
dest->bufsize = bufsize;
|
||||
dest->buffer = (JOCTET *)malloc(bufsize);
|
||||
|
||||
cinfo->dest->next_output_byte = dest->buffer;
|
||||
cinfo->dest->free_in_buffer = dest->bufsize;
|
||||
}
|
||||
|
||||
void
|
||||
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, int quality, bool progressive, closure_t *closure){
|
||||
int w = cairo_image_surface_get_width(surface);
|
||||
int h = cairo_image_surface_get_height(surface);
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
|
||||
JSAMPROW slr;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.image_width = w;
|
||||
cinfo.image_height = h;
|
||||
jpeg_set_defaults(&cinfo);
|
||||
if (progressive)
|
||||
jpeg_simple_progression(&cinfo);
|
||||
jpeg_set_quality(&cinfo, quality, (quality<25)?0:1);
|
||||
jpeg_closure_dest(&cinfo, closure, bufsize);
|
||||
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
unsigned char *dst;
|
||||
unsigned int *src = (unsigned int *) cairo_image_surface_get_data(surface);
|
||||
int sl = 0;
|
||||
dst = (unsigned char *) malloc(w * 3);
|
||||
while (sl < h) {
|
||||
unsigned char *dp = dst;
|
||||
int x = 0;
|
||||
while (x < w) {
|
||||
dp[0] = (*src >> 16) & 255;
|
||||
dp[1] = (*src >> 8) & 255;
|
||||
dp[2] = *src & 255;
|
||||
src++;
|
||||
dp += 3;
|
||||
x++;
|
||||
}
|
||||
slr = dst;
|
||||
jpeg_write_scanlines(&cinfo, &slr, 1);
|
||||
sl++;
|
||||
}
|
||||
free(dst);
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
}
|
||||
|
||||
#endif
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
#ifndef _CANVAS_PNG_H
|
||||
#define _CANVAS_PNG_H
|
||||
#include <png.h>
|
||||
#include <pngconf.h>
|
||||
#include <cairo.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "closure.h"
|
||||
|
||||
#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
|
||||
|
||||
#ifndef CAIRO_FORMAT_INVALID
|
||||
#define CAIRO_FORMAT_INVALID -1
|
||||
#endif
|
||||
|
||||
static void canvas_png_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 canvas_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 canvas_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct canvas_png_write_closure_t {
|
||||
cairo_write_func_t write_func;
|
||||
void *closure;
|
||||
};
|
||||
|
||||
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_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, canvas_png_flush);
|
||||
png_set_compression_level(png, ((closure_t *) ((canvas_png_write_closure_t *) closure)->closure)->compression_level);
|
||||
png_set_filter(png, 0, ((closure_t *) ((canvas_png_write_closure_t *) closure)->closure)->filter);
|
||||
|
||||
switch (cairo_image_surface_get_format(surface)) {
|
||||
case CAIRO_FORMAT_ARGB32:
|
||||
bpc = 8;
|
||||
png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
break;
|
||||
#ifdef CAIRO_FORMAT_RGB30
|
||||
case CAIRO_FORMAT_RGB30:
|
||||
bpc = 10;
|
||||
png_color_type = PNG_COLOR_TYPE_RGB;
|
||||
break;
|
||||
#endif
|
||||
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, canvas_unpremultiply_data);
|
||||
} else if (png_color_type == PNG_COLOR_TYPE_RGB) {
|
||||
png_set_write_user_transform_fn(png, canvas_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;
|
||||
}
|
||||
|
||||
static void canvas_stream_write_func(png_structp png, png_bytep data, png_size_t size) {
|
||||
cairo_status_t status;
|
||||
struct canvas_png_write_closure_t *png_closure;
|
||||
|
||||
png_closure = (struct canvas_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 canvas_png_write_closure_t png_closure;
|
||||
|
||||
if (cairo_surface_status(surface)) {
|
||||
return cairo_surface_status(surface);
|
||||
}
|
||||
|
||||
png_closure.write_func = write_func;
|
||||
png_closure.closure = closure;
|
||||
|
||||
return canvas_write_png(surface, canvas_stream_write_func, &png_closure);
|
||||
}
|
||||
#endif
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
//
|
||||
// Point.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_POINT_H__
|
||||
#define __NODE_POINT_H__
|
||||
|
||||
template <class T>
|
||||
class Point {
|
||||
public:
|
||||
T x, y;
|
||||
Point(T x, T y): x(x), y(y) {}
|
||||
};
|
||||
|
||||
#endif /* __NODE_POINT_H__ */
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
//
|
||||
// closure.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __NODE_CLOSURE_H__
|
||||
#define __NODE_CLOSURE_H__
|
||||
|
||||
#ifdef __unix__
|
||||
#include<sys/user.h>
|
||||
#endif
|
||||
|
||||
#ifndef PAGE_SIZE
|
||||
#define PAGE_SIZE 4096
|
||||
#endif
|
||||
|
||||
#include <nan.h>
|
||||
|
||||
/*
|
||||
* PNG stream closure.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
Nan::Callback *pfn;
|
||||
Local<Function> fn;
|
||||
unsigned len;
|
||||
unsigned max_len;
|
||||
uint8_t *data;
|
||||
Canvas *canvas;
|
||||
cairo_status_t status;
|
||||
uint32_t compression_level;
|
||||
uint32_t filter;
|
||||
} closure_t;
|
||||
|
||||
/*
|
||||
* Initialize the given closure.
|
||||
*/
|
||||
|
||||
cairo_status_t
|
||||
closure_init(closure_t *closure, Canvas *canvas, unsigned int compression_level, unsigned int filter) {
|
||||
closure->len = 0;
|
||||
closure->canvas = canvas;
|
||||
closure->data = (uint8_t *) malloc(closure->max_len = PAGE_SIZE);
|
||||
if (!closure->data) return CAIRO_STATUS_NO_MEMORY;
|
||||
closure->compression_level = compression_level;
|
||||
closure->filter = filter;
|
||||
return CAIRO_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* Free the given closure's data,
|
||||
* and hint V8 at the memory dealloc.
|
||||
*/
|
||||
|
||||
void
|
||||
closure_destroy(closure_t *closure) {
|
||||
if (closure->len) {
|
||||
free(closure->data);
|
||||
Nan::AdjustExternalMemory(-((intptr_t) closure->max_len));
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __NODE_CLOSURE_H__ */
|
||||
+744
@@ -0,0 +1,744 @@
|
||||
|
||||
//
|
||||
// color.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include "color.h"
|
||||
#include <stdlib.h>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
// Compatibility with Visual Studio versions prior to VS2015
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Parse integer value
|
||||
*/
|
||||
|
||||
template <typename parsed_t>
|
||||
static bool
|
||||
parse_integer(const char** pStr, parsed_t *pParsed) {
|
||||
parsed_t& c = *pParsed;
|
||||
const char*& str = *pStr;
|
||||
int8_t sign=1;
|
||||
|
||||
c = 0;
|
||||
if (*str == '-') {
|
||||
sign=-1;
|
||||
++str;
|
||||
}
|
||||
else if (*str == '+')
|
||||
++str;
|
||||
|
||||
if (*str >= '0' && *str <= '9') {
|
||||
do {
|
||||
c *= 10;
|
||||
c += *str++ - '0';
|
||||
} while (*str >= '0' && *str <= '9');
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (sign<0)
|
||||
c=-c;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Parse CSS <number> value
|
||||
* Adapted from http://crackprogramming.blogspot.co.il/2012/10/implement-atof.html
|
||||
*/
|
||||
|
||||
template <typename parsed_t>
|
||||
static bool
|
||||
parse_css_number(const char** pStr, parsed_t *pParsed) {
|
||||
parsed_t &parsed = *pParsed;
|
||||
const char*& str = *pStr;
|
||||
const char* startStr = str;
|
||||
if (!str || !*str)
|
||||
return false;
|
||||
parsed_t integerPart = 0;
|
||||
parsed_t fractionPart = 0;
|
||||
int divisorForFraction = 1;
|
||||
int sign = 1;
|
||||
int exponent = 0;
|
||||
int digits = 0;
|
||||
bool inFraction = false;
|
||||
|
||||
if (*str == '-') {
|
||||
++str;
|
||||
sign = -1;
|
||||
}
|
||||
else if (*str == '+')
|
||||
++str;
|
||||
while (*str != '\0') {
|
||||
if (*str >= '0' && *str <= '9') {
|
||||
if (digits>=std::numeric_limits<parsed_t>::digits10) {
|
||||
if (!inFraction)
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
++digits;
|
||||
|
||||
if (inFraction) {
|
||||
fractionPart = fractionPart*10 + (*str - '0');
|
||||
divisorForFraction *= 10;
|
||||
}
|
||||
else {
|
||||
integerPart = integerPart*10 + (*str - '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (*str == '.') {
|
||||
if (inFraction)
|
||||
break;
|
||||
else
|
||||
inFraction = true;
|
||||
}
|
||||
else if (*str == 'e') {
|
||||
++str;
|
||||
if (!parse_integer(&str, &exponent))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
break;
|
||||
++str;
|
||||
}
|
||||
if (str != startStr) {
|
||||
parsed = sign * (integerPart + fractionPart/divisorForFraction);
|
||||
for (;exponent>0;--exponent)
|
||||
parsed *= 10;
|
||||
for (;exponent<0;++exponent)
|
||||
parsed /= 10;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clip value to the range [minValue, maxValue]
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
static T
|
||||
clip(T value, T minValue, T maxValue) {
|
||||
if (value > maxValue)
|
||||
value = maxValue;
|
||||
if (value < minValue)
|
||||
value = minValue;
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrap value to the range [0, limit]
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
static T
|
||||
wrap_float(T value, T limit) {
|
||||
return fmod(fmod(value, limit) + limit, limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrap value to the range [0, limit] - currently-unused integer version of wrap_float
|
||||
*/
|
||||
|
||||
// template <typename T>
|
||||
// static T wrap_int(T value, T limit) {
|
||||
// return (value % limit + limit) % limit;
|
||||
// }
|
||||
|
||||
/*
|
||||
* Parse color channel value
|
||||
*/
|
||||
|
||||
static bool
|
||||
parse_rgb_channel(const char** pStr, uint8_t *pChannel) {
|
||||
int channel;
|
||||
if (parse_integer(pStr, &channel)) {
|
||||
*pChannel = clip(channel, 0, 255);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse a value in degrees
|
||||
*/
|
||||
|
||||
static bool
|
||||
parse_degrees(const char** pStr, float *pDegrees) {
|
||||
float degrees;
|
||||
if (parse_css_number(pStr, °rees)) {
|
||||
*pDegrees = wrap_float(degrees, 360.0f);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse and clip a percentage value. Returns a float in the range [0, 1].
|
||||
*/
|
||||
|
||||
static bool
|
||||
parse_clipped_percentage(const char** pStr, float *pFraction) {
|
||||
float percentage;
|
||||
bool result = parse_css_number(pStr,&percentage);
|
||||
const char*& str = *pStr;
|
||||
if (result) {
|
||||
if (*str == '%') {
|
||||
++str;
|
||||
*pFraction = clip(percentage, 0.0f, 100.0f) / 100.0f;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Macros to help with parsing inside rgba_from_*_string
|
||||
*/
|
||||
|
||||
#define WHITESPACE \
|
||||
while (' ' == *str) ++str;
|
||||
|
||||
#define WHITESPACE_OR_COMMA \
|
||||
while (' ' == *str || ',' == *str) ++str;
|
||||
|
||||
#define CHANNEL(NAME) \
|
||||
if (!parse_rgb_channel(&str, &NAME)) \
|
||||
return 0; \
|
||||
|
||||
#define HUE(NAME) \
|
||||
if (!parse_degrees(&str, &NAME)) \
|
||||
return 0;
|
||||
|
||||
#define SATURATION(NAME) \
|
||||
if (!parse_clipped_percentage(&str, &NAME)) \
|
||||
return 0;
|
||||
|
||||
#define LIGHTNESS(NAME) SATURATION(NAME)
|
||||
|
||||
#define ALPHA(NAME) \
|
||||
if (*str >= '1' && *str <= '9') { \
|
||||
NAME = 1; \
|
||||
} else { \
|
||||
if ('0' == *str) ++str; \
|
||||
if ('.' == *str) { \
|
||||
++str; \
|
||||
float n = .1f; \
|
||||
while (*str >= '0' && *str <= '9') { \
|
||||
NAME += (*str++ - '0') * n; \
|
||||
n *= .1f; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
do {} while (0) // require trailing semicolon
|
||||
|
||||
/*
|
||||
* Named colors.
|
||||
*/
|
||||
|
||||
static struct named_color {
|
||||
const char *name;
|
||||
uint32_t val;
|
||||
} named_colors[] = {
|
||||
{ "transparent", 0xFFFFFF00}
|
||||
, { "aliceblue", 0xF0F8FFFF }
|
||||
, { "antiquewhite", 0xFAEBD7FF }
|
||||
, { "aqua", 0x00FFFFFF }
|
||||
, { "aquamarine", 0x7FFFD4FF }
|
||||
, { "azure", 0xF0FFFFFF }
|
||||
, { "beige", 0xF5F5DCFF }
|
||||
, { "bisque", 0xFFE4C4FF }
|
||||
, { "black", 0x000000FF }
|
||||
, { "blanchedalmond", 0xFFEBCDFF }
|
||||
, { "blue", 0x0000FFFF }
|
||||
, { "blueviolet", 0x8A2BE2FF }
|
||||
, { "brown", 0xA52A2AFF }
|
||||
, { "burlywood", 0xDEB887FF }
|
||||
, { "cadetblue", 0x5F9EA0FF }
|
||||
, { "chartreuse", 0x7FFF00FF }
|
||||
, { "chocolate", 0xD2691EFF }
|
||||
, { "coral", 0xFF7F50FF }
|
||||
, { "cornflowerblue", 0x6495EDFF }
|
||||
, { "cornsilk", 0xFFF8DCFF }
|
||||
, { "crimson", 0xDC143CFF }
|
||||
, { "cyan", 0x00FFFFFF }
|
||||
, { "darkblue", 0x00008BFF }
|
||||
, { "darkcyan", 0x008B8BFF }
|
||||
, { "darkgoldenrod", 0xB8860BFF }
|
||||
, { "darkgray", 0xA9A9A9FF }
|
||||
, { "darkgreen", 0x006400FF }
|
||||
, { "darkgrey", 0xA9A9A9FF }
|
||||
, { "darkkhaki", 0xBDB76BFF }
|
||||
, { "darkmagenta", 0x8B008BFF }
|
||||
, { "darkolivegreen", 0x556B2FFF }
|
||||
, { "darkorange", 0xFF8C00FF }
|
||||
, { "darkorchid", 0x9932CCFF }
|
||||
, { "darkred", 0x8B0000FF }
|
||||
, { "darksalmon", 0xE9967AFF }
|
||||
, { "darkseagreen", 0x8FBC8FFF }
|
||||
, { "darkslateblue", 0x483D8BFF }
|
||||
, { "darkslategray", 0x2F4F4FFF }
|
||||
, { "darkslategrey", 0x2F4F4FFF }
|
||||
, { "darkturquoise", 0x00CED1FF }
|
||||
, { "darkviolet", 0x9400D3FF }
|
||||
, { "deeppink", 0xFF1493FF }
|
||||
, { "deepskyblue", 0x00BFFFFF }
|
||||
, { "dimgray", 0x696969FF }
|
||||
, { "dimgrey", 0x696969FF }
|
||||
, { "dodgerblue", 0x1E90FFFF }
|
||||
, { "firebrick", 0xB22222FF }
|
||||
, { "floralwhite", 0xFFFAF0FF }
|
||||
, { "forestgreen", 0x228B22FF }
|
||||
, { "fuchsia", 0xFF00FFFF }
|
||||
, { "gainsboro", 0xDCDCDCFF }
|
||||
, { "ghostwhite", 0xF8F8FFFF }
|
||||
, { "gold", 0xFFD700FF }
|
||||
, { "goldenrod", 0xDAA520FF }
|
||||
, { "gray", 0x808080FF }
|
||||
, { "green", 0x008000FF }
|
||||
, { "greenyellow", 0xADFF2FFF }
|
||||
, { "grey", 0x808080FF }
|
||||
, { "honeydew", 0xF0FFF0FF }
|
||||
, { "hotpink", 0xFF69B4FF }
|
||||
, { "indianred", 0xCD5C5CFF }
|
||||
, { "indigo", 0x4B0082FF }
|
||||
, { "ivory", 0xFFFFF0FF }
|
||||
, { "khaki", 0xF0E68CFF }
|
||||
, { "lavender", 0xE6E6FAFF }
|
||||
, { "lavenderblush", 0xFFF0F5FF }
|
||||
, { "lawngreen", 0x7CFC00FF }
|
||||
, { "lemonchiffon", 0xFFFACDFF }
|
||||
, { "lightblue", 0xADD8E6FF }
|
||||
, { "lightcoral", 0xF08080FF }
|
||||
, { "lightcyan", 0xE0FFFFFF }
|
||||
, { "lightgoldenrodyellow", 0xFAFAD2FF }
|
||||
, { "lightgray", 0xD3D3D3FF }
|
||||
, { "lightgreen", 0x90EE90FF }
|
||||
, { "lightgrey", 0xD3D3D3FF }
|
||||
, { "lightpink", 0xFFB6C1FF }
|
||||
, { "lightsalmon", 0xFFA07AFF }
|
||||
, { "lightseagreen", 0x20B2AAFF }
|
||||
, { "lightskyblue", 0x87CEFAFF }
|
||||
, { "lightslategray", 0x778899FF }
|
||||
, { "lightslategrey", 0x778899FF }
|
||||
, { "lightsteelblue", 0xB0C4DEFF }
|
||||
, { "lightyellow", 0xFFFFE0FF }
|
||||
, { "lime", 0x00FF00FF }
|
||||
, { "limegreen", 0x32CD32FF }
|
||||
, { "linen", 0xFAF0E6FF }
|
||||
, { "magenta", 0xFF00FFFF }
|
||||
, { "maroon", 0x800000FF }
|
||||
, { "mediumaquamarine", 0x66CDAAFF }
|
||||
, { "mediumblue", 0x0000CDFF }
|
||||
, { "mediumorchid", 0xBA55D3FF }
|
||||
, { "mediumpurple", 0x9370DBFF }
|
||||
, { "mediumseagreen", 0x3CB371FF }
|
||||
, { "mediumslateblue", 0x7B68EEFF }
|
||||
, { "mediumspringgreen", 0x00FA9AFF }
|
||||
, { "mediumturquoise", 0x48D1CCFF }
|
||||
, { "mediumvioletred", 0xC71585FF }
|
||||
, { "midnightblue", 0x191970FF }
|
||||
, { "mintcream", 0xF5FFFAFF }
|
||||
, { "mistyrose", 0xFFE4E1FF }
|
||||
, { "moccasin", 0xFFE4B5FF }
|
||||
, { "navajowhite", 0xFFDEADFF }
|
||||
, { "navy", 0x000080FF }
|
||||
, { "oldlace", 0xFDF5E6FF }
|
||||
, { "olive", 0x808000FF }
|
||||
, { "olivedrab", 0x6B8E23FF }
|
||||
, { "orange", 0xFFA500FF }
|
||||
, { "orangered", 0xFF4500FF }
|
||||
, { "orchid", 0xDA70D6FF }
|
||||
, { "palegoldenrod", 0xEEE8AAFF }
|
||||
, { "palegreen", 0x98FB98FF }
|
||||
, { "paleturquoise", 0xAFEEEEFF }
|
||||
, { "palevioletred", 0xDB7093FF }
|
||||
, { "papayawhip", 0xFFEFD5FF }
|
||||
, { "peachpuff", 0xFFDAB9FF }
|
||||
, { "peru", 0xCD853FFF }
|
||||
, { "pink", 0xFFC0CBFF }
|
||||
, { "plum", 0xDDA0DDFF }
|
||||
, { "powderblue", 0xB0E0E6FF }
|
||||
, { "purple", 0x800080FF }
|
||||
, { "rebeccapurple", 0x663399FF } // Source: CSS Color Level 4 draft
|
||||
, { "red", 0xFF0000FF }
|
||||
, { "rosybrown", 0xBC8F8FFF }
|
||||
, { "royalblue", 0x4169E1FF }
|
||||
, { "saddlebrown", 0x8B4513FF }
|
||||
, { "salmon", 0xFA8072FF }
|
||||
, { "sandybrown", 0xF4A460FF }
|
||||
, { "seagreen", 0x2E8B57FF }
|
||||
, { "seashell", 0xFFF5EEFF }
|
||||
, { "sienna", 0xA0522DFF }
|
||||
, { "silver", 0xC0C0C0FF }
|
||||
, { "skyblue", 0x87CEEBFF }
|
||||
, { "slateblue", 0x6A5ACDFF }
|
||||
, { "slategray", 0x708090FF }
|
||||
, { "slategrey", 0x708090FF }
|
||||
, { "snow", 0xFFFAFAFF }
|
||||
, { "springgreen", 0x00FF7FFF }
|
||||
, { "steelblue", 0x4682B4FF }
|
||||
, { "tan", 0xD2B48CFF }
|
||||
, { "teal", 0x008080FF }
|
||||
, { "thistle", 0xD8BFD8FF }
|
||||
, { "tomato", 0xFF6347FF }
|
||||
, { "turquoise", 0x40E0D0FF }
|
||||
, { "violet", 0xEE82EEFF }
|
||||
, { "wheat", 0xF5DEB3FF }
|
||||
, { "white", 0xFFFFFFFF }
|
||||
, { "whitesmoke", 0xF5F5F5FF }
|
||||
, { "yellow", 0xFFFF00FF }
|
||||
, { "yellowgreen", 0x9ACD32FF }
|
||||
, { NULL, 0 }
|
||||
};
|
||||
|
||||
/*
|
||||
* Hex digit int val.
|
||||
*/
|
||||
|
||||
static int
|
||||
h(char c) {
|
||||
switch (c) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return c - '0';
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return (c - 'a') + 10;
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case 'E':
|
||||
case 'F':
|
||||
return (c - 'A') + 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgba_t from rgba.
|
||||
*/
|
||||
|
||||
rgba_t
|
||||
rgba_create(uint32_t rgba) {
|
||||
rgba_t color;
|
||||
color.r = (double) (rgba >> 24) / 255;
|
||||
color.g = (double) (rgba >> 16 & 0xff) / 255;
|
||||
color.b = (double) (rgba >> 8 & 0xff) / 255;
|
||||
color.a = (double) (rgba & 0xff) / 255;
|
||||
return color;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a string representation of the color.
|
||||
*/
|
||||
|
||||
void
|
||||
rgba_to_string(rgba_t rgba, char *buf, size_t len) {
|
||||
if (1 == rgba.a) {
|
||||
snprintf(buf, len, "#%.2x%.2x%.2x"
|
||||
, (int) (rgba.r * 255)
|
||||
, (int) (rgba.g * 255)
|
||||
, (int) (rgba.b * 255));
|
||||
} else {
|
||||
snprintf(buf, len, "rgba(%d, %d, %d, %.2f)"
|
||||
, (int) (rgba.r * 255)
|
||||
, (int) (rgba.g * 255)
|
||||
, (int) (rgba.b * 255)
|
||||
, rgba.a);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgba from (r,g,b,a).
|
||||
*/
|
||||
|
||||
static inline int32_t
|
||||
rgba_from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||
return
|
||||
r << 24
|
||||
| g << 16
|
||||
| b << 8
|
||||
| a;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function used in rgba_from_hsla().
|
||||
* Based on http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
|
||||
*/
|
||||
|
||||
static float
|
||||
hue_to_rgb(float t1, float t2, float hue) {
|
||||
if (hue < 0)
|
||||
hue += 6;
|
||||
if (hue >= 6)
|
||||
hue -= 6;
|
||||
|
||||
if (hue < 1)
|
||||
return (t2 - t1) * hue + t1;
|
||||
else if (hue < 3)
|
||||
return t2;
|
||||
else if (hue < 4)
|
||||
return (t2 - t1) * (4 - hue) + t1;
|
||||
else
|
||||
return t1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgba from (h,s,l,a).
|
||||
* Expects h values in the range [0, 360), and s, l, a in the range [0, 1].
|
||||
* Adapted from http://dev.w3.org/csswg/css-color-4/#hsl-to-rgb
|
||||
*/
|
||||
|
||||
static inline int32_t
|
||||
rgba_from_hsla(float h_deg, float s, float l, float a) {
|
||||
uint8_t r, g, b;
|
||||
float h = (6 * h_deg) / 360.0f, m1, m2;
|
||||
|
||||
if (l<=0.5)
|
||||
m2=l*(s+1);
|
||||
else
|
||||
m2=l+s-l*s;
|
||||
m1 = l*2 - m2;
|
||||
|
||||
// Scale and round the RGB components
|
||||
r = (uint8_t)floor(hue_to_rgb(m1, m2, h + 2) * 255 + 0.5);
|
||||
g = (uint8_t)floor(hue_to_rgb(m1, m2, h ) * 255 + 0.5);
|
||||
b = (uint8_t)floor(hue_to_rgb(m1, m2, h - 2) * 255 + 0.5);
|
||||
|
||||
return rgba_from_rgba(r, g, b, (uint8_t) (a * 255));
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgba from (h,s,l).
|
||||
* Expects h values in the range [0, 360), and s, l in the range [0, 1].
|
||||
*/
|
||||
|
||||
static inline int32_t
|
||||
rgba_from_hsl(float h_deg, float s, float l) {
|
||||
return rgba_from_hsla(h_deg, s, l, 1.0);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return rgba from (r,g,b).
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_rgb(uint8_t r, uint8_t g, uint8_t b) {
|
||||
return rgba_from_rgba(r, g, b, 255);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "#RRGGBB".
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_hex6_string(const char *str) {
|
||||
return rgba_from_rgb(
|
||||
(h(str[0]) << 4) + h(str[1])
|
||||
, (h(str[2]) << 4) + h(str[3])
|
||||
, (h(str[4]) << 4) + h(str[5])
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "#RGB"
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_hex3_string(const char *str) {
|
||||
return rgba_from_rgb(
|
||||
(h(str[0]) << 4) + h(str[0])
|
||||
, (h(str[1]) << 4) + h(str[1])
|
||||
, (h(str[2]) << 4) + h(str[2])
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "rgb()"
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_rgb_string(const char *str, short *ok) {
|
||||
if (str == strstr(str, "rgb(")) {
|
||||
str += 4;
|
||||
WHITESPACE;
|
||||
uint8_t r = 0, g = 0, b = 0;
|
||||
CHANNEL(r);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(g);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(b);
|
||||
WHITESPACE;
|
||||
return *ok = 1, rgba_from_rgb(r, g, b);
|
||||
}
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "rgba()"
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_rgba_string(const char *str, short *ok) {
|
||||
if (str == strstr(str, "rgba(")) {
|
||||
str += 5;
|
||||
WHITESPACE;
|
||||
uint8_t r = 0, g = 0, b = 0;
|
||||
float a = 0;
|
||||
CHANNEL(r);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(g);
|
||||
WHITESPACE_OR_COMMA;
|
||||
CHANNEL(b);
|
||||
WHITESPACE_OR_COMMA;
|
||||
ALPHA(a);
|
||||
WHITESPACE;
|
||||
return *ok = 1, rgba_from_rgba(r, g, b, (int) (a * 255));
|
||||
}
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "hsla()"
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_hsla_string(const char *str, short *ok) {
|
||||
if (str == strstr(str, "hsla(")) {
|
||||
str += 5;
|
||||
WHITESPACE;
|
||||
float h_deg = 0;
|
||||
float s = 0, l = 0;
|
||||
float a = 0;
|
||||
HUE(h_deg);
|
||||
WHITESPACE_OR_COMMA;
|
||||
SATURATION(s);
|
||||
WHITESPACE_OR_COMMA;
|
||||
LIGHTNESS(l);
|
||||
WHITESPACE_OR_COMMA;
|
||||
ALPHA(a);
|
||||
WHITESPACE;
|
||||
return *ok = 1, rgba_from_hsla(h_deg, s, l, a);
|
||||
}
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from "hsl()"
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_hsl_string(const char *str, short *ok) {
|
||||
if (str == strstr(str, "hsl(")) {
|
||||
str += 4;
|
||||
WHITESPACE;
|
||||
float h_deg = 0;
|
||||
float s = 0, l = 0;
|
||||
HUE(h_deg);
|
||||
WHITESPACE_OR_COMMA;
|
||||
SATURATION(s);
|
||||
WHITESPACE_OR_COMMA;
|
||||
LIGHTNESS(l);
|
||||
WHITESPACE;
|
||||
return *ok = 1, rgba_from_hsl(h_deg, s, l);
|
||||
}
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Return rgb from:
|
||||
*
|
||||
* - "#RGB"
|
||||
* - "#RRGGBB"
|
||||
*
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_hex_string(const char *str, short *ok) {
|
||||
size_t len = strlen(str);
|
||||
*ok = 1;
|
||||
if (6 == len) return rgba_from_hex6_string(str);
|
||||
if (3 == len) return rgba_from_hex3_string(str);
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return named color value.
|
||||
*/
|
||||
|
||||
static int32_t
|
||||
rgba_from_name_string(const char *str, short *ok) {
|
||||
int i = 0;
|
||||
struct named_color color;
|
||||
while ((color = named_colors[i++]).name) {
|
||||
if (*str == *color.name && 0 == strcmp(str, color.name))
|
||||
return *ok = 1, color.val;
|
||||
}
|
||||
return *ok = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return rgb from:
|
||||
*
|
||||
* - #RGB
|
||||
* - #RRGGBB
|
||||
* - rgb(r,g,b)
|
||||
* - rgba(r,g,b,a)
|
||||
* - hsl(h,s,l)
|
||||
* - hsla(h,s,l,a)
|
||||
* - name
|
||||
*
|
||||
*/
|
||||
|
||||
int32_t
|
||||
rgba_from_string(const char *str, short *ok) {
|
||||
if ('#' == str[0])
|
||||
return rgba_from_hex_string(++str, ok);
|
||||
if (str == strstr(str, "rgba"))
|
||||
return rgba_from_rgba_string(str, ok);
|
||||
if (str == strstr(str, "rgb"))
|
||||
return rgba_from_rgb_string(str, ok);
|
||||
if (str == strstr(str, "hsla"))
|
||||
return rgba_from_hsla_string(str, ok);
|
||||
if (str == strstr(str, "hsl"))
|
||||
return rgba_from_hsl_string(str, ok);
|
||||
return rgba_from_name_string(str, ok);
|
||||
}
|
||||
|
||||
/*
|
||||
* Inspect the given rgba color.
|
||||
*/
|
||||
|
||||
void
|
||||
rgba_inspect(int32_t rgba) {
|
||||
printf("rgba(%d,%d,%d,%d)\n"
|
||||
, rgba >> 24 & 0xff
|
||||
, rgba >> 16 & 0xff
|
||||
, rgba >> 8 & 0xff
|
||||
, rgba & 0xff
|
||||
);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
|
||||
//
|
||||
// color.h
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#ifndef __COLOR_PARSER_H__
|
||||
#define __COLOR_PARSER_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/*
|
||||
* RGBA struct.
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
double r, g, b, a;
|
||||
} rgba_t;
|
||||
|
||||
/*
|
||||
* Prototypes.
|
||||
*/
|
||||
|
||||
rgba_t
|
||||
rgba_create(uint32_t rgba);
|
||||
|
||||
int32_t
|
||||
rgba_from_string(const char *str, short *ok);
|
||||
|
||||
void
|
||||
rgba_to_string(rgba_t rgba, char *buf, size_t len);
|
||||
|
||||
void
|
||||
rgba_inspect(int32_t rgba);
|
||||
|
||||
#endif /* __COLOR_PARSER_H__ */
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
|
||||
//
|
||||
// init.cc
|
||||
//
|
||||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include "Canvas.h"
|
||||
#include "Image.h"
|
||||
#include "ImageData.h"
|
||||
#include "CanvasGradient.h"
|
||||
#include "CanvasPattern.h"
|
||||
#include "CanvasRenderingContext2d.h"
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
#include "FontFace.h"
|
||||
#include FT_FREETYPE_H
|
||||
#endif
|
||||
|
||||
// Compatibility with Visual Studio versions prior to VS2015
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
NAN_MODULE_INIT(init) {
|
||||
Canvas::Initialize(target);
|
||||
Image::Initialize(target);
|
||||
ImageData::Initialize(target);
|
||||
Context2d::Initialize(target);
|
||||
Gradient::Initialize(target);
|
||||
Pattern::Initialize(target);
|
||||
#ifdef HAVE_FREETYPE
|
||||
FontFace::Initialize(target);
|
||||
#endif
|
||||
|
||||
target->Set(Nan::New<String>("cairoVersion").ToLocalChecked(), Nan::New<String>(cairo_version_string()).ToLocalChecked());
|
||||
#ifdef HAVE_JPEG
|
||||
|
||||
#ifndef JPEG_LIB_VERSION_MAJOR
|
||||
#ifdef JPEG_LIB_VERSION
|
||||
#define JPEG_LIB_VERSION_MAJOR (JPEG_LIB_VERSION / 10)
|
||||
#else
|
||||
#define JPEG_LIB_VERSION_MAJOR 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef JPEG_LIB_VERSION_MINOR
|
||||
#ifdef JPEG_LIB_VERSION
|
||||
#define JPEG_LIB_VERSION_MINOR (JPEG_LIB_VERSION % 10)
|
||||
#else
|
||||
#define JPEG_LIB_VERSION_MINOR 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
char jpeg_version[10];
|
||||
if (JPEG_LIB_VERSION_MINOR > 0) {
|
||||
snprintf(jpeg_version, 10, "%d%c", JPEG_LIB_VERSION_MAJOR, JPEG_LIB_VERSION_MINOR + 'a' - 1);
|
||||
} else {
|
||||
snprintf(jpeg_version, 10, "%d", JPEG_LIB_VERSION_MAJOR);
|
||||
}
|
||||
target->Set(Nan::New<String>("jpegVersion").ToLocalChecked(), Nan::New<String>(jpeg_version).ToLocalChecked());
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GIF
|
||||
#ifndef GIF_LIB_VERSION
|
||||
char gif_version[10];
|
||||
snprintf(gif_version, 10, "%d.%d.%d", GIFLIB_MAJOR, GIFLIB_MINOR, GIFLIB_RELEASE);
|
||||
target->Set(Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(gif_version).ToLocalChecked());
|
||||
#else
|
||||
target->Set(Nan::New<String>("gifVersion").ToLocalChecked(), Nan::New<String>(GIF_LIB_VERSION).ToLocalChecked());
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_FREETYPE
|
||||
char freetype_version[10];
|
||||
snprintf(freetype_version, 10, "%d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
|
||||
target->Set(Nan::New<String>("freetypeVersion").ToLocalChecked(), Nan::New<String>(freetype_version).ToLocalChecked());
|
||||
#endif
|
||||
}
|
||||
|
||||
NODE_MODULE(canvas,init);
|
||||
Reference in New Issue
Block a user