generated from napi-rs/package-template
-
-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement loadImage function (#483)
- Loading branch information
Showing
4 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { join } from 'path' | ||
import test from 'ava' | ||
|
||
import { createCanvas, Image, loadImage } from '../index' | ||
|
||
import { snapshotImage } from './image-snapshot' | ||
|
||
test('should load file src', async (t) => { | ||
const img = await loadImage(join(__dirname, '../example/simple.png')) | ||
t.is(img instanceof Image, true) | ||
}) | ||
|
||
test('should load remote url', async (t) => { | ||
const img = await loadImage( | ||
'https://raw.githubusercontent.com/Brooooooklyn/canvas/462fce53afeaee6d6b4ae5d1b407c17e2359ff7e/example/anime-girl.png', | ||
) | ||
t.is(img instanceof Image, true) | ||
}) | ||
|
||
test('should load data uri', async (t) => { | ||
const img = await loadImage( | ||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII', | ||
) | ||
t.is(img instanceof Image, true) | ||
}) | ||
|
||
test('should draw img', async (t) => { | ||
const img = await loadImage( | ||
'https://raw.githubusercontent.com/Brooooooklyn/canvas/462fce53afeaee6d6b4ae5d1b407c17e2359ff7e/example/anime-girl.png', | ||
) | ||
|
||
// create a canvas of the same size as the image | ||
const canvas = createCanvas(img.width, img.height) | ||
const ctx = canvas.getContext('2d') | ||
|
||
// fill the canvas with the image | ||
ctx.fillStyle = '#23eff0' | ||
ctx.fillRect(0, 0, canvas.width, canvas.height) | ||
ctx.drawImage(img, 250, 250) | ||
|
||
await snapshotImage(t, { canvas }, 'jpeg') | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
const fs = require('fs') | ||
const { Image } = require('./js-binding') | ||
|
||
let http, https | ||
|
||
const MAX_REDIRECTS = 20, | ||
REDIRECT_STATUSES = [301, 302], | ||
DATA_URI = /^\s*data:/ | ||
|
||
/** | ||
* 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)) return createImage(source) | ||
// construct a buffer if the source is buffer-like | ||
if (isBufferLike(source)) return createImage(Buffer.from(source)) | ||
// if the source is Image instance, copy the image src to new image | ||
if (source instanceof Image) return createImage(source.src) | ||
// if source is string and in data uri format, construct image using data uri | ||
if (typeof source === 'string' && DATA_URI.test(source)) { | ||
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) | ||
} | ||
// 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 (fs.existsSync(source)) { | ||
return createImage(await fs.promises.readFile(source)) | ||
} 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, options.maxRedirects ?? MAX_REDIRECTS, options.requestOptions), | ||
) | ||
return createImage(data) | ||
} | ||
} | ||
|
||
// throw error as dont 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, requestOptions ?? {}, (res) => { | ||
const shouldRedirect = REDIRECT_STATUSES.includes(res.statusCode) && typeof res.headers.location === 'string' | ||
if (shouldRedirect && redirectCount > 0) | ||
return makeRequest(res.headers.location, 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) | ||
}) | ||
} | ||
|
||
// 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) { | ||
const image = new Image() | ||
image.src = src | ||
return image | ||
} | ||
|
||
function isBufferLike(src) { | ||
return ( | ||
Array.isArray(src) || | ||
src instanceof ArrayBuffer || | ||
src instanceof SharedArrayBuffer || | ||
src instanceof Object.getPrototypeOf(Uint8Array) | ||
) | ||
} |