Browse Source

Support canvas.getBuffer('raw')

This should help interface with custom image libraries like LodePNG or
WebP.
v1.x
Adam Hooper 8 years ago
parent
commit
3ab222e43b
  1. 16
      Readme.md
  2. 30
      src/Canvas.cc
  3. 2
      src/Canvas.h
  4. 90
      test/canvas.test.js

16
Readme.md

@ -140,10 +140,22 @@ var stream = canvas.jpegStream({
### Canvas#toBuffer()
A call to `Canvas#toBuffer()` will return a node `Buffer` instance containing all of the PNG data.
A call to `Canvas#toBuffer()` will return a node `Buffer` instance containing image data.
```javascript
canvas.toBuffer();
// PNG Buffer, default settings
var buf = canvas.toBuffer();
// PNG Buffer, zlib compression level 3 (from 0-9), faster but bigger
var buf2 = canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
// ARGB32 Buffer, native-endian
var buf3 = canvas.toBuffer('raw');
var stride = canvas.stride;
// In memory, this is `canvas.height * canvas.stride` bytes long.
// The top row of pixels, in ARGB order, left-to-right, is:
var topPixelsARGBLeftToRight = buf3.slice(0, canvas.width * 4);
var row3 = buf3.slice(2 * canvas.stride, 2 * canvas.stride + canvas.width * 4);
```
### Canvas#toBuffer() async

30
src/Canvas.cc

@ -45,6 +45,7 @@ Canvas::Initialize(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
Nan::SetPrototypeMethod(ctor, "streamJPEGSync", StreamJPEGSync);
#endif
Nan::SetAccessor(proto, Nan::New("type").ToLocalChecked(), GetType);
Nan::SetAccessor(proto, Nan::New("stride").ToLocalChecked(), GetStride);
Nan::SetAccessor(proto, Nan::New("width").ToLocalChecked(), GetWidth, SetWidth);
Nan::SetAccessor(proto, Nan::New("height").ToLocalChecked(), GetHeight, SetHeight);
@ -91,6 +92,14 @@ NAN_GETTER(Canvas::GetType) {
info.GetReturnValue().Set(Nan::New<String>(canvas->isPDF() ? "pdf" : canvas->isSVG() ? "svg" : "image").ToLocalChecked());
}
/*
* Get stride.
*/
NAN_GETTER(Canvas::GetStride) {
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This());
info.GetReturnValue().Set(Nan::New<Number>(canvas->stride()));
}
/*
* Get width.
*/
@ -248,6 +257,17 @@ NAN_METHOD(Canvas::ToBuffer) {
return;
}
if (info.Length() == 1 && info[0]->StrictEquals(Nan::New<String>("raw").ToLocalChecked())) {
// Return raw ARGB data -- just a memcpy()
cairo_surface_t *surface = canvas->surface();
cairo_surface_flush(surface);
const unsigned char *data = cairo_image_surface_get_data(surface);
printf("%x %x %x %x %x\n", data[0], data[1], data[2], data[3], data[4]);
Local<Object> buf = Nan::CopyBuffer(reinterpret_cast<const char*>(data), canvas->nBytes()).ToLocalChecked();
info.GetReturnValue().Set(buf);
return;
}
if (info.Length() > 1 && !(info[1]->IsUndefined() && info[2]->IsUndefined())) {
if (!info[1]->IsUndefined()) {
bool good = true;
@ -571,7 +591,7 @@ Canvas::Canvas(int w, int h, canvas_type_t t): Nan::ObjectWrap() {
} else {
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
assert(_surface);
Nan::AdjustExternalMemory(4 * w * h);
Nan::AdjustExternalMemory(nBytes());
}
}
@ -589,8 +609,9 @@ Canvas::~Canvas() {
cairo_surface_destroy(_surface);
break;
case CANVAS_TYPE_IMAGE:
int oldNBytes = nBytes();
cairo_surface_destroy(_surface);
Nan::AdjustExternalMemory(-4 * width * height);
Nan::AdjustExternalMemory(-oldNBytes);
break;
}
}
@ -626,11 +647,10 @@ Canvas::resurface(Local<Object> canvas) {
break;
case CANVAS_TYPE_IMAGE:
// Re-surface
int old_width = cairo_image_surface_get_width(_surface);
int old_height = cairo_image_surface_get_height(_surface);
size_t oldNBytes = nBytes();
cairo_surface_destroy(_surface);
_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
Nan::AdjustExternalMemory(4 * (width * height - old_width * old_height));
Nan::AdjustExternalMemory(nBytes() - oldNBytes);
// Reset context
context = canvas->Get(Nan::New<String>("context").ToLocalChecked());

2
src/Canvas.h

@ -57,6 +57,7 @@ class Canvas: public Nan::ObjectWrap {
static NAN_METHOD(New);
static NAN_METHOD(ToBuffer);
static NAN_GETTER(GetType);
static NAN_GETTER(GetStride);
static NAN_GETTER(GetWidth);
static NAN_GETTER(GetHeight);
static NAN_SETTER(SetWidth);
@ -85,6 +86,7 @@ class Canvas: public Nan::ObjectWrap {
inline void *closure(){ return _closure; }
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
inline int stride(){ return cairo_image_surface_get_stride(_surface); }
inline int nBytes(){ return height * stride(); }
Canvas(int width, int height, canvas_type_t type);
void resurface(Local<Object> canvas);

90
test/canvas.test.js

@ -5,7 +5,8 @@
var Canvas = require('../')
, assert = require('assert')
, parseFont = Canvas.Context2d.parseFont
, fs = require('fs');
, fs = require('fs')
, os = require('os');
console.log();
console.log(' canvas: %s', Canvas.version);
@ -246,6 +247,12 @@ describe('Canvas', function () {
assert.equal(50, canvas.height);
});
it('Canvas#stride', function() {
var canvas = new Canvas(24, 10);
assert.ok(canvas.stride >= 24, 'canvas.stride is too short');
assert.ok(canvas.stride < 1024, 'canvas.stride seems too long');
});
it('Canvas#getContext("invalid")', function () {
assert.equal(null, new Canvas(200, 300).getContext('invalid'));
});
@ -377,6 +384,87 @@ describe('Canvas', function () {
});
});
describe('#toBuffer("raw")', function() {
var canvas = new Canvas(10, 10)
, ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 10, 10);
ctx.fillStyle = 'rgba(200, 200, 200, 0.505)';
ctx.fillRect(0, 0, 5, 5);
ctx.fillStyle = 'red';
ctx.fillRect(5, 0, 5, 5);
ctx.fillStyle = '#00ff00';
ctx.fillRect(0, 5, 5, 5);
ctx.fillStyle = 'black';
ctx.fillRect(5, 5, 4, 5);
/** Output:
* *****RRRRR
* *****RRRRR
* *****RRRRR
* *****RRRRR
* *****RRRRR
* GGGGGBBBB-
* GGGGGBBBB-
* GGGGGBBBB-
* GGGGGBBBB-
* GGGGGBBBB-
*/
var buf = canvas.toBuffer('raw');
var stride = canvas.stride;
// Buffer doesn't have readUInt32(): it only has readUInt32LE() and
// readUInt32BE().
if (os.endianness() === 'LE') buf.swap32();
function assertPixel(u32, x, y, message) {
var expected = '0x' + u32.toString(16);
var actual = '0x' + buf.readUInt32BE(y * stride + x * 4).toString(16);
assert.equal(actual, expected, message);
}
it('should have the correct size', function() {
assert.equal(buf.length, stride * 10);
});
it('does not premultiply alpha', function() {
assertPixel(0x80646464, 0, 0, 'first semitransparent pixel');
assertPixel(0x80646464, 4, 4, 'last semitransparent pixel');
});
it('draws red', function() {
assertPixel(0xffff0000, 5, 0, 'first red pixel');
assertPixel(0xffff0000, 9, 4, 'last red pixel');
});
it('draws green', function() {
assertPixel(0xff00ff00, 0, 5, 'first green pixel');
assertPixel(0xff00ff00, 4, 9, 'last green pixel');
});
it('draws black', function() {
assertPixel(0xff000000, 5, 5, 'first black pixel');
assertPixel(0xff000000, 8, 9, 'last black pixel');
});
it('leaves undrawn pixels black, transparent', function() {
assertPixel(0x0, 9, 5, 'first undrawn pixel');
assertPixel(0x0, 9, 9, 'last undrawn pixel');
});
it('is immutable', function() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 10, 10);
canvas.toBuffer('raw'); // (side-effect: flushes canvas)
assertPixel(0xffff0000, 5, 0, 'first red pixel');
});
});
describe('#toDataURL()', function () {
var canvas = new Canvas(200, 200)
, ctx = canvas.getContext('2d');

Loading…
Cancel
Save