| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 | import Check from "../Core/Check.js";import defaultValue from "../Core/defaultValue.js";import defer from "../Core/defer.js";import defined from "../Core/defined.js";import loadImageFromTypedArray from "../Core/loadImageFromTypedArray.js";import loadKTX2 from "../Core/loadKTX2.js";import RuntimeError from "../Core/RuntimeError.js";import ResourceLoader from "./ResourceLoader.js";import ResourceLoaderState from "./ResourceLoaderState.js";/** * Loads a glTF image. * <p> * Implements the {@link ResourceLoader} interface. * </p> * * @alias GltfImageLoader * @constructor * @augments ResourceLoader * * @param {Object} options Object with the following properties: * @param {ResourceCache} options.resourceCache The {@link ResourceCache} (to avoid circular dependencies). * @param {Object} options.gltf The glTF JSON. * @param {Number} options.imageId The image ID. * @param {Resource} options.gltfResource The {@link Resource} containing the glTF. * @param {Resource} options.baseResource The {@link Resource} that paths in the glTF JSON are relative to. * @param {String} [options.cacheKey] The cache key of the resource. * * @private */export default function GltfImageLoader(options) {  options = defaultValue(options, defaultValue.EMPTY_OBJECT);  const resourceCache = options.resourceCache;  const gltf = options.gltf;  const imageId = options.imageId;  const gltfResource = options.gltfResource;  const baseResource = options.baseResource;  const cacheKey = options.cacheKey;  //>>includeStart('debug', pragmas.debug);  Check.typeOf.func("options.resourceCache", resourceCache);  Check.typeOf.object("options.gltf", gltf);  Check.typeOf.number("options.imageId", imageId);  Check.typeOf.object("options.gltfResource", gltfResource);  Check.typeOf.object("options.baseResource", baseResource);  //>>includeEnd('debug');  const image = gltf.images[imageId];  const bufferViewId = image.bufferView;  const uri = image.uri;  this._resourceCache = resourceCache;  this._gltfResource = gltfResource;  this._baseResource = baseResource;  this._gltf = gltf;  this._bufferViewId = bufferViewId;  this._uri = uri;  this._cacheKey = cacheKey;  this._bufferViewLoader = undefined;  this._image = undefined;  this._mipLevels = undefined;  this._state = ResourceLoaderState.UNLOADED;  this._promise = defer();}if (defined(Object.create)) {  GltfImageLoader.prototype = Object.create(ResourceLoader.prototype);  GltfImageLoader.prototype.constructor = GltfImageLoader;}Object.defineProperties(GltfImageLoader.prototype, {  /**   * A promise that resolves to the resource when the resource is ready.   *   * @memberof GltfImageLoader.prototype   *   * @type {Promise.<GltfImageLoader>}   * @readonly   * @private   */  promise: {    get: function () {      return this._promise.promise;    },  },  /**   * The cache key of the resource.   *   * @memberof GltfImageLoader.prototype   *   * @type {String}   * @readonly   * @private   */  cacheKey: {    get: function () {      return this._cacheKey;    },  },  /**   * The image.   *   * @memberof GltfImageLoader.prototype   *   * @type {Image|ImageBitmap|CompressedTextureBuffer}   * @readonly   * @private   */  image: {    get: function () {      return this._image;    },  },  /**   * The mip levels. Only defined for KTX2 files containing mip levels.   *   * @memberof GltfImageLoader.prototype   *   * @type {Uint8Array[]}   * @readonly   * @private   */  mipLevels: {    get: function () {      return this._mipLevels;    },  },});/** * Loads the resource. * @private */GltfImageLoader.prototype.load = function () {  if (defined(this._bufferViewId)) {    loadFromBufferView(this);  } else {    loadFromUri(this);  }};function getImageAndMipLevels(image) {  // Images transcoded from KTX2 can contain multiple mip levels:  // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu  let mipLevels;  if (Array.isArray(image)) {    // highest detail mip should be level 0    mipLevels = image.slice(1, image.length).map(function (mipLevel) {      return mipLevel.bufferView;    });    image = image[0];  }  return {    image: image,    mipLevels: mipLevels,  };}function loadFromBufferView(imageLoader) {  const resourceCache = imageLoader._resourceCache;  const bufferViewLoader = resourceCache.loadBufferView({    gltf: imageLoader._gltf,    bufferViewId: imageLoader._bufferViewId,    gltfResource: imageLoader._gltfResource,    baseResource: imageLoader._baseResource,  });  imageLoader._bufferViewLoader = bufferViewLoader;  imageLoader._state = ResourceLoaderState.LOADING;  bufferViewLoader.promise    .then(function () {      if (imageLoader.isDestroyed()) {        return;      }      const typedArray = bufferViewLoader.typedArray;      return loadImageFromBufferTypedArray(typedArray).then(function (image) {        if (imageLoader.isDestroyed()) {          return;        }        const imageAndMipLevels = getImageAndMipLevels(image);        // Unload everything except the image        imageLoader.unload();        imageLoader._image = imageAndMipLevels.image;        imageLoader._mipLevels = imageAndMipLevels.mipLevels;        imageLoader._state = ResourceLoaderState.READY;        imageLoader._promise.resolve(imageLoader);      });    })    .catch(function (error) {      if (imageLoader.isDestroyed()) {        return;      }      handleError(imageLoader, error, "Failed to load embedded image");    });}function loadFromUri(imageLoader) {  const baseResource = imageLoader._baseResource;  const uri = imageLoader._uri;  const resource = baseResource.getDerivedResource({    url: uri,  });  imageLoader._state = ResourceLoaderState.LOADING;  loadImageFromUri(resource)    .then(function (image) {      if (imageLoader.isDestroyed()) {        return;      }      const imageAndMipLevels = getImageAndMipLevels(image);      // Unload everything except the image      imageLoader.unload();      imageLoader._image = imageAndMipLevels.image;      imageLoader._mipLevels = imageAndMipLevels.mipLevels;      imageLoader._state = ResourceLoaderState.READY;      imageLoader._promise.resolve(imageLoader);    })    .catch(function (error) {      if (imageLoader.isDestroyed()) {        return;      }      handleError(imageLoader, error, `Failed to load image: ${uri}`);    });}function handleError(imageLoader, error, errorMessage) {  imageLoader.unload();  imageLoader._state = ResourceLoaderState.FAILED;  imageLoader._promise.reject(imageLoader.getError(errorMessage, error));}function getMimeTypeFromTypedArray(typedArray) {  const header = typedArray.subarray(0, 2);  const webpHeaderRIFFChars = typedArray.subarray(0, 4);  const webpHeaderWEBPChars = typedArray.subarray(8, 12);  if (header[0] === 0xff && header[1] === 0xd8) {    // See https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format    return "image/jpeg";  } else if (header[0] === 0x89 && header[1] === 0x50) {    // See http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html    return "image/png";  } else if (header[0] === 0xab && header[1] === 0x4b) {    // See http://github.khronos.org/KTX-Specification/#_identifier    return "image/ktx2";  } else if (    // See https://developers.google.com/speed/webp/docs/riff_container#webp_file_header    webpHeaderRIFFChars[0] === 0x52 &&    webpHeaderRIFFChars[1] === 0x49 &&    webpHeaderRIFFChars[2] === 0x46 &&    webpHeaderRIFFChars[3] === 0x46 &&    webpHeaderWEBPChars[0] === 0x57 &&    webpHeaderWEBPChars[1] === 0x45 &&    webpHeaderWEBPChars[2] === 0x42 &&    webpHeaderWEBPChars[3] === 0x50  ) {    return "image/webp";  }  throw new RuntimeError("Image format is not recognized");}function loadImageFromBufferTypedArray(typedArray) {  const mimeType = getMimeTypeFromTypedArray(typedArray);  if (mimeType === "image/ktx2") {    // Need to make a copy of the embedded KTX2 buffer otherwise the underlying    // ArrayBuffer may be accessed on both the worker and the main thread and    // throw an error like "Cannot perform Construct on a detached ArrayBuffer".    // Look into SharedArrayBuffer at some point to get around this.    const ktxBuffer = new Uint8Array(typedArray);    // Resolves to a CompressedTextureBuffer    return loadKTX2(ktxBuffer);  }  // Resolves to an Image or ImageBitmap  return GltfImageLoader._loadImageFromTypedArray({    uint8Array: typedArray,    format: mimeType,    flipY: false,    skipColorSpaceConversion: true,  });}const ktx2Regex = /(^data:image\/ktx2)|(\.ktx2$)/i;function loadImageFromUri(resource) {  const uri = resource.url;  if (ktx2Regex.test(uri)) {    // Resolves to a CompressedTextureBuffer    return loadKTX2(resource);  }  // Resolves to an ImageBitmap or Image  return resource.fetchImage({    skipColorSpaceConversion: true,    preferImageBitmap: true,  });}/** * Unloads the resource. * @private */GltfImageLoader.prototype.unload = function () {  if (defined(this._bufferViewLoader)) {    this._resourceCache.unload(this._bufferViewLoader);  }  this._bufferViewLoader = undefined;  this._uri = undefined; // Free in case the uri is a data uri  this._image = undefined;  this._mipLevels = undefined;  this._gltf = undefined;};// Exposed for testingGltfImageLoader._loadImageFromTypedArray = loadImageFromTypedArray;
 |