commit
3a9d122456
14 changed files with 1058 additions and 0 deletions
@ -0,0 +1,7 @@ |
|||
build |
|||
.DS_Store |
|||
test.js |
|||
.lock-wscript |
|||
test.png |
|||
test.html |
|||
test/*.png |
@ -0,0 +1,6 @@ |
|||
|
|||
test: |
|||
@./support/expresso/bin/expresso \
|
|||
-I lib |
|||
|
|||
.PHONY: test |
@ -0,0 +1,42 @@ |
|||
|
|||
# node-canvas |
|||
|
|||
## TODO |
|||
|
|||
[ ] drawing shapes |
|||
[ ] line styles |
|||
[ ] fix colors |
|||
[ ] gradients |
|||
[ ] Image |
|||
[ ] patterns |
|||
[ ] text |
|||
[ ] rectangles |
|||
[ ] transformations |
|||
[ ] compositing |
|||
[ ] PixelArray |
|||
[ ] async saving to disk (png, svg, etc) |
|||
|
|||
## License |
|||
|
|||
(The MIT License) |
|||
|
|||
Copyright (c) 2009-2010 TJ Holowaychuk <tj@vision-media.ca> |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining |
|||
a copy of this software and associated documentation files (the |
|||
'Software'), to deal in the Software without restriction, including |
|||
without limitation the rights to use, copy, modify, merge, publish, |
|||
distribute, sublicense, and/or sell copies of the Software, and to |
|||
permit persons to whom the Software is furnished to do so, subject to |
|||
the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be |
|||
included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, |
|||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
|||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
|||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
|||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,127 @@ |
|||
|
|||
/*! |
|||
* Canvas |
|||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
|||
* MIT Licensed |
|||
*/ |
|||
|
|||
/** |
|||
* Module dependencies. |
|||
*/ |
|||
|
|||
var canvas = require('../build/default/canvas') |
|||
, colors = require('./colors') |
|||
, Canvas = canvas.Canvas |
|||
, Context2d = canvas.Context2d; |
|||
|
|||
/** |
|||
* Export `Canvas` as the module. |
|||
*/ |
|||
|
|||
var Canvas = exports = module.exports = Canvas; |
|||
|
|||
/** |
|||
* Library version. |
|||
*/ |
|||
|
|||
exports.version = '0.0.1'; |
|||
|
|||
/** |
|||
* Parse the given color `str`. |
|||
* |
|||
* Current supports: |
|||
* |
|||
* - #nnn |
|||
* - #nnnnnn |
|||
* - rgb(r,g,b) |
|||
* - rgba(r,g,b,a) |
|||
* - color |
|||
* |
|||
* Examples |
|||
* |
|||
* - #fff |
|||
* - #FFF |
|||
* - #FFFFFF |
|||
* - rgb(255,255,5) |
|||
* - rgba(255,255,5,.8) |
|||
* - rgba(255,255,5,0.8) |
|||
* - white |
|||
* - red |
|||
* |
|||
* @param {String} str |
|||
* @return {Array} |
|||
* @api public |
|||
*/ |
|||
|
|||
exports.parseColor = function(str){ |
|||
str = colors[str] || String(str); |
|||
if (0 == str.indexOf('rgba')) { |
|||
var captures = /rgba\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *, *(\d+\.\d+|\.\d+|\d+) *\)/.exec(str); |
|||
if (!captures) return; |
|||
return [ |
|||
parseInt(captures[1], 10) |
|||
, parseInt(captures[2], 10) |
|||
, parseInt(captures[3], 10) |
|||
, parseFloat(captures[4], 10) |
|||
]; |
|||
} else if (0 == str.indexOf('rgb')) { |
|||
var captures = /rgb\((\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *\)/.exec(str); |
|||
if (!captures) return; |
|||
return [ |
|||
parseInt(captures[1], 10) |
|||
, parseInt(captures[2], 10) |
|||
, parseInt(captures[3], 10) |
|||
, 1 |
|||
]; |
|||
} else if ('#' == str.charAt(0) && str.length > 4) { |
|||
var captures = /#(\w{2})(\w{2})(\w{2})/.exec(str); |
|||
if (!captures) return; |
|||
return [ |
|||
parseInt(captures[1], 16) |
|||
, parseInt(captures[2], 16) |
|||
, parseInt(captures[3], 16) |
|||
, 1 |
|||
]; |
|||
} else if ('#' == str.charAt(0)) { |
|||
var captures = /#(\w)(\w)(\w)/.exec(str); |
|||
if (!captures) return; |
|||
return [ |
|||
parseInt(captures[1] + captures[1], 16) |
|||
, parseInt(captures[2] + captures[2], 16) |
|||
, parseInt(captures[3] + captures[3], 16) |
|||
, 1 |
|||
]; |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Get a context object. |
|||
* |
|||
* @param {String} contextId |
|||
* @return {Context2d} |
|||
* @api public |
|||
*/ |
|||
|
|||
Canvas.prototype.getContext = function(contextId){ |
|||
if ('2d' == contextId) { |
|||
var ctx = new Context2d(this); |
|||
this.context = ctx; |
|||
ctx.canvas = this; |
|||
return ctx; |
|||
} |
|||
}; |
|||
|
|||
Context2d.prototype.__defineSetter__('fillStyle', function(val){ |
|||
var rgba = exports.parseColor(val) || [0,0,0,1]; |
|||
this.lastFillStyle = rgba; |
|||
this.setFillRGBA( |
|||
rgba[0] |
|||
, rgba[1] |
|||
, rgba[2] |
|||
, rgba[3]); |
|||
}); |
|||
|
|||
Context2d.prototype.__defineGetter__('fillStyle', function(){ |
|||
var rgba = this.lastFillStyle; |
|||
return 'rgba(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ',' + rgba[3] + ')'; |
|||
}); |
@ -0,0 +1,152 @@ |
|||
|
|||
/*! |
|||
* Canvas - colors |
|||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> |
|||
* MIT Licensed |
|||
*/ |
|||
|
|||
module.exports = { |
|||
aliceblue: '#f0f8ff' |
|||
, antiquewhite: '#faebd7' |
|||
, aqua: '#00ffff' |
|||
, aquamarine: '#7fffd4' |
|||
, azure: '#f0ffff' |
|||
, beige: '#f5f5dc' |
|||
, bisque: '#ffe4c4' |
|||
, black: '#000000' |
|||
, blanchedalmond: '#ffebcd' |
|||
, blue: '#0000ff' |
|||
, blueviolet: '#8a2be2' |
|||
, brown: '#a52a2a' |
|||
, burlywood: '#deb887' |
|||
, cadetblue: '#5f9ea0' |
|||
, chartreuse: '#7fff00' |
|||
, chocolate: '#d2691e' |
|||
, coral: '#ff7f50' |
|||
, cornflowerblue: '#6495ed' |
|||
, cornsilk: '#fff8dc' |
|||
, crimson: '#dc143c' |
|||
, cyan: '#00ffff' |
|||
, darkblue: '#00008b' |
|||
, darkcyan: '#008b8b' |
|||
, darkgoldenrod: '#b8860b' |
|||
, darkgray: '#a9a9a9' |
|||
, darkgreen: '#006400' |
|||
, darkkhaki: '#bdb76b' |
|||
, darkmagenta: '#8b008b' |
|||
, darkolivegreen: '#556b2f' |
|||
, darkorange: '#ff8c00' |
|||
, darkorchid: '#9932cc' |
|||
, darkred: '#8b0000' |
|||
, darksalmon: '#e9967a' |
|||
, darkseagreen: '#8fbc8f' |
|||
, darkslateblue: '#483d8b' |
|||
, darkslategray: '#2f4f4f' |
|||
, darkturquoise: '#00ced1' |
|||
, darkviolet: '#9400d3' |
|||
, deeppink: '#ff1493' |
|||
, deepskyblue: '#00bfff' |
|||
, dimgray: '#696969' |
|||
, dodgerblue: '#1e90ff' |
|||
, feldspar: '#d19275' |
|||
, firebrick: '#b22222' |
|||
, floralwhite: '#fffaf0' |
|||
, forestgreen: '#228b22' |
|||
, fuchsia: '#ff00ff' |
|||
, gainsboro: '#dcdcdc' |
|||
, ghostwhite: '#f8f8ff' |
|||
, gold: '#ffd700' |
|||
, goldenrod: '#daa520' |
|||
, gray: '#808080' |
|||
, green: '#008000' |
|||
, greenyellow: '#adff2f' |
|||
, honeydew: '#f0fff0' |
|||
, hotpink: '#ff69b4' |
|||
, indianred : '#cd5c5c' |
|||
, indigo : '#4b0082' |
|||
, ivory: '#fffff0' |
|||
, khaki: '#f0e68c' |
|||
, lavender: '#e6e6fa' |
|||
, lavenderblush: '#fff0f5' |
|||
, lawngreen: '#7cfc00' |
|||
, lemonchiffon: '#fffacd' |
|||
, lightblue: '#add8e6' |
|||
, lightcoral: '#f08080' |
|||
, lightcyan: '#e0ffff' |
|||
, lightgoldenrodyellow: '#fafad2' |
|||
, lightgrey: '#d3d3d3' |
|||
, lightgreen: '#90ee90' |
|||
, lightpink: '#ffb6c1' |
|||
, lightsalmon: '#ffa07a' |
|||
, lightseagreen: '#20b2aa' |
|||
, lightskyblue: '#87cefa' |
|||
, lightslateblue: '#8470ff' |
|||
, lightslategray: '#778899' |
|||
, lightsteelblue: '#b0c4de' |
|||
, lightyellow: '#ffffe0' |
|||
, lime: '#00ff00' |
|||
, limegreen: '#32cd32' |
|||
, linen: '#faf0e6' |
|||
, magenta: '#ff00ff' |
|||
, maroon: '#800000' |
|||
, mediumaquamarine: '#66cdaa' |
|||
, mediumblue: '#0000cd' |
|||
, mediumorchid: '#ba55d3' |
|||
, mediumpurple: '#9370d8' |
|||
, mediumseagreen: '#3cb371' |
|||
, mediumslateblue: '#7b68ee' |
|||
, mediumspringgreen: '#00fa9a' |
|||
, mediumturquoise: '#48d1cc' |
|||
, mediumvioletred: '#c71585' |
|||
, midnightblue: '#191970' |
|||
, mintcream: '#f5fffa' |
|||
, mistyrose: '#ffe4e1' |
|||
, moccasin: '#ffe4b5' |
|||
, navajowhite: '#ffdead' |
|||
, navy: '#000080' |
|||
, oldlace: '#fdf5e6' |
|||
, olive: '#808000' |
|||
, olivedrab: '#6b8e23' |
|||
, orange: '#ffa500' |
|||
, orangered: '#ff4500' |
|||
, orchid: '#da70d6' |
|||
, palegoldenrod: '#eee8aa' |
|||
, palegreen: '#98fb98' |
|||
, paleturquoise: '#afeeee' |
|||
, palevioletred: '#d87093' |
|||
, papayawhip: '#ffefd5' |
|||
, peachpuff: '#ffdab9' |
|||
, peru: '#cd853f' |
|||
, pink: '#ffc0cb' |
|||
, plum: '#dda0dd' |
|||
, powderblue: '#b0e0e6' |
|||
, purple: '#800080' |
|||
, red: '#ff0000' |
|||
, rosybrown: '#bc8f8f' |
|||
, royalblue: '#4169e1' |
|||
, saddlebrown: '#8b4513' |
|||
, salmon: '#fa8072' |
|||
, sandybrown: '#f4a460' |
|||
, seagreen: '#2e8b57' |
|||
, seashell: '#fff5ee' |
|||
, sienna: '#a0522d' |
|||
, silver: '#c0c0c0' |
|||
, skyblue: '#87ceeb' |
|||
, slateblue: '#6a5acd' |
|||
, slategray: '#708090' |
|||
, snow: '#fffafa' |
|||
, springgreen: '#00ff7f' |
|||
, steelblue: '#4682b4' |
|||
, tan: '#d2b48c' |
|||
, teal: '#008080' |
|||
, thistle: '#d8bfd8' |
|||
, tomato: '#ff6347' |
|||
, turquoise: '#40e0d0' |
|||
, violet: '#ee82ee' |
|||
, violetred: '#d02090' |
|||
, wheat: '#f5deb3' |
|||
, white: '#ffffff' |
|||
, whitesmoke: '#f5f5f5' |
|||
, yellow: '#ffff00' |
|||
, yellowgreen: '#9acd32' |
|||
}; |
@ -0,0 +1,11 @@ |
|||
{ "name": "canvas" |
|||
, "description": "node canvas implementation backed by Cairo" |
|||
, "version": "0.0.1" |
|||
, "author": "TJ Holowaychuk <tj@vision-media.ca>" |
|||
, "keywords": [] |
|||
, "main": "./lib/canvas.js" |
|||
, "scripts": { |
|||
"preinstall": "node-waf configure build" |
|||
} |
|||
, "engines": { "node": ">= 0.2.0" } |
|||
} |
@ -0,0 +1,76 @@ |
|||
|
|||
//
|
|||
// canvas.cc
|
|||
//
|
|||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|||
//
|
|||
|
|||
#include "canvas.h" |
|||
|
|||
using namespace v8; |
|||
|
|||
/*
|
|||
* Initialize Canvas. |
|||
*/ |
|||
|
|||
void |
|||
Canvas::Initialize(Handle<Object> target) { |
|||
HandleScope scope; |
|||
Local<FunctionTemplate> t = FunctionTemplate::New(Canvas::New); |
|||
t->InstanceTemplate()->SetInternalFieldCount(1); |
|||
t->SetClassName(String::NewSymbol("Canvas")); |
|||
|
|||
NODE_SET_PROTOTYPE_METHOD(t, "savePNG", SavePNG); |
|||
target->Set(String::NewSymbol("Canvas"), t->GetFunction()); |
|||
} |
|||
|
|||
/*
|
|||
* Initialize a Canvas with the given width and height. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Canvas::New(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("width required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("height required"))); |
|||
|
|||
Canvas *canvas = new Canvas(args[0]->Uint32Value(), args[1]->Uint32Value()); |
|||
canvas->Wrap(args.This()); |
|||
return args.This(); |
|||
} |
|||
|
|||
/*
|
|||
* Initialize cairo surface. |
|||
*/ |
|||
|
|||
Canvas::Canvas(int width, int height): ObjectWrap() { |
|||
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); |
|||
} |
|||
|
|||
/*
|
|||
* Destroy cairo surface. |
|||
*/ |
|||
|
|||
Canvas::~Canvas() { |
|||
cairo_surface_destroy(_surface); |
|||
} |
|||
|
|||
/*
|
|||
* Save a PNG at the given path. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Canvas::SavePNG(const Arguments &args) { |
|||
HandleScope scope; |
|||
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This()); |
|||
|
|||
if (!args[0]->IsString()) |
|||
return ThrowException(Exception::TypeError(String::New("path required"))); |
|||
|
|||
String::Utf8Value path(args[0]->ToString()); |
|||
cairo_surface_write_to_png(canvas->getSurface(), *path); |
|||
return Undefined(); |
|||
} |
@ -0,0 +1,33 @@ |
|||
|
|||
//
|
|||
// 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 <cairo.h> |
|||
|
|||
using namespace v8; |
|||
|
|||
class Canvas: public node::ObjectWrap { |
|||
public: |
|||
static void Initialize(Handle<Object> target); |
|||
static Handle<Value> New(const Arguments &args); |
|||
static Handle<Value> SavePNG(const Arguments &args); |
|||
inline cairo_surface_t *getSurface(){ return _surface; } |
|||
|
|||
protected: |
|||
Canvas(int width, int height); |
|||
|
|||
private: |
|||
~Canvas(); |
|||
cairo_surface_t *_surface; |
|||
}; |
|||
|
|||
#endif |
@ -0,0 +1,331 @@ |
|||
|
|||
//
|
|||
// context2d.cc
|
|||
//
|
|||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|||
//
|
|||
|
|||
#include "canvas.h" |
|||
#include "context2d.h" |
|||
#include <math.h> |
|||
|
|||
using namespace v8; |
|||
using namespace node; |
|||
|
|||
/*
|
|||
* Rectangle arg assertions. |
|||
*/ |
|||
|
|||
#define RECT_ARGS \ |
|||
if (!args[0]->IsNumber()) \ |
|||
return ThrowException(Exception::TypeError(String::New("x required"))); \ |
|||
if (!args[1]->IsNumber()) \ |
|||
return ThrowException(Exception::TypeError(String::New("y required"))); \ |
|||
if (!args[2]->IsNumber()) \ |
|||
return ThrowException(Exception::TypeError(String::New("width required"))); \ |
|||
if (!args[3]->IsNumber()) \ |
|||
return ThrowException(Exception::TypeError(String::New("height required"))); \ |
|||
int x = args[0]->Int32Value(); \ |
|||
int y = args[1]->Int32Value(); \ |
|||
int width = args[2]->Int32Value(); \ |
|||
int height = args[3]->Int32Value(); |
|||
|
|||
/*
|
|||
* Initialize Context2d. |
|||
*/ |
|||
|
|||
void |
|||
Context2d::Initialize(Handle<Object> target) { |
|||
HandleScope scope; |
|||
// Constructor
|
|||
Local<FunctionTemplate> t = FunctionTemplate::New(Context2d::New); |
|||
t->InstanceTemplate()->SetInternalFieldCount(1); |
|||
t->SetClassName(String::NewSymbol("Context2d")); |
|||
|
|||
// Prototype
|
|||
NODE_SET_PROTOTYPE_METHOD(t, "fill", Fill); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "stroke", Stroke); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "fillRect", FillRect); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "strokeRect", StrokeRect); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "clearRect", ClearRect); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "moveTo", MoveTo); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "lineTo", LineTo); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "bezierCurveTo", BezierCurveTo); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "beginPath", BeginPath); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "closePath", ClosePath); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "arc", Arc); |
|||
NODE_SET_PROTOTYPE_METHOD(t, "setFillRGBA", SetFillRGBA); |
|||
target->Set(String::NewSymbol("Context2d"), t->GetFunction()); |
|||
} |
|||
|
|||
/*
|
|||
* Initialize a new Context2d with the given canvas. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::New(const Arguments &args) { |
|||
HandleScope scope; |
|||
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args[0]->ToObject()); |
|||
Context2d *context = new Context2d(canvas); |
|||
context->Wrap(args.This()); |
|||
return args.This(); |
|||
} |
|||
|
|||
/*
|
|||
* Create a cairo context. |
|||
*/ |
|||
|
|||
Context2d::Context2d(Canvas *canvas): ObjectWrap() { |
|||
_canvas = canvas; |
|||
_context = cairo_create(canvas->getSurface()); |
|||
cairo_set_source_rgba(_context, 0, 0, 0, 1); |
|||
} |
|||
|
|||
/*
|
|||
* Destroy cairo context. |
|||
*/ |
|||
|
|||
Context2d::~Context2d() { |
|||
cairo_destroy(_context); |
|||
} |
|||
|
|||
/*
|
|||
* Set fill RGBA, use internally for fillStyle= |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::SetFillRGBA(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("r required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("g required"))); |
|||
if (!args[2]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("b required"))); |
|||
if (!args[3]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("alpha required"))); |
|||
|
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
|
|||
cairo_set_source_rgba(context->getContext() |
|||
, args[0]->Int32Value() |
|||
, args[1]->Int32Value() |
|||
, args[2]->Int32Value() |
|||
, args[3]->NumberValue()); |
|||
|
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Bezier curve. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::BezierCurveTo(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("cp1x required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("cp1y required"))); |
|||
if (!args[2]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("cp2x required"))); |
|||
if (!args[3]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("cp2y required"))); |
|||
if (!args[4]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("x required"))); |
|||
if (!args[5]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("y required"))); |
|||
|
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_curve_to(context->getContext() |
|||
, args[0]->NumberValue() |
|||
, args[1]->NumberValue() |
|||
, args[2]->NumberValue() |
|||
, args[3]->NumberValue() |
|||
, args[4]->NumberValue() |
|||
, args[5]->NumberValue()); |
|||
|
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Creates a new subpath. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::BeginPath(const Arguments &args) { |
|||
HandleScope scope; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_new_path(context->getContext()); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Marks the subpath as closed. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::ClosePath(const Arguments &args) { |
|||
HandleScope scope; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_close_path(context->getContext()); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Fill the shape. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::Fill(const Arguments &args) { |
|||
HandleScope scope; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_fill(context->getContext()); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Stroke the shape. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::Stroke(const Arguments &args) { |
|||
HandleScope scope; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_stroke(context->getContext()); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Adds a point to the current subpath. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::LineTo(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("x required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("y required"))); |
|||
|
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_line_to(context->getContext() |
|||
, args[0]->Int32Value() |
|||
, args[1]->Int32Value()); |
|||
|
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Creates a new subpath at the given point. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::MoveTo(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("x required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("y required"))); |
|||
|
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_move_to(context->getContext() |
|||
, args[0]->Int32Value() |
|||
, args[1]->Int32Value()); |
|||
|
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Fill the rectangle defined by x, y, with and height. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::FillRect(const Arguments &args) { |
|||
HandleScope scope; |
|||
RECT_ARGS; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_t *ctx = context->getContext(); |
|||
cairo_rectangle(ctx, x, y, width, height); |
|||
cairo_fill(ctx); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Stroke the rectangle defined by x, y, width and height. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::StrokeRect(const Arguments &args) { |
|||
HandleScope scope; |
|||
RECT_ARGS; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_t *ctx = context->getContext(); |
|||
cairo_rectangle(ctx, x, y, width, height); |
|||
cairo_stroke(ctx); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Clears all pixels defined by x, y, width and height. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::ClearRect(const Arguments &args) { |
|||
HandleScope scope; |
|||
RECT_ARGS; |
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_t *ctx = context->getContext(); |
|||
cairo_set_operator(ctx, CAIRO_OPERATOR_CLEAR); |
|||
cairo_rectangle(ctx, x, y, width, height); |
|||
cairo_fill(ctx); |
|||
cairo_set_operator(ctx, CAIRO_OPERATOR_OVER); |
|||
return Undefined(); |
|||
} |
|||
|
|||
/*
|
|||
* Adds an arc at x, y with the given radis and start/end angles. |
|||
*/ |
|||
|
|||
Handle<Value> |
|||
Context2d::Arc(const Arguments &args) { |
|||
HandleScope scope; |
|||
|
|||
if (!args[0]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("x required"))); |
|||
if (!args[1]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("y required"))); |
|||
if (!args[2]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("radius required"))); |
|||
if (!args[3]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("startAngle required"))); |
|||
if (!args[4]->IsNumber()) |
|||
return ThrowException(Exception::TypeError(String::New("endAngle required"))); |
|||
|
|||
bool anticlockwise = args[5]->BooleanValue(); |
|||
|
|||
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This()); |
|||
cairo_t *ctx = context->getContext(); |
|||
|
|||
if (anticlockwise && M_PI * 2 != args[4]->NumberValue()) { |
|||
cairo_arc_negative(ctx |
|||
, args[0]->NumberValue() |
|||
, args[1]->NumberValue() |
|||
, args[2]->NumberValue() |
|||
, args[3]->NumberValue() |
|||
, args[4]->NumberValue()); |
|||
} else { |
|||
cairo_arc(ctx |
|||
, args[0]->NumberValue() |
|||
, args[1]->NumberValue() |
|||
, args[2]->NumberValue() |
|||
, args[3]->NumberValue() |
|||
, args[4]->NumberValue()); |
|||
} |
|||
|
|||
return Undefined(); |
|||
} |
@ -0,0 +1,41 @@ |
|||
|
|||
//
|
|||
// context2d.h
|
|||
//
|
|||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|||
//
|
|||
|
|||
#ifndef __NODE_CONTEXT2D_H__ |
|||
#define __NODE_CONTEXT2D_H__ |
|||
|
|||
#include "canvas.h" |
|||
|
|||
class Context2d: public node::ObjectWrap { |
|||
public: |
|||
static void Initialize(Handle<Object> target); |
|||
static Handle<Value> New(const Arguments &args); |
|||
static Handle<Value> BeginPath(const Arguments &args); |
|||
static Handle<Value> ClosePath(const Arguments &args); |
|||
static Handle<Value> Fill(const Arguments &args); |
|||
static Handle<Value> Stroke(const Arguments &args); |
|||
static Handle<Value> SetFillRGBA(const Arguments &args); |
|||
static Handle<Value> BezierCurveTo(const Arguments &args); |
|||
static Handle<Value> LineTo(const Arguments &args); |
|||
static Handle<Value> MoveTo(const Arguments &args); |
|||
static Handle<Value> FillRect(const Arguments &args); |
|||
static Handle<Value> StrokeRect(const Arguments &args); |
|||
static Handle<Value> ClearRect(const Arguments &args); |
|||
static Handle<Value> Arc(const Arguments &args); |
|||
inline cairo_t *getContext(){ return _context; } |
|||
inline Canvas *getCanvas(){ return _canvas; } |
|||
|
|||
protected: |
|||
Context2d(Canvas *canvas); |
|||
|
|||
private: |
|||
~Context2d(); |
|||
Canvas *_canvas; |
|||
cairo_t *_context; |
|||
}; |
|||
|
|||
#endif |
@ -0,0 +1,16 @@ |
|||
|
|||
//
|
|||
// node-canvas.cc
|
|||
//
|
|||
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
|
|||
//
|
|||
|
|||
#include "canvas.h" |
|||
#include "context2d.h" |
|||
|
|||
extern "C" void |
|||
init (Handle<Object> target) { |
|||
HandleScope scope; |
|||
Canvas::Initialize(target); |
|||
Context2d::Initialize(target); |
|||
} |
@ -0,0 +1,197 @@ |
|||
|
|||
/** |
|||
* Module dependencies. |
|||
*/ |
|||
|
|||
var Canvas = require('canvas') |
|||
, assert = require('assert') |
|||
, crypto = require('crypto') |
|||
, fs = require('fs'); |
|||
|
|||
function hash(val) { |
|||
return crypto.createHash('md5').update(val).digest('hex'); |
|||
} |
|||
|
|||
function assertChecksum(canvas, path, checksum, msg) { |
|||
canvas.savePNG(path); |
|||
fs.readFile(path, function(err, buf){ |
|||
assert.equal(hash(buf), checksum, msg); |
|||
}); |
|||
} |
|||
|
|||
module.exports = { |
|||
'test .version': function(assert){ |
|||
assert.match(Canvas.version, /^\d+\.\d+\.\d+$/); |
|||
}, |
|||
|
|||
'test .parseColor()': function(assert){ |
|||
assert.equal(null, Canvas.parseColor()); |
|||
assert.equal(null, Canvas.parseColor('')); |
|||
|
|||
// rgb()
|
|||
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255,165,0)')); |
|||
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255, 165, 0)')); |
|||
assert.eql([255,165,0,1], Canvas.parseColor('rgb(255 , 165 , 0)')); |
|||
assert.equal(null, Canvas.parseColor('rgb()')); |
|||
|
|||
// rgba()
|
|||
assert.eql([255,165,0,1], Canvas.parseColor('rgba(255,165,0,1)')); |
|||
assert.eql([255,165,0,1], Canvas.parseColor('rgba(255,165,0,1)')); |
|||
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165,0,0.6)')); |
|||
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165, 0, 0.6)')); |
|||
assert.eql([255,165,0,.6], Canvas.parseColor('rgba(255,165 , 0 ,.6)')); |
|||
assert.equal(null, Canvas.parseColor('rgba(2554,165 , 0 ,.6)')); |
|||
assert.equal(null, Canvas.parseColor('rgba()')); |
|||
|
|||
// hex
|
|||
assert.eql([165,89,89,1], Canvas.parseColor('#A55959')); |
|||
assert.eql([255,255,255,1], Canvas.parseColor('#FFFFFF')); |
|||
assert.eql([255,255,255,1], Canvas.parseColor('#ffffff')); |
|||
assert.eql([255,255,255,1], Canvas.parseColor('#FFF')); |
|||
assert.eql([255,255,255,1], Canvas.parseColor('#fff')); |
|||
|
|||
// name
|
|||
assert.eql([255,255,255,1], Canvas.parseColor('white')); |
|||
assert.eql([0,0,0,1], Canvas.parseColor('black')); |
|||
}, |
|||
|
|||
'test Canvas#getContext("2d")': function(assert){ |
|||
var canvas = new Canvas(200, 300) |
|||
, ctx = canvas.getContext('2d'); |
|||
assert.ok('object' == typeof ctx); |
|||
assert.equal(canvas, ctx.canvas, 'context.canvas is not canvas'); |
|||
assert.equal(ctx, canvas.context, 'canvas.context is not context'); |
|||
}, |
|||
|
|||
'test Canvas#getContext("invalid")': function(assert){ |
|||
assert.equal(null, new Canvas(200, 300).getContext('invalid')); |
|||
}, |
|||
|
|||
'test Canvas#clearRect()': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/clearRect.png'; |
|||
|
|||
ctx.fillRect(25,25,100,100); |
|||
ctx.clearRect(45,45,60,60); |
|||
ctx.fillRect(50,50,50,50); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, 'e21404e97142a76c0c8d14cf0fab400f' |
|||
, 'Canvas#clearRect() failed'); |
|||
}, |
|||
|
|||
'test Canvas#strokeRect()': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/strokeRect.png'; |
|||
|
|||
ctx.fillRect(25,25,100,100); |
|||
ctx.clearRect(45,45,60,60); |
|||
ctx.strokeRect(50,50,50,50); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, '1cd349f7d3d2ae5a2bce13ca35dcaa94' |
|||
, 'Canvas#strokeRect() failed'); |
|||
}, |
|||
|
|||
'test Canvas#lineTo()': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/lineTo.png'; |
|||
|
|||
// Filled triangle
|
|||
ctx.beginPath(); |
|||
ctx.moveTo(25.5,25); |
|||
ctx.lineTo(105,25); |
|||
ctx.lineTo(25,105); |
|||
ctx.fill(); |
|||
|
|||
// Stroked triangle
|
|||
ctx.beginPath(); |
|||
ctx.moveTo(125,125); |
|||
ctx.lineTo(125,45); |
|||
ctx.lineTo(45,125); |
|||
ctx.closePath(); |
|||
ctx.stroke(); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, '44cce447dcb15918c9baf9170f87911f' |
|||
, 'Canvas#lineTo() failed' |
|||
); |
|||
}, |
|||
|
|||
'test Canvas#arc()': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/arc.png'; |
|||
|
|||
ctx.beginPath(); |
|||
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
|
|||
ctx.moveTo(110,75); |
|||
ctx.arc(75,75,35,0,Math.PI,false); // Mouth
|
|||
ctx.moveTo(65,65); |
|||
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
|
|||
ctx.moveTo(95,65); |
|||
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
|
|||
ctx.stroke(); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, '3c48a221b24c582f46e39c16678b12dd' |
|||
, 'Canvas#arc() failed'); |
|||
}, |
|||
|
|||
'test Canvas#bezierCurveTo()': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/bezierCurveTo.png'; |
|||
|
|||
ctx.beginPath(); |
|||
ctx.moveTo(75,40); |
|||
ctx.bezierCurveTo(75,37,70,25,50,25); |
|||
ctx.bezierCurveTo(20,25,20,62.5,20,62.5); |
|||
ctx.bezierCurveTo(20,80,40,102,75,120); |
|||
ctx.bezierCurveTo(110,102,130,80,130,62.5); |
|||
ctx.bezierCurveTo(130,62.5,130,25,100,25); |
|||
ctx.bezierCurveTo(85,25,75,37,75,40); |
|||
ctx.fill(); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, '5626a53780d77aecc490ec807ee0bc63' |
|||
, 'Canvas#bezierCurveTo() failed'); |
|||
}, |
|||
|
|||
'test Canvas#fillStyle=': function(assert){ |
|||
var canvas = new Canvas(200, 200) |
|||
, ctx = canvas.getContext('2d') |
|||
, path = __dirname + '/fillStyle.png'; |
|||
|
|||
ctx.fillStyle = '#000'; |
|||
ctx.fillRect(110, 110, 50, 50); |
|||
assert.equal('rgba(0,0,0,1)', ctx.fillStyle); |
|||
|
|||
ctx.fillStyle = 'rgb(0,55,0)'; |
|||
ctx.fillRect(10, 10, 50, 50); |
|||
assert.equal('rgba(0,55,0,1)', ctx.fillStyle); |
|||
|
|||
ctx.fillStyle = 'rgba(0,0,0,0.5)'; |
|||
ctx.fillRect(60, 60, 50, 50); |
|||
assert.equal('rgba(0,0,0,0.5)', ctx.fillStyle); |
|||
|
|||
assertChecksum( |
|||
canvas |
|||
, path |
|||
, '01632d060ba4702a53862a955382d30d' |
|||
, 'Canvas#fillStyle= failed'); |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
import glob |
|||
|
|||
srcdir = '.' |
|||
blddir = 'build' |
|||
VERSION = '0.0.1' |
|||
|
|||
def set_options(opt): |
|||
opt.tool_options('compiler_cxx') |
|||
|
|||
def configure(conf): |
|||
conf.check_tool('compiler_cxx') |
|||
conf.check_tool('node_addon') |
|||
conf.check_cfg(package='cairo', mandatory=1, args='--cflags --libs') |
|||
|
|||
def build(bld): |
|||
obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') |
|||
obj.target = 'canvas' |
|||
obj.source = bld.glob('src/*.cc') |
|||
obj.uselib = ['CAIRO'] |
Loading…
Reference in new issue