智慧水务管理系统 - 精河县供水工程综合管理平台

GltfImageLoader.js 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import Check from "../Core/Check.js";
  2. import Frozen from "../Core/Frozen.js";
  3. import defined from "../Core/defined.js";
  4. import loadImageFromTypedArray from "../Core/loadImageFromTypedArray.js";
  5. import loadKTX2 from "../Core/loadKTX2.js";
  6. import RuntimeError from "../Core/RuntimeError.js";
  7. import ResourceLoader from "./ResourceLoader.js";
  8. import ResourceLoaderState from "./ResourceLoaderState.js";
  9. /**
  10. * Loads a glTF image.
  11. * <p>
  12. * Implements the {@link ResourceLoader} interface.
  13. * </p>
  14. *
  15. * @private
  16. */
  17. class GltfImageLoader extends ResourceLoader {
  18. /**
  19. * @param {object} options Object with the following properties:
  20. * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies).
  21. * @param {object} options.gltf The glTF JSON.
  22. * @param {number} options.imageId The image ID.
  23. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF.
  24. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to.
  25. * @param {string} [options.cacheKey] The cache key of the resource.
  26. */
  27. constructor(options) {
  28. super();
  29. options = options ?? Frozen.EMPTY_OBJECT;
  30. const resourceCache = options.resourceCache;
  31. const gltf = options.gltf;
  32. const imageId = options.imageId;
  33. const gltfResource = options.gltfResource;
  34. const baseResource = options.baseResource;
  35. const cacheKey = options.cacheKey;
  36. //>>includeStart('debug', pragmas.debug);
  37. Check.typeOf.func("options.resourceCache", resourceCache);
  38. Check.typeOf.object("options.gltf", gltf);
  39. Check.typeOf.number("options.imageId", imageId);
  40. Check.typeOf.object("options.gltfResource", gltfResource);
  41. Check.typeOf.object("options.baseResource", baseResource);
  42. //>>includeEnd('debug');
  43. const image = gltf.images[imageId];
  44. const bufferViewId = image.bufferView;
  45. const uri = image.uri;
  46. this._resourceCache = resourceCache;
  47. this._gltfResource = gltfResource;
  48. this._baseResource = baseResource;
  49. this._gltf = gltf;
  50. this._bufferViewId = bufferViewId;
  51. this._uri = uri;
  52. this._cacheKey = cacheKey;
  53. this._bufferViewLoader = undefined;
  54. this._image = undefined;
  55. this._mipLevels = undefined;
  56. this._state = ResourceLoaderState.UNLOADED;
  57. this._promise = undefined;
  58. }
  59. /**
  60. * The cache key of the resource.
  61. *
  62. *
  63. * @type {string}
  64. * @readonly
  65. * @private
  66. */
  67. get cacheKey() {
  68. return this._cacheKey;
  69. }
  70. /**
  71. * The image.
  72. *
  73. *
  74. * @type {Image|ImageBitmap|CompressedTextureBuffer}
  75. * @readonly
  76. * @private
  77. */
  78. get image() {
  79. return this._image;
  80. }
  81. /**
  82. * The mip levels. Only defined for KTX2 files containing mip levels.
  83. *
  84. *
  85. * @type {Uint8Array[]}
  86. * @readonly
  87. * @private
  88. */
  89. get mipLevels() {
  90. return this._mipLevels;
  91. }
  92. /**
  93. * Loads the resource.
  94. * @returns {Promise<GltfImageLoader>} A promise which resolves to the loader when the resource loading is completed.
  95. * @private
  96. */
  97. load() {
  98. if (defined(this._promise)) {
  99. return this._promise;
  100. }
  101. if (defined(this._bufferViewId)) {
  102. this._promise = loadFromBufferView(this);
  103. return this._promise;
  104. }
  105. this._promise = loadFromUri(this);
  106. return this._promise;
  107. }
  108. /**
  109. * Unloads the resource.
  110. * @private
  111. */
  112. unload() {
  113. if (
  114. defined(this._bufferViewLoader) &&
  115. !this._bufferViewLoader.isDestroyed()
  116. ) {
  117. this._resourceCache.unload(this._bufferViewLoader);
  118. }
  119. this._bufferViewLoader = undefined;
  120. this._uri = undefined; // Free in case the uri is a data uri
  121. this._image = undefined;
  122. this._mipLevels = undefined;
  123. this._gltf = undefined;
  124. }
  125. }
  126. function getImageAndMipLevels(image) {
  127. // Images transcoded from KTX2 can contain multiple mip levels:
  128. // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
  129. let mipLevels;
  130. if (Array.isArray(image)) {
  131. // highest detail mip should be level 0
  132. mipLevels = image.slice(1, image.length).map(function (mipLevel) {
  133. return mipLevel.bufferView;
  134. });
  135. image = image[0];
  136. }
  137. return {
  138. image: image,
  139. mipLevels: mipLevels,
  140. };
  141. }
  142. async function loadFromBufferView(imageLoader) {
  143. imageLoader._state = ResourceLoaderState.LOADING;
  144. const resourceCache = imageLoader._resourceCache;
  145. try {
  146. const bufferViewLoader = resourceCache.getBufferViewLoader({
  147. gltf: imageLoader._gltf,
  148. bufferViewId: imageLoader._bufferViewId,
  149. gltfResource: imageLoader._gltfResource,
  150. baseResource: imageLoader._baseResource,
  151. });
  152. imageLoader._bufferViewLoader = bufferViewLoader;
  153. await bufferViewLoader.load();
  154. if (imageLoader.isDestroyed()) {
  155. return;
  156. }
  157. const typedArray = bufferViewLoader.typedArray;
  158. const image = await loadImageFromBufferTypedArray(typedArray);
  159. if (imageLoader.isDestroyed()) {
  160. return;
  161. }
  162. const imageAndMipLevels = getImageAndMipLevels(image);
  163. // Unload everything except the image
  164. imageLoader.unload();
  165. imageLoader._image = imageAndMipLevels.image;
  166. imageLoader._mipLevels = imageAndMipLevels.mipLevels;
  167. imageLoader._state = ResourceLoaderState.READY;
  168. return imageLoader;
  169. } catch (error) {
  170. if (imageLoader.isDestroyed()) {
  171. return;
  172. }
  173. return handleError(imageLoader, error, "Failed to load embedded image");
  174. }
  175. }
  176. async function loadFromUri(imageLoader) {
  177. imageLoader._state = ResourceLoaderState.LOADING;
  178. const baseResource = imageLoader._baseResource;
  179. const uri = imageLoader._uri;
  180. const resource = baseResource.getDerivedResource({
  181. url: uri,
  182. });
  183. try {
  184. const image = await loadImageFromUri(resource);
  185. if (imageLoader.isDestroyed()) {
  186. return;
  187. }
  188. const imageAndMipLevels = getImageAndMipLevels(image);
  189. // Unload everything except the image
  190. imageLoader.unload();
  191. imageLoader._image = imageAndMipLevels.image;
  192. imageLoader._mipLevels = imageAndMipLevels.mipLevels;
  193. imageLoader._state = ResourceLoaderState.READY;
  194. return imageLoader;
  195. } catch (error) {
  196. if (imageLoader.isDestroyed()) {
  197. return;
  198. }
  199. return handleError(imageLoader, error, `Failed to load image: ${uri}`);
  200. }
  201. }
  202. function handleError(imageLoader, error, errorMessage) {
  203. imageLoader.unload();
  204. imageLoader._state = ResourceLoaderState.FAILED;
  205. return Promise.reject(imageLoader.getError(errorMessage, error));
  206. }
  207. function getMimeTypeFromTypedArray(typedArray) {
  208. const header = typedArray.subarray(0, 2);
  209. const webpHeaderRIFFChars = typedArray.subarray(0, 4);
  210. const webpHeaderWEBPChars = typedArray.subarray(8, 12);
  211. if (header[0] === 0xff && header[1] === 0xd8) {
  212. // See https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format
  213. return "image/jpeg";
  214. } else if (header[0] === 0x89 && header[1] === 0x50) {
  215. // See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html
  216. return "image/png";
  217. } else if (header[0] === 0xab && header[1] === 0x4b) {
  218. // See http://github.khronos.org/KTX-Specification/#_identifier
  219. return "image/ktx2";
  220. } else if (
  221. // See https://developers.google.com/speed/webp/docs/riff_container#webp_file_header
  222. webpHeaderRIFFChars[0] === 0x52 &&
  223. webpHeaderRIFFChars[1] === 0x49 &&
  224. webpHeaderRIFFChars[2] === 0x46 &&
  225. webpHeaderRIFFChars[3] === 0x46 &&
  226. webpHeaderWEBPChars[0] === 0x57 &&
  227. webpHeaderWEBPChars[1] === 0x45 &&
  228. webpHeaderWEBPChars[2] === 0x42 &&
  229. webpHeaderWEBPChars[3] === 0x50
  230. ) {
  231. return "image/webp";
  232. }
  233. throw new RuntimeError("Image format is not recognized");
  234. }
  235. async function loadImageFromBufferTypedArray(typedArray) {
  236. const mimeType = getMimeTypeFromTypedArray(typedArray);
  237. if (mimeType === "image/ktx2") {
  238. // Need to make a copy of the embedded KTX2 buffer otherwise the underlying
  239. // ArrayBuffer may be accessed on both the worker and the main thread and
  240. // throw an error like "Cannot perform Construct on a detached ArrayBuffer".
  241. // Look into SharedArrayBuffer at some point to get around this.
  242. const ktxBuffer = new Uint8Array(typedArray);
  243. // Resolves to a CompressedTextureBuffer
  244. return loadKTX2(ktxBuffer);
  245. }
  246. // Resolves to an Image or ImageBitmap
  247. return GltfImageLoader._loadImageFromTypedArray({
  248. uint8Array: typedArray,
  249. format: mimeType,
  250. flipY: false,
  251. skipColorSpaceConversion: true,
  252. });
  253. }
  254. const ktx2Regex = /(^data:image\/ktx2)|(\.ktx2$)/i;
  255. function loadImageFromUri(resource) {
  256. const uri = resource.getUrlComponent(false, true);
  257. if (ktx2Regex.test(uri)) {
  258. // Resolves to a CompressedTextureBuffer
  259. return loadKTX2(resource);
  260. }
  261. // Resolves to an ImageBitmap or Image
  262. return resource.fetchImage({
  263. skipColorSpaceConversion: true,
  264. preferImageBitmap: true,
  265. });
  266. }
  267. // Exposed for testing
  268. GltfImageLoader._loadImageFromTypedArray = loadImageFromTypedArray;
  269. export default GltfImageLoader;