Initial commit

This commit is contained in:
WatermelonModders
2022-05-31 12:35:46 -04:00
commit fc5cb0c32c
4097 changed files with 447075 additions and 0 deletions
+673
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+185
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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, &degrees)) {
*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
View File
@@ -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
View File
@@ -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);