@loaders.gl/images 3.3.2 → 3.4.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dist.min.js +1 -1
- package/dist/dist.min.js.map +3 -3
- package/dist/es5/image-loader.js +2 -2
- package/dist/es5/image-loader.js.map +1 -1
- package/dist/es5/index.js +12 -6
- package/dist/es5/index.js.map +1 -1
- package/dist/es5/lib/category-api/binary-image-api.js +15 -1
- package/dist/es5/lib/category-api/binary-image-api.js.map +1 -1
- package/dist/es5/lib/category-api/image-format.js +170 -20
- package/dist/es5/lib/category-api/image-format.js.map +1 -1
- package/dist/es5/lib/category-api/parse-isobmff-binary.js +55 -0
- package/dist/es5/lib/category-api/parse-isobmff-binary.js.map +1 -0
- package/dist/es5/lib/utils/version.js +1 -1
- package/dist/es5/lib/utils/version.js.map +1 -1
- package/dist/esm/image-loader.js +2 -2
- package/dist/esm/image-loader.js.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/category-api/binary-image-api.js +16 -1
- package/dist/esm/lib/category-api/binary-image-api.js.map +1 -1
- package/dist/esm/lib/category-api/image-format.js +58 -21
- package/dist/esm/lib/category-api/image-format.js.map +1 -1
- package/dist/esm/lib/category-api/parse-isobmff-binary.js +45 -0
- package/dist/esm/lib/category-api/parse-isobmff-binary.js.map +1 -0
- package/dist/esm/lib/utils/version.js +1 -1
- package/dist/esm/lib/utils/version.js.map +1 -1
- package/dist/image-loader.d.ts.map +1 -1
- package/dist/image-loader.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/lib/category-api/binary-image-api.d.ts +2 -6
- package/dist/lib/category-api/binary-image-api.d.ts.map +1 -1
- package/dist/lib/category-api/binary-image-api.js +22 -2
- package/dist/lib/category-api/image-format.d.ts +4 -2
- package/dist/lib/category-api/image-format.d.ts.map +1 -1
- package/dist/lib/category-api/image-format.js +86 -39
- package/dist/lib/category-api/parse-isobmff-binary.d.ts +19 -0
- package/dist/lib/category-api/parse-isobmff-binary.d.ts.map +1 -0
- package/dist/lib/category-api/parse-isobmff-binary.js +94 -0
- package/package.json +3 -3
- package/src/image-loader.ts +2 -1
- package/src/index.ts +3 -2
- package/src/lib/category-api/binary-image-api.ts +25 -6
- package/src/lib/category-api/image-format.ts +92 -39
- package/src/lib/category-api/parse-isobmff-binary.ts +105 -0
|
@@ -1,66 +1,119 @@
|
|
|
1
|
+
// loaders.gl, MIT license
|
|
2
|
+
|
|
1
3
|
import {isBrowser} from '@loaders.gl/loader-utils';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
const MIME_TYPES = [
|
|
6
|
+
'image/png',
|
|
7
|
+
'image/jpeg',
|
|
8
|
+
'image/gif',
|
|
9
|
+
'image/webp',
|
|
10
|
+
'image/avif',
|
|
11
|
+
'image/tiff',
|
|
12
|
+
// TODO - what is the correct type for SVG
|
|
13
|
+
'image/svg',
|
|
14
|
+
'image/svg+xml',
|
|
15
|
+
'image/bmp',
|
|
16
|
+
'image/vnd.microsoft.icon'
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
/** Only one round of tests is performed */
|
|
20
|
+
const mimeTypeSupportedPromise: Promise<Set<string>> | null = null;
|
|
21
|
+
|
|
22
|
+
/** Run-time browser detection of file formats requires async tests for most precise results */
|
|
23
|
+
export async function getSupportedImageFormats(): Promise<Set<string>> {
|
|
24
|
+
if (mimeTypeSupportedPromise) {
|
|
25
|
+
return await mimeTypeSupportedPromise;
|
|
26
|
+
}
|
|
5
27
|
|
|
6
|
-
|
|
7
|
-
const
|
|
28
|
+
const supportedMimeTypes = new Set<string>();
|
|
29
|
+
for (const mimeType of MIME_TYPES) {
|
|
30
|
+
const supported = isBrowser
|
|
31
|
+
? await checkBrowserImageFormatSupportAsync(mimeType)
|
|
32
|
+
: checkNodeImageFormatSupport(mimeType);
|
|
33
|
+
if (supported) {
|
|
34
|
+
supportedMimeTypes.add(mimeType);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return supportedMimeTypes;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Cache sync values for speed */
|
|
42
|
+
const mimeTypeSupportedSync: {[mimeType: string]: boolean} = {};
|
|
8
43
|
|
|
9
44
|
/**
|
|
10
|
-
* Check if image MIME type is supported. Result is cached.
|
|
45
|
+
* Check if image MIME type is supported. Result is cached to avoid repeated tests.
|
|
11
46
|
*/
|
|
12
|
-
export function
|
|
13
|
-
if (
|
|
14
|
-
|
|
47
|
+
export function isImageFormatSupported(mimeType: string): boolean {
|
|
48
|
+
if (mimeTypeSupportedSync[mimeType] === undefined) {
|
|
49
|
+
const supported = isBrowser
|
|
50
|
+
? checkBrowserImageFormatSupport(mimeType)
|
|
51
|
+
: checkNodeImageFormatSupport(mimeType);
|
|
52
|
+
mimeTypeSupportedSync[mimeType] = supported;
|
|
15
53
|
}
|
|
16
|
-
return
|
|
54
|
+
return mimeTypeSupportedSync[mimeType];
|
|
17
55
|
}
|
|
18
56
|
|
|
19
57
|
/**
|
|
20
|
-
*
|
|
58
|
+
* Checks that polyfills are installed and that mimeType is supported by polyfills
|
|
59
|
+
* @todo Ideally polyfills should declare what formats they support, instead of storing that data here.
|
|
60
|
+
*/
|
|
61
|
+
function checkNodeImageFormatSupport(mimeType: string): boolean {
|
|
62
|
+
/** @deprecated Remove these in 4.0 and rely on polyfills to inject them */
|
|
63
|
+
const NODE_FORMAT_SUPPORT = ['image/png', 'image/jpeg', 'image/gif'];
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
const {_parseImageNode, _imageFormatsNode = NODE_FORMAT_SUPPORT} = globalThis;
|
|
66
|
+
return Boolean(_parseImageNode) && _imageFormatsNode.includes(mimeType);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Checks image format support synchronously.
|
|
70
|
+
* @note Unreliable, fails on AVIF
|
|
21
71
|
*/
|
|
22
|
-
function
|
|
72
|
+
function checkBrowserImageFormatSupport(mimeType: string): boolean {
|
|
23
73
|
switch (mimeType) {
|
|
74
|
+
case 'image/avif': // Will fail
|
|
24
75
|
case 'image/webp':
|
|
25
|
-
return
|
|
26
|
-
case 'image/svg':
|
|
27
|
-
return isBrowser;
|
|
76
|
+
return testBrowserImageFormatSupport(mimeType);
|
|
28
77
|
default:
|
|
29
|
-
if (!isBrowser) {
|
|
30
|
-
// @ts-ignore
|
|
31
|
-
const {_parseImageNode} = globalThis;
|
|
32
|
-
return Boolean(_parseImageNode) && NODE_FORMAT_SUPPORT.includes(mimeType);
|
|
33
|
-
}
|
|
34
78
|
return true;
|
|
35
79
|
}
|
|
36
80
|
}
|
|
37
81
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
const TEST_IMAGE = {
|
|
83
|
+
'image/avif':
|
|
84
|
+
'data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=',
|
|
85
|
+
// Lossy test image. Support for lossy images doesn't guarantee support for all WebP images.
|
|
86
|
+
'image/webp': 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Checks WebP and AVIF support asynchronously */
|
|
90
|
+
async function checkBrowserImageFormatSupportAsync(mimeType: string): Promise<boolean> {
|
|
91
|
+
const dataURL = TEST_IMAGE[mimeType];
|
|
92
|
+
return dataURL ? await testBrowserImageFormatSupportAsync(dataURL) : true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks browser synchronously
|
|
97
|
+
* Checks if toDataURL supports the mimeType.
|
|
98
|
+
* @note Imperfect testOn Chrome this is true for WebP but not for AVIF
|
|
99
|
+
*/
|
|
100
|
+
function testBrowserImageFormatSupport(mimeType: string): boolean {
|
|
43
101
|
try {
|
|
44
102
|
const element = document.createElement('canvas');
|
|
45
|
-
|
|
103
|
+
const dataURL = element.toDataURL(mimeType);
|
|
104
|
+
return dataURL.indexOf(`data:${mimeType}`) === 0;
|
|
46
105
|
} catch {
|
|
47
106
|
// Probably Safari...
|
|
48
107
|
return false;
|
|
49
108
|
}
|
|
50
109
|
}
|
|
51
110
|
|
|
52
|
-
// Note: better test but asynchronous
|
|
53
|
-
|
|
54
|
-
// Lossy test image. Support for lossy images doesn't guarantee support for all WebP images.
|
|
55
|
-
// https://stackoverflow.com/questions/5573096/detecting-webp-support
|
|
56
|
-
// const WEBP_TEST_IMAGE = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';
|
|
57
|
-
|
|
58
111
|
// Check WebPSupport asynchronously
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
112
|
+
async function testBrowserImageFormatSupportAsync(testImageDataURL: string): Promise<boolean> {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
const image = new Image();
|
|
115
|
+
image.src = testImageDataURL;
|
|
116
|
+
image.onload = () => resolve(image.height > 0);
|
|
117
|
+
image.onerror = () => resolve(false);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// loaders.gl, MIT license
|
|
2
|
+
// code adapted from https://github.com/sindresorhus/file-type under MIT license
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Box is a container format that can contain a variety of media related files,
|
|
6
|
+
* so we want to return information about which type of file is actually contained inside
|
|
7
|
+
*/
|
|
8
|
+
export type BoxFileType = {extension: string; mimeType: string};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests if a buffer is in ISO base media file format (ISOBMFF) @see https://en.wikipedia.org/wiki/ISO_base_media_file_format
|
|
12
|
+
* (ISOBMFF is a media container standard based on the Apple QuickTime container format)
|
|
13
|
+
*/
|
|
14
|
+
export function getISOBMFFMediaType(buffer: Uint8Array): BoxFileType | null {
|
|
15
|
+
// Almost all ISO base media files start with `ftyp` box. (It's not required to be first, but it's recommended to be.)
|
|
16
|
+
if (!checkString(buffer, 'ftyp', 4)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Extra check: test for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character).
|
|
21
|
+
if ((buffer[8] & 0x60) === 0x00) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters.
|
|
26
|
+
return decodeMajorBrand(buffer);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* brands explained @see https://github.com/strukturag/libheif/issues/83
|
|
31
|
+
* code adapted from @see https://github.com/sindresorhus/file-type/blob/main/core.js#L489-L492
|
|
32
|
+
*/
|
|
33
|
+
export function decodeMajorBrand(buffer: Uint8Array): BoxFileType | null {
|
|
34
|
+
const brandMajor = getUTF8String(buffer, 8, 12).replace('\0', ' ').trim();
|
|
35
|
+
|
|
36
|
+
switch (brandMajor) {
|
|
37
|
+
case 'avif':
|
|
38
|
+
case 'avis':
|
|
39
|
+
return {extension: 'avif', mimeType: 'image/avif'};
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// We don't need these now, but they are easy to add
|
|
44
|
+
// case 'mif1':
|
|
45
|
+
// return {extension: 'heic', mimeType: 'image/heif'};
|
|
46
|
+
// case 'msf1':
|
|
47
|
+
// return {extension: 'heic', mimeType: 'image/heif-sequence'};
|
|
48
|
+
// case 'heic':
|
|
49
|
+
// case 'heix':
|
|
50
|
+
// return {extension: 'heic', mimeType: 'image/heic'};
|
|
51
|
+
// case 'hevc':
|
|
52
|
+
// case 'hevx':
|
|
53
|
+
// return {extension: 'heic', mimeType: 'image/heic-sequence'};
|
|
54
|
+
// case 'qt':
|
|
55
|
+
// return {ext: 'mov', mime: 'video/quicktime'};
|
|
56
|
+
// case 'M4V':
|
|
57
|
+
// case 'M4VH':
|
|
58
|
+
// case 'M4VP':
|
|
59
|
+
// return {ext: 'm4v', mime: 'video/x-m4v'};
|
|
60
|
+
// case 'M4P':
|
|
61
|
+
// return {ext: 'm4p', mime: 'video/mp4'};
|
|
62
|
+
// case 'M4B':
|
|
63
|
+
// return {ext: 'm4b', mime: 'audio/mp4'};
|
|
64
|
+
// case 'M4A':
|
|
65
|
+
// return {ext: 'm4a', mime: 'audio/x-m4a'};
|
|
66
|
+
// case 'F4V':
|
|
67
|
+
// return {ext: 'f4v', mime: 'video/mp4'};
|
|
68
|
+
// case 'F4P':
|
|
69
|
+
// return {ext: 'f4p', mime: 'video/mp4'};
|
|
70
|
+
// case 'F4A':
|
|
71
|
+
// return {ext: 'f4a', mime: 'audio/mp4'};
|
|
72
|
+
// case 'F4B':
|
|
73
|
+
// return {ext: 'f4b', mime: 'audio/mp4'};
|
|
74
|
+
// case 'crx':
|
|
75
|
+
// return {ext: 'cr3', mime: 'image/x-canon-cr3'};
|
|
76
|
+
// default:
|
|
77
|
+
// if (brandMajor.startsWith('3g')) {
|
|
78
|
+
// if (brandMajor.startsWith('3g2')) {
|
|
79
|
+
// return {ext: '3g2', mime: 'video/3gpp2'};
|
|
80
|
+
// }
|
|
81
|
+
// return {ext: '3gp', mime: 'video/3gpp'};
|
|
82
|
+
// }
|
|
83
|
+
// return {ext: 'mp4', mime: 'video/mp4'};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Interpret a chunk of bytes as a UTF8 string */
|
|
87
|
+
function getUTF8String(array: Uint8Array, start: number, end: number): string {
|
|
88
|
+
return String.fromCharCode(...array.slice(start, end));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stringToBytes(string: string): number[] {
|
|
92
|
+
return [...string].map((character) => character.charCodeAt(0));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkString(buffer: ArrayLike<number>, header: string, offset: number = 0): boolean {
|
|
96
|
+
const headerBytes = stringToBytes(header);
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < headerBytes.length; ++i) {
|
|
99
|
+
if (headerBytes[i] !== buffer[i + offset]) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return true;
|
|
105
|
+
}
|