Skip to content

Commit a9147fe

Browse files
committed
Support toBuffer("image/jpeg"), unify encoding configs
See updated Readme.md and CHANGELOG.md Still needs more testing and possibly cleanup of the closure mess.
1 parent 4f77afb commit a9147fe

21 files changed

+738
-535
lines changed

CHANGELOG.md

+39-5
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,43 @@ project adheres to [Semantic Versioning](http://semver.org/).
88
2.0.0 (unreleased -- encompasses all alpha versions)
99
==================
1010

11+
**Upgrading from 1.x**
12+
```js
13+
// (1) The quality argument for canvas.jpegStream now goes from 0 to 1 instead
14+
// of from 0 to 100:
15+
canvas.jpegStream({quality: 50}) // old
16+
canvas.jpegStream({quality: 0.5}) // new
17+
18+
// (2) The ZLIB compression level and PNG filter options for canvas.toBuffer are
19+
// now named instead of positional arguments:
20+
canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE) // old
21+
canvas.toBuffer(undefined, {compressionLevel: 3, filters: canvas.PNG_FILTER_NONE}) // new
22+
// or specify the mime type explicitly:
23+
canvas.toBuffer("image/png", {compressionLevel: 3, filters: canvas.PNG_FILTER_NONE}) // new
24+
25+
// (3) #2 also applies for canvas.pngStream, although these arguments were not
26+
// documented:
27+
canvas.pngStream(3, canvas.PNG_FILTER_NONE) // old
28+
canvas.pngStream({compressionLevel: 3, filters: canvas.PNG_FILTER_NONE}) // new
29+
30+
// (4) canvas.syncPNGStream() and canvas.syncJPEGStream() have been removed:
31+
canvas.syncPNGStream() // old
32+
canvas.pngStream() // new
33+
34+
canvas.syncJPEGStream() // old
35+
canvas.jpegStream() // new
36+
```
37+
1138
### Breaking
1239
* Drop support for Node.js <4.x
13-
* Remove sync streams (bc53059). Note that all or most streams are still
14-
synchronous to some degree; this change just removed `syncPNGStream` and
15-
friends.
40+
* Remove sync stream functions (bc53059). Note that most streams are still
41+
synchronous (run in the main thread); this change just removed `syncPNGStream`
42+
and `syncJPEGStream`.
1643
* Pango is now *required* on all platforms (7716ae4).
44+
* Make the `quality` argument for JPEG output go from 0 to 1 to match HTML spec.
45+
* Make the `compressionLevel` and `filters` arguments for `canvas.toBuffer()`
46+
named instead of positional. Same for `canvas.pngStream()`, although these
47+
arguments were not documented.
1748

1849
### Fixed
1950
* Prevent segfaults caused by loading invalid fonts (#1105)
@@ -33,8 +64,8 @@ project adheres to [Semantic Versioning](http://semver.org/).
3364

3465
### Added
3566
* Prebuilds (#992) with different libc versions to the prebuilt binary (#1140)
36-
* Support canvas.getContext("2d", {alpha: boolean}) and
37-
canvas.getContext("2d", {pixelFormat: "..."})
67+
* Support `canvas.getContext("2d", {alpha: boolean})` and
68+
`canvas.getContext("2d", {pixelFormat: "..."})`
3869
* Support indexed PNG encoding.
3970
* Support `currentTransform` (d6714ee)
4071
* Export `CanvasGradient` (6a4c0ab)
@@ -46,6 +77,9 @@ project adheres to [Semantic Versioning](http://semver.org/).
4677
* Browser-compatible API (6a29a23)
4778
* Support for jpeg on Windows (42e9a74)
4879
* Support for backends (1a6dffe)
80+
* Support for `canvas.toBuffer("image/jpeg")`
81+
* Unified configuration options for `canvas.toBuffer()`, `canvas.pngStream()`
82+
and `canvas.jpegStream()`
4983

5084
1.6.x (unreleased)
5185
==================

Readme.md

+119-52
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
## This is the documentation for version 2.0.0-alpha
66
Alpha versions of 2.0 can be installed using `npm install canvas@next`.
77

8+
See the [changelog](https://github.com/Automattic/node-canvas/blob/master/CHANGELOG.md)
9+
for a guide to upgrading from 1.x to 2.x.
10+
811
**For version 1.x documentation, see [the v1.x branch](https://github.com/Automattic/node-canvas/tree/v1.x)**
912

1013
-----
@@ -80,9 +83,11 @@ loadImage('examples/images/lime-cat.jpg').then((image) => {
8083
})
8184
```
8285

83-
## Non-Standard API
86+
## Non-Standard APIs
8487

85-
node-canvas extends the canvas API to provide interfacing with node, for example streaming PNG data, converting to a `Buffer` instance, etc. Among the interfacing API, in some cases the drawing API has been extended for SSJS image manipulation / creation usage, however keep in mind these additions may fail to render properly within browsers.
88+
node-canvas implements the [HTML Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) as closely as possible.
89+
(See [Compatibility Status](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status)
90+
for the current API compliance.) All non-standard APIs are documented below.
8691

8792
### Image#src=Buffer
8893

@@ -125,84 +130,146 @@ img.dataMode = Image.MODE_MIME | Image.MODE_IMAGE; // Both are tracked
125130

126131
If image data is not tracked, and the Image is drawn to an image rather than a PDF canvas, the output will be junk. Enabling mime data tracking has no benefits (only a slow down) unless you are generating a PDF.
127132

128-
### Canvas#pngStream(options)
133+
### Canvas#toBuffer()
129134

130-
To create a `PNGStream` simply call `canvas.pngStream()`, and the stream will start to emit _data_ events, emitting _end_ when the data stream ends. If an exception occurs the _error_ event is emitted.
135+
Creates a [`Buffer`](https://nodejs.org/api/buffer.html) object representing the
136+
image contained in the canvas.
137+
138+
> `canvas.toBuffer((err: Error|null, result: Buffer) => void[, mimeType[, config]]) => void`
139+
> `canvas.toBuffer([mimeType[, config]]) => Buffer`
140+
141+
* **callback** If provided, the buffer will be provided in the callback instead
142+
of being returned by the function. Invoked with an error as the first argument
143+
if encoding failed, or the resulting buffer as the second argument if it
144+
succeeded. Not supported for mimeType `raw` or for PDF or SVG canvases (there
145+
is no async work to do in those cases).
146+
* **mimeType** A string indicating the image format. Valid options are `image/png`,
147+
`image/jpeg` (if node-canvas was built with JPEG support) and `raw` (unencoded
148+
ARGB32 data in native-endian byte order, top-to-bottom). Defaults to
149+
`image/png`. If the canvas is a PDF or SVG canvas, this argument is ignored
150+
and a PDF or SVG is returned always.
151+
* **config**
152+
* For `image/jpeg` an object specifying the quality (0 to 1), if progressive
153+
compression should be used and/or if chroma subsampling should be used:
154+
`{quality: 0.75, progressive: false, chromaSubsampling: true}`. All
155+
properties are optional.
156+
* For `image/png`, an object specifying the ZLIB compression level (between 0
157+
and 9), the compression filter(s), the palette (indexed PNGs only) and/or
158+
the background palette index (indexed PNGs only):
159+
`{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0}`.
160+
All properties are optional.
161+
162+
**Return value**
163+
164+
If no callback is provided, a [`Buffer`](https://nodejs.org/api/buffer.html).
165+
If a callback is provided, none.
166+
167+
#### Examples
131168

132169
```javascript
133-
var fs = require('fs')
134-
, out = fs.createWriteStream(__dirname + '/text.png')
135-
, stream = canvas.pngStream();
170+
// Default: buf contains a PNG-encoded image
171+
const buf = canvas.toBuffer()
136172

137-
stream.pipe(out);
173+
// PNG-encoded, zlib compression level 3 for faster compression but bigger files, no filtering
174+
const buf2 = canvas.toBuffer('image/png', {compressionLevel: 3, filters: canvas.PNG_FILTER_NONE})
138175

139-
out.on('finish', function(){
140-
console.log('The PNG file was created.');
141-
});
176+
// JPEG-encoded, 50% quality
177+
const buf3 = canvas.toBuffer('image/jpeg', {quality: 0.5})
178+
179+
// Asynchronous PNG
180+
canvas.toBuffer((err, buf) => {
181+
if (err) throw err; // encoding failed
182+
// buf is PNG-encoded image
183+
})
184+
185+
canvas.toBuffer((err, buf) => {
186+
if (err) throw err; // encoding failed
187+
// buf is JPEG-encoded image at 95% quality
188+
// Note that this callback is currently called synchronously.
189+
}, 'image/jpeg', {quality: 0.95})
190+
191+
// ARGB32 pixel values, native-endian
192+
const buf4 = canvas.toBuffer('raw')
193+
const {stride, width} = canvas
194+
// In memory, this is `canvas.height * canvas.stride` bytes long.
195+
// The top row of pixels, in ARGB order, left-to-right, is:
196+
const topPixelsARGBLeftToRight = buf4.slice(0, width * 4)
197+
// And the third row is:
198+
const row3 = buf4.slice(2 * stride, 2 * stride + width * 4)
199+
200+
// SVG and PDF canvases ignore the mimeType argument
201+
const myCanvas = createCanvas(w, h, 'pdf')
202+
myCanvas.toBuffer() // returns a buffer containing a PDF-encoded canvas
203+
```
204+
205+
### Canvas#pngStream(options)
206+
207+
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable)
208+
that emits PNG-encoded data.
209+
210+
> `canvas.pngStream([config]) => ReadableStream`
211+
212+
* `config` An object specifying the ZLIB compression level (between 0 and 9),
213+
the compression filter(s), the palette (indexed PNGs only) and/or the
214+
background palette index (indexed PNGs only):
215+
`{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0}`.
216+
All properties are optional.
217+
218+
#### Examples
219+
220+
```javascript
221+
const fs = require('fs')
222+
const out = fs.createWriteStream(__dirname + '/test.png')
223+
const stream = canvas.pngStream()
224+
stream.pipe(out)
225+
out.on('finish', () => console.log('The PNG file was created.'))
142226
```
143227

144228
To encode indexed PNGs from canvases with `pixelFormat: 'A8'` or `'A1'`, provide an options object:
145229

146230
```js
147-
var palette = new Uint8ClampedArray([
231+
const palette = new Uint8ClampedArray([
148232
//r g b a
149233
0, 50, 50, 255, // index 1
150234
10, 90, 90, 255, // index 2
151235
127, 127, 255, 255
152236
// ...
153-
]);
237+
])
154238
canvas.pngStream({
155239
palette: palette,
156240
backgroundIndex: 0 // optional, defaults to 0
157241
})
158242
```
159243

160-
### Canvas#jpegStream() and Canvas#syncJPEGStream()
161-
162-
You can likewise create a `JPEGStream` by calling `canvas.jpegStream()` with
163-
some optional parameters; functionality is otherwise identical to
164-
`pngStream()`. See `examples/crop.js` for an example.
165-
166-
_Note: At the moment, `jpegStream()` is the same as `syncJPEGStream()`, both
167-
are synchronous_
168-
169-
```javascript
170-
var stream = canvas.jpegStream({
171-
bufsize: 4096 // output buffer size in bytes, default: 4096
172-
, quality: 75 // JPEG quality (0-100) default: 75
173-
, progressive: false // true for progressive compression, default: false
174-
, chromaSubsampling: true // false to disable 2x2 subsampling of the chroma components, default: true
175-
});
176-
```
177-
178-
### Canvas#toBuffer()
244+
### Canvas#jpegStream()
179245

180-
A call to `Canvas#toBuffer()` will return a node `Buffer` instance containing image data.
246+
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable)
247+
that emits JPEG-encoded data.
181248

182-
```javascript
183-
// PNG Buffer, default settings
184-
var buf = canvas.toBuffer();
249+
_Note: At the moment, `jpegStream()` is synchronous under the hood. That is, it
250+
runs in the main thread, not in the libuv threadpool._
185251

186-
// PNG Buffer, zlib compression level 3 (from 0-9), faster but bigger
187-
var buf2 = canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
252+
> `canvas.pngStream([config]) => ReadableStream`
188253
189-
// ARGB32 Buffer, native-endian
190-
var buf3 = canvas.toBuffer('raw');
191-
var stride = canvas.stride;
192-
// In memory, this is `canvas.height * canvas.stride` bytes long.
193-
// The top row of pixels, in ARGB order, left-to-right, is:
194-
var topPixelsARGBLeftToRight = buf3.slice(0, canvas.width * 4);
195-
var row3 = buf3.slice(2 * canvas.stride, 2 * canvas.stride + canvas.width * 4);
196-
```
254+
* `config` an object specifying the quality (0 to 1), if progressive compression
255+
should be used and/or if chroma subsampling should be used:
256+
`{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties
257+
are optional.
197258

198-
### Canvas#toBuffer() async
199-
200-
Optionally we may pass a callback function to `Canvas#toBuffer()`, and this process will be performed asynchronously, and will `callback(err, buf)`.
259+
#### Examples
201260

202261
```javascript
203-
canvas.toBuffer(function(err, buf){
204-
205-
});
262+
const fs = require('fs')
263+
const out = fs.createWriteStream(__dirname + '/test.jpeg')
264+
const stream = canvas.jpegStream()
265+
stream.pipe(out)
266+
out.on('finish', () => console.log('The JPEG file was created.'))
267+
268+
// Disable 2x2 chromaSubsampling for deeper colors and use a higher quality
269+
const stream = canvas.jpegStream({
270+
quality: 95,
271+
chromaSubsampling: false
272+
})
206273
```
207274

208275
### Canvas#toDataURL() sync and async

binding.gyp

+8-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,14 @@
135135
'<!@(pkg-config libpng --cflags-only-I | sed s/-I//g)',
136136
'<!@(pkg-config pangocairo --cflags-only-I | sed s/-I//g)',
137137
'<!@(pkg-config freetype2 --cflags-only-I | sed s/-I//g)'
138-
]
138+
],
139+
'cflags!': ['-fno-exceptions'],
140+
'cflags_cc!': ['-fno-exceptions']
141+
}],
142+
['OS=="mac"', {
143+
'xcode_settings': {
144+
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES'
145+
}
139146
}],
140147
['with_jpeg=="true"', {
141148
'defines': [

lib/canvas.js

+24-34
Original file line numberDiff line numberDiff line change
@@ -50,27 +50,32 @@ Canvas.prototype.getContext = function (contextType, contextAttributes) {
5050
/**
5151
* Create a `PNGStream` for `this` canvas.
5252
*
53-
* @param {Object} options
54-
* @param {Uint8ClampedArray} options.palette Provide for indexed PNG encoding.
55-
* entries should be R-G-B-A values.
56-
* @param {Number} options.backgroundIndex Optional index of background color
53+
* @param {Object} [options]
54+
* @param {number} [options.compressionLevel] Number from 0 to 9 corresponding
55+
* to the ZLIB compression level. Defaults to 6.
56+
* @param {number} [options.filters] Any bitwise combination of
57+
* `PNG_FILTER_NONE`, `PNG_FITLER_SUB`, `PNG_FILTER_UP`, `PNG_FILTER_AVG`,
58+
* `PNG_FILTER_PATETH`; or one of `PNG_ALL_FILTERS` or `PNG_NO_FILTERS`. These
59+
* specify which filters *may* be used by libpng. During encoding, libpng will
60+
* select the best filter from this list of allowed filters.
61+
* @param {Uint8ClampedArray} [options.palette] Provide for indexed PNG
62+
* encoding. Entries should be R-G-B-A values.
63+
* @param {number} [options.backgroundIndex] Optional index of background color
5764
* for indexed PNGs. Defaults to 0.
5865
* @return {PNGStream}
59-
* @api public
66+
* @public
6067
*/
61-
62-
Canvas.prototype.pngStream =
68+
Canvas.prototype.pngStream =
6369
Canvas.prototype.createPNGStream = function(options){
64-
return new PNGStream(this, false, options);
70+
return new PNGStream(this, options);
6571
};
6672

6773
/**
6874
* Create a `PDFStream` for `this` canvas.
6975
*
7076
* @return {PDFStream}
71-
* @api public
77+
* @public
7278
*/
73-
7479
Canvas.prototype.pdfStream =
7580
Canvas.prototype.createPDFStream = function(){
7681
return new PDFStream(this);
@@ -79,33 +84,18 @@ Canvas.prototype.createPDFStream = function(){
7984
/**
8085
* Create a `JPEGStream` for `this` canvas.
8186
*
82-
* @param {Object} options
87+
* @param {Object} [options]
88+
* @param {number} [options.quality] Number from 0 to 1. Defaults to 0.75.
89+
* @param {boolean|1|2} [options.chromaSubsampling] Enables 2x2 chroma
90+
* subsampling. `true` is equivalent to `2`, `false` is equivalent to `1`.
91+
* @param {boolean} [options.progressive] Enables progressive encoding. Defautls
92+
* to false.
8393
* @return {JPEGStream}
84-
* @api public
94+
* @public
8595
*/
86-
87-
Canvas.prototype.jpegStream =
96+
Canvas.prototype.jpegStream =
8897
Canvas.prototype.createJPEGStream = function(options){
89-
options = options || {};
90-
// Don't allow the buffer size to exceed the size of the canvas (#674)
91-
var maxBufSize = this.width * this.height * 4;
92-
var clampedBufSize = Math.min(options.bufsize || 4096, maxBufSize);
93-
var chromaFactor;
94-
if (typeof options.chromaSubsampling === "number") {
95-
// libjpeg-turbo seems to complain about values above 2, but hopefully this
96-
// can be supported in the future. For now 1 and 2 are valid.
97-
// https://github.com/Automattic/node-canvas/pull/1092#issuecomment-366558028
98-
chromaFactor = options.chromaSubsampling;
99-
} else {
100-
chromaFactor = options.chromaSubsampling === false ? 1 : 2;
101-
}
102-
return new JPEGStream(this, {
103-
bufsize: clampedBufSize
104-
, quality: options.quality || 75
105-
, progressive: options.progressive || false
106-
, chromaHSampFactor: chromaFactor
107-
, chromaVSampFactor: chromaFactor
108-
});
98+
return new JPEGStream(this, options);
10999
};
110100

111101
/**

0 commit comments

Comments
 (0)