ImageBitmapLoader.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { Cache } from './Cache.js';
  2. import { Loader } from './Loader.js';
  3. import { warn } from '../utils.js';
  4. const _errorMap = new WeakMap();
  5. /**
  6. * A loader for loading images as an [ImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmap).
  7. * An `ImageBitmap` provides an asynchronous and resource efficient pathway to prepare
  8. * textures for rendering.
  9. *
  10. * Note that {@link Texture#flipY} and {@link Texture#premultiplyAlpha} are ignored with image bitmaps.
  11. * These options need to be configured via {@link ImageBitmapLoader#setOptions} prior to loading,
  12. * unlike regular images which can be configured on the Texture to set these options on GPU upload instead.
  13. *
  14. * To match the default behaviour of {@link Texture}, the following options are needed:
  15. *
  16. * ```js
  17. * { imageOrientation: 'flipY', premultiplyAlpha: 'none' }
  18. * ```
  19. *
  20. * Also note that unlike {@link FileLoader}, this loader will only avoid multiple concurrent requests to the same URL if {@link Cache} is enabled.
  21. *
  22. * ```js
  23. * const loader = new THREE.ImageBitmapLoader();
  24. * loader.setOptions( { imageOrientation: 'flipY' } ); // set options if needed
  25. * const imageBitmap = await loader.loadAsync( 'image.png' );
  26. *
  27. * const texture = new THREE.Texture( imageBitmap );
  28. * texture.needsUpdate = true;
  29. * ```
  30. *
  31. * @augments Loader
  32. */
  33. class ImageBitmapLoader extends Loader {
  34. /**
  35. * Constructs a new image bitmap loader.
  36. *
  37. * @param {LoadingManager} [manager] - The loading manager.
  38. */
  39. constructor( manager ) {
  40. super( manager );
  41. /**
  42. * This flag can be used for type testing.
  43. *
  44. * @type {boolean}
  45. * @readonly
  46. * @default true
  47. */
  48. this.isImageBitmapLoader = true;
  49. if ( typeof createImageBitmap === 'undefined' ) {
  50. warn( 'ImageBitmapLoader: createImageBitmap() not supported.' );
  51. }
  52. if ( typeof fetch === 'undefined' ) {
  53. warn( 'ImageBitmapLoader: fetch() not supported.' );
  54. }
  55. /**
  56. * Represents the loader options.
  57. *
  58. * @type {Object}
  59. * @default {premultiplyAlpha:'none'}
  60. */
  61. this.options = { premultiplyAlpha: 'none' };
  62. /**
  63. * Used for aborting requests.
  64. *
  65. * @private
  66. * @type {AbortController}
  67. */
  68. this._abortController = new AbortController();
  69. }
  70. /**
  71. * Sets the given loader options. The structure of the object must match the `options` parameter of
  72. * [createImageBitmap](https://developer.mozilla.org/en-US/docs/Web/API/Window/createImageBitmap).
  73. *
  74. * Note: When caching is enabled, the cache key is based on the URL only. Loading the same URL with
  75. * different options will return the cached result of the first request.
  76. *
  77. * @param {Object} options - The loader options to set.
  78. * @return {ImageBitmapLoader} A reference to this image bitmap loader.
  79. */
  80. setOptions( options ) {
  81. this.options = options;
  82. return this;
  83. }
  84. /**
  85. * Starts loading from the given URL and pass the loaded image bitmap to the `onLoad()` callback.
  86. *
  87. * @param {string} url - The path/URL of the file to be loaded. This can also be a data URI.
  88. * @param {function(ImageBitmap)} onLoad - Executed when the loading process has been finished.
  89. * @param {onProgressCallback} onProgress - Unsupported in this loader.
  90. * @param {onErrorCallback} onError - Executed when errors occur.
  91. * @return {ImageBitmap|undefined} The image bitmap.
  92. */
  93. load( url, onLoad, onProgress, onError ) {
  94. if ( url === undefined ) url = '';
  95. if ( this.path !== undefined ) url = this.path + url;
  96. url = this.manager.resolveURL( url );
  97. const scope = this;
  98. const cached = Cache.get( `image-bitmap:${url}` );
  99. if ( cached !== undefined ) {
  100. scope.manager.itemStart( url );
  101. // If cached is a promise, wait for it to resolve
  102. if ( cached.then ) {
  103. cached.then( imageBitmap => {
  104. // check if there is an error for the cached promise
  105. if ( _errorMap.has( cached ) === true ) {
  106. if ( onError ) onError( _errorMap.get( cached ) );
  107. scope.manager.itemError( url );
  108. scope.manager.itemEnd( url );
  109. } else {
  110. if ( onLoad ) onLoad( imageBitmap );
  111. scope.manager.itemEnd( url );
  112. return imageBitmap;
  113. }
  114. } );
  115. return;
  116. }
  117. // If cached is not a promise (i.e., it's already an imageBitmap)
  118. setTimeout( function () {
  119. if ( onLoad ) onLoad( cached );
  120. scope.manager.itemEnd( url );
  121. }, 0 );
  122. return cached;
  123. }
  124. const fetchOptions = {};
  125. fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
  126. fetchOptions.headers = this.requestHeader;
  127. fetchOptions.signal = ( typeof AbortSignal.any === 'function' ) ? AbortSignal.any( [ this._abortController.signal, this.manager.abortController.signal ] ) : this._abortController.signal;
  128. const promise = fetch( url, fetchOptions ).then( function ( res ) {
  129. return res.blob();
  130. } ).then( function ( blob ) {
  131. return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) );
  132. } ).then( function ( imageBitmap ) {
  133. Cache.add( `image-bitmap:${url}`, imageBitmap );
  134. if ( onLoad ) onLoad( imageBitmap );
  135. scope.manager.itemEnd( url );
  136. return imageBitmap;
  137. } ).catch( function ( e ) {
  138. if ( onError ) onError( e );
  139. _errorMap.set( promise, e );
  140. Cache.remove( `image-bitmap:${url}` );
  141. scope.manager.itemError( url );
  142. scope.manager.itemEnd( url );
  143. } );
  144. Cache.add( `image-bitmap:${url}`, promise );
  145. scope.manager.itemStart( url );
  146. }
  147. /**
  148. * Aborts ongoing fetch requests.
  149. *
  150. * @return {ImageBitmapLoader} A reference to this instance.
  151. */
  152. abort() {
  153. this._abortController.abort();
  154. this._abortController = new AbortController();
  155. return this;
  156. }
  157. }
  158. export { ImageBitmapLoader };
粤ICP备19079148号