IMDB UTE: IMDB Ultimate Thumbnail Expander

Expands thumbnail images in IMDB into larger floating preview images on mouse hover.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           IMDB UTE: IMDB Ultimate Thumbnail Expander
// @description    Expands thumbnail images in IMDB into larger floating preview images on mouse hover.
// @namespace      http://myxp.anandkumar.me
// @include        http://www.imdb.com/*
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_registerMenuCommand
// @grant          GM_log
// @grant          GM_addStyle
// @version        1.2.5
// ==/UserScript==

/**
Change Log:
Date      Version    Notes
20131011  1.2.5      *Reposition loaded image on a timer.
20130827  1.2.4      *Revert 1.2.1 since script was corrected by author.
20130804  1.2.3      *Updated rules for video thumbnail images.
20130426  1.2.2      *Changed require URLs to https.
20130108  1.2.1      *Moved the "gm_config extras" script with corrections to anandkumar.me domain.
20120829  1.2.0      *Added @grant directives to comply with Greasemonkey 1.0.
                     *Removed dependence on custom updater since Greasemonkey now has an internal updater.
                     *Copied the floating image repositioning logic from my Flickr script.
                     *Moved all floating image related code to a single class. This is the first step towards unifying my other scripts that use the same logic.
20120606  1.1.0      *Fine-tuned the image positioning algorithm.
                     *Added "Ctrl" key override to persist preview image.
20120604  1.0.2      *Added support for a delay timer and a corresponding configuration item under settings.
                     *Added support for the setting the preview image to "Snap to Cursor".
20111112  1.0.1      *Added missing escaping to some reg-expressions.
20111103  1.0.0      *Official release of script.
 */
var DEBUG     = false;
var newWidth  = "200";
var newHeight = "200";
var GMImg_position   = "absolute";
var GMImg_marginleft = "0";
var GMImg_margintop  = "0";
var GMImg_left   = "";
var GMImg_top    = "";
var GMImg_right  = "";
var GMImg_bottom = "";
var prevTarget = null;
var floatimg;
var timerRunning = false, timerID = null;
var global_mouse_x=0, global_mouse_y=0;
var persistPreview = false;

//From Config//
var settings_max_width, settings_max_height, settings_auto_size, settings_snap_to_cursor;
var settings_expand_actorimgs,settings_expand_titleimgs,settings_expand_previewimgs;
var settings_x_factor,settings_y_factor;
var settings_timer_ms;

var xhrCnt = 0;
var debug_msg_div = "flute_debug_msg_div";

var fio;
/**
 **Config screen
*/
var lang = GM_config.gets('lang','en'); // get the language - or set it to 'en' if it was not yet stored
GM_config.init('Configuration for IMDB UTE',{
    timer_ms : { label: 'Preview Delay (milliseconds)', type: 'int', default: 0, min: 0, max: 10000 },
    max_width: { label: 'Max Display Width:',section:['Preview Image properties'], type: 'int', default: 640, min: 50, max: 5000 },
    max_height:{ label: 'Max Display Height:', type: 'int', default: 480, min: 50, max: 5000 },
    auto_size: { label: 'Enable Auto size:', type: 'checkbox', default: true },
    x_factor : { label: 'Auto-size Screen Width % :', type: 'int', default: 60, min: 1, max: 100 },
    y_factor : { label: 'Auto-size Screen Height %:', type: 'int', default: 60, min: 1, max: 100 },
    snap_to_cursor : { label: 'Snap to Cursor', type: 'checkbox', default: false }
    /*expand_actorimgs  : { label: 'Actor Images', section:['Control which images get expanded'], type: 'checkbox', default: true },
    expand_titleimgs  : { label: 'Title Images', type: 'checkbox', default: true },
    expand_previewimgs: { label: 'Preview Images', type: 'checkbox', default: true }*/
  },
  GM_config.eCSS,
  {
    open: function() {
      var idx=0;
      GM_config.addBorder(); // add a fancy border
      GM_config.resizeFrame('480px','360px'); // resize the config window
      GM_config.addTooltip(idx++,'Set the delay (in milliseconds) to wait before displaying the preview image.');
      GM_config.addTooltip(idx++,'The maximum Width, in pixels, of the displayed image. Use this if you want to retreive large images but want to constrain the displayed image size.');
      GM_config.addTooltip(idx++,'The maximum Height, in pixels, of the displayed image. Use this if you want to retreive large images but want to constrain the displayed image size.');
      GM_config.addTooltip(idx++,'When enabled, the maximum width and height of the displayed image is automatically constrained to two-thirds of the browser window size. Selecting this option will ignore the custom maximum size settings above.');
      GM_config.addTooltip(idx++,'If Auto-Size is enabled, this option specifies the screen Width % to use.');
      GM_config.addTooltip(idx++,'If Auto-Size is enabled, this option specifies the screen Height % to use.');
      GM_config.addTooltip(idx++,'Enable this option to make the preview image follow the mouse cursor.');
      // GM_config.sections2tabs(); // convert the sections to tabs
      GM_config.fadeOut(); //Fadeout the rest of the screen.
    },
    save: function() { 
        //Update the settings and close the dialog
        GetUserSettings();
        //GM_config.fadeIn();
        GM_config.close();
    },
    close: function() {
        GM_config.fadeIn();
        //GM_config.close();
    }
  }
);

/**
 **Main code starts here
*/
(function(){
    try{
        if (typeof unsafeWindow != 'object') {
          window.unsafeWindow = window;
        }
        window.addEventListener('load'     ,function(event){Init  ();},true);
        window.addEventListener('mousemove',function(event){DoMouseOver(event);},true);
        window.addEventListener('keydown'  ,function(event){DoKeyDown  (event);},true);
        window.addEventListener('keyup'    ,function(event){DoKeyUp    (event);},true);
    }
    catch(e){
        GM_log(e.message);
    }
    GM_registerMenuCommand('imute: Configuration',GM_config.open);
})();

/**
 * Get the user-settings for this script.
 */
function GetUserSettings(){
    //Correct any incorrect settings
    
    //Read the settings into local variables.
    settings_max_width  = GM_config.get('max_width');
    settings_max_height = GM_config.get('max_height');
    settings_auto_size  = GM_config.get('auto_size');
    settings_x_factor   = GM_config.get('x_factor');
    settings_y_factor   = GM_config.get('y_factor');
    settings_snap_to_cursor = GM_config.get('snap_to_cursor');
    settings_timer_ms       = GM_config.get('timer_ms');
    //settings_expand_actorimgs  = GM_config.get('expand_actorimgs');
    //settings_expand_titleimgs  = GM_config.get('expand_titleimgs');
    //settings_expand_previewimgs = GM_config.get('expand_previewimgs');
    
    //Perform any validation and re-calculation of the retreived settings here.
    settings_x_factor = settings_x_factor/100;
    settings_y_factor = settings_y_factor/100;
}

/**
 * Handle all the initializations here.
 */
function Init(){
    var isInIFrame = (window.location != window.parent.location) ? true : false;
    if(isInIFrame) return;
    //Get the user's settings
    GetUserSettings();
    //Create and attach the floating image.
    var newlink;
    var settingsimg

    fio = new FloatImg();
    
    //Create the settings icon
    settingsimg = document.createElement('IMG');
    settingsimg.className="shadow";
    settingsimg.id="imute_settings_icon";
    settingsimg.alt="imute settings";
    settingsimg.src="data:image/png;base64,"+ "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";
    settingsimg.width  = "32";
    settingsimg.height = "32";
    settingsimg.loaded = "false";
    settingsimg.addEventListener('click', function(){GM_config.open();}, true);
    document.body.appendChild(settingsimg);
    
    var debugmsg = document.createElement('div');
    debugmsg.id  = debug_msg_div;
    document.body.appendChild(debugmsg);
    
    //Set defaults.
    GMImg_left = "1";
    GMImg_top  = "1";
    GMImg_position  = "fixed";
    AddScriptCSS();
    
    RepositionTimer();
}

/**
 * Handles the global mouseover event.
 * The main entry function from where everything is kicked-off.
 */
function DoMouseOver(e){
    //If the floating image is not yet initialized, exit.
    if(fio==null) return;
    //Get the event variable.
    if (!e) var e = window.event;
    var tg = (window.event) ? e.srcElement : e.target;
    //Extract the mouse co-ordinates
    GetCoordinate(e);
    //If the mouse is over the floatimg, either accidentally or deliberately, ignore it.
    if(tg.id == fio.getId()) return;
    //If the preview image must be repositioned upon mouse move, do it here:
    if(settings_snap_to_cursor){
        if(persistPreview==false){
            fio.reposition();
        }
        else{
            return;
        }
    }
    //If this mouseover event is for the same element as before, exit.
    //We are only interested when the mouse enters/leaves an element.
    if(prevTarget !=null && tg == prevTarget) {
        return;
    }
    //Store the current target to compare against in the next mouse over event.
    prevTarget = tg;
    
    //If the mouse is over any "other" element, hide the floating image.
    ImgDoMouseOut();
    //If the target does not have a valid image, url, exit.
    var img_src = GetDisplayImageURL(tg);
    if(img_src == "") return;

    //If the current image does not meet criteria, exit.
    if(!canBeExpanded(img_src) || img_src.match(/\/nopicture|nophoto\//)){
        return;
    }
    /**END of validations**/
    
    //Once all the validations are passed, kick-start the image preview process.
    fio.srcElem = tg;
    StartImagePreviewTimer(tg);
}


/**
 * Starts the preview image display timer.
 */
function StartImagePreviewTimer(tg){
    //First, stop the global timer, if it's running.
    if(timerRunning){
       StopImagePreviewTimer();
    }
    timerRunning = true;
    timerID = setTimeout(function(){ShowImagePreview(tg);}, settings_timer_ms);
    timerID = setTimeout(function(){ShowImagePreview(tg);}, settings_timer_ms);
}

/**
 * Stops the active global timer.
 */
function StopImagePreviewTimer(){
    timerRunning = false;
    if(timerID!=null)
        clearTimeout(timerID);
}

/**
 * Before the new image loads, the image size is not known. This results in the image not positioned correctly until the user moves the mouse.
 * So, keep repositioning the image on a timer.
 */
function RepositionTimer(){
    if(fio != null && fio.isLoaded()){
        fio.reposition();
    }
    setTimeout(function(){RepositionTimer();}, 100);
}

/**
 * The main function to call to begin the process of popping-up the preview image.
 * Accepts an event object's target (usually, an image node).
 */
function ShowImagePreview(tg){
    StopImagePreviewTimer();
    
    //Invalidate current and older XHRs.
    xhrCnt++;
    //Get the expanded image url.
    var zoomedUrl = GetPreviewURL(tg, 1000, settings_max_width, settings_max_height);
    
    //message("zoomedUrl="+zoomedUrl,false,true);
    if(zoomedUrl != ""){
        //Assign the unchecked image URL so that the image displays immediately.
        fio.setUrl(zoomedUrl);
        if(persistPreview==false){
            //Do not reposition if the user is currently pressing the ctrl-key down.
            fio.reposition();
        }
    }
    else{
        fio.clear();
        return;
    }
    
    //If the image is surrounded by an anchor tag, copy its href to the floatimg's parent anchor tag.
    //This helps if the user wants to click the image but the floating image gets "in the way".
    if(tg.parentNode && tg.parentNode.href){
        fio.setLinkUrl(tg.parentNode.href);
    }
    else if(tg.href){
        fio.setLinkUrl(tg.href);
    }
    
    //Finally, show the image.
    fio.show();
}


/**
 * Hide the floating image once the mouse cursor leaves an element.
 * This function is not event-driven by the browser but is called when
 * "DoMouseOver()" detects a change of target.
 */
function ImgDoMouseOut(){
    if(persistPreview==false){
        //Stop any timers:
        StopImagePreviewTimer();
        
        //If the floating image is not yet created, exit.
        if(fio==null) return;
        fio.clear();
    }
}

/**
 * Event handler that captures control key presses.
 */
function DoKeyDown(e){
    if (e.ctrlKey){
        persistPreview = true;
    }
}

/**
 * Event handler that cleans-up actions done during the keydown actions.
 */
function DoKeyUp(e){
    if (e.keyCode == 17){
        persistPreview = false;
    }
}


/**
 * Add CSS for our custom elements.
 */
function AddScriptCSS(){
    //Common style
    var GM_style = 
      "img.GM_thumbnail { display:none; "+
        "height:auto !important;"+
        "width:auto !important; "+
        "position:   " + GMImg_position   + "   ; " +
        "margin-left:" + GMImg_marginleft + "px ; " +
        "margin-top:"  + GMImg_margintop  + "px ; " +
        "z-index:9999;"+
        "background-color:transparent !important;"+
        (GMImg_left  ==""?"":"left:"  +GMImg_left  + "px ; ") +
        (GMImg_top   ==""?"":"top:"   +GMImg_top   + "px ; ") +
        (GMImg_bottom==""?"":"bottom:"+GMImg_bottom+ "px ; ") +
        (GMImg_right ==""?"":"right:" +GMImg_right + "px ; ") +
        "} ";
    GM_style += 
        ".shadow {"+
            "-moz-box-shadow: 3px 3px 4px #000;"+
            "-webkit-box-shadow: 3px 3px 4px #000;"+
            "box-shadow: 3px 3px 4px #000;"+
        "}";
    GM_style += 
        "img#imute_settings_icon{ "+
            "position:fixed;"+
            "right:0px;"+
            "bottom:0px;"+
            "opacity:0.75"+
        "}";
    GM_style +=
        "#"+debug_msg_div+"{ "+
            "position:fixed;"+
            "left:0px;"+
            "top:0px;"+
            "background-color:white;"+
            "width:100%;"+
            "z-index:99999;"+
        "}";
    GM_addStyle(GM_style);
}


/**
 * Checks if the image can be expanded or not.
 */
function canBeExpanded(img_url){
    var bRet = false;
    if(img_url.match(/\_CR(\d+),(\d+),(\d+),(\d+)/)){ //For imdb hosted images.
        bRet = true;
    }
    else if(img_url.match(/\_SX\d+/) || img_url.match(/\_SY\d+/)){
        bRet = true;
    }
    else if(img_url.match(/\_AA\d+\_/)){ //For amazon hosted images for imdb.
        bRet = true;
    }
    else if (img_url.match(/\_SS\d+/)){
        bRet = true;
    }
    else if(img_url.match(/fbcdn-profile(.*)_q.jpg$/)){
        //Expand facebook profile images
        bRet = true
    }
    //message(img_url+".match(/fbcdn-profile(.*)_q.jpg$/) = "+img_url.match(/fbcdn-profile(.*)_q.jpg$/),true,true);
    return bRet;
}


/**
 * Given a target element, this function returns the image url associated with the element.
 * Note: The image url returned is not validated.
 */
 function GetDisplayImageURL(elem){
    var url = "";
    if (elem.nodeName == 'IMG'){
        url = elem.src;
    }
    else if (elem.nodeName == 'LI' || elem.nodeName == 'DIV'){
        if(elem.style!= null && elem.style.backgroundImage!= null) {
            try{
                url = elem.style.backgroundImage.match(/\"(.*)\"/)[1];
            }catch(e){
                d(e);
                return "";
            }
        }
    }
    return url;
 }
 

/**
* Custom zooming function for imdb after MANY trial and error!
**/
function GetPreviewURL(tg,newSize,imgWidth,imgHeight){
  var zoomedUrl;
  var url = GetDisplayImageURL(tg);
  //message("url="+url,true,true);
  if(url == "") return;
  
  //Try to expand facebook profile pictures.
  if(url.match(/fbcdn-profile(.*)_q.jpg$/)){
        zoomedUrl = url.replace(/_q.jpg$/,"_b.jpg");
        message("zoomedurl="+zoomedUrl,true,true);
        return zoomedUrl;
  }
  
  var urlSX = url.match(/\_SX\d+/)==null?"":url.match(/\_SX(\d+)/)[1];
  var urlSY = url.match(/\_SY\d+/)==null?"":url.match(/\_SY(\d+)/)[1];
  var urlSS = url.match(/\_SS\d+/)==null?"":url.match(/\_SS(\d+)/)[1];;
  //var imgType = url.split('.').pop();
  var urlC1 = "", urlC2 = "", urlC3 = "", urlC4 = "";
  var urlRatio, imgRatio;
  var newX, newY, newWidth, newHeight;
  var cropArr = url.match(/\_CR(\d+),(\d+),(\d+),(\d+)/);
  if (cropArr != null){
    urlC1 = cropArr[1];
    urlC2 = cropArr[2];
    urlC3 = cropArr[3];
    urlC4 = cropArr[4];
    newX = urlC1;
    newY = urlC2;
    /*urlRatio  = 1.00;
    newWidth  = urlC3;
    newHeight = urlC4;
    if (urlC1 != ""){
      urlRatio = newWidth/newHeight;
    }*/
  }
  else if(urlSX !="" || urlSY != ""){
    //No CR found. Using SX, SY
    /*newWidth  = parseInt(urlSX==""?newSize:urlSX);
    newHeight = parseInt(urlSY==""?newSize:urlSY);
    urlRatio  = newWidth / newHeight;*/
  }
  
  imgWidth  = parseInt(imgWidth);
  imgHeight = parseInt(imgHeight);
  imgRatio  = imgWidth / imgHeight;
  
  //Actual Zooming takes place here
  if (imgRatio < 1.0){
    newHeight  = newSize;
    newWidth = Math.round(newSize * imgRatio);
  }
  else if (imgRatio > 1.0){
    newWidth  = newSize;
    newHeight = Math.round(newSize / imgRatio);
  }
  else{
    newWidth  = newSize;
    newHeight = newSize;
  }
  
  if(urlC1 != "" && urlC1 != "0") {
    newX = parseInt(urlC1) + parseInt(newWidth);
  }
  if(urlC2 != "" && urlC2 != "0") {
    newY = 0 + parseInt(urlC2) + parseInt(newHeight);
  }

  /*
  GM_log("urlC1 = "+urlC1+", urlC2 = "+urlC2+", urlC3 = "+urlC3+", urlC4 = "+urlC4);
  GM_log( "urlRatio="+urlRatio+" , newWidth="+newWidth+" newHeight="+newHeight );
  */
  zoomedUrl = url;
  /*If we have both the CR and SS, just change the SS to preserve correct cropping.*/
  if(zoomedUrl.match(/\_CR\d+/) != null && zoomedUrl.match(/\_SS\d+_/) != null){
    zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "_SS"+newSize+"_");
  }
  else{
    //zoomedUrl = zoomedUrl.replace(/_CR\d+,\d+,\d+,\d+_/, "_CR" + newX + "," + newY + "," + newWidth + "," + newHeight + "_");
    zoomedUrl = zoomedUrl.replace(/\_CR\d+,\d+,\d+,\d+\_/g, "_CR" + newX + "," + newY + ",0,0" + "_");
    zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "");
  }
  /*Always replace SX and SY, if present.*/
  zoomedUrl = zoomedUrl.replace(/\_SY\d+\_/, "_SY"+newHeight+"_");
  zoomedUrl = zoomedUrl.replace(/\_SX\d+\_/, "_SX"+newWidth+"_");
  /*Prevent too large an image from being used by Squaring it.*/
  if(zoomedUrl.match(/\_CR\d+/) != null){
    if(urlC3 < newWidth || urlC4 < newHeight){
      zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "");
    }
    else{
      zoomedUrl = zoomedUrl.replace(/\_SS\d+\_/, "_SS"+newSize+"_");
    }
  }
  if(url.match(/\_AA\d+\_/)){ //For amazon hosted images for imdb.
    zoomedUrl = zoomedUrl.replace(/\_AA(\d+)\_/, "_");
  }
  
  /*Erase any Border Overlays in the URL to get a clean image*/
  zoomedUrl = zoomedUrl.replace(/\_BO.+\_/, "");
  zoomedUrl = zoomedUrl.replace(/_PIimdb\-blackband\-.+\_/,"");
  
  /*Zero-out the SP param (needs more research)*/
  zoomedUrl = zoomedUrl.replace(/_SP\d+,\d+/,"_SP0,0");
  
  return zoomedUrl;
}


/**
 * Helper function that parses and stores the mouse positon from an event object.
 */
function GetCoordinate(e){
    global_mouse_x = e.clientX + document.body.scrollLeft;
    global_mouse_y = e.clientY + document.body.scrollTop;
    return true
}


/**
 * Custom debug output function for writing to Error Console.
 */
function d(msg,force){
    if(force==null) force=false;
    
    if(DEBUG==true || force==true){
        GM_log(msg);
    }
}

/**
 * Custom debug output function that displays log output on the webpage.
 */
function message(msg,clearPrevious,force){
    if(force==null) force=false;
    if(clearPrevious==null) clearPrevious=true;
    
    if(DEBUG == true || force == true){
        var msgdiv = document.getElementById(debug_msg_div);
        if(clearPrevious){
            msgdiv.innerHTML = msg;
        }
        else{
            msgdiv.innerHTML += "<br />"+msg;
        }
    }
}

/**
 * Common class representing the floating preview image container.
 */
function FloatImg(img){
    var element; //The image element represented by this class;
    var link_element; //The (optional) anchor element.
    var link_url; //The url to navigate to when the image is clicked.
    var srcElem; //From which element was the image url captured.
    var loaded;
    var id;
    var width, height; //Image dimensions.
    /*var url; //The image url.
    var top, bottom, left, right; //Image style co-ordinates.
    var maxWidth, maxHeight; //Image style dimentions.*/
    var thisObj = this;
    
    this.getImageElement = GetImageElement;
    this.setLinkElement  = SetLinkElement;
    this.getId       = GetId;
    this.setUrl      = LoadImg;
    this.clear       = Clear;
    this.show        = Show;
    this.reposition  = RepositionPreviewImage;
    this.setSrc      = SetSrc;
    this.setLinkUrl  = SetLinkUrl;
    this.setTop      = SetTop;
    this.getTop      = GetTop;
    this.setBottom   = SetBottom;
    this.getBottom   = GetBottom;
    this.setLeft     = SetLeft;
    this.getLeft     = GetLeft;
    this.setRight    = SetRight;
    this.getRight    = GetRight;
    this.setWidth    = SetWidth;
    this.getWidth    = GetWidth;
    this.setHeight   = SetHeight;
    this.getHeight   = GetHeight;
    this.setMaxWidth = SetMaxWidth;
    this.getMaxWidth = GetMaxWidth;
    this.setMaxHeight= SetMaxHeight;
    this.getMaxHeight= GetMaxHeight;
    this.isLoaded    = GetLoaded;
    this.setLoaded   = SetLoaded;

    //Perform object initialization.
    //"img" is the floating image element.
    if(img){
        try{
            this.element = img;
            this.id      = img.id;
            this.Clear();
        }
        catch(e){
            d(e.message);
        }
    }
    else{
        //Try to create a floating image.
        this.element = document.createElement('IMG');
        this.element.className = 'GM_thumbnail shadow';
        this.element.id  = "floatimg";
        this.element.src = "";
        this.element.alt = "Loading...";
        
        //Now create the surrounding link element.
        this.link_element = document.createElement('A');
        this.link_element.id="floatimglink";
        this.link_element.href="";
        this.link_element.appendChild(this.element);
        document.body.appendChild(this.link_element);
    }

    /**
     * Function to initiate loading of an image into the floating image container.
     * @param callback A function to call once the image has been loaded into cache. If given, then the image is displayed only once the image has been downloaded.
     */
    function LoadImg(url,callback) {
        if(callback){
            var img  = new Image();
            var that = this;
            img.onload = function(){
                /*message("New height="+this.height,true,false);
                message("New width="+this.width,true,true);*/
                that.height = this.height;
                that.width  = this.width;
                if(callback){
                    that.setSrc(url);
                    that.setLoaded(true);
                    callback();
                }
                return true;
            };
            img.onerror = function(){
                d('Image '+url+' could not be loaded...');
            };
            
            img.src = url;
            this.setLoaded(false);
        }
        else{
            this.clear(true);
            this.element.src = url;
            this.setLoaded(true);
        }
    }
    
    /**
     * Clears the image url and link, and hides the image element.
     */
    function Clear(onlyImg){
        //Clear the image.
        this.setLoaded(false);
        this.element.src = "";
        this.element.style.display='none';
        
        if(onlyImg==null || onlyImg == false){
            //Clear the link.
            this.link_url = "";
        }
    }
    
    function Show(){
        this.element.style.display = 'block';
    }
    
    /**
     * This function resizes and positions the floating preview image node.
     * The function assumes that the image is already loaded or is already loading.
     * TODO::Declare local variables for the "settings_*" variables inside the class.
     */
    function RepositionPreviewImage(){
        var pageWidth  = PageWidth();
        var pageHeight = PageHeight();
        var maxWidth, maxHeight;
        var mouseX, mouseY;
        var cursorPadding;
        mouseX = global_mouse_x;
        mouseY = global_mouse_y;
        cursorPadding = 20;
        
        //Now to compute the display size of the floating image.
        if(settings_auto_size){
            maxWidth  = Math.floor(Math.max(100,pageWidth  * settings_x_factor));
            maxHeight = Math.floor(Math.max(100,pageHeight * settings_y_factor));
        }
        else{
            maxWidth  = Math.floor(Math.max(settings_max_width ,1));
            maxHeight = Math.floor(Math.max(settings_max_height,1));
        }
        //We only set the maximum size for the image. This way, the image will keep its aspect ratio.
        this.setMaxWidth (maxWidth);
        this.setMaxHeight(maxHeight);
        
        //Reset the IMG position based on mouse position.
        if(settings_snap_to_cursor){
            var imgWidth=0, imgHeight=0, availableWidth=0, availableHeight=0;
            var displaySide = {horizontal: "right", vertical: "bottom"};
            var newTop = 0;
            
            //Find out the maximum available size in the x and y axes.
            availableWidth  = Math.max(pageWidth  - (int(mouseX) + int(cursorPadding) ), (mouseX - cursorPadding) );
            availableHeight = Math.max(pageHeight - (int(mouseY) + int(cursorPadding) ), (mouseY - cursorPadding) );
            
            //Resize the image container if the available area is not enough.
            this.setMaxWidth( Math.min(availableWidth,maxWidth) );
            //fio.setMaxHeight( Math.min(availableHeight,maxHeight) );
            
            //Do this after manipulating the image's max size. Width and Height are only available after.
            imgWidth  = this.getWidth();
            imgHeight = this.getHeight();
            
            //Choose which side of the cursor the image should be displayed.
            if( (int(imgWidth)+int(cursorPadding)) > (pageWidth-mouseX) ){
                displaySide.horizontal = "left";
            }
            /*if( (int(imgHeight)+int(cursorPadding)) > (pageHeight-mouseY) ){
                displaySide.vertical = "top";
            }*/
            
            //Now to position the image.
            //First, decide on the horizontal position for the image.
            if(displaySide.horizontal == "left"){
                this.setRight( int(mouseX) - int(cursorPadding) );
                this.setLeft( int(mouseX) - int(cursorPadding) - int(imgWidth) );
            }
            else if(displaySide.horizontal == "right"){
                this.setLeft( int(mouseX) +int(cursorPadding) );
                this.setRight( int(mouseX) + int(cursorPadding) + int(imgWidth) );
            }
            
            //Next, decide on the vertical position for the image.
            /*
             *Here's my logic with this one (after MANY trial-and-error!):
             * By default, we want the image to appear below the mouse cursor. So, we set the preview's top to the mouseY, plus some padding.
             * Next, we check if, given this new top position, the image will go further than the page's height.
             * If so, we pull up the image's top by the amount that it exceeds the page's height.
             * This will make the preview image's bottom exactly align with the page's bottom automatically.
             * TODO::Check if, after correcting the image's bottom, the image's top goes above the screen. If so, shrink the image by the overflowing amount.
             */
            newTop = int(mouseY) + int(cursorPadding);
            if(newTop + imgHeight > pageHeight){
                newTop = newTop - (newTop+imgHeight - pageHeight);
            }
            this.setTop( int(newTop) );
            
        }
        else{
            //If the image is set not to follow the cursor, choose which corner of the screen to position the image in.
            //First, decide on the horizontal corner for the image.
            if(mouseX < pageWidth/2){
                this.setLeft(); //Set to "auto"
                this.setRight(1);
            }
            else{
                this.setRight(); //Set to "auto"
                this.setLeft(1);
            }
            //Next, decide on the vertical corner for the image.
            if(mouseY < pageHeight/2){
                this.setTop(); //Set to "auto"
                this.setBottom(1);
            }
            else{
                this.setTop(1);
                this.setBottom(); //Set to "auto"
            }
        }
    }

    /**
     * Returns the page width in pixels
     */
    function PageWidth() {
        return window.innerWidth != null? window.innerWidth: document.body != null? document.body.clientWidth:null;
    }

    /**
     * Returns the page height in pixels
     */
    function PageHeight() {
        return window.innerHeight != null? window.innerHeight: document.body != null? document.body.clientHeight:null;
    }

    /**
     * Returns the integer representation of a string.
     */
    function int(str){
      return parseInt(str);
    }

    
    function SetSrc(src){
        this.element.src = src;
    }
    
    function GetId(){
        return this.id;
    }
    
    function SetLinkElement(elem){
        this.link_element = elem;
    }
    
    function SetLinkUrl(url){
        this.link_element.href = url;
    }
    
    function GetLinkUrl(){
        return this.link_element.href;
    }
    
    function GetImageElement(){
        return this.element;
    }
    
    function GetHeight(){
        return this.element.height;
    }
    
    function SetHeight(height){
        this.element.height = height;
    }
    
    function GetWidth(){
        return this.element.width;
    }
    
    function SetWidth(width){
        this.element.width = width;
    }
    
    function GetMaxWidth(){
        return this.element.style.maxWidth;
    }
    
    function SetMaxHeight(height){
        this.element.style.maxHeight = height+"px";
    }
    
    function GetMaxHeight(){
        return this.element.style.maxHeight;
    }
    
    function SetMaxWidth(width){
        this.element.style.maxWidth = width+"px";
    }
    
    function GetMaxWidth(){
        return this.element.style.maxWidth;
    }
    
    function GetTop(){
        return this.element.style.top;
    }
    
    function SetTop(top){
        if(top){
            this.element.style.top = top+"px";
        }
        else{
            this.element.style.top = "auto";
        }
    }
    
    function GetBottom(){
        return this.element.style.bottom;
    }
    
    function SetBottom(bottom){
        if(bottom){
            this.element.style.bottom = bottom+"px";
        }
        else{
            this.element.style.bottom = "auto";
        }
    }
    
    function GetLeft(){
        return this.element.style.left;
    }
    
    function SetLeft(left){
        if(left){
            this.element.style.left = left+"px";
        }
        else{
            this.element.style.left = "auto";
        }
    }
    
    function GetRight(){
        return this.element.style.right;
    }
    
    function SetRight(right){
        if(right){
            this.element.style.right = right+"px";
        }
        else{
            this.element.style.right = "auto";
        }
    }
    
    function GetLoaded(){
        return this.loaded;
    }
    
    function SetLoaded(loaded){
        this.loaded = loaded;
    }
}