Skip to content

Commit da5f1af

Browse files
committed
Support toBuffer("image/jpeg"), unify encoding configs
See updated Readme.md and CHANGELOG.md
1 parent 4e04eff commit da5f1af

File tree

8 files changed

+524
-370
lines changed

8 files changed

+524
-370
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)
@@ -32,8 +63,8 @@ project adheres to [Semantic Versioning](http://semver.org/).
3263

3364
### Added
3465
* Prebuilds (#992)
35-
* Support canvas.getContext("2d", {alpha: boolean}) and
36-
canvas.getContext("2d", {pixelFormat: "..."})
66+
* Support `canvas.getContext("2d", {alpha: boolean})` and
67+
`canvas.getContext("2d", {pixelFormat: "..."})`
3768
* Support indexed PNG encoding.
3869
* Support `currentTransform` (d6714ee)
3970
* Export `CanvasGradient` (6a4c0ab)
@@ -45,6 +76,9 @@ project adheres to [Semantic Versioning](http://semver.org/).
4576
* Browser-compatible API (6a29a23)
4677
* Support for jpeg on Windows (42e9a74)
4778
* Support for backends (1a6dffe)
79+
* Support for `canvas.toBuffer("image/jpeg")`
80+
* Unified configuration options for `canvas.toBuffer()`, `canvas.pngStream()`
81+
and `canvas.jpegStream()`
4882

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

Readme.md

+120-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,147 @@ 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). *Note*: Currently the callback
146+
function is invoked synchronously for `image/jpeg`.
147+
* **mimeType** A string indicating the image format. Valid options are `image/png`,
148+
`image/jpeg` (if node-canvas was built with JPEG support) and `raw` (unencoded
149+
ARGB32 data in native-endian byte order, top-to-bottom). Defaults to
150+
`image/png`. If the canvas is a PDF or SVG canvas, this argument is ignored
151+
and a PDF or SVG is returend always.
152+
* **config**
153+
* For `image/jpeg` an object specifying the quality (0 to 1), if progressive
154+
compression should be used and/or if chroma subsampling should be used:
155+
`{quality: 0.75, progressive: false, chromaSubsampling: true}`. All
156+
properties are optional.
157+
* For `image/png`, an object specifying the ZLIB compression level (between 0
158+
and 9), the compression filter(s), the palette (indexed PNGs only) and/or
159+
the background palette index (indexed PNGs only):
160+
`{compressionLevel: 6, filters: canvas.PNG_ALL_FILTERS, palette: undefined, backgroundIndex: 0}`.
161+
All properties are optional.
162+
163+
**Return value**
164+
165+
If no callback is provided, a [`Buffer`](https://nodejs.org/api/buffer.html).
166+
If a callback is provided, none.
167+
168+
#### Examples
131169

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

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

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

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

146231
```js
147-
var palette = new Uint8ClampedArray([
232+
const palette = new Uint8ClampedArray([
148233
//r g b a
149234
0, 50, 50, 255, // index 1
150235
10, 90, 90, 255, // index 2
151236
127, 127, 255, 255
152237
// ...
153-
]);
238+
])
154239
canvas.pngStream({
155240
palette: palette,
156241
backgroundIndex: 0 // optional, defaults to 0
157242
})
158243
```
159244

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()
245+
### Canvas#jpegStream()
179246

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

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

186-
// PNG Buffer, zlib compression level 3 (from 0-9), faster but bigger
187-
var buf2 = canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
253+
> `canvas.pngStream([config]) => ReadableStream`
188254
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-
```
255+
* `config` an object specifying the quality (0 to 1), if progressive compression
256+
should be used and/or if chroma subsampling should be used:
257+
`{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties
258+
are optional.
197259

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)`.
260+
#### Examples
201261

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

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

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)