Skip to content

Commit

Permalink
WIP: Replace CanvasPixelArray with Uint8ClampedArray to meet spec.
Browse files Browse the repository at this point in the history
  • Loading branch information
zbjornson committed Jul 30, 2015
1 parent 46a26de commit 76d221b
Show file tree
Hide file tree
Showing 13 changed files with 173 additions and 285 deletions.
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.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"should": "*"
},
"engines": {
"node": ">= 0.6.0"
"node": ">= 0.12.0"
},
"main": "./lib/canvas.js",
"license": "MIT"
Expand Down
100 changes: 93 additions & 7 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "CanvasGradient.h"
#include "CanvasPattern.h"

#include <iostream>

#ifdef HAVE_FREETYPE
#include "FontFace.h"
#endif
Expand Down Expand Up @@ -103,6 +105,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 +579,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,8 +598,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:
Expand All @@ -607,8 +609,8 @@ NAN_METHOD(Context2d::PutImageData) {
sh = args[6]->Int32Value();
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;
if (sx + sw > imageData->width()) sw = imageData->width() - sx;
if (sy + sh > imageData->height()) sh = imageData->height() - sy;
dx += sx;
dy += sy;
cols = std::min(sw, context->canvas()->width - dx);
Expand Down Expand Up @@ -653,6 +655,90 @@ 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();

std::cout << "sx,sy,sw,sh=" << sx << "," << sy << "," << sw << "," << sh << std::endl;

if (!sw)
return NanThrowError("IndexSizeError: The source width is 0.");
if (!sh)
return NanThrowError("IndexSizeError: The source height is 0.");

// Chromium has this behavior, which is different from what node-canvas used
// to do (sw += sx, sx = 0). Going in favor what's more common.
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;
}

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);

// 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);
}
std::cout << x << "," << y << ":(" << r << "," << g << "," << b << ")" << std::endl;
}
dst += dstStride;
}

Local<Object> imagedata = ImageData::NewInstance(dst, sw, sh);

NanReturnValue(imagedata);
}

/*
* Draw image src image to the destination (context).
*
Expand Down
1 change: 1 addition & 0 deletions src/CanvasRenderingContext2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
56 changes: 48 additions & 8 deletions src/ImageData.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

#include "ImageData.h"
#include <iostream>

Persistent<FunctionTemplate> ImageData::constructor;

Expand All @@ -30,21 +31,60 @@ ImageData::Initialize(Handle<Object> target) {
target->Set(NanNew("ImageData"), ctor->GetFunction());
}

Local<Object> ImageData::NewInstance(uint8_t* data, int width, int height) {
NanEscapableScope();
std::cout << "Received d*, w, h=" << data << "," << width << "," << height << std::endl;

const int argc = 3;
int size = width * height * 4;
Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
buffer->SetIndexedPropertiesToExternalArrayData(data, ExternalArrayType::kExternalUint8ClampedArray, size);
Local<Uint8ClampedArray> clampedArray = Uint8ClampedArray::New(buffer, 0, size);
Local<Value> argv[argc] = { clampedArray, NanNew(width), NanNew(height) };
Local<FunctionTemplate> cons = NanNew(constructor);

Local<Object> instance = cons->GetFunction()->NewInstance(argc, argv);

return NanEscapeScope(instance);
}

/*
* Initialize a new ImageData object.
*/

NAN_METHOD(ImageData::New) {
NanScope();
Local<Object> obj = args[0]->ToObject();

if (!NanHasInstance(PixelArray::constructor, obj))
return NanThrowTypeError("CanvasPixelArray expected");
Local<Uint8ClampedArray> clampedArray;
int width;
int height;

if (args[0]->IsUint32() && args[1]->IsUint32()) {
std::cout << "path a" << std::endl;
width = args[0]->Uint32Value();
std::cout << "width:" << width << std::endl;
height = args[1]->Uint32Value();
int size = width * height;
clampedArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), size), 0, size);
} else if (args[0]->IsUint8ClampedArray() && args[1]->IsUint32()) {
std::cout << "path b" << std::endl;
clampedArray = args[0].As<Uint8ClampedArray>();
width = args[1]->Uint32Value();
if (args[2]->IsUint32()) {
height = args[2]->Uint32Value();
} else {
height = clampedArray->Length() / width;
}
} else {
NanThrowTypeError("Expected (Uint8ClampedArray, width[, height]) or (width, height)");
NanReturnUndefined();
}

void *dataPtr = clampedArray->GetIndexedPropertiesExternalArrayData();

PixelArray *arr = ObjectWrap::Unwrap<PixelArray>(obj);
ImageData *imageData = new ImageData(arr);
args.This()->Set(NanNew("data"), args[0]);
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(dataPtr), width, height);
imageData->Wrap(args.This());
args.This()->Set(NanNew("data"), clampedArray);
NanReturnValue(args.This());
}

Expand All @@ -55,7 +95,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()));
}

/*
Expand All @@ -65,5 +105,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()));
}
Loading

0 comments on commit 76d221b

Please sign in to comment.