function init() {
    // Display an error for IE 9 as it doesn't work but also doesn't otherwise
    // show an error (older versions don't work at all)
    // Based on: http://stackoverflow.com/a/10965203
    var div = document.createElement("div");
    div.innerHTML = "<!--[if lte IE 9]><i></i><![endif]-->";
    if (div.getElementsByTagName("i").length == 1) {
        anError();
        return;
    }

    origHfov = config.hfov;
    origPitch = config.pitch;

    var i, p;
    
    if (config.type == 'cubemap') {
        panoImage = [];
        for (i = 0; i < 6; i++) {
            panoImage.push(new Image());
            panoImage[i].crossOrigin = config.crossOrigin;
        }
        infoDisplay.load.lbox.style.display = 'block';
        infoDisplay.load.lbar.style.display = 'none';
    } else if (config.type == 'multires') {
        var c = JSON.parse(JSON.stringify(config.multiRes));    // Deep copy
        // Avoid "undefined" in path, check (optional) multiRes.basePath, too
        // Use only multiRes.basePath if it's an absolute URL
        if (config.basePath && config.multiRes.basePath &&
            !(/^(?:[a-z]+:)?\/\//i.test(config.multiRes.basePath))) {
            c.basePath = config.basePath + config.multiRes.basePath;
        } else if (config.multiRes.basePath) {
            c.basePath = config.multiRes.basePath;
        } else if(config.basePath) {
            c.basePath = config.basePath;
        }
        panoImage = c;
    } else {
        if (config.dynamic === true) {
            panoImage = config.panorama;
        } else {
            if (config.panorama === undefined) {
                anError(config.strings.noPanoramaError);
                return;
            }
            panoImage = new Image();
        }
    }

    // Configure image loading
    if (config.type == 'cubemap') {
        // Quick loading counter for synchronous loading
        var itemsToLoad = 6;
        
        var onLoad = function() {
            itemsToLoad--;
            if (itemsToLoad === 0) {
                onImageLoad();
            }
        };
        
        var onError = function(e) {
            var a = document.createElement('a');
            a.href = e.target.src;
            a.textContent = a.href;
            anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
        };
        
        for (i = 0; i < panoImage.length; i++) {
            p = config.cubeMap[i];
            if (p == "null") { // support partial cubemap image with explicitly empty faces
                console.log('Will use background instead of missing cubemap face ' + i);
                onLoad();
            } else {
                if (config.basePath && !absoluteURL(p)) {
                    p = config.basePath + p;
                }
                panoImage[i].onload = onLoad;
                panoImage[i].onerror = onError;
                panoImage[i].src = sanitizeURL(p);
            }
        }
    } else if (config.type == 'multires') {
        onImageLoad();
    } else {
        p = '';
        if (config.basePath) {
            p = config.basePath;
        }
        
        if (config.dynamic !== true) {
            // Still image
            p = absoluteURL(config.panorama) ? config.panorama : p + config.panorama;
            
            panoImage.onload = function() {
                window.URL.revokeObjectURL(this.src);  // Clean up
                onImageLoad();
            };
            
            var xhr = new XMLHttpRequest();
            xhr.onloadend = function() {
                if (xhr.status != 200) {
                    // Display error if image can't be loaded
                    var a = document.createElement('a');
                    a.href = p;
                    a.textContent = a.href;
                    anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
                }
                var img = this.response;
                parseGPanoXMP(img);
                infoDisplay.load.msg.innerHTML = '';
            };
            xhr.onprogress = function(e) {
                if (e.lengthComputable) {
                    // Display progress
                    var percent = e.loaded / e.total * 100;
                    infoDisplay.load.lbarFill.style.width = percent + '%';
                    var unit, numerator, denominator;
                    if (e.total > 1e6) {
                        unit = 'MB';
                        numerator = (e.loaded / 1e6).toFixed(2);
                        denominator = (e.total / 1e6).toFixed(2);
                    } else if (e.total > 1e3) {
                        unit = 'kB';
                        numerator = (e.loaded / 1e3).toFixed(1);
                        denominator = (e.total / 1e3).toFixed(1);
                    } else {
                        unit = 'B';
                        numerator = e.loaded;
                        denominator = e.total;
                    }
                    infoDisplay.load.msg.innerHTML = numerator + ' / ' + denominator + ' ' + unit;
                } else {
                    // Display loading spinner
                    infoDisplay.load.lbox.style.display = 'block';
                    infoDisplay.load.lbar.style.display = 'none';
                }
            };
            try {
                xhr.open('GET', p, true);
            } catch (e) {
                // Malformed URL
                anError(config.strings.malformedURLError);
            }
            xhr.responseType = 'blob';
            xhr.setRequestHeader('Accept', 'image/*,*/*;q=0.9');
            xhr.withCredentials = config.crossOrigin === 'use-credentials';
            xhr.send();
        }
    }
    
    if (config.draggable)
        uiContainer.classList.add('pnlm-grab');
    uiContainer.classList.remove('pnlm-grabbing');
}