├── .gitattributes
├── .gitignore
├── src
├── native-func.js
├── image-loaders.js
├── ImageBitmap-wrapper.js
├── constants.js
├── options-parser.js
├── check-usability.js
├── misc.js
├── monkeypatch.js
├── image-cropper.js
└── test-support.js
├── LICENSE
├── README.md
└── dist
└── createImageBitmap.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | package-lock.json
3 | .DS_Store
4 | webpack.config.js
5 | package.json
6 |
--------------------------------------------------------------------------------
/src/native-func.js:
--------------------------------------------------------------------------------
1 | const native_func = globalThis.createImageBitmap;
2 | const nativeCreateImageBitmap = native_func ?
3 | (...args) => native_func.call( globalThis, ...args ) : false;
4 |
5 | export { nativeCreateImageBitmap };
6 |
--------------------------------------------------------------------------------
/src/image-loaders.js:
--------------------------------------------------------------------------------
1 | import { XLINK_NS, error_messages } from "./constants.js";
2 | import { checkUsability } from "./check-usability.js";
3 | function loadImage( url ) {
4 | const img = new Image();
5 | img.src = url;
6 | return new Promise( (resolve, reject) => {
7 | img.onload = (evt) => {
8 | // might be broken
9 | checkUsability( img );
10 | resolve( img );
11 | };
12 | img.onerror = (evt) => {
13 | const error = new DOMException(
14 | error_messages.INVALID_STATE_IMAGE,
15 | "InvalidStateError"
16 | );
17 | reject( error );
18 | };
19 | } );
20 | }
21 | function loadSVGImage( elem ) {
22 | const url = elem.getAttribute( "href" ) || elem.getAttributeNS( XLINK_NS, "href" );
23 | return loadImage( url );
24 | }
25 |
26 | export { loadImage, loadSVGImage };
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Kaiido
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/ImageBitmap-wrapper.js:
--------------------------------------------------------------------------------
1 | import { cropImage } from "./image-cropper.js";
2 | import { nativeCreateImageBitmap } from "./native-func.js";
3 |
4 | const getCanvasProtoDesc = ( prop ) =>
5 | Object.getOwnPropertyDescriptor( HTMLCanvasElement.prototype, prop );
6 |
7 | // we can't use the 'class' syntax here
8 | // see https://esdiscuss.org/topic/classes-and-enumerability
9 | function ImageBitmapPolyfill() {
10 | throw new TypeError( "Illegal Constructor" );
11 | }
12 | ImageBitmapPolyfill.prototype = Object.create( {
13 | close() {
14 | // there is no real way to "detach" a canvas's buffer
15 | // after a drawing context has been created.
16 | // the closest is to set its width and height to 0
17 | const width_setter = getCanvasProtoDesc( "width" ).set;
18 | width_setter.call( this, 0);
19 | const height_setter = getCanvasProtoDesc( "height" ).set;
20 | height_setter.call( this, 0 );
21 | },
22 | get width() {
23 | const canvas_getter = getCanvasProtoDesc( "width" ).get;
24 | return canvas_getter.call( this );
25 | },
26 | get height() {
27 | const canvas_getter = getCanvasProtoDesc( "height" ).get;
28 | return canvas_getter.call( this );
29 | },
30 | get [ Symbol.toStringTag ]() {
31 | return "ImageBitmap";
32 | }
33 | } );
34 |
35 | function customWrapper( source ) {
36 | if( !(source instanceof HTMLCanvasElement) ) {
37 | source = cropImage( source );
38 | }
39 | Object.setPrototypeOf( source, ImageBitmapPolyfill.prototype );
40 | return source;
41 | }
42 |
43 | function convertToImageBitmap( source ) {
44 | if( (source instanceof globalThis.ImageBitmap) ) {
45 | return source;
46 | }
47 | if( nativeCreateImageBitmap ) {
48 | return nativeCreateImageBitmap( source );
49 | }
50 | return customWrapper( source );
51 | }
52 |
53 | export { convertToImageBitmap, ImageBitmapPolyfill };
54 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | // the properties we look for
2 | // the ones that aren't real property names are prefixed with a _
3 | // they are separated in two lists
4 | // so we can perform synchronous and async tests separately
5 | const synchronous_polyfills = [
6 | "_imageBitmapOptions",
7 | "resizeWidth",
8 | "resizeHeight",
9 | "resizeQuality",
10 | "imageOrientation"
11 | ]
12 | const asynchronous_polyfills = [
13 | "_Blob",
14 | "_ImageData",
15 | "_SVGBlob",
16 | "_SVGImageElement",
17 | ];
18 | // imageBitmapOptions properties we know of,
19 | // we don't necessarily have a polyfill for all of these
20 | // we still test them anyway
21 | const known_properties = [
22 | "resizeWidth",
23 | "resizeHeight",
24 | "resizeQuality",
25 | "imageOrientation",
26 | "premultiplyAlpha",
27 | "colorSpaceConversion"
28 | ];
29 | // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#imagebitmap
30 | const enums = {
31 | ResizeQuality: [ "pixelated", "low", "medium", "high" ],
32 | ImageOrientation: [ "none", "flipY" ]
33 | };
34 |
35 | const error_messages = {
36 | COMMON_HEADER: "Failed to execute 'createImageBitmap': ",
37 | INVALID_STATE_IMAGE: "Provided image was in an invalid state.",
38 | ARGUMENT_COUNT_1: "At least one argument is required.",
39 | ARGUMENT_COUNT_N: "%s is not a valid argument count for any overload",
40 | CROP_RECT_ZERO: "The crop rect width passed to createImageBitmap must be nonzero",
41 | ALLOCATION_FAILED: "The ImageBitmap could not be allocated.",
42 | INVALID_SOURCE: "Argument 1 could not be converted to any of: HTMLImageElement, " +
43 | "SVGImageElement, HTMLCanvasElement, HTMLVideoElement, ImageBitmap, Blob, " +
44 | "CanvasRenderingContext2D, ImageData.",
45 | ENUM: "'%v' is not a valid value for enumeration %e",
46 | ALLOCATION: "The ImageBitmap couldn't be allocated."
47 | }
48 |
49 | const canvas_sources = [
50 | "HTMLImageElement",
51 | "SVGImageElement",
52 | "HTMLVideoElement",
53 | "HTMLCanvasElement",
54 | "OffscreenCanvas",
55 | "ImageBitmap"
56 | ];
57 |
58 | const SVG_MIME = "image/svg+xml";
59 | const SVG_NS = "http://www.w3.org/2000/svg";
60 | const XLINK_NS = "http://www.w3.org/1999/xlink";
61 | const GIF_URL = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
62 |
63 | export {
64 | synchronous_polyfills,
65 | asynchronous_polyfills,
66 | enums,
67 | known_properties,
68 | error_messages,
69 | canvas_sources,
70 | SVG_MIME,
71 | SVG_NS,
72 | XLINK_NS,
73 | GIF_URL
74 | };
75 |
--------------------------------------------------------------------------------
/src/options-parser.js:
--------------------------------------------------------------------------------
1 | import { convertToLong, enforceRangeLong } from "./misc.js";
2 | import { enums, error_messages } from "./constants.js";
3 |
4 | const common_error_header = error_messages.COMMON_HEADER;
5 |
6 | function testEnum( options, property, enum_name ) {
7 | const enum_list = enums[ enum_name ];
8 | const value = options[ property ];
9 | if( value !== undefined && !enum_list.includes( value ) ) {
10 | const error_msg = error_messages.ENUM
11 | .replace( "%v", value )
12 | .replace( "%e", enum_name );
13 | throw new TypeError( error_msg );
14 | }
15 | }
16 | function parseOptions( source, ...args ) {
17 |
18 | let [ sx, sy, sw, sh ] = args.map( convertToLong );
19 | let cropRect; // optional
20 | const options = (args.length === 1 ? args[ 0 ] : args[ 4 ]) || {};
21 |
22 | const resizeWidth = ("resizeWidth" in options) &&
23 | enforceRangeLong( options.resizeWidth );
24 | const resizeHeight = ("resizeHeight" in options) &&
25 | enforceRangeLong( options.resizeHeight );
26 |
27 | testEnum( options, "resizeQuality", "ResizeQuality" );
28 | testEnum( options, "imageOrientation", "ImageOrientation" );
29 |
30 | const arguments_count = arguments.length;
31 |
32 | if( !arguments_count ) {
33 | throw new TypeError( common_error_header + error_messages.ARGUMENT_COUNT_1 );
34 | }
35 | if( arguments_count > 2 && arguments_count < 5 ) {
36 | const error_count_msg = error_messages.ARGUMENT_COUNT_N.replace( "%s", arguments_count );
37 | throw new TypeError( common_error_header + error_count_msg );
38 | }
39 | if( arguments_count >= 5 ) { // 'sw' and 'sh' are defined
40 | if( !sw || !sh ) { // convertToLong converts invalid longs to NaN
41 | throw new RangeError( common_error_header + error_messages.CROP_RECT_ZERO );
42 | }
43 | // invalid sx and sy are treated as 0
44 | sx = sx || 0;
45 | sy = sy || 0;
46 | cropRect = { sx, sy, sw, sh };
47 | }
48 | if( resizeWidth === 0 || resizeHeight === 0 ) {
49 | const message = common_error_header + error_messages.ALLOCATION_FAILED;
50 | throw new DOMException( message, "InvalidStateError" );
51 | }
52 |
53 | return {
54 | cropRect,
55 | ...options
56 | };
57 |
58 | }
59 | function cleanArguments( missing_features, ...args ) {
60 | const number_of_arguments = args.length;
61 | if(
62 | missing_features.has( "_imageBitmapOptions" ) &&
63 | [ 2, 6 ].includes( number_of_arguments )
64 | ) {
65 | return args.slice( 0, number_of_arguments - 1 );
66 | }
67 | return args;
68 | }
69 |
70 | export { parseOptions, cleanArguments };
71 |
--------------------------------------------------------------------------------
/src/check-usability.js:
--------------------------------------------------------------------------------
1 | import { error_messages } from "./constants.js";
2 | import { get2dContext, buildSourceIs } from "./misc.js";
3 | import { getSourceDimensions } from "./image-cropper.js";
4 |
5 | function invalidStateImageError() {
6 | const message = error_messages.COMMON_HEADER + error_messages.INVALID_STATE_IMAGE;
7 | throw new DOMException( message, "InvalidStateError" );
8 | }
9 | //
10 | // https://html.spec.whatwg.org/multipage/canvas.html#check-the-usability-of-the-image-argument
11 | //
12 | function checkUsability( source ) {
13 |
14 | const sourceIs = buildSourceIs( source );
15 |
16 | // HTMLCanvasElement, and OffscreenCanvas are only invalid if their width or height is zero
17 | // ImageBitmap would be when detached, but detaching it will set its width and height to zero
18 | // so we can share their check
19 | if(
20 | sourceIs( "HTMLCanvasElement" ) ||
21 | sourceIs( "OffscreenCanvas" ) ||
22 | sourceIs( "ImageBitmap" )
23 | ) {
24 |
25 | if( source.width === 0 || source.height === 0 ) {
26 | invalidStateImageError();
27 | }
28 |
29 | }
30 | // there is no synchronous way to tell if an HTMLImageElement
31 | // is in a broken state, such an element could have a width and height
32 | // at the time we check it
33 | // HTMLImageElement::decode() could tell this asynchronously
34 | // but 1. we need a synchrounous method, 2. it's not available everywhere
35 | // and 3. it only concerns HTMLImageElement
36 | // SVGImageElements don't have any API letting us know (not even a width nor height)
37 | // HTMLVideoElements only have unreliable videoWidth and videoHeight
38 | // however, CanvasRenderingContext2d.createPattern is supposed to call this algorithm too
39 | // so we can (ab)use it, even though it works only in Firefox...
40 | // see https://wpt.fyi/results/html/canvas/element/fill-and-stroke-styles/2d.pattern.image.broken.html
41 | else if(
42 | sourceIs( "HTMLImageElement" ) ||
43 | sourceIs( "HTMLVideoElement" ) ||
44 | sourceIs( "SVGImageElement" )
45 | ) {
46 |
47 | // we can still start by checking the size, if 0 no need to go farther
48 | const { width, height } = getSourceDimensions( source );
49 | // look for strict equality, SVGImageElement returns NaN
50 | if( width === 0 || height === 0 ) {
51 | invalidStateImageError();
52 | }
53 |
54 | let failed = false;
55 | try {
56 | const pat = get2dContext( 0, 0 ).createPattern( source, "no-repeat" );
57 | failed = !pat;
58 | }
59 | catch( err ) {
60 | // Safari doesn't support drawing SVGImageElement on a canvas,
61 | // nor do they support creating a CanvasPattern from it
62 | // we thus return a "maybe" string to let the other side handle it as they wish
63 | return "maybe";
64 | }
65 | if( failed ) {
66 | invalidStateImageError();
67 | }
68 |
69 | }
70 |
71 | return true;
72 |
73 | }
74 |
75 | export { checkUsability, invalidStateImageError };
76 |
--------------------------------------------------------------------------------
/src/misc.js:
--------------------------------------------------------------------------------
1 | import { error_messages } from "./constants.js";
2 |
3 | // we could be lazy an just check for length 0,
4 | // which would probably be enough in 99% of the case
5 | // but we won't call it many times anyway,
6 | // so better be precise
7 | function isDetachedBuffer( buffer ) {
8 | try {
9 | buffer.slice( 0,0 );
10 | return false;
11 | }
12 | catch( err ) { return true; }
13 | }
14 |
15 | // https://heycam.github.io/webidl/#idl-long
16 | const long_tester = new Int32Array( 1 );
17 | function convertToLong( value ) {
18 | if( !Number.isFinite( value ) ) {
19 | return NaN;
20 | }
21 | long_tester[ 0 ] = value;
22 | return long_tester[ 0 ];
23 | }
24 | // https://heycam.github.io/webidl/#EnforceRange
25 | function enforceRangeLong( value ) {
26 | const as_long = convertToLong( value );
27 | if( isNaN( as_long ) ) {
28 | throw new TypeError( "Invalid long value" );
29 | }
30 | return as_long;
31 | }
32 | // we can be quite strict here
33 | // because to load it in a
,
34 | // the type must be set to this value
35 | function blobCanBeSVG( blob ) {
36 | return blob && blob.type === "image/svg+xml";
37 | }
38 |
39 | function get2dContext( width, height ) {
40 | const canvas = Object.assign( document.createElement( "canvas" ), { width, height } );
41 | const ctx = canvas.getContext( "2d" );
42 | // not yet in the specs
43 | // https://github.com/whatwg/html/issues/3323
44 | if(
45 | width && height && // 0 width and height should not throw here
46 | !contextIsCorrectlyAllocated( ctx )
47 | ) {
48 | throw new DOMException( error_messages.ALLOCATION, "InvalidStateError" );
49 | }
50 | return ctx;
51 | }
52 |
53 | function contextIsCorrectlyAllocated( ctx ) {
54 | // https://github.com/fserb/canvas2D/blob/master/spec/context-loss.md
55 | if( ctx.isContextLost ) {
56 | // weirdness in the API,
57 | // we need to interact with the context for the status to change
58 | ctx.translate( 0, 0 );
59 | return !ctx.isContextLost();
60 | }
61 | // Chrome has a isContextLost method, but it seems to only work async...
62 | let allocated = false;
63 | // Firefox does throw if the context is lost when we call getImageData
64 | try {
65 | ctx.fillRect( 0, 0, 1, 1 );
66 | allocated = ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] !== 0;
67 | }
68 | finally {
69 | ctx.clearRect( 0, 0, 1, 1 );
70 | return allocated;
71 | }
72 | }
73 |
74 | // ImageData constructor may not be available
75 | // fortunately we can fall back to ctx2D.createImageData
76 | function createImageData( width, height ) {
77 | try {
78 | // low finger-print if available
79 | return new ImageData( width, height );
80 | }
81 | catch( err ) {
82 | const context = create2dContext( 0, 0 );
83 | return context.createImageData( width, height );
84 | }
85 | }
86 |
87 | // a helper to build a 'sourceIs' function
88 | function buildSourceIs( source ) {
89 | return ( type ) => {
90 | const constructor = globalThis[ type ];
91 | return constructor && (source instanceof constructor);
92 | };
93 | }
94 |
95 | export {
96 | isDetachedBuffer,
97 | convertToLong,
98 | enforceRangeLong,
99 | blobCanBeSVG,
100 | get2dContext,
101 | createImageData,
102 | buildSourceIs
103 | };
104 |
--------------------------------------------------------------------------------
/src/monkeypatch.js:
--------------------------------------------------------------------------------
1 | import {
2 | requiresPolyfill,
3 | requiresAsyncTests,
4 | tests_results,
5 | async_tests
6 | } from "./test-support.js";
7 | import { checkUsability, invalidStateImageError } from "./check-usability.js";
8 | import { parseOptions, cleanArguments } from "./options-parser.js";
9 | import { nativeCreateImageBitmap } from "./native-func.js";
10 | import { canvas_sources, error_messages } from "./constants.js";
11 | import { loadImage, loadSVGImage } from "./image-loaders.js";
12 | import { cropImage, cropBlobImage, cropImageData } from "./image-cropper.js";
13 | import { convertToImageBitmap, ImageBitmapPolyfill } from "./ImageBitmap-wrapper.js";
14 | import { isDetachedBuffer, buildSourceIs } from "./misc.js";
15 |
16 | if( !nativeCreateImageBitmap ) {
17 | globalThis.ImageBitmap = ImageBitmapPolyfill;
18 | }
19 |
20 | // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-createimagebitmap
21 | globalThis.createImageBitmap = async function( source, ...args ) { // length 1
22 |
23 | const options = parseOptions( ...arguments );
24 |
25 | // with some sources (canvas & video) we need to stay synchronous until
26 | // the source's buffer gets painted
27 | // but some tests are async (e.g SVGImageElement)
28 | // so we only await for these tests when we are sure they are required
29 | const missing_features = requiresAsyncTests( source, options ) ?
30 | await async_tests : tests_results;
31 |
32 | // full compat
33 | if( missing_features.size === 0 ) {
34 | return nativeCreateImageBitmap( ...arguments );
35 | }
36 |
37 | // polyfill is not required, but some features may not be supported
38 | if(
39 | nativeCreateImageBitmap &&
40 | !requiresPolyfill( missing_features, options, source )
41 | ) {
42 | return nativeCreateImageBitmap( ...cleanArguments( missing_features, ...arguments ) );
43 | }
44 |
45 | const sourceIs = buildSourceIs( source );
46 |
47 | let cropped_image;
48 | if( canvas_sources.some( sourceIs ) ) {
49 |
50 | const is_usable = checkUsability( source );
51 |
52 | // checkUsability failed to determine if source is usable,
53 | // as of this writing, this only concerns SVGImageElement
54 | // and since we do convert SVGImageElements anyway right after
55 | // we don't have much to do now
56 | if( is_usable === "maybe" ) {
57 | // might want to do something someday.
58 | }
59 |
60 | // SVGImageElement have a very poor API to work with
61 | // (e.g no intrinsic dimension is exposed)
62 | // so we convert these to HTMLImageElement
63 | // must be called after 'checkUsability' for we correctly throw with unusable media
64 | // (that is, for UAs that do support SVGImageElement as CanvasSource)
65 | if( sourceIs( "SVGImageElement" ) ) {
66 | source = await loadSVGImage( source );
67 | }
68 |
69 | if( sourceIs( "HTMLImageElement" ) ) {
70 | // Since we can't detect true "image has no dimensions" (see test-support.js)
71 | // we unfortunately can't fall here...
72 | // so while per specs this should work, it's just dead code...
73 | if(
74 | (!source.naturalWidth && !source.naturalHeight) &&
75 | (!options.resizeWidth || !options.resizeHeight)
76 | ) {
77 | invalidStateImageError()
78 | }
79 |
80 | }
81 | cropped_image = cropImage( source, options );
82 |
83 | }
84 | else if( sourceIs( "Blob" ) ) {
85 | cropped_image = await cropBlobImage( source, options );
86 | }
87 | else if( sourceIs( "ImageData" ) ) {
88 | if( isDetachedBuffer( source.data.buffer ) ) {
89 | invalidStateImageError();
90 | }
91 | cropped_image = await cropImageData( source, options );
92 | }
93 | if( !cropped_image ) {
94 | throw new TypeError( error_messages.INVALID_SOURCE );
95 | }
96 |
97 | return await convertToImageBitmap( cropped_image );
98 |
99 | };
100 |
101 | globalThis.createImageBitmap._original = nativeCreateImageBitmap;
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # createImageBitmap MonkeyPatch
2 |
3 | ### A monkey-patch to the HTML's [`createImageBitmap`][1] method.
4 |
5 | Only Chromium based browsers currently support the full API, Firefox supporting it only partially and Safari not at all.
6 | This means that for cross-browser projects, we can't use it, despite its great usefulness and ease of use.
7 |
8 | This monkey-patch aims at filling as much gaps as possible between all these browsers.
9 |
10 | However, truth be told, it can not polyfill everything in this API, here is a list of what it supports in every browsers and what it doesn't:
11 |
12 | Feature | ![Chromium based][2] | ![Firefox][3] | ![Safari][4]
13 | ----------------------------------------- | -------------------- | --------------------- | ----------------
14 | createImageBitmap + cropping | native | native | ✔ (1)
15 | HTMLImageElement as source | native | native | ✔
16 | HTMLCanvasElement as source | native | native | ✔
17 | OffscreenCanvas as source | native | :x: | :x:
18 | SVGImageElement as source | native | native | ✔
19 | ImageBitmap as source | native | native | ✔
20 | ImageData as source | native | native | ✔
21 | Raster image in Blob as source | native | native | ✔
22 | SVG image in Blob as source (2)| ✔ | ✔ | ✔
23 | ImageBitmapOptions | native | ✔ | ✔
24 | resizeWidth & resizeHeight | native | ✔ | ✔
25 | resizeQuality: pixelated | native | ✔ | ✔
26 | resizeQuality: low | native | native | ✔
27 | resizeQuality: medium | native | :x: | :x:
28 | resizeQuality: high | native | :x: | :x:
29 | imageOrientation | native | ✔ | ✔
30 | colorSpaceConversion | native | :x: | :x:
31 | premultiplyAlpha | native | :x: | :x:
32 | Available in Workers | native | native (3) | :x:
33 | Transferrable | native | native | :x:
34 |
35 | (1) Returns a modified HTMLCanvasElement instance in place of an ImageBitmap object.
36 | (2) Only for SVG images with an intrinsic width and height, as per the specs recommendation.
37 | (3) Only the native implementation is available in Workers
38 |
39 | ### How to use
40 |
41 | You can include the bundled version available in `/src/` in a <script> tag before the first use of `createImageBitmap`,
42 |
43 | ```html
44 |
45 | ```
46 |
47 | or you can use the ESM modules available in `/src/`.
48 |
49 | ```html
50 |
51 | ```
52 |
53 | ### Why isn't the monkey-patch available in web Workers?
54 |
55 | The monkey-patch makes most of its work on an HTMLCanvasElement 2d context, and relies in various places in the ability to have DOM Elements (for instance HTMLImageElements are used to load images from Blobs).
56 | Since Web-Workers don't have access to the DOM nor any Elements, the scope of what this monkey-patch could do currently seems too limited to be worth the pain.
57 |
58 | ### Why isn't the monkey-patch available in node-js?
59 |
60 | People at [node-canvas][2] probably are the best to implement such a thing.
61 | It should take only a few tweaks from here to get a node-canvas + jsdom version working though, so feel free to fork this repo and propose them a PR if you wish.
62 |
63 | ### Why doesn't this monkey-patch add support for options like all resize qualities, *colorSpaceConversion* or *premultiplyAlpha**?
64 |
65 | Mainly lack of time. We accept PR though ¯\_(ツ)_/¯
66 |
67 | ### Why no ImageBitmapRenderer at least?
68 |
69 | Still lack of time, and a Safari weirdness where they do expose this interface, even though they don't have the `ImageBitmap` that goes with it, and which won't accept our "fake" version.
70 | But hopefully, this will be one of the first next additions.
71 |
72 | ### What about my old Internet Explorer? Can I use it there?
73 |
74 | Not as-is. The scripts are written using ES2021 syntax and are targeting recent browsers.
75 | You should be able to transpile it to ES5 though, and it should work even in IE9, **but it hasn't been tested there**.
76 |
77 | ### Does this mean even my shiny Chromium browser will use the less than optimal 2D context instead of a true ImageBitmap?
78 |
79 | No, the monkey-patch only patches what it detects is not supported by the current browser. It leaves the native implementation do its job when it can do it.
80 |
81 | [1]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
82 | [2]: https://raw.githubusercontent.com/alrra/browser-logos/main/src/chromium/chromium_48x48.png
83 | [3]: https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png
84 | [4]: https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png
85 | [5]: https://github.com/Automattic/node-canvas
86 |
--------------------------------------------------------------------------------
/src/image-cropper.js:
--------------------------------------------------------------------------------
1 | import { nativeCreateImageBitmap } from "./native-func.js";
2 | import { blobCanBeSVG, get2dContext, buildSourceIs } from "./misc.js";
3 | import { loadImage } from "./image-loaders.js";
4 | import { cleanArguments } from "./options-parser.js";
5 |
6 | function getSourceDimensions( source ) {
7 | const sourceIs = buildSourceIs( source );
8 | if( sourceIs( "HTMLImageElement" ) ) {
9 | return { width: source.naturalWidth, height: source.naturalHeight };
10 | }
11 | else if( sourceIs( "HTMLVideoElement" ) ) {
12 | return { width: source.videoWidth, height: source.videoHeight };
13 | }
14 | else if( sourceIs( "SVGImageElement" ) ) {
15 | return { width: NaN, height: NaN };
16 | }
17 | else if(
18 | sourceIs( "HTMLCanvasElement" ) ||
19 | sourceIs( "OffscreenCanvas" ) ||
20 | sourceIs( "ImageBitmap" ) ||
21 | sourceIs( "ImageData" )
22 | ) {
23 | return source;
24 | }
25 | return { width: NaN, height: NaN };
26 | }
27 |
28 | function cropImage( source, options ) {
29 |
30 | // merge passed cropRect with resize and orientation options
31 | const safe_rect = getFinalCropRect( source, options );
32 |
33 | const { height } = getSourceDimensions( source );
34 | const { sx, sy, sw, sh, dx, dy, dw, dh, flipY, resizeQuality } = safe_rect;
35 | const out_ctx = get2dContext( safe_rect.width, safe_rect.height );
36 | const out_canvas = out_ctx.canvas;
37 |
38 | if( resizeQuality === "pixelated" ) {
39 | out_ctx.imageSmoothingEnabled = false;
40 | }
41 | else if( resizeQuality ) {
42 | // to do: better image quality resizers
43 | out_ctx.imageSmoothingQuality = resizeQuality;
44 | }
45 | out_ctx.drawImage( source, sx, sy, sw, sh, dx, dy, dw, dh );
46 |
47 | if( flipY ) {
48 | out_ctx.globalCompositeOperation = "copy";
49 | out_ctx.setTransform( 1, 0, 0, -1, 0, out_canvas.height );
50 | out_ctx.drawImage( out_canvas, 0, 0 );
51 | }
52 |
53 | return out_canvas;
54 |
55 | }
56 |
57 |
58 | async function cropBlobImage( blob, options ) {
59 |
60 | const url = globalThis.URL.createObjectURL( blob );
61 | const img = await loadImage( url );
62 | globalThis.URL.revokeObjectURL( blob );
63 |
64 | return cropImage( img, options );
65 |
66 | }
67 | async function cropImageData( img, options ) {
68 |
69 | const { resizeWidth, resizeHeight } = parseResizeOptions( img, options );
70 | const { resizeQuality, imageOrientation } = options;
71 | const cropRect = options.cropRect || {};
72 | // beware, sw and sh can be negative
73 | const sx = cropRect.sx || 0;
74 | const sy = cropRect.sy || 0;
75 | const sw = cropRect.sw || img.width;
76 | const sh = cropRect.sh || img.height;
77 | const dx = sw < 0 ? (sw * -1) - sx : -sx;
78 | const dy = sh < 0 ? (sh * -1) - sy : -sy;
79 | const dw = Math.abs( sw );
80 | const dh = Math.abs( sh );
81 |
82 | const a_ctx = get2dContext( dw, dh )
83 | a_ctx.putImageData( img, dx, dy, sx, sy, sw, sh );
84 |
85 | const flipY = imageOrientation === "flipY";
86 | const should_redraw = resizeWidth || flipY;
87 | let out_ctx;
88 |
89 | if( resizeWidth ) {
90 | out_ctx = get2dContext( resizeWidth, resizeHeight );
91 | if( resizeQuality === "pixelated" ) {
92 | out_ctx.imageSmoothingEnabled = false;
93 | }
94 | else {
95 | out_ctx.imageSmoothingQuality = resizeQuality;
96 | }
97 | }
98 | else {
99 | out_ctx = a_ctx;
100 | a_ctx.globalCompositeOperation = "copy";
101 | }
102 |
103 | const a_canvas = a_ctx.canvas;
104 | const out_canvas = out_ctx.canvas;
105 |
106 | if( flipY ){
107 | out_ctx.setTransform( 1, 0, 0, -1, 0, out_canvas.height );
108 | }
109 |
110 | if( should_redraw ) {
111 | out_ctx.drawImage( a_canvas,
112 | 0, 0, a_canvas.width, a_canvas.height,
113 | 0, 0, out_canvas.width, out_canvas.height
114 | );
115 | }
116 |
117 | return out_canvas;
118 |
119 | }
120 |
121 | function getFinalCropRect( source, options ) {
122 |
123 | const { width, height } = getSourceDimensions( source );
124 | const { resizeWidth, resizeHeight } = parseResizeOptions( source, options );
125 |
126 | const crop_rect = options.cropRect ||
127 | { sx: 0, sy: 0, sw: width, sh: height };
128 | const dest_rect = {
129 | dx: 0,
130 | dy: 0,
131 | dw: resizeWidth || Math.abs( crop_rect.sw ), // resizeXXX must be non-zero
132 | dh: resizeHeight || Math.abs( crop_rect.sh )
133 | };
134 |
135 | const safe_rect = getSafeRect( width, height, crop_rect, dest_rect );
136 | safe_rect.resizeQuality = options.resizeQuality;
137 | safe_rect.flipY = options.imageOrientation === "flipY";
138 | safe_rect.width = dest_rect.dw;
139 | safe_rect.height = dest_rect.dh;
140 |
141 | return safe_rect;
142 |
143 | }
144 |
145 | function getSafeRect( width, height, { sx, sy, sw, sh }, { dx, dy, dw, dh } ) {
146 | // Safari doesn't support cropping outside of source's boundaries through drawImage
147 | // so we need to provide a "safe" rect
148 |
149 | if( sw < 0 ) {
150 | sx += sw;
151 | sw = Math.abs( sw );
152 | }
153 | if( sh < 0 ) {
154 | sy += sh;
155 | sh = Math.abs( sh );
156 | }
157 | if( dw < 0 ) {
158 | dx += dw;
159 | dw = Math.abs( dw );
160 | }
161 | if( dh < 0 ) {
162 | dy += dh;
163 | dh = Math.abs( dh );
164 | }
165 | const x1 = Math.max( sx, 0 );
166 | const x2 = Math.min( sx + sw, width );
167 | const y1 = Math.max( sy, 0 );
168 | const y2 = Math.min( sy + sh, height );
169 | const w_ratio = dw / sw;
170 | const h_ratio = dh / sh;
171 |
172 | return {
173 | sx: x1,
174 | sy: y1,
175 | sw: x2 - x1,
176 | sh: y2 - y1,
177 | dx: sx < 0 ? dx - (sx * w_ratio) : dx,
178 | dy: sy < 0 ? dy - (sy * h_ratio) : dy,
179 | dw: (x2 - x1) * w_ratio,
180 | dh: (y2 - y1) * h_ratio
181 | };
182 |
183 | }
184 |
185 | function parseResizeOptions( source, options ) {
186 |
187 | const { width, height } = getSourceDimensions( source );
188 | let { resizeWidth, resizeHeight } = options;
189 | if( resizeWidth === undefined && resizeHeight !== undefined ) {
190 | const { width, height } = getSourceDimensions( source );
191 | resizeWidth = Math.ceil( width * (resizeHeight / height) );
192 | }
193 | if( resizeHeight === undefined && resizeWidth !== undefined ) {
194 | const { width, height } = getSourceDimensions( source );
195 | resizeHeight = Math.ceil( height * (resizeWidth / width) );
196 | }
197 | return { resizeWidth, resizeHeight };
198 |
199 | }
200 |
201 | export {
202 | getSourceDimensions,
203 | cropImage,
204 | cropBlobImage,
205 | cropImageData
206 | };
207 |
--------------------------------------------------------------------------------
/src/test-support.js:
--------------------------------------------------------------------------------
1 | import {
2 | known_properties,
3 | synchronous_polyfills,
4 | asynchronous_polyfills,
5 | SVG_MIME,
6 | SVG_NS,
7 | GIF_URL
8 | } from "./constants.js";
9 | import { checkUsability } from "./check-usability.js";
10 | import { nativeCreateImageBitmap } from "./native-func.js";
11 | import { createImageData, blobCanBeSVG, buildSourceIs } from "./misc.js";
12 |
13 | // to avoid having the grabbing of the source's buffer delayed
14 | // we try to return the results of the tests synchronously when possible.
15 | // for this matter, we update a single list of results.
16 | let async_tests_are_done = false;
17 |
18 | // some tests can be performed synchronously
19 | // we initiate our Set with these directly
20 | const tests_results = performSynchronousTests();
21 | const async_tests = performAsynchronousTests()
22 |
23 | function performSynchronousTests() {
24 | // nothing to test...
25 | if( !nativeCreateImageBitmap ) {
26 | const all_polyfills = synchronous_polyfills.concat( asynchronous_polyfills );
27 | return new Set( all_polyfills );
28 | }
29 | const synchronous_tests = [ testOptionsBag() ];
30 | const supported_features = new Set( synchronous_tests.flat() );
31 | const required_polyfills = synchronous_polyfills
32 | .filter( (type) => !supported_features.has( type ) );
33 |
34 | return new Set( required_polyfills );
35 | }
36 | async function performAsynchronousTests() {
37 |
38 | // nothing to test...
39 | if( !nativeCreateImageBitmap ) {
40 | async_tests_are_done = true;
41 | return tests_results;
42 | }
43 |
44 | const asynchronous_tests = [
45 | testBitmapBlobSource(),
46 | testImageDataSource(),
47 | testSVGBlobSource(),
48 | testSVGImageElementSource()
49 | ];
50 |
51 | const results = await Promise.all( asynchronous_tests );
52 | const supported_features = new Set( results.flat() );
53 |
54 | const required_polyfills = asynchronous_polyfills
55 | .filter( (type) => !supported_features.has( type ) );
56 |
57 | required_polyfills.forEach( (prop) => tests_results.add( prop ) );
58 |
59 | async_tests_are_done = true;
60 |
61 | return tests_results;
62 |
63 | }
64 |
65 | function testOptionsBag() {
66 |
67 | const out = [];
68 | const tester = {};
69 | const addTestProperty = ( prop ) => {
70 | Object.defineProperty( tester, prop , { get(){ out.push( prop ) } } );
71 | };
72 | known_properties.forEach( addTestProperty );
73 |
74 | nativeCreateImageBitmap( new ImageData(1,1), tester )
75 | .then( (img) => img.close() )
76 | .catch( () => {} );
77 |
78 | // some implementations will throw in the Promise
79 | // if the option bag is passed
80 | // to avoid doing it ourselves when calling the native method
81 | // we need to mark its support here
82 | // however since we need this method to be synchronous
83 | // we can't wait for the Promise to reject
84 | // so we assume any implementation supporting the options bag
85 | // will support at least one of the currently known options
86 | if( out.length ) {
87 | out.push( "_imageBitmapOptions" );
88 | }
89 |
90 | return out;
91 |
92 | }
93 | async function testBitmapBlobSource() {
94 |
95 | const out = [];
96 | const blob = await fetch( GIF_URL )
97 | .then( resp => resp.ok && resp.blob() );
98 |
99 | try {
100 | const img = await nativeCreateImageBitmap( blob );
101 | if( img.width === 1 ) {
102 | out.push( "_Blob" );
103 | }
104 | }
105 | catch( err ) {
106 |
107 | }
108 |
109 | return out;
110 |
111 | }
112 | async function testImageDataSource() {
113 |
114 | const out = [];
115 | const image = createImageData( 1, 1 );
116 |
117 | try {
118 | const img = await nativeCreateImageBitmap( image );
119 | if( img.width === 1 ) {
120 | out.push( "_ImageData" );
121 | }
122 | }
123 | catch( err ) {
124 |
125 | }
126 |
127 | return out;
128 |
129 | }
130 | async function testSVGBlobSource() {
131 |
132 | const out = [];
133 | const svg_markup = ``;
136 | const svg_blob = new Blob( [ svg_markup ], { type: SVG_MIME } );
137 |
138 | try {
139 | const img = await nativeCreateImageBitmap( svg_blob );
140 | if( img.width === 1 ) {
141 | out.push( '_SVGBlob' );
142 | }
143 | }
144 | catch( err ) {
145 |
146 | }
147 |
148 | return out;
149 |
150 | }
151 | async function testSVGImageElementSource() {
152 |
153 | const out = [];
154 | if( !("SVGImageElement" in globalThis) ) { // Worker
155 | return out;
156 | }
157 |
158 | const wait = (time) => new Promise( res => setTimeout( res, time ) );
159 |
160 | const img = document.createElementNS( SVG_NS, 'image' );
161 | img.setAttributeNS( 'http://www.w3.org/1999/xlink', 'xlink:href', GIF_URL );
162 |
163 | const imgIsNotAvailable = () => {
164 | try {
165 | checkUsability( img );
166 | return false;
167 | }
168 | catch( err ) {
169 | return true;
170 | }
171 | };
172 |
173 | let retries = 0;
174 | do {
175 | await wait( 10 );
176 | }
177 | while( imgIsNotAvailable() && (++retries < 300) );
178 |
179 | try {
180 | const bmp = await nativeCreateImageBitmap( img );
181 | out.push( "_SVGImageElement" );
182 | }
183 | catch( err ) {
184 |
185 | }
186 |
187 | return out;
188 |
189 | }
190 |
191 | function requiresPolyfill( missing_features, options, source ) {
192 |
193 | const sourceIs = buildSourceIs( source );
194 |
195 | if( sourceIs( "Blob" ) ) {
196 | if( missing_features.has( "_Blob" ) ) {
197 | return true;
198 | }
199 | const is_svg_blob = blobCanBeSVG( source );
200 | if( is_svg_blob && missing_features.has( "_SVGBlob" ) ) {
201 | return true;
202 | }
203 | }
204 | if(
205 | sourceIs( "ImageData" ) &&
206 | missing_features.has( "_ImageData" )
207 | ) {
208 | return true;
209 | }
210 | if(
211 | sourceIs( "SVGImageElement" ) &&
212 | missing_features.has( "_SVGImageElement" )
213 | ) {
214 | return true;
215 | }
216 | if(
217 | Object.keys( options )
218 | .some( (key) => missing_features.has( key ) )
219 | ) {
220 | return true;
221 | }
222 |
223 | return false;
224 |
225 | }
226 |
227 | function requiresAsyncTests( source, options ) {
228 | if( async_tests_are_done ) {
229 | return false;
230 | }
231 | // async tests are currently all for different types of source
232 | // so we can currently only check for these types
233 | const sourceIs = buildSourceIs( source );
234 | const types_requiring_async_tests = [
235 | "Blob",
236 | "ImageData",
237 | "SVGImageElement"
238 | ];
239 | return types_requiring_async_tests.some( sourceIs );
240 | }
241 |
242 | export {
243 | requiresPolyfill,
244 | requiresAsyncTests,
245 | tests_results,
246 | async_tests
247 | };
248 |
--------------------------------------------------------------------------------
/dist/createImageBitmap.js:
--------------------------------------------------------------------------------
1 | (()=>{"use strict";const t=["_imageBitmapOptions","resizeWidth","resizeHeight","resizeQuality","imageOrientation"],e=["_Blob","_ImageData","_SVGBlob","_SVGImageElement"],a=["resizeWidth","resizeHeight","resizeQuality","imageOrientation","premultiplyAlpha","colorSpaceConversion"],n={ResizeQuality:["pixelated","low","medium","high"],ImageOrientation:["none","flipY"]},i={COMMON_HEADER:"Failed to execute 'createImageBitmap': ",INVALID_STATE_IMAGE:"Provided image was in an invalid state.",ARGUMENT_COUNT_1:"At least one argument is required.",ARGUMENT_COUNT_N:"%s is not a valid argument count for any overload",CROP_RECT_ZERO:"The crop rect width passed to createImageBitmap must be nonzero",ALLOCATION_FAILED:"The ImageBitmap could not be allocated.",INVALID_SOURCE:"Argument 1 could not be converted to any of: HTMLImageElement, SVGImageElement, HTMLCanvasElement, HTMLVideoElement, ImageBitmap, Blob, CanvasRenderingContext2D, ImageData.",ENUM:"'%v' is not a valid value for enumeration %e",ALLOCATION:"The ImageBitmap couldn't be allocated."},r=["HTMLImageElement","SVGImageElement","HTMLVideoElement","HTMLCanvasElement","OffscreenCanvas","ImageBitmap"],o="http://www.w3.org/2000/svg",s="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";function h(t){try{return t.slice(0,0),!1}catch(t){return!0}}const c=new Int32Array(1);function l(t){return Number.isFinite(t)?(c[0]=t,c[0]):NaN}function g(t){const e=l(t);if(isNaN(e))throw new TypeError("Invalid long value");return e}function m(t,e){const a=Object.assign(document.createElement("canvas"),{width:t,height:e}).getContext("2d");if(t&&e&&!function(t){if(t.isContextLost)return t.translate(0,0),!t.isContextLost();let e=!1;try{t.fillRect(0,0,1,1),e=0!==t.getImageData(0,0,1,1).data[3]}finally{return t.clearRect(0,0,1,1),e}}(a))throw new DOMException(i.ALLOCATION,"InvalidStateError");return a}function u(t){return e=>{const a=globalThis[e];return a&&t instanceof a}}const d=globalThis.createImageBitmap,w=!!d&&((...t)=>d.call(globalThis,...t));function f(t){const e=new Image;return e.src=t,new Promise(((t,a)=>{e.onload=a=>{N(e),t(e)},e.onerror=t=>{const e=new DOMException(i.INVALID_STATE_IMAGE,"InvalidStateError");a(e)}}))}function p(t){return f(t.getAttribute("href")||t.getAttributeNS("http://www.w3.org/1999/xlink","href"))}const I=i.COMMON_HEADER;function E(t,e,a){const r=n[a],o=t[e];if(void 0!==o&&!r.includes(o)){const t=i.ENUM.replace("%v",o).replace("%e",a);throw new TypeError(t)}}function A(t,...e){let a,[n,r,o,s]=e.map(l);const h=(1===e.length?e[0]:e[4])||{},c="resizeWidth"in h&&g(h.resizeWidth),m="resizeHeight"in h&&g(h.resizeHeight);E(h,"resizeQuality","ResizeQuality"),E(h,"imageOrientation","ImageOrientation");const u=arguments.length;if(!u)throw new TypeError(I+i.ARGUMENT_COUNT_1);if(u>2&&u<5){const t=i.ARGUMENT_COUNT_N.replace("%s",u);throw new TypeError(I+t)}if(u>=5){if(!o||!s)throw new RangeError(I+i.CROP_RECT_ZERO);n=n||0,r=r||0,a={sx:n,sy:r,sw:o,sh:s}}if(0===c||0===m){const t=I+i.ALLOCATION_FAILED;throw new DOMException(t,"InvalidStateError")}return{cropRect:a,...h}}function y(t,...e){const a=e.length;return t.has("_imageBitmapOptions")&&[2,6].includes(a)?e.slice(0,a-1):e}function T(t){const e=u(t);return e("HTMLImageElement")?{width:t.naturalWidth,height:t.naturalHeight}:e("HTMLVideoElement")?{width:t.videoWidth,height:t.videoHeight}:e("SVGImageElement")?{width:NaN,height:NaN}:e("HTMLCanvasElement")||e("OffscreenCanvas")||e("ImageBitmap")||e("ImageData")?t:{width:NaN,height:NaN}}function b(t,e){const a=function(t,e){const{width:a,height:n}=T(t),{resizeWidth:i,resizeHeight:r}=v(t,e),o=e.cropRect||{sx:0,sy:0,sw:a,sh:n},s={dx:0,dy:0,dw:i||Math.abs(o.sw),dh:r||Math.abs(o.sh)},h=function(t,e,{sx:a,sy:n,sw:i,sh:r},{dx:o,dy:s,dw:h,dh:c}){i<0&&(a+=i,i=Math.abs(i)),r<0&&(n+=r,r=Math.abs(r)),h<0&&(o+=h,h=Math.abs(h)),c<0&&(s+=c,c=Math.abs(c));const l=Math.max(a,0),g=Math.min(a+i,t),m=Math.max(n,0),u=Math.min(n+r,e),d=h/i,w=c/r;return{sx:l,sy:m,sw:g-l,sh:u-m,dx:a<0?o-a*d:o,dy:n<0?s-n*w:s,dw:(g-l)*d,dh:(u-m)*w}}(a,n,o,s);return h.resizeQuality=e.resizeQuality,h.flipY="flipY"===e.imageOrientation,h.width=s.dw,h.height=s.dh,h}(t,e),{height:n}=T(t),{sx:i,sy:r,sw:o,sh:s,dx:h,dy:c,dw:l,dh:g,flipY:u,resizeQuality:d}=a,w=m(a.width,a.height),f=w.canvas;return"pixelated"===d?w.imageSmoothingEnabled=!1:d&&(w.imageSmoothingQuality=d),w.drawImage(t,i,r,o,s,h,c,l,g),u&&(w.globalCompositeOperation="copy",w.setTransform(1,0,0,-1,0,f.height),w.drawImage(f,0,0)),f}async function O(t,e){const a=globalThis.URL.createObjectURL(t),n=await f(a);return globalThis.URL.revokeObjectURL(t),b(n,e)}async function M(t,e){const{resizeWidth:a,resizeHeight:n}=v(t,e),{resizeQuality:i,imageOrientation:r}=e,o=e.cropRect||{},s=o.sx||0,h=o.sy||0,c=o.sw||t.width,l=o.sh||t.height,g=c<0?-1*c-s:-s,u=l<0?-1*l-h:-h,d=m(Math.abs(c),Math.abs(l));d.putImageData(t,g,u,s,h,c,l);const w="flipY"===r,f=a||w;let p;a?(p=m(a,n),"pixelated"===i?p.imageSmoothingEnabled=!1:p.imageSmoothingQuality=i):(p=d,d.globalCompositeOperation="copy");const I=d.canvas,E=p.canvas;return w&&p.setTransform(1,0,0,-1,0,E.height),f&&p.drawImage(I,0,0,I.width,I.height,0,0,E.width,E.height),E}function v(t,e){const{width:a,height:n}=T(t);let{resizeWidth:i,resizeHeight:r}=e;if(void 0===i&&void 0!==r){const{width:e,height:a}=T(t);i=Math.ceil(e*(r/a))}if(void 0===r&&void 0!==i){const{width:e,height:a}=T(t);r=Math.ceil(a*(i/e))}return{resizeWidth:i,resizeHeight:r}}function _(){const t=i.COMMON_HEADER+i.INVALID_STATE_IMAGE;throw new DOMException(t,"InvalidStateError")}function N(t){const e=u(t);if(e("HTMLCanvasElement")||e("OffscreenCanvas")||e("ImageBitmap"))0!==t.width&&0!==t.height||_();else if(e("HTMLImageElement")||e("HTMLVideoElement")||e("SVGImageElement")){const{width:e,height:a}=T(t);0!==e&&0!==a||_();let n=!1;try{n=!m(0,0).createPattern(t,"no-repeat")}catch(t){return"maybe"}n&&_()}return!0}let L=!1;const S=function(){if(!w){const a=t.concat(e);return new Set(a)}const a=[B()],n=new Set(a.flat()),i=t.filter((t=>!n.has(t)));return new Set(i)}(),C=async function(){if(!w)return L=!0,S;const t=[x(),H(),D(),R()],a=await Promise.all(t),n=new Set(a.flat());return e.filter((t=>!n.has(t))).forEach((t=>S.add(t))),L=!0,S}();function B(){const t=[],e={};return a.forEach((a=>{Object.defineProperty(e,a,{get(){t.push(a)}})})),w(new ImageData(1,1),e).then((t=>t.close())).catch((()=>{})),t.length&&t.push("_imageBitmapOptions"),t}async function x(){const t=[],e=await fetch(s).then((t=>t.ok&&t.blob()));try{1===(await w(e)).width&&t.push("_Blob")}catch(t){}return t}async function H(){const t=[],e=function(t,e){try{return new ImageData(1,1)}catch(t){return create2dContext(0,0).createImageData(1,1)}}();try{1===(await w(e)).width&&t.push("_ImageData")}catch(t){}return t}async function D(){const t=[],e=new Blob([``],{type:"image/svg+xml"});try{1===(await w(e)).width&&t.push("_SVGBlob")}catch(t){}return t}async function R(){const t=[];if(!("SVGImageElement"in globalThis))return t;const e=t=>new Promise((e=>setTimeout(e,t))),a=document.createElementNS(o,"image");a.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",s);const n=()=>{try{return N(a),!1}catch(t){return!0}};let i=0;do{await e(10)}while(n()&&++i<300);try{await w(a),t.push("_SVGImageElement")}catch(t){}return t}function z(t,e,a){const n=u(a);if(n("Blob")){if(t.has("_Blob"))return!0;if((i=a)&&"image/svg+xml"===i.type&&t.has("_SVGBlob"))return!0}var i;return!(!n("ImageData")||!t.has("_ImageData"))||!(!n("SVGImageElement")||!t.has("_SVGImageElement"))||!!Object.keys(e).some((e=>t.has(e)))}function V(t,e){if(L)return!1;const a=u(t);return["Blob","ImageData","SVGImageElement"].some(a)}const G=t=>Object.getOwnPropertyDescriptor(HTMLCanvasElement.prototype,t);function U(){throw new TypeError("Illegal Constructor")}function Q(t){return t instanceof globalThis.ImageBitmap?t:w?w(t):function(t){return t instanceof HTMLCanvasElement||(t=b(t)),Object.setPrototypeOf(t,U.prototype),t}(t)}U.prototype=Object.create({close(){G("width").set.call(this,0),G("height").set.call(this,0)},get width(){return G("width").get.call(this)},get height(){return G("height").get.call(this)},get[Symbol.toStringTag](){return"ImageBitmap"}}),w||(globalThis.ImageBitmap=U),globalThis.createImageBitmap=async function(t,...e){const a=A(...arguments),n=V(t)?await C:S;if(0===n.size)return w(...arguments);if(w&&!z(n,a,t))return w(...y(n,...arguments));const o=u(t);let s;if(r.some(o)?(N(t),o("SVGImageElement")&&(t=await p(t)),o("HTMLImageElement")&&(t.naturalWidth||t.naturalHeight||a.resizeWidth&&a.resizeHeight||_()),s=b(t,a)):o("Blob")?s=await O(t,a):o("ImageData")&&(h(t.data.buffer)&&_(),s=await M(t,a)),!s)throw new TypeError(i.INVALID_SOURCE);return await Q(s)},globalThis.createImageBitmap._original=w})();
--------------------------------------------------------------------------------