loadImage(
        source,
        textureOptions = {},
        successCallback,
        errorCallback
    ) {
        // check for cache
        const cachedTexture = this.renderer.cache.getTextureFromSource(source);

        let options = Object.assign({}, textureOptions);
        // merge texture options with its parent textures options if needed
        if(this._parent) {
            options = Object.assign(options, this._parent._texturesOptions);
        }

        options.loader = this;

        if(cachedTexture) {
            options.sampler = typeof source !== "string" && source.hasAttribute("data-sampler") ? source.getAttribute("data-sampler") : options.sampler;

            options.fromTexture = cachedTexture;
            const texture = new Texture(this.renderer, options);

            // execute source loaded callback directly
            this._sourceLoaded(cachedTexture.source, texture, successCallback);

            // if there's a parent (PlaneTextureLoader) add texture and source to it
            this._parent && this._addToParent(texture, cachedTexture.source, "image");

            // that's all!
            return;
        }

        const image = this._createImage(source);

        options.sampler = image.hasAttribute("data-sampler") ? image.getAttribute("data-sampler") : options.sampler;

        // create a new texture that will use our image later
        const texture = new Texture(this.renderer, options);

        // add a new entry in our elements array
        const el = this._addElement(image, texture, successCallback, errorCallback);

        // If the image is in the cache of the browser,
        // the 'load' event might have been triggered
        // before we registered the event handler.
        if(image.complete) {
            this._sourceLoaded(image, texture, successCallback);
        }
        else if(image.decode) {
            image.decode().then(this._sourceLoaded.bind(this, image, texture, successCallback)).catch(() => {
                // fallback to classic load & error events
                image.addEventListener('load', el.load, false);
                image.addEventListener('error', el.error, false);
            });
        }
        else {
            image.addEventListener('load', el.load, false);
            image.addEventListener('error', el.error, false);
        }

        // if there's a parent (PlaneTextureLoader) add texture and source to it
        this._parent && this._addToParent(texture, image,  "image");
    }
This method loads an image
     Creates a new texture object right away and once the image is loaded it uses it as our WebGL texture

     params:
     @source (image): html image element
     @textureOptions (object): parameters to apply to the textures, such as sampler name, repeat wrapping, filters, anisotropy...
     @successCallback (function): function to execute when the source has been loaded
     @errorCallback (function): function to execute if the source fails to load
*