Browse Source

Merge pull request #604 from zbjornson/master

Replace CanvasPixelArray with Uint8ClampedArray; optimizations; bugfixes
v1.x
Linus Unnebäck 9 years ago
parent
commit
6633300144
  1. 5
      History.md
  2. 7
      binding.gyp
  3. 8
      lib/canvas.js
  4. 21
      lib/context2d.js
  5. 29
      lib/pixelarray.js
  6. 7
      package.json
  7. 189
      src/CanvasRenderingContext2d.cc
  8. 1
      src/CanvasRenderingContext2d.h
  9. 62
      src/ImageData.cc
  10. 16
      src/ImageData.h
  11. 163
      src/PixelArray.cc
  12. 33
      src/PixelArray.h
  13. 2
      src/init.cc
  14. 6
      test/canvas.test.js
  15. 103
      test/public/tests.js
  16. 7
      test/server.js
  17. 2
      test/views/layout.jade

5
History.md

@ -1,3 +1,8 @@
Future / Future
==================
* Replace CanvasPixelArray with Uint8ClampedArray to be API-compliant (#xxx)
1.2.7 / 2015-07-29
==================

7
binding.gyp

@ -49,8 +49,7 @@
'src/color.cc',
'src/Image.cc',
'src/ImageData.cc',
'src/init.cc',
'src/PixelArray.cc'
'src/init.cc'
],
'conditions': [
['OS=="win"', {
@ -72,7 +71,7 @@
'VCCLCompilerTool': {
'WarningLevel': 4,
'ExceptionHandling': 1,
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714]
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714, 4512]
}
}
},
@ -81,7 +80,7 @@
'VCCLCompilerTool': {
'WarningLevel': 4,
'ExceptionHandling': 1,
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714]
'DisableSpecificWarnings': [4100, 4127, 4201, 4244, 4267, 4506, 4611, 4714, 4512]
}
}
}

8
lib/canvas.js

@ -13,7 +13,6 @@ var canvas = require('./bindings')
, Canvas = canvas.Canvas
, Image = canvas.Image
, cairoVersion = canvas.cairoVersion
, PixelArray = canvas.CanvasPixelArray
, Context2d = require('./context2d')
, PNGStream = require('./pngstream')
, JPEGStream = require('./jpegstream')
@ -62,7 +61,6 @@ if (canvas.gifVersion) {
exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
exports.JPEGStream = JPEGStream;
exports.PixelArray = PixelArray;
exports.Image = Image;
if (FontFace) {
@ -100,12 +98,6 @@ require('./context2d');
require('./image');
/**
* PixelArray implementation.
*/
require('./pixelarray');
/**
* Inspect canvas.
*

21
lib/context2d.js

@ -12,8 +12,7 @@ var canvas = require('./bindings')
, Context2d = canvas.CanvasRenderingContext2d
, CanvasGradient = canvas.CanvasGradient
, CanvasPattern = canvas.CanvasPattern
, ImageData = canvas.ImageData
, PixelArray = canvas.CanvasPixelArray;
, ImageData = canvas.ImageData;
/**
* Export `Context2d` as the module.
@ -351,22 +350,6 @@ Context2d.prototype.__defineGetter__('textAlign', function(){
return this.lastTextAlignment || 'start';
});
/**
* Get `ImageData` with the given rect.
*
* @param {Number} x
* @param {Number} y
* @param {Number} width
* @param {Number} height
* @return {ImageData}
* @api public
*/
Context2d.prototype.getImageData = function(x, y, width, height){
var arr = new PixelArray(this.canvas, x, y, width, height);
return new ImageData(arr);
};
/**
* Create `ImageData` with the given dimensions or
* `ImageData` instance for dimensions.
@ -382,5 +365,5 @@ Context2d.prototype.createImageData = function(width, height){
height = width.height;
width = width.width;
}
return new ImageData(new PixelArray(width, height));
return new ImageData(new Uint8ClampedArray(width * height * 4), width, height);
};

29
lib/pixelarray.js

@ -1,29 +0,0 @@
/*!
* Canvas - PixelArray
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Canvas = require('./bindings')
, PixelArray = Canvas.CanvasPixelArray;
/**
* Custom inspect.
*/
PixelArray.prototype.inspect = function(){
var buf = '[PixelArray ';
for (var i = 0, len = this.length; i < len; i += 4) {
buf += '\n ' + i + ': rgba('
+ this[i + 0] + ','
+ this[i + 1] + ','
+ this[i + 2] + ','
+ this[i + 3] + ')';
}
return buf + '\n]';
};

7
package.json

@ -27,13 +27,14 @@
"nan": "^1.8.4"
},
"devDependencies": {
"express": "3.0",
"jade": "0.28.1",
"body-parser": "^1.13.3",
"express": "^4.13.2",
"jade": "^1.11.0",
"mocha": "*",
"should": "*"
},
"engines": {
"node": ">= 0.6.0"
"node": ">=0.8.0 <3"
},
"main": "./lib/canvas.js",
"license": "MIT"

189
src/CanvasRenderingContext2d.cc

@ -103,6 +103,7 @@ Context2d::Initialize(Handle<Object> target) {
Local<ObjectTemplate> proto = ctor->PrototypeTemplate();
NODE_SET_PROTOTYPE_METHOD(ctor, "drawImage", DrawImage);
NODE_SET_PROTOTYPE_METHOD(ctor, "putImageData", PutImageData);
NODE_SET_PROTOTYPE_METHOD(ctor, "getImageData", GetImageData);
NODE_SET_PROTOTYPE_METHOD(ctor, "addPage", AddPage);
NODE_SET_PROTOTYPE_METHOD(ctor, "save", Save);
NODE_SET_PROTOTYPE_METHOD(ctor, "restore", Restore);
@ -576,12 +577,11 @@ NAN_METHOD(Context2d::PutImageData) {
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(obj);
PixelArray *arr = imageData->pixelArray();
uint8_t *src = arr->data();
uint8_t *src = imageData->data();
uint8_t *dst = context->canvas()->data();
int srcStride = arr->stride()
int srcStride = imageData->stride()
, dstStride = context->canvas()->stride();
int sx = 0
@ -596,8 +596,8 @@ NAN_METHOD(Context2d::PutImageData) {
switch (args.Length()) {
// imageData, dx, dy
case 3:
cols = std::min(arr->width(), context->canvas()->width - dx);
rows = std::min(arr->height(), context->canvas()->height - dy);
cols = std::min(imageData->width(), context->canvas()->width - dx);
rows = std::min(imageData->height(), context->canvas()->height - dy);
break;
// imageData, dx, dy, sx, sy, sw, sh
case 7:
@ -605,12 +605,22 @@ NAN_METHOD(Context2d::PutImageData) {
sy = args[4]->Int32Value();
sw = args[5]->Int32Value();
sh = args[6]->Int32Value();
// fix up negative height, width
if (sw < 0) sx += sw, sw = -sw;
if (sh < 0) sy += sh, sh = -sh;
// clamp the left edge
if (sx < 0) sw += sx, sx = 0;
if (sy < 0) sh += sy, sy = 0;
if (sx + sw > arr->width()) sw = arr->width() - sx;
if (sy + sh > arr->height()) sh = arr->height() - sy;
// clamp the right edge
if (sx + sw > imageData->width()) sw = imageData->width() - sx;
if (sy + sh > imageData->height()) sh = imageData->height() - sy;
// start destination at source offset
dx += sx;
dy += sy;
// chop off outlying source data
if (dx < 0) sw += dx, sx -= dx, dx = 0;
if (dy < 0) sh += dy, sy -= dy, dy = 0;
// clamp width at canvas size
cols = std::min(sw, context->canvas()->width - dx);
rows = std::min(sh, context->canvas()->height - dy);
break;
@ -620,27 +630,41 @@ NAN_METHOD(Context2d::PutImageData) {
if (cols <= 0 || rows <= 0) NanReturnUndefined();
uint8_t *srcRows = src + sy * srcStride + sx * 4;
src += sy * srcStride + sx * 4;
dst += dstStride * dy + 4 * dx;
for (int y = 0; y < rows; ++y) {
uint32_t *row = (uint32_t *)(dst + dstStride * (y + dy));
uint8_t *dstRow = dst;
uint8_t *srcRow = src;
for (int x = 0; x < cols; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + dx;
// RGBA
uint8_t a = srcRows[bx + 3];
uint8_t r = srcRows[bx + 0];
uint8_t g = srcRows[bx + 1];
uint8_t b = srcRows[bx + 2];
float alpha = (float) a / 255;
// ARGB
*pixel = a << 24
| (int)((float) r * alpha) << 16
| (int)((float) g * alpha) << 8
| (int)((float) b * alpha);
// rgba
uint8_t r = *srcRow++;
uint8_t g = *srcRow++;
uint8_t b = *srcRow++;
uint8_t a = *srcRow++;
// argb
// performance optimization: fully transparent/opaque pixels can be
// processed more efficiently.
if (a == 0) {
*dstRow++ = 0;
*dstRow++ = 0;
*dstRow++ = 0;
*dstRow++ = 0;
} else if (a == 255) {
*dstRow++ = b;
*dstRow++ = g;
*dstRow++ = r;
*dstRow++ = a;
} else {
float alpha = (float)a / 255;
*dstRow++ = b * alpha;
*dstRow++ = g * alpha;
*dstRow++ = r * alpha;
*dstRow++ = a;
}
}
srcRows += srcStride;
dst += dstStride;
src += srcStride;
}
cairo_surface_mark_dirty_rectangle(
@ -653,6 +677,121 @@ NAN_METHOD(Context2d::PutImageData) {
NanReturnUndefined();
}
/*
* Get image data.
*
* - sx, sy, sw, sh
*
*/
NAN_METHOD(Context2d::GetImageData) {
NanScope();
Context2d *context = ObjectWrap::Unwrap<Context2d>(args.This());
Canvas *canvas = context->canvas();
int sx = args[0]->Int32Value();
int sy = args[1]->Int32Value();
int sw = args[2]->Int32Value();
int sh = args[3]->Int32Value();
if (!sw)
return NanThrowError("IndexSizeError: The source width is 0.");
if (!sh)
return NanThrowError("IndexSizeError: The source height is 0.");
// WebKit and Firefox have this behavior:
// Flip the coordinates so the origin is top/left-most:
if (sw < 0) {
sx += sw;
sw = -sw;
}
if (sh < 0) {
sy += sh;
sh = -sh;
}
if (sx + sw > canvas->width) sw = canvas->width - sx;
if (sy + sh > canvas->height) sh = canvas->height - sy;
// WebKit/moz functionality. node-canvas used to return in either case.
if (sw <= 0) sw = 1;
if (sh <= 0) sh = 1;
// Non-compliant. "Pixels outside the canvas must be returned as transparent
// black." This instead clips the returned array to the canvas area.
if (sx < 0) {
sw += sx;
sx = 0;
}
if (sy < 0) {
sh += sy;
sy = 0;
}
int size = sw * sh * 4;
int srcStride = canvas->stride();
int dstStride = sw * 4;
uint8_t *src = canvas->data();
uint8_t *dst = (uint8_t *)calloc(1, size);
NanAdjustExternalMemory(size);
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Local<Object> global = Context::GetCurrent()->Global();
Handle<Value> bufargv[] = { NanNew(size) };
Local<Object> buffer = global->Get(NanNew("ArrayBuffer")).As<Function>()->NewInstance(1, bufargv);
Handle<Value> caargv[] = { buffer, NanNew(0), NanNew(size) };
Local<Object> clampedArray = global->Get(NanNew("Uint8ClampedArray")).As<Function>()->NewInstance(3, caargv);
clampedArray->SetIndexedPropertiesToExternalArrayData(dst, kExternalPixelArray, size);
#else
Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
Local<Uint8ClampedArray> clampedArray = Uint8ClampedArray::New(buffer, 0, size);
clampedArray->SetIndexedPropertiesToExternalArrayData(dst, kExternalUint8ClampedArray, size);
#endif
// Normalize data (argb -> rgba)
for (int y = 0; y < sh; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < sw; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t a = *pixel >> 24;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;
dst[bx + 3] = a;
// Performance optimization: fully transparent/opaque pixels can be
// processed more efficiently.
if (a == 0 || a == 255) {
dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
} else {
float alpha = (float)a / 255;
dst[bx + 0] = (int)((float)r / alpha);
dst[bx + 1] = (int)((float)g / alpha);
dst[bx + 2] = (int)((float)b / alpha);
}
}
dst += dstStride;
}
const int argc = 3;
Local<Value> argv[argc] = { clampedArray, NanNew(sw), NanNew(sh) };
Local<FunctionTemplate> cons = NanNew(ImageData::constructor);
Local<Object> instance = cons->GetFunction()->NewInstance(argc, argv);
NanReturnValue(instance);
}
/*
* Draw image src image to the destination (context).
*

1
src/CanvasRenderingContext2d.h

@ -113,6 +113,7 @@ class Context2d: public node::ObjectWrap {
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);

62
src/ImageData.cc

@ -36,15 +36,61 @@ ImageData::Initialize(Handle<Object> target) {
NAN_METHOD(ImageData::New) {
NanScope();
Local<Object> obj = args[0]->ToObject();
if (!NanHasInstance(PixelArray::constructor, obj))
return NanThrowTypeError("CanvasPixelArray expected");
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Local<v8::Object> clampedArray;
Local<Object> global = Context::GetCurrent()->Global();
#else
Local<Uint8ClampedArray> clampedArray;
#endif
PixelArray *arr = ObjectWrap::Unwrap<PixelArray>(obj);
ImageData *imageData = new ImageData(arr);
args.This()->Set(NanNew("data"), args[0]);
int width;
int height;
if (args[0]->IsUint32() && args[1]->IsUint32()) {
width = args[0]->Uint32Value();
height = args[1]->Uint32Value();
int size = width * height;
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
Handle<Value> caargv[] = { NanNew(size) };
Local<Object> clampedArray = global->Get(NanNew("Uint8ClampedArray")).As<Function>()->NewInstance(1, caargv);
#else
clampedArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), size), 0, size);
#endif
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
} else if (args[0]->ToObject()->GetIndexedPropertiesExternalArrayDataType() == kExternalPixelArray && args[1]->IsUint32()) {
clampedArray = args[0]->ToObject();
#else
} else if (args[0]->IsUint8ClampedArray() && args[1]->IsUint32()) {
clampedArray = args[0].As<Uint8ClampedArray>();
#endif
width = args[1]->Uint32Value();
if (args[2]->IsUint32()) {
height = args[2]->Uint32Value();
} else {
#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION <= 10
height = clampedArray->GetIndexedPropertiesExternalArrayDataLength() / width;
#else
height = clampedArray->Length() / width;
#endif
}
} else {
NanThrowTypeError("Expected (Uint8ClampedArray, width[, height]) or (width, height)");
NanReturnUndefined();
}
// No behavior defined in spec. This is what WebKit does:
if (width < 1) width = 1;
if (height < 1) height = 1;
void *dataPtr = clampedArray->GetIndexedPropertiesExternalArrayData();
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(dataPtr), width, height);
imageData->Wrap(args.This());
args.This()->Set(NanNew("data"), clampedArray);
NanReturnValue(args.This());
}
@ -55,7 +101,7 @@ NAN_METHOD(ImageData::New) {
NAN_GETTER(ImageData::GetWidth) {
NanScope();
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This());
NanReturnValue(NanNew<Number>(imageData->pixelArray()->width()));
NanReturnValue(NanNew<Number>(imageData->width()));
}
/*
@ -65,5 +111,5 @@ NAN_GETTER(ImageData::GetWidth) {
NAN_GETTER(ImageData::GetHeight) {
NanScope();
ImageData *imageData = ObjectWrap::Unwrap<ImageData>(args.This());
NanReturnValue(NanNew<Number>(imageData->pixelArray()->height()));
NanReturnValue(NanNew<Number>(imageData->height()));
}

16
src/ImageData.h

@ -9,8 +9,8 @@
#define __NODE_IMAGE_DATA_H__
#include "Canvas.h"
#include "PixelArray.h"
#include <stdlib.h>
#include "v8.h"
class ImageData: public node::ObjectWrap {
public:
@ -19,10 +19,18 @@ class ImageData: public node::ObjectWrap {
static NAN_METHOD(New);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
inline PixelArray *pixelArray(){ return _arr; }
ImageData(PixelArray *arr): _arr(arr) {}
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:
PixelArray *_arr;
int _width;
int _height;
uint8_t *_data;
};
#endif

163
src/PixelArray.cc

@ -1,163 +0,0 @@
//
// PixelArray.cc
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#include "PixelArray.h"
#include <stdlib.h>
#include <string.h>
Persistent<FunctionTemplate> PixelArray::constructor;
/*
* Initialize PixelArray.
*/
void
PixelArray::Initialize(Handle<Object> target) {
NanScope();
// Constructor
Local<FunctionTemplate> ctor = NanNew<FunctionTemplate>(PixelArray::New);
NanAssignPersistent(constructor, ctor);
ctor->InstanceTemplate()->SetInternalFieldCount(1);
ctor->SetClassName(NanNew("CanvasPixelArray"));
// Prototype
Local<ObjectTemplate> proto = ctor->InstanceTemplate();
proto->SetAccessor(NanNew("length"), GetLength);
target->Set(NanNew("CanvasPixelArray"), ctor->GetFunction());
}
/*
* Initialize a new PixelArray.
*/
NAN_METHOD(PixelArray::New) {
NanScope();
PixelArray *arr;
Local<Object> obj = args[0]->ToObject();
switch (args.Length()) {
// width, height
case 2:
arr = new PixelArray(
args[0]->Int32Value()
, args[1]->Int32Value());
break;
// canvas, x, y, width, height
case 5: {
if (!NanHasInstance(Canvas::constructor, obj))
return NanThrowTypeError("Canvas expected");
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(obj);
arr = new PixelArray(
canvas
, args[1]->Int32Value()
, args[2]->Int32Value()
, args[3]->Int32Value()
, args[4]->Int32Value());
}
break;
default:
return NanThrowTypeError("invalid arguments");
}
// Let v8 handle accessors (and clamping)
args.This()->SetIndexedPropertiesToPixelData(
arr->data()
, arr->length());
arr->Wrap(args.This());
NanReturnValue(args.This());
}
/*
* Get length.
*/
NAN_GETTER(PixelArray::GetLength) {
NanScope();
NanReturnValue(NanNew<Number>(args.This()->GetIndexedPropertiesPixelDataLength()));
}
/*
* Initialize a new PixelArray copying data
* from the canvas surface using the given rect.
*/
PixelArray::PixelArray(Canvas *canvas, int sx, int sy, int width, int height):
_width(width), _height(height) {
// Alloc space for our new data
uint8_t *dst = alloc();
uint8_t *src = canvas->data();
int srcStride = canvas->stride()
, dstStride = stride();
if (sx < 0) width += sx, sx = 0;
if (sy < 0) height += sy, sy = 0;
if (sx + width > canvas->width) width = canvas->width - sx;
if (sy + height > canvas->height) height = canvas->height - sy;
if (width <= 0 || height <= 0) return;
// Normalize data (argb -> rgba)
for (int y = 0; y < height; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < width; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t a = *pixel >> 24;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;
dst[bx + 3] = a;
// Performance optimization: fully transparent/opaque pixels
// can be processed more efficiently
if (a != 0 && a != 255) {
float alpha = (float) a / 255;
dst[bx + 0] = (int)((float) r / alpha);
dst[bx + 1] = (int)((float) g / alpha);
dst[bx + 2] = (int)((float) b / alpha);
} else {
dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
}
}
dst += dstStride;
}
}
/*
* Initialize an empty PixelArray with the given dimensions.
*/
PixelArray::PixelArray(int width, int height):
_width(width), _height(height) {
alloc();
}
/*
* Allocate / zero data buffer. Hint mem adjustment.
*/
uint8_t *
PixelArray::alloc() {
int len = length();
_data = (uint8_t *) calloc(1, len);
NanAdjustExternalMemory(len);
return _data;
}
/*
* Hint mem adjustment.
*/
PixelArray::~PixelArray() {
NanAdjustExternalMemory(-length());
free(_data);
}

33
src/PixelArray.h

@ -1,33 +0,0 @@
//
// PixelArray.h
//
// Copyright (c) 2010 LearnBoost <tj@learnboost.com>
//
#ifndef __NODE_PIXEL_ARRAY_H__
#define __NODE_PIXEL_ARRAY_H__
#include "Canvas.h"
class PixelArray: public node::ObjectWrap {
public:
static Persistent<FunctionTemplate> constructor;
static void Initialize(Handle<Object> target);
static NAN_METHOD(New);
static NAN_GETTER(GetLength);
inline int length(){ return _width * _height * 4; }
inline int width(){ return _width; }
inline int height(){ return _height; }
inline int stride(){ return _width * 4; }
inline uint8_t *data(){ return _data; }
PixelArray(Canvas *canvas, int x, int y, int width, int height);
PixelArray(int width, int height);
~PixelArray();
private:
uint8_t *alloc();
uint8_t *_data;
int _width, _height;
};
#endif

2
src/init.cc

@ -9,7 +9,6 @@
#include "Canvas.h"
#include "Image.h"
#include "ImageData.h"
#include "PixelArray.h"
#include "CanvasGradient.h"
#include "CanvasPattern.h"
#include "CanvasRenderingContext2d.h"
@ -24,7 +23,6 @@ init (Handle<Object> target) {
Canvas::Initialize(target);
Image::Initialize(target);
ImageData::Initialize(target);
PixelArray::Initialize(target);
Context2d::Initialize(target);
Gradient::Initialize(target);
Pattern::Initialize(target);

6
test/canvas.test.js

@ -76,10 +76,6 @@ module.exports = {
}
},
'test .PixelArray': function(){
assert.equal(typeof Canvas.PixelArray, 'function');
},
'test color serialization': function(){
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');
@ -169,7 +165,7 @@ module.exports = {
ctx.fillStyle = 'hsl(10, 100%, 42%)';
assert.equal('#d62400', ctx.fillStyle);
ctx.fillStyle = 'hsl(370, 120%, 42%)';
assert.equal('#d62400', ctx.fillStyle);

103
test/public/tests.js

@ -14,7 +14,7 @@ tests['strokeRect()'] = function(ctx){
};
tests['fillRect()'] = function(ctx){
function render(level){
ctx.fillStyle = getPointColour(122,122);
ctx.fillRect(0,0,240,240);
@ -386,7 +386,7 @@ tests['createLinearGradient()'] = function(ctx){
ctx.strokeStyle = lingrad2;
ctx.fillRect(10,10,130,130);
ctx.strokeRect(50,50,50,50);
ctx.strokeRect(50,50,50,50);
};
tests['createRadialGradient()'] = function(ctx){
@ -458,7 +458,7 @@ tests['globalAlpha 2'] = function(ctx){
tests['fillStyle'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -468,7 +468,7 @@ tests['fillStyle'] = function(ctx){
tests['strokeStyle'] = function(ctx){
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ')';
ctx.beginPath();
ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
@ -593,22 +593,22 @@ tests['states'] = function(ctx){
};
tests['states with stroke/fill/globalAlpha'] = function(ctx){
ctx.fillRect(0,0,150,150);
ctx.save();
ctx.fillStyle = '#09F'
ctx.fillRect(0,0,150,150);
ctx.save();
ctx.fillStyle = '#09F'
ctx.fillRect(15,15,120,120);
ctx.save();
ctx.fillStyle = '#FFF'
ctx.globalAlpha = 0.5;
ctx.fillRect(30,30,90,90);
ctx.restore();
ctx.fillRect(45,45,60,60);
ctx.restore();
ctx.fillRect(60,60,30,30);
ctx.save();
ctx.fillStyle = '#FFF'
ctx.globalAlpha = 0.5;
ctx.fillRect(30,30,90,90);
ctx.restore();
ctx.fillRect(45,45,60,60);
ctx.restore();
ctx.fillRect(60,60,30,30);
};
tests['path through fillRect/strokeRect/clearRect'] = function(ctx){
@ -1208,12 +1208,12 @@ tests['shadowBlur'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1234,12 +1234,12 @@ tests['shadowColor'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1262,12 +1262,12 @@ tests['shadowOffset{X,Y}'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1290,12 +1290,12 @@ tests['shadowOffset{X,Y} large'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1318,12 +1318,12 @@ tests['shadowOffset{X,Y} negative'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1350,12 +1350,12 @@ tests['shadowOffset{X,Y} transform'] = function(ctx){
ctx.stroke();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1378,12 +1378,12 @@ tests['shadowBlur values'] = function(ctx){
ctx.stroke();
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.fillRect(150,150,20,20);
};
@ -1406,12 +1406,12 @@ tests['shadow strokeRect()'] = function(ctx){
ctx.stroke();
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.strokeRect(150,150,20,20);
};
@ -1435,12 +1435,12 @@ tests['shadow fill()'] = function(ctx){
ctx.stroke();
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.strokeRect(150,150,20,20);
};
@ -1464,12 +1464,12 @@ tests['shadow stroke()'] = function(ctx){
ctx.stroke();
ctx.shadowColor = 'rgba(0,0,0,0)';
ctx.beginPath();
ctx.lineTo(20,180);
ctx.lineTo(100,180);
ctx.stroke();
ctx.strokeRect(150,150,20,20);
};
@ -1693,7 +1693,7 @@ tests['drawImage(img,0,0) clip'] = function(ctx, done){
tests['putImageData()'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1705,7 +1705,7 @@ tests['putImageData()'] = function(ctx){
tests['putImageData() 2'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1717,7 +1717,7 @@ tests['putImageData() 2'] = function(ctx){
tests['putImageData() 3'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1729,7 +1729,7 @@ tests['putImageData() 3'] = function(ctx){
tests['putImageData() 4'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1742,7 +1742,7 @@ tests['putImageData() 4'] = function(ctx){
tests['putImageData() 5'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1755,7 +1755,7 @@ tests['putImageData() 5'] = function(ctx){
tests['putImageData() 6'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1768,7 +1768,7 @@ tests['putImageData() 6'] = function(ctx){
tests['putImageData() 7'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1782,7 +1782,7 @@ tests['putImageData() 7'] = function(ctx){
tests['putImageData() 8'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1795,7 +1795,7 @@ tests['putImageData() 8'] = function(ctx){
tests['putImageData() 9'] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1867,7 +1867,7 @@ tests['putImageData() png data'] = function(ctx, done){
ctx.putImageData(imageData,50,50);
done();
};
img.onerror = function(){}
img.src = 'state.png';
@ -1980,7 +1980,7 @@ tests['lineDashOffset'] = function(ctx, done){
tests['fillStyle=\'hsl(...)\''] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'hsl(' + (360-60*i) + ',' +
ctx.fillStyle = 'hsl(' + (360-60*i) + ',' +
(100-16.66*j) + '%,' + (50+(i+j)*(50/12)) + '%)';
ctx.fillRect(j*25,i*25,25,25);
}
@ -1990,9 +1990,8 @@ tests['fillStyle=\'hsl(...)\''] = function(ctx){
tests['fillStyle=\'hsla(...)\''] = function(ctx){
for (i=0;i<6;i++){
for (j=0;j<6;j++){
ctx.fillStyle = 'hsla(' + (360-60*i) + ',' +
ctx.fillStyle = 'hsla(' + (360-60*i) + ',' +
(100-16.66*j) + '%,50%,' + (1-0.16*j) + ')';
console.log((100-16.66*j));
ctx.fillRect(j*25,i*25,25,25);
}
}

7
test/server.js

@ -6,6 +6,7 @@
var express = require('express')
, Canvas = require('../lib/canvas')
, Image = Canvas.Image
, bodyParser = require('body-parser')
, app = express();
// Config
@ -15,12 +16,8 @@ app.set('view engine', 'jade');
// Middleware
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(app.router);
app.use(bodyParser.json());
app.use(express.static(__dirname + '/public'));
app.use(express.errorHandler());
// Routes

2
test/views/layout.jade

@ -1,4 +1,4 @@
!!!
doctype
html
head
title node-canvas

Loading…
Cancel
Save