diff --git a/lib/context2d.js b/lib/context2d.js index 7eb2407..ef3b6a7 100644 --- a/lib/context2d.js +++ b/lib/context2d.js @@ -12,6 +12,7 @@ var canvas = require('./bindings') , Context2d = canvas.CanvasRenderingContext2d , CanvasGradient = canvas.CanvasGradient + , CanvasPattern = canvas.CanvasPattern , ImageData = canvas.ImageData , PixelArray = canvas.CanvasPixelArray; @@ -98,6 +99,20 @@ var parseFont = exports.parseFont = function(str){ return cache[str] = font; }; +/** + * Create a pattern from `Image` or `Canvas`. + * + * @param {Image|Canvas} image + * @param {String} repetition + * @return {CanvasPattern} + * @api public + */ + +Context2d.prototype.createPattern = function(image, repetition){ + // TODO Use repetition (currently always 'repeat') + return new CanvasPattern(image); +}; + /** * Create a linear gradient at the given point `(x0, y0)` and `(x1, y1)`. * @@ -150,7 +165,8 @@ Context2d.prototype.setTransform = function(){ */ Context2d.prototype.__defineSetter__('fillStyle', function(val){ - if ('CanvasGradient' == val.constructor.name) { + if ('CanvasGradient' == val.constructor.name + || 'CanvasPattern' == val.constructor.name) { this.lastFillStyle = val; this._setFillPattern(val); } else if ('string' == typeof val) { @@ -176,7 +192,8 @@ Context2d.prototype.__defineGetter__('fillStyle', function(){ */ Context2d.prototype.__defineSetter__('strokeStyle', function(val){ - if ('CanvasGradient' == val.constructor.name) { + if ('CanvasGradient' == val.constructor.name + || 'CanvasPattern' == val.constructor.name) { this.lastStrokeStyle = val; this._setStrokePattern(val); } else if ('string' == typeof val) { diff --git a/src/CanvasPattern.cc b/src/CanvasPattern.cc new file mode 100644 index 0000000..8802735 --- /dev/null +++ b/src/CanvasPattern.cc @@ -0,0 +1,88 @@ + +// +// Pattern.cc +// +// Copyright (c) 2010 LearnBoost +// + +#include "Canvas.h" +#include "Image.h" +#include "CanvasPattern.h" + +Persistent Pattern::constructor; + +/* + * Initialize CanvasPattern. + */ + +void +Pattern::Initialize(Handle target) { + HandleScope scope; + + // Constructor + constructor = Persistent::New(FunctionTemplate::New(Pattern::New)); + constructor->InstanceTemplate()->SetInternalFieldCount(1); + constructor->SetClassName(String::NewSymbol("CanvasPattern")); + + // Prototype + target->Set(String::NewSymbol("CanvasPattern"), constructor->GetFunction()); +} + +/* + * Initialize a new CanvasPattern. + */ + +Handle +Pattern::New(const Arguments &args) { + HandleScope scope; + + int w = 0 + , h = 0; + cairo_surface_t *surface; + + Local obj = args[0]->ToObject(); + + // Image + if (Image::constructor->HasInstance(obj)) { + Image *img = ObjectWrap::Unwrap(obj); + if (!img->isComplete()) { + return ThrowException(Exception::Error(String::New("Image given has not completed loading"))); + } + w = img->width; + h = img->height; + surface = img->surface(); + + // Canvas + } else if (Canvas::constructor->HasInstance(obj)) { + Canvas *canvas = ObjectWrap::Unwrap(obj); + w = canvas->width; + h = canvas->height; + surface = canvas->surface(); + + // Invalid + } else { + return ThrowException(Exception::TypeError(String::New("Image or Canvas expected"))); + } + + Pattern *pattern = new Pattern(surface,w,h); + pattern->Wrap(args.This()); + return args.This(); +} + + +/* + * Initialize linear gradient. + */ + +Pattern::Pattern(cairo_surface_t *surface, int w, int h): + _width(w), _height(h) { + _pattern = cairo_pattern_create_for_surface(surface); +} + +/* + * Destroy the pattern. + */ + +Pattern::~Pattern() { + cairo_pattern_destroy(_pattern); +} diff --git a/src/CanvasPattern.h b/src/CanvasPattern.h new file mode 100644 index 0000000..7020d69 --- /dev/null +++ b/src/CanvasPattern.h @@ -0,0 +1,28 @@ + +// +// CanvasPattern.h +// +// Copyright (c) 2011 LearnBoost +// + +#ifndef __NODE_PATTERN_H__ +#define __NODE_PATTERN_H__ + +#include "Canvas.h" + +class Pattern: public node::ObjectWrap { + public: + static Persistent constructor; + static void Initialize(Handle target); + static Handle New(const Arguments &args); + Pattern(cairo_surface_t *surface, int w, int h); + inline cairo_pattern_t *pattern(){ return _pattern; } + + private: + ~Pattern(); + int _width, _height; + // TODO REPEAT/REPEAT_X/REPEAT_Y + cairo_pattern_t *_pattern; +}; + +#endif diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 2beb95d..e976e0f 100644 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -14,6 +14,7 @@ #include "ImageData.h" #include "CanvasRenderingContext2d.h" #include "CanvasGradient.h" +#include "CanvasPattern.h" Persistent Context2d::constructor; @@ -125,6 +126,7 @@ Context2d::Context2d(Canvas *canvas) { state->globalAlpha = 1; state->textAlignment = -1; state->fillPattern = state->strokePattern = NULL; + state->fillGradient = state->strokeGradient = NULL; state->textBaseline = NULL; rgba_t transparent = { 0,0,0,1 }; rgba_t transparent_black = { 0,0,0,0 }; @@ -218,8 +220,12 @@ Context2d::restorePath() { void Context2d::fill(bool preserve) { if (state->fillPattern) { - cairo_pattern_set_filter(state->fillPattern, state->patternQuality); - cairo_set_source(_context, state->fillPattern); + cairo_set_source(_context, state->fillPattern); + cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT); + // TODO repeat/repeat-x/repeat-y + } else if (state->fillGradient) { + cairo_pattern_set_filter(state->fillGradient, state->patternQuality); + cairo_set_source(_context, state->fillGradient); } else { setSourceRGBA(state->fill); } @@ -242,8 +248,11 @@ Context2d::fill(bool preserve) { void Context2d::stroke(bool preserve) { if (state->strokePattern) { - cairo_pattern_set_filter(state->strokePattern, state->patternQuality); - cairo_set_source(_context, state->fillPattern); + cairo_set_source(_context, state->strokePattern); + cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT); + } else if (state->strokeGradient) { + cairo_pattern_set_filter(state->strokeGradient, state->patternQuality); + cairo_set_source(_context, state->strokeGradient); } else { setSourceRGBA(state->stroke); } @@ -1052,12 +1061,17 @@ Context2d::SetFillPattern(const Arguments &args) { HandleScope scope; Local obj = args[0]->ToObject(); - if (!Gradient::constructor->HasInstance(obj)) - return ThrowException(Exception::TypeError(String::New("Gradient expected"))); - - Context2d *context = ObjectWrap::Unwrap(args.This()); - Gradient *grad = ObjectWrap::Unwrap(obj); - context->state->fillPattern = grad->pattern(); + if (Gradient::constructor->HasInstance(obj)){ + Context2d *context = ObjectWrap::Unwrap(args.This()); + Gradient *grad = ObjectWrap::Unwrap(obj); + context->state->fillGradient = grad->pattern(); + } else if(Pattern::constructor->HasInstance(obj)){ + Context2d *context = ObjectWrap::Unwrap(args.This()); + Pattern *pattern = ObjectWrap::Unwrap(obj); + context->state->fillPattern = pattern->pattern(); + } else { + return ThrowException(Exception::TypeError(String::New("Gradient or Pattern expected"))); + } return Undefined(); } @@ -1070,12 +1084,18 @@ Context2d::SetStrokePattern(const Arguments &args) { HandleScope scope; Local obj = args[0]->ToObject(); - if (!Gradient::constructor->HasInstance(obj)) - return ThrowException(Exception::TypeError(String::New("Gradient expected"))); + if (Gradient::constructor->HasInstance(obj)){ + Context2d *context = ObjectWrap::Unwrap(args.This()); + Gradient *grad = ObjectWrap::Unwrap(obj); + context->state->strokeGradient = grad->pattern(); + } else if(Pattern::constructor->HasInstance(obj)){ + Context2d *context = ObjectWrap::Unwrap(args.This()); + Pattern *pattern = ObjectWrap::Unwrap(obj); + context->state->strokePattern = pattern->pattern(); + } else { + return ThrowException(Exception::TypeError(String::New("Gradient or Pattern expected"))); + } - Context2d *context = ObjectWrap::Unwrap(args.This()); - Gradient *grad = ObjectWrap::Unwrap(obj); - context->state->strokePattern = grad->pattern(); return Undefined(); } @@ -1120,7 +1140,7 @@ Context2d::SetFillColor(const Arguments &args) { uint32_t rgba = rgba_from_string(*str, &ok); if (!ok) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); - context->state->fillPattern = NULL; + context->state->fillPattern = context->state->fillGradient = NULL; context->state->fill = rgba_create(rgba); return Undefined(); } @@ -1151,7 +1171,7 @@ Context2d::SetStrokeColor(const Arguments &args) { uint32_t rgba = rgba_from_string(*str, &ok); if (!ok) return Undefined(); Context2d *context = ObjectWrap::Unwrap(args.This()); - context->state->strokePattern = NULL; + context->state->strokePattern = context->state->strokeGradient = NULL; context->state->stroke = rgba_create(rgba); return Undefined(); } diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index 4bfc49c..ad08331 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -25,6 +25,8 @@ typedef struct { 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; diff --git a/src/init.cc b/src/init.cc index 69ff28f..4b869e2 100644 --- a/src/init.cc +++ b/src/init.cc @@ -10,6 +10,7 @@ #include "ImageData.h" #include "PixelArray.h" #include "CanvasGradient.h" +#include "CanvasPattern.h" #include "CanvasRenderingContext2d.h" extern "C" void @@ -21,5 +22,6 @@ init (Handle target) { PixelArray::Initialize(target); Context2d::Initialize(target); Gradient::Initialize(target); + Pattern::Initialize(target); target->Set(String::New("cairoVersion"), String::New(cairo_version_string())); } diff --git a/test/canvas.test.js b/test/canvas.test.js index 49375ac..7049c5c 100644 --- a/test/canvas.test.js +++ b/test/canvas.test.js @@ -436,5 +436,142 @@ module.exports = { assert.equal(255, data[0]); data[0] = -4444; assert.equal(0, data[0]); + }, + + 'test Context2d#createPattern(Canvas)': function(){ + var pattern = new Canvas(2,2) + , checkers = pattern.getContext('2d'); + + // white + checkers.fillStyle = '#fff'; + checkers.fillRect(0,0,2,2); + + // black + checkers.fillStyle = '#000'; + checkers.fillRect(0,0,1,1); + checkers.fillRect(1,1,1,1); + + var imageData = checkers.getImageData(0,0,2,2); + assert.equal(2, imageData.width); + assert.equal(2, imageData.height); + assert.equal(16, imageData.data.length); + + // (0,0) black + assert.equal(0, imageData.data[0]); + assert.equal(0, imageData.data[1]); + assert.equal(0, imageData.data[2]); + assert.equal(255, imageData.data[3]); + + // (1,0) white + assert.equal(255, imageData.data[4]); + assert.equal(255, imageData.data[5]); + assert.equal(255, imageData.data[6]); + assert.equal(255, imageData.data[7]); + + // (0,1) white + assert.equal(255, imageData.data[8]); + assert.equal(255, imageData.data[9]); + assert.equal(255, imageData.data[10]); + assert.equal(255, imageData.data[11]); + + // (1,1) black + assert.equal(0, imageData.data[12]); + assert.equal(0, imageData.data[13]); + assert.equal(0, imageData.data[14]); + assert.equal(255, imageData.data[15]); + + var canvas = new Canvas(20, 20) + , ctx = canvas.getContext('2d') + , pattern = ctx.createPattern(pattern); + + ctx.fillStyle = pattern; + ctx.fillRect(0,0,20,20); + + var imageData = ctx.getImageData(0,0,20,20); + assert.equal(20, imageData.width); + assert.equal(20, imageData.height); + assert.equal(1600, imageData.data.length); + + var i=0, b = true; + while(i