Skip to content

Commit 02080e4

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

File tree

6 files changed

+453
-224
lines changed

6 files changed

+453
-224
lines changed

CHANGELOG.md

Lines changed: 39 additions & 5 deletions
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, filter: canvas.PNG_FILTER_NONE}) // new
22+
// or specify the mime type explicitly:
23+
canvas.toBuffer("image/png", {compressionLevel: 3, filter: 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, filter: 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 `filter` 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

Lines changed: 117 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,11 @@ loadImage('examples/images/lime-cat.jpg').then((image) => {
8080
})
8181
```
8282

83-
## Non-Standard API
83+
## Non-Standard APIs
8484

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.
85+
node-canvas implements the [HTML Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) as closely as possible.
86+
(See [Compatibility Status](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status)
87+
for the current API compliance.) All non-standard APIs are documented below.
8688

8789
### Image#src=Buffer
8890

@@ -125,84 +127,147 @@ img.dataMode = Image.MODE_MIME | Image.MODE_IMAGE; // Both are tracked
125127

126128
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.
127129

128-
### Canvas#pngStream(options)
130+
### Canvas#toBuffer()
129131

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

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

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

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

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

146228
```js
147-
var palette = new Uint8ClampedArray([
229+
const palette = new Uint8ClampedArray([
148230
//r g b a
149231
0, 50, 50, 255, // index 1
150232
10, 90, 90, 255, // index 2
151233
127, 127, 255, 255
152234
// ...
153-
]);
235+
])
154236
canvas.pngStream({
155237
palette: palette,
156238
backgroundIndex: 0 // optional, defaults to 0
157239
})
158240
```
159241

160-
### Canvas#jpegStream() and Canvas#syncJPEGStream()
242+
### Canvas#jpegStream()
161243

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.
244+
Creates a [`ReadableStream`](https://nodejs.org/api/stream.html#stream_class_stream_readable)
245+
that emits JPEG-encoded data.
165246

166-
_Note: At the moment, `jpegStream()` is the same as `syncJPEGStream()`, both
167-
are synchronous_
247+
_Note: At the moment, `jpegStream()` is synchronous under the hood. That is, it
248+
runs in the main thread, not in the libuv threadpool._
168249

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-
```
250+
> `canvas.pngStream([config]) => ReadableStream`
177251
178-
### Canvas#toBuffer()
252+
* `config` an object specifying the quality (0 to 1), if progressive compression
253+
should be used and/or if chroma subsampling should be used:
254+
`{quality: 0.75, progressive: false, chromaSubsampling: true}`. All properties
255+
are optional.
179256

180-
A call to `Canvas#toBuffer()` will return a node `Buffer` instance containing image data.
257+
#### Examples
181258

182259
```javascript
183-
// PNG Buffer, default settings
184-
var buf = canvas.toBuffer();
185-
186-
// PNG Buffer, zlib compression level 3 (from 0-9), faster but bigger
187-
var buf2 = canvas.toBuffer(undefined, 3, canvas.PNG_FILTER_NONE);
188-
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-
```
197-
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)`.
201-
202-
```javascript
203-
canvas.toBuffer(function(err, buf){
204-
205-
});
260+
const fs = require('fs')
261+
const out = fs.createWriteStream(__dirname + '/test.jpeg')
262+
const stream = canvas.jpegStream()
263+
stream.pipe(out)
264+
out.on('finish', () => console.log('The JPEG file was created.'))
265+
266+
// Disable 2x2 chromaSubsampling for deeper colors and use a higher quality
267+
const stream = canvas.jpegStream({
268+
quality: 95,
269+
chromaSubsampling: false
270+
})
206271
```
207272

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

lib/canvas.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ Canvas.prototype.createJPEGStream = function(options){
9191
var maxBufSize = this.width * this.height * 4;
9292
var clampedBufSize = Math.min(options.bufsize || 4096, maxBufSize);
9393
var chromaFactor;
94-
if (typeof options.chromaSubsampling === "number") {
94+
if (typeof options.chromaSubsampling === 'number') {
9595
// libjpeg-turbo seems to complain about values above 2, but hopefully this
9696
// can be supported in the future. For now 1 and 2 are valid.
9797
// https://github.com/Automattic/node-canvas/pull/1092#issuecomment-366558028
@@ -100,11 +100,11 @@ Canvas.prototype.createJPEGStream = function(options){
100100
chromaFactor = options.chromaSubsampling === false ? 1 : 2;
101101
}
102102
return new JPEGStream(this, {
103-
bufsize: clampedBufSize
104-
, quality: options.quality || 75
105-
, progressive: options.progressive || false
106-
, chromaHSampFactor: chromaFactor
107-
, chromaVSampFactor: chromaFactor
103+
bufsize: clampedBufSize,
104+
quality: typeof options.quality === 'number' ? options.quality : 0.75,
105+
progressive: options.progressive || false,
106+
chromaHSampFactor: chromaFactor,
107+
chromaVSampFactor: chromaFactor
108108
});
109109
};
110110

0 commit comments

Comments
 (0)