generated from napi-rs/package-template
-
-
Notifications
You must be signed in to change notification settings - Fork 76
/
load-image.js
122 lines (109 loc) · 4.27 KB
/
load-image.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const fs = require('fs')
const { Readable } = require('stream')
const { Image } = require('./js-binding')
let http, https
const MAX_REDIRECTS = 20
const REDIRECT_STATUSES = new Set([301, 302])
/**
* Loads the given source into canvas Image
* @param {string|URL|Image|Buffer} source The image source to be loaded
* @param {object} options Options passed to the loader
*/
module.exports = async function loadImage(source, options = {}) {
// use the same buffer without copying if the source is a buffer
if (Buffer.isBuffer(source) || source instanceof Uint8Array) return createImage(source, options.alt)
// load readable stream as image
if (source instanceof Readable) return createImage(await consumeStream(source), options.alt)
// construct a Uint8Array if the source is ArrayBuffer or SharedArrayBuffer
if (source instanceof ArrayBuffer || source instanceof SharedArrayBuffer)
return createImage(new Uint8Array(source), options.alt)
// construct a buffer if the source is buffer-like
if (isBufferLike(source)) return createImage(Buffer.from(source), options.alt)
// if the source is Image instance, copy the image src to new image
if (source instanceof Image) return createImage(source.src, options.alt)
// if source is string and in data uri format, construct image using data uri
if (typeof source === 'string' && source.trimStart().startsWith('data:')) {
const commaIdx = source.indexOf(',')
const encoding = source.lastIndexOf('base64', commaIdx) < 0 ? 'utf-8' : 'base64'
const data = Buffer.from(source.slice(commaIdx + 1), encoding)
return createImage(data, options.alt)
}
// if source is a string or URL instance
if (typeof source === 'string' || source instanceof URL) {
// if the source exists as a file, construct image from that file
if (await exists(source)) {
return createImage(await fs.promises.readFile(source), options.alt)
} else {
// the source is a remote url here
source = !(source instanceof URL) ? new URL(source) : source
// attempt to download the remote source and construct image
const data = await new Promise((resolve, reject) =>
makeRequest(
source,
resolve,
reject,
typeof options.maxRedirects === 'number' && options.maxRedirects >= 0 ? options.maxRedirects : MAX_REDIRECTS,
options.requestOptions,
),
)
return createImage(data, options.alt)
}
}
// throw error as don't support that source
throw new TypeError('unsupported image source')
}
function makeRequest(url, resolve, reject, redirectCount, requestOptions) {
const isHttps = url.protocol === 'https:'
// lazy load the lib
const lib = isHttps ? (!https ? (https = require('https')) : https) : !http ? (http = require('http')) : http
lib
.get(url.toString(), requestOptions || {}, (res) => {
try {
const shouldRedirect = REDIRECT_STATUSES.has(res.statusCode) && typeof res.headers.location === 'string'
if (shouldRedirect && redirectCount > 0)
return makeRequest(
new URL(res.headers.location, url.origin),
resolve,
reject,
redirectCount - 1,
requestOptions,
)
if (typeof res.statusCode === 'number' && (res.statusCode < 200 || res.statusCode >= 300)) {
return reject(new Error(`remote source rejected with status code ${res.statusCode}`))
}
consumeStream(res).then(resolve, reject)
} catch (err) {
reject(err)
}
})
.on('error', reject)
}
// use stream/consumers in the future?
function consumeStream(res) {
return new Promise((resolve, reject) => {
const chunks = []
res.on('data', (chunk) => chunks.push(chunk))
res.on('end', () => resolve(Buffer.concat(chunks)))
res.on('error', reject)
})
}
function createImage(src, alt) {
return new Promise((resolve, reject) => {
const image = new Image()
if (typeof alt === 'string') image.alt = alt
image.onload = () => resolve(image)
image.onerror = (e) => reject(e)
image.src = src
})
}
function isBufferLike(src) {
return (src && src.type === 'Buffer') || Array.isArray(src)
}
async function exists(path) {
try {
await fs.promises.access(path, fs.constants.F_OK)
return true
} catch {
return false
}
}