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