Skip to content

Commit

Permalink
Clean up img.src= (#1193)
Browse files Browse the repository at this point in the history
* Clean up img.src=

Fixes #945 - support img.src = <url>
Fixes #807 - support for non-base64 `data:` URIs
Fixes #1079 - ditto
Closes #564 - it works, probably didn't pass a string or buffer

Makes all examples and the README use onload, onerror.

* image: throw if no onerror listener attached
  • Loading branch information
zbjornson authored Jul 6, 2018
1 parent 0f74eed commit 230b1db
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 75 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ canvas.createJPEGStream() // new
* Allow assigning non-string values to fillStyle and strokeStyle
* Fix drawing zero-width and zero-height images.
* Fix DEP0005 deprecation warning
* Don't assume `data:` URIs assigned to `img.src` are always base64-encoded

### Added
* Prebuilds (#992) with different libc versions to the prebuilt binary (#1140)
Expand All @@ -89,6 +90,7 @@ canvas.createJPEGStream() // new
* Added `resolution` option for `canvas.toBuffer("image/png")` and
`canvas.createPNGStream()`
* Support for `canvas.toDataURI("image/jpeg")` (sync)
* Support for `img.src = <url>` to match browsers

1.6.x (unreleased)
==================
Expand Down
56 changes: 35 additions & 21 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,31 +97,42 @@ node-canvas implements the [HTML Canvas API](https://developer.mozilla.org/en-US
(See [Compatibility Status](https://github.com/Automattic/node-canvas/wiki/Compatibility-Status)
for the current API compliance.) All non-standard APIs are documented below.

### Image#src=Buffer
### Image#src

node-canvas adds `Image#src=Buffer` support, allowing you to read images from disc, redis, etc and apply them via `ctx.drawImage()`. Below we draw scaled down squid png by reading it from the disk with node's I/O.
As in browsers, `img.src` can be set to a `data:` URI or a remote URL. In addition,
node-canvas allows setting `src` to a local file path or to a `Buffer` instance.

```javascript
const { Image } = require('canvas');
fs.readFile(__dirname + '/images/squid.png', function(err, squid){
if (err) throw err;
img = new Image;
img.src = squid;
ctx.drawImage(img, 0, 0, img.width / 4, img.height / 4);
});
```

Below is an example of a canvas drawing it-self as the source several time:
// From a buffer:
fs.readFile('images/squid.png', (err, squid) => {
if (err) throw err
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.onerror = err => { throw err }
img.src = squid
})

```javascript
const { Image } = require('canvas');
var img = new Image;
img.src = canvas.toBuffer();
ctx.drawImage(img, 0, 0, 50, 50);
ctx.drawImage(img, 50, 0, 50, 50);
ctx.drawImage(img, 100, 0, 50, 50);
// From a local file path:
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.onerror = err => { throw err }
img.src = 'images/squid.png'

// From a remote URL:
img.src = 'http://picsum.photos/200/300'
// ... as above

// From a `data:` URI:
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
// ... as above
```

*Note: In some cases, `img.src=` is currently synchronous. However, you should
always use `img.onload` and `img.onerror`, as we intend to make `img.src=` always
asynchronous as it is in browsers. See https://github.com/Automattic/node-canvas/issues/1007.*

### Image#dataMode

node-canvas adds `Image#dataMode` support, which can be used to opt-in to mime data tracking of images (currently only JPEGs).
Expand Down Expand Up @@ -414,12 +425,15 @@ fs.writeFileSync('out.svg', canvas.toBuffer());

## SVG Image Support

If librsvg is on your system when node-canvas is installed, node-canvas can render SVG images within your canvas context. Note that this currently works by simply rasterizing the SVG image using librsvg.
If librsvg is available when node-canvas is installed, node-canvas can render
SVG images to your canvas context. This currently works by rasterizing the SVG
image (i.e. drawing an SVG image to an SVG canvas will not preserve the SVG data).

```js
var img = new Image;
img.src = './example.svg';
ctx.drawImage(img, 0, 0, 100, 100);
const img = new Image()
img.onload = () => ctx.drawImage(img, 0, 0)
img.onerror = err => { throw err }
img.src = './example.svg'
```

## Image pixel formats (experimental)
Expand Down
26 changes: 15 additions & 11 deletions examples/grayscale-image.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
var fs = require('fs')
var path = require('path')
var Canvas = require('..')
const fs = require('fs')
const path = require('path')
const Canvas = require('..')

var Image = Canvas.Image
var canvas = Canvas.createCanvas(288, 288)
var ctx = canvas.getContext('2d')
const Image = Canvas.Image
const canvas = Canvas.createCanvas(288, 288)
const ctx = canvas.getContext('2d')

var img = new Image()
img.src = fs.readFileSync(path.join(__dirname, 'images', 'grayscaleImage.jpg'))
const img = new Image()
img.onload = () => {
ctx.drawImage(img, 0, 0)
canvas.createJPEGStream().pipe(fs.createWriteStream(path.join(__dirname, 'passedThroughGrayscale.jpg')))
}
img.onerror = err => {
throw err
}

ctx.drawImage(img, 0, 0)

canvas.createJPEGStream().pipe(fs.createWriteStream(path.join(__dirname, 'passedThroughGrayscale.jpg')))
img.src = path.join(__dirname, 'images', 'grayscaleImage.jpg')
18 changes: 18 additions & 0 deletions examples/image-src-url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const fs = require('fs')
const path = require('path')
const Canvas = require('..')

const Image = Canvas.Image
const canvas = Canvas.createCanvas(200, 300)
const ctx = canvas.getContext('2d')

const img = new Image()
img.onload = () => {
ctx.drawImage(img, 0, 0)
canvas.createPNGStream()
.pipe(fs.createWriteStream(path.join(__dirname, 'image-src-url.png')))
}
img.onerror = err => {
console.log(err)
}
img.src = 'http://picsum.photos/200/300'
39 changes: 22 additions & 17 deletions examples/image-src.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
var fs = require('fs')
var path = require('path')
var Canvas = require('..')
const fs = require('fs')
const path = require('path')
const Canvas = require('..')

var Image = Canvas.Image
var canvas = Canvas.createCanvas(200, 200)
var ctx = canvas.getContext('2d')
const Image = Canvas.Image
const canvas = Canvas.createCanvas(200, 200)
const ctx = canvas.getContext('2d')

ctx.fillRect(0, 0, 150, 150)
ctx.save()
Expand All @@ -23,14 +23,19 @@ ctx.fillRect(45, 45, 60, 60)
ctx.restore()
ctx.fillRect(60, 60, 30, 30)

var img = new Image()
img.src = canvas.toBuffer()
ctx.drawImage(img, 0, 0, 50, 50)
ctx.drawImage(img, 50, 0, 50, 50)
ctx.drawImage(img, 100, 0, 50, 50)

img = new Image()
img.src = fs.readFileSync(path.join(__dirname, 'images', 'squid.png'))
ctx.drawImage(img, 30, 50, img.width / 4, img.height / 4)

canvas.createPNGStream().pipe(fs.createWriteStream(path.join(__dirname, 'image-src.png')))
const img = new Image()
img.onerror = err => { throw err }
img.onload = () => {
img.src = canvas.toBuffer()
ctx.drawImage(img, 0, 0, 50, 50)
ctx.drawImage(img, 50, 0, 50, 50)
ctx.drawImage(img, 100, 0, 50, 50)

const img2 = new Image()
img2.onload = () => {
ctx.drawImage(img2, 30, 50, img2.width / 4, img2.height / 4)
canvas.createPNGStream().pipe(fs.createWriteStream(path.join(__dirname, 'image-src.png')))
}
img2.onerror = err => { throw err }
img2.src = path.join(__dirname, 'images', 'squid.png')
}
2 changes: 2 additions & 0 deletions examples/kraken.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ img.onload = function () {
ctx.drawImage(img, 0, 0)
}

img.onerror = err => { throw err }

img.src = path.join(__dirname, 'images', 'squid.png')

var sigma = 10 // radius
Expand Down
74 changes: 48 additions & 26 deletions lib/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,59 @@

const bindings = require('./bindings')
const Image = module.exports = bindings.Image
const http = require("http")

/**
* Src setter.
*
* - convert data uri to `Buffer`
*
* @param {String|Buffer} val filename, buffer, data uri
* @api public
*/
Object.defineProperty(Image.prototype, 'src', {
/**
* src setter. Valid values:
* * `data:` URI
* * Local file path
* * HTTP or HTTPS URL
* * Buffer containing image data (i.e. not a `data:` URI stored in a Buffer)
*
* @param {String|Buffer} val filename, buffer, data URI, URL
* @api public
*/
set(val) {
if (typeof val === 'string') {
if (/^\s*data:/.test(val)) { // data: URI
const commaI = val.indexOf(',')
// 'base64' must come before the comma
const isBase64 = val.lastIndexOf('base64', commaI)
val = val.slice(commaI + 1)
this.source = Buffer.from(val, isBase64 ? 'base64' : 'utf8')
} else if (/^\s*http/.test(val)) { // remote URL
const onerror = err => {
if (typeof this.onerror === 'function') {
this.onerror(err)
} else {
throw err
}
}
http.get(val, res => {
if (res.statusCode !== 200) {
return onerror(new Error(`Server responded with ${res.statusCode}`))
}
const buffers = []
res.on('data', buffer => buffers.push(buffer))
res.on('end', () => {
this.source = Buffer.concat(buffers)
})
}).on('error', onerror)
} else { // local file path assumed
this.source = val
}
} else if (Buffer.isBuffer(val)) {
this.source = val
}
},

Image.prototype.__defineSetter__('src', function(val){
if ('string' == typeof val && 0 == val.indexOf('data:')) {
val = val.slice(val.indexOf(',') + 1);
this.source = Buffer.from(val, 'base64');
} else {
this.source = val;
get() {
// TODO https://github.com/Automattic/node-canvas/issues/118
return this.source;
}
});

/**
* Src getter.
*
* TODO: return buffer
*
* @api public
*/

Image.prototype.__defineGetter__('src', function(){
return this.source;
});

/**
* Inspect image.
*
Expand Down

0 comments on commit 230b1db

Please sign in to comment.