Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace CanvasPixelArray with Uint8ClampedArray; optimizations; bugfixes #604

Merged
merged 7 commits into from
Aug 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Future / Future
==================

* Replace CanvasPixelArray with Uint8ClampedArray to be API-compliant (#xxx)

1.2.7 / 2015-07-29
==================

Expand Down
7 changes: 3 additions & 4 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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"', {
Expand All @@ -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]
}
}
},
Expand All @@ -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]
}
}
}
Expand Down
8 changes: 0 additions & 8 deletions lib/canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -62,7 +61,6 @@ if (canvas.gifVersion) {
exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
exports.JPEGStream = JPEGStream;
exports.PixelArray = PixelArray;
exports.Image = Image;

if (FontFace) {
Expand Down Expand Up @@ -100,12 +98,6 @@ require('./context2d');

require('./image');

/**
* PixelArray implementation.
*/

require('./pixelarray');

/**
* Inspect canvas.
*
Expand Down
21 changes: 2 additions & 19 deletions lib/context2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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 changes: 0 additions & 29 deletions lib/pixelarray.js

This file was deleted.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
189 changes: 164 additions & 25 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -596,21 +596,31 @@ 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:
sx = args[3]->Int32Value();
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;
Expand All @@ -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(
Expand All @@ -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).
*
Expand Down
Loading