Skip to content

Commit 989a1d4

Browse files
committed
Support A1, A8, RGB30, RGB16_565, RGB24 pixelFormats; alpha ctx option
1 parent 98b7591 commit 989a1d4

18 files changed

Lines changed: 772 additions & 180 deletions

History.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ Unreleased / patch
22
==================
33

44
* Port has_lib.sh to javascript (#872)
5+
* Support canvas.getContext("2d", {alpha: boolean}) and
6+
canvas.getContext("2d", {pixelFormat: "..."})
57

68
1.6.0 / 2016-10-16
79
==================

Readme.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,73 @@ var canvas = new Canvas(200, 500, 'svg');
306306
fs.writeFile('out.svg', canvas.toBuffer());
307307
```
308308

309+
## Image pixel formats (experimental)
310+
311+
node-canvas has experimental support for additional pixel formats, roughly
312+
following the [Canvas color space proposal](https://github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md).
313+
314+
```js
315+
var canvas = new Canvas(200, 200);
316+
var ctx = canvas.getContext('2d', {pixelFormat: 'A8'});
317+
```
318+
319+
By default, canvases are created in the `RGBA32` format, which corresponds to
320+
the native HTML Canvas behavior. Each pixel is 32 bits. The JavaScript APIs
321+
that involve pixel data (`getImageData`, `putImageData`) store the colors in
322+
the order {red, green, blue, alpha} without alpha pre-multiplication. (The C++
323+
API stores the colors in the order {alpha, red, green, blue} in native-[endian](https://en.wikipedia.org/wiki/Endianness)
324+
ordering, with alpha pre-multiplication.)
325+
326+
These additional pixel formats have experimental support:
327+
328+
* `RGB24` Like `RGBA32`, but the 8 alpha bits are always opaque. This format is
329+
always used if the `alpha` context attribute is set to false (i.e.
330+
`canvas.getContext('2d', {alpha: false})`). This format can be faster than
331+
`RGBA32` because transparency does not need to be calculated.
332+
* `A8` Each pixel is 8 bits. This format can either be used for creating
333+
grayscale images (treating each byte as an alpha value), or for creating
334+
indexed PNGs (treating each byte as a palette index).
335+
* `RGB16_565` Each pixel is 16 bits, with red in the upper 5 bits, green in the
336+
middle 6 bits, and blue in the lower 5 bits, in native platform endianness.
337+
Some hardware devices and frame buffers use this format. Note that PNG does
338+
not support this format; when creating a PNG, the image will be converted to
339+
24-bit RGB. This format is thus suboptimal for generating PNGs.
340+
* `A1` Each pixel is 1 bit, and pixels are packed together into 32-bit
341+
quantities. The ordering of the bits matches the endianness of the
342+
platform: on a little-endian machine, the first pixel is the least-
343+
significant bit. This format can be used for creating single-color images.
344+
*Support for this format is incomplete, see note below.*
345+
* `RGB30` Each pixel is 30 bits, with red in the upper 10, green
346+
in the middle 10, and blue in the lower 10. (Requires Cairo 1.12 or later.)
347+
*Support for this format is incomplete, see note below.*
348+
349+
Notes and caveats:
350+
351+
* Using a non-default format can affect the behavior of APIs that involve pixel
352+
data:
353+
354+
* `context2d.createImageData` The size of the array returned depends on the
355+
number of bit per pixel for the underlying image data format, per the above
356+
descriptions.
357+
* `context2d.getImageData` The format of the array returned depends on the
358+
underlying image mode, per the above descriptions. Be aware of platform
359+
endianness, which can be determined using node.js's [`os.endianness()`](https://nodejs.org/api/os.html#os_os_endianness)
360+
function.
361+
* `context2d.putImageData` As above.
362+
363+
* `A1` and `RGB30` do not yet support `getImageData` or `putImageData`. Have a
364+
use case and/or opinion on working with these formats? Open an issue and let
365+
us know!
366+
367+
* `A1`, `A8`, `RGB30` and `RGB16_565` with shadow blurs may crash or not render
368+
properly.
369+
370+
* The `ImageData(width, height)` and `ImageData(Uint8ClampedArray, width)`
371+
constructors assume 4 bytes per pixel. To create an `ImageData` instance with
372+
a different number of bytes per pixel, use
373+
`new ImageData(new Uint8ClampedArray(size), width, height)` or
374+
`new ImageData(new Uint16ClampedArray(size), width, height)`.
375+
309376
## Benchmarks
310377

311378
Although node-canvas is extremely new, and we have not even begun optimization yet it is already quite fast. For benchmarks vs other node canvas implementations view this [gist](https://gist.github.com/664922), or update the submodules and run `$ make benchmark` yourself.

lib/canvas.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,15 @@ Canvas.prototype.inspect = function(){
112112
/**
113113
* Get a context object.
114114
*
115-
* @param {String} contextId
115+
* @param {String} contextType must be "2d"
116+
* @param {Object {alpha: boolean, pixelFormat: PIXEL_FORMAT} } contextAttributes Optional
116117
* @return {Context2d}
117118
* @api public
118119
*/
119120

120-
Canvas.prototype.getContext = function(contextId){
121-
if ('2d' == contextId) {
122-
var ctx = this._context2d || (this._context2d = new Context2d(this));
121+
Canvas.prototype.getContext = function (contextType, contextAttributes) {
122+
if ('2d' == contextType) {
123+
var ctx = this._context2d || (this._context2d = new Context2d(this, contextAttributes));
123124
this.context = ctx;
124125
ctx.canvas = this;
125126
return ctx;

lib/context2d.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,5 +339,7 @@ Context2d.prototype.createImageData = function(width, height){
339339
height = width.height;
340340
width = width.width;
341341
}
342-
return new ImageData(new Uint8ClampedArray(width * height * 4), width, height);
342+
var Bpp = this.canvas.stride / this.canvas.width;
343+
var nBytes = Bpp * width * height;
344+
return new ImageData(new Uint8ClampedArray(nBytes), width, height);
343345
};

src/Canvas.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ NAN_METHOD(Canvas::New) {
103103
backend = new ImageBackend(width, height);
104104
}
105105
else if (info[0]->IsObject()) {
106+
// TODO need to check if this is actually an instance of a Backend to avoid a fault
106107
backend = Nan::ObjectWrap::Unwrap<Backend>(info[0]->ToObject());
107108
}
108109
else {
@@ -304,6 +305,8 @@ NAN_METHOD(Canvas::ToBuffer) {
304305

305306
uv_work_t* req = new uv_work_t;
306307
req->data = closure;
308+
// Make sure the surface exists since we won't have an isolate context in the async block:
309+
canvas->surface();
307310
uv_queue_work(uv_default_loop(), req, ToBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
308311

309312
return;

0 commit comments

Comments
 (0)