text: /*
   * OpenSeadragon - Tile
   *
   * Copyright (C) 2009 CodePlex Foundation
   * Copyright (C) 2010-2013 OpenSeadragon contributors
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   * - Redistributions of source code must retain the above copyright notice,
   *   this list of conditions and the following disclaimer.
   *
   * - Redistributions in binary form must reproduce the above copyright
   *   notice, this list of conditions and the following disclaimer in the
   *   documentation and/or other materials provided with the distribution.
   *
   * - Neither the name of CodePlex Foundation nor the names of its
   *   contributors may be used to endorse or promote products derived from
   *   this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  (function( $ ){
  
  /**
   * @class Tile
   * @memberof OpenSeadragon
   * @param {Number} level The zoom level this tile belongs to.
   * @param {Number} x The vector component 'x'.
   * @param {Number} y The vector component 'y'.
   * @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized
   *      coordinates.
   * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
   *      this tile failed to load? )
   * @param {String} url The URL of this tile's image.
   * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
   *      is provided directly by the tile source.
   * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
   * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
   * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
   *      drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
   *      with HTML the entire tile is always used.
   * @param {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
   *      see TileSource::getPostData) or null
   * @param {String} cacheKey key to act as a tile cache, must be unique for tiles with unique image data
   */
  $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds, postData, cacheKey) {
      /**
       * The zoom level this tile belongs to.
       * @member {Number} level
       * @memberof OpenSeadragon.Tile#
       */
      this.level   = level;
      /**
       * The vector component 'x'.
       * @member {Number} x
       * @memberof OpenSeadragon.Tile#
       */
      this.x       = x;
      /**
       * The vector component 'y'.
       * @member {Number} y
       * @memberof OpenSeadragon.Tile#
       */
      this.y       = y;
      /**
       * Where this tile fits, in normalized coordinates
       * @member {OpenSeadragon.Rect} bounds
       * @memberof OpenSeadragon.Tile#
       */
      this.bounds  = bounds;
      /**
       * The portion of the tile to use as the source of the drawing operation, in pixels. Note that
       * this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
       * @member {OpenSeadragon.Rect} sourceBounds
       * @memberof OpenSeadragon.Tile#
       */
      this.sourceBounds = sourceBounds;
      /**
       * Is this tile a part of a sparse image? Also has this tile failed to load?
       * @member {Boolean} exists
       * @memberof OpenSeadragon.Tile#
       */
      this.exists  = exists;
      /**
       * The URL of this tile's image.
       * @member {String} url
       * @memberof OpenSeadragon.Tile#
       */
      this.url     = url;
      /**
       * Post parameters for this tile. For example, it can be an URL-encoded string
       * in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used
       * @member {String} postData HTTP POST data (usually but not necessarily in k=v&k2=v2... form,
       *      see TileSource::getPostData) or null
       * @memberof OpenSeadragon.Tile#
       */
      this.postData  = postData;
      /**
       * The context2D of this tile if it is provided directly by the tile source.
       * @member {CanvasRenderingContext2D} context2D
       * @memberOf OpenSeadragon.Tile#
       */
      this.context2D = context2D;
      /**
       * Whether to load this tile's image with an AJAX request.
       * @member {Boolean} loadWithAjax
       * @memberof OpenSeadragon.Tile#
       */
      this.loadWithAjax = loadWithAjax;
      /**
       * The headers to be used in requesting this tile's image.
       * Only used if loadWithAjax is set to true.
       * @member {Object} ajaxHeaders
       * @memberof OpenSeadragon.Tile#
       */
      this.ajaxHeaders = ajaxHeaders;
  
      if (cacheKey === undefined) {
          $.console.error("Tile constructor needs 'cacheKey' variable: creation tile cache" +
              " in Tile class is deprecated. TileSource.prototype.getTileHashKey will be used.");
          cacheKey = $.TileSource.prototype.getTileHashKey(level, x, y, url, ajaxHeaders, postData);
      }
      /**
       * The unique cache key for this tile.
       * @member {String} cacheKey
       * @memberof OpenSeadragon.Tile#
       */
      this.cacheKey = cacheKey;
      /**
       * Is this tile loaded?
       * @member {Boolean} loaded
       * @memberof OpenSeadragon.Tile#
       */
      this.loaded  = false;
      /**
       * Is this tile loading?
       * @member {Boolean} loading
       * @memberof OpenSeadragon.Tile#
       */
      this.loading = false;
  
      /**
       * The HTML div element for this tile
       * @member {Element} element
       * @memberof OpenSeadragon.Tile#
       */
      this.element    = null;
      /**
       * The HTML img element for this tile.
       * @member {Element} imgElement
       * @memberof OpenSeadragon.Tile#
       */
      this.imgElement = null;
  
      /**
       * The alias of this.element.style.
       * @member {String} style
       * @memberof OpenSeadragon.Tile#
       */
      this.style      = null;
      /**
       * This tile's position on screen, in pixels.
       * @member {OpenSeadragon.Point} position
       * @memberof OpenSeadragon.Tile#
       */
      this.position   = null;
      /**
       * This tile's size on screen, in pixels.
       * @member {OpenSeadragon.Point} size
       * @memberof OpenSeadragon.Tile#
       */
      this.size       = null;
      /**
       * Whether to flip the tile when rendering.
       * @member {Boolean} flipped
       * @memberof OpenSeadragon.Tile#
       */
      this.flipped    = false;
      /**
       * The start time of this tile's blending.
       * @member {Number} blendStart
       * @memberof OpenSeadragon.Tile#
       */
      this.blendStart = null;
      /**
       * The current opacity this tile should be.
       * @member {Number} opacity
       * @memberof OpenSeadragon.Tile#
       */
      this.opacity    = null;
      /**
       * The squared distance of this tile to the viewport center.
       * Use for comparing tiles.
       * @private
       * @member {Number} squaredDistance
       * @memberof OpenSeadragon.Tile#
       */
      this.squaredDistance   = null;
      /**
       * The visibility score of this tile.
       * @member {Number} visibility
       * @memberof OpenSeadragon.Tile#
       */
      this.visibility = null;
  
      /**
       * The transparency indicator of this tile.
       * @member {Boolean} hasTransparency true if tile contains transparency for correct rendering
       * @memberof OpenSeadragon.Tile#
       */
      this.hasTransparency = false;
  
      /**
       * Whether this tile is currently being drawn.
       * @member {Boolean} beingDrawn
       * @memberof OpenSeadragon.Tile#
       */
      this.beingDrawn     = false;
  
      /**
       * Timestamp the tile was last touched.
       * @member {Number} lastTouchTime
       * @memberof OpenSeadragon.Tile#
       */
      this.lastTouchTime  = 0;
  
      /**
       * Whether this tile is in the right-most column for its level.
       * @member {Boolean} isRightMost
       * @memberof OpenSeadragon.Tile#
       */
      this.isRightMost = false;
  
      /**
       * Whether this tile is in the bottom-most row for its level.
       * @member {Boolean} isBottomMost
       * @memberof OpenSeadragon.Tile#
       */
      this.isBottomMost = false;
  };
  
  /** @lends OpenSeadragon.Tile.prototype */
  $.Tile.prototype = {
  
      /**
       * Provides a string representation of this tiles level and (x,y)
       * components.
       * @function
       * @returns {String}
       */
      toString: function() {
          return this.level + "/" + this.x + "_" + this.y;
      },
  
      // private
      _hasTransparencyChannel: function() {
          console.warn("Tile.prototype._hasTransparencyChannel() has been " +
              "deprecated and will be removed in the future. Use TileSource.prototype.hasTransparency() instead.");
          return !!this.context2D || this.url.match('.png');
      },
  
      /**
       * Renders the tile in an html container.
       * @function
       * @param {Element} container
       */
      drawHTML: function( container ) {
          if (!this.cacheImageRecord) {
              $.console.warn(
                  '[Tile.drawHTML] attempting to draw tile %s when it\'s not cached',
                  this.toString());
              return;
          }
  
          if ( !this.loaded ) {
              $.console.warn(
                  "Attempting to draw tile %s when it's not yet loaded.",
                  this.toString()
              );
              return;
          }
  
          //EXPERIMENTAL - trying to figure out how to scale the container
          //               content during animation of the container size.
  
          if ( !this.element ) {
              var image = this.getImage();
              if (!image) {
                  return;
              }
  
              this.element                              = $.makeNeutralElement( "div" );
              this.imgElement                           = image.cloneNode();
              this.imgElement.style.msInterpolationMode = "nearest-neighbor";
              this.imgElement.style.width               = "100%";
              this.imgElement.style.height              = "100%";
  
              this.style                     = this.element.style;
              this.style.position            = "absolute";
          }
          if ( this.element.parentNode !== container ) {
              container.appendChild( this.element );
          }
          if ( this.imgElement.parentNode !== this.element ) {
              this.element.appendChild( this.imgElement );
          }
  
          this.style.top     = this.position.y + "px";
          this.style.left    = this.position.x + "px";
          this.style.height  = this.size.y + "px";
          this.style.width   = this.size.x + "px";
  
          if (this.flipped) {
              this.style.transform = "scaleX(-1)";
          }
  
          $.setElementOpacity( this.element, this.opacity );
      },
  
      /**
       * The Image object for this tile.
       * @member {Object} image
       * @memberof OpenSeadragon.Tile#
       * @deprecated
       * @return {Image}
       */
      get image() {
          $.console.error("[Tile.image] property has been deprecated. Use [Tile.prototype.getImage] instead.");
          return this.getImage();
      },
  
      /**
       * Get the Image object for this tile.
       * @return {Image}
       */
      getImage: function() {
          return this.cacheImageRecord.getImage();
      },
  
      /**
       * Get the CanvasRenderingContext2D instance for tile image data drawn
       * onto Canvas if enabled and available
       * @return {CanvasRenderingContext2D}
       */
      getCanvasContext: function() {
          return this.context2D || this.cacheImageRecord.getRenderedContext();
      },
  
      /**
       * Renders the tile in a canvas-based context.
       * @function
       * @param {Canvas} context
       * @param {Function} drawingHandler - Method for firing the drawing event.
       * drawingHandler({context, tile, rendered})
       * where <code>rendered</code> is the context with the pre-drawn image.
       * @param {Number} [scale=1] - Apply a scale to position and size
       * @param {OpenSeadragon.Point} [translate] - A translation vector
       * @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
       * position and size of tiles supporting alpha channel in non-transparency
       * context.
       * @param {OpenSeadragon.TileSource} source - The source specification of the tile.
       */
      drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) {
  
          var position = this.position.times($.pixelDensityRatio),
              size     = this.size.times($.pixelDensityRatio),
              rendered;
  
          if (!this.context2D && !this.cacheImageRecord) {
              $.console.warn(
                  '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached',
                  this.toString());
              return;
          }
  
          rendered = this.getCanvasContext();
  
          if ( !this.loaded || !rendered ){
              $.console.warn(
                  "Attempting to draw tile %s when it's not yet loaded.",
                  this.toString()
              );
  
              return;
          }
  
          context.save();
          context.globalAlpha = this.opacity;
  
          if (typeof scale === 'number' && scale !== 1) {
              // draw tile at a different scale
              position = position.times(scale);
              size = size.times(scale);
          }
  
          if (translate instanceof $.Point) {
              // shift tile position slightly
              position = position.plus(translate);
          }
  
          //if we are supposed to be rendering fully opaque rectangle,
          //ie its done fading or fading is turned off, and if we are drawing
          //an image with an alpha channel, then the only way
          //to avoid seeing the tile underneath is to clear the rectangle
          if (context.globalAlpha === 1 && this.hasTransparency) {
              if (shouldRoundPositionAndSize) {
                  // Round to the nearest whole pixel so we don't get seams from overlap.
                  position.x = Math.round(position.x);
                  position.y = Math.round(position.y);
                  size.x = Math.round(size.x);
                  size.y = Math.round(size.y);
              }
  
              //clearing only the inside of the rectangle occupied
              //by the png prevents edge flikering
              context.clearRect(
                  position.x,
                  position.y,
                  size.x,
                  size.y
              );
          }
  
          // This gives the application a chance to make image manipulation
          // changes as we are rendering the image
          drawingHandler({context: context, tile: this, rendered: rendered});
  
          var sourceWidth, sourceHeight;
          if (this.sourceBounds) {
              sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
              sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
          } else {
              sourceWidth = rendered.canvas.width;
              sourceHeight = rendered.canvas.height;
          }
  
          context.translate(position.x + size.x / 2, 0);
          if (this.flipped) {
              context.scale(-1, 1);
          }
          context.drawImage(
              rendered.canvas,
              0,
              0,
              sourceWidth,
              sourceHeight,
              -size.x / 2,
              position.y,
              size.x,
              size.y
          );
  
          context.restore();
      },
  
      /**
       * Get the ratio between current and original size.
       * @function
       * @return {Float}
       */
      getScaleForEdgeSmoothing: function() {
          var context;
          if (this.cacheImageRecord) {
              context = this.cacheImageRecord.getRenderedContext();
          } else if (this.context2D) {
              context = this.context2D;
          } else {
              $.console.warn(
                  '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
                  this.toString());
              return 1;
          }
          return context.canvas.width / (this.size.x * $.pixelDensityRatio);
      },
  
      /**
       * Get a translation vector that when applied to the tile position produces integer coordinates.
       * Needed to avoid swimming and twitching.
       * @function
       * @param {Number} [scale=1] - Scale to be applied to position.
       * @return {OpenSeadragon.Point}
       */
      getTranslationForEdgeSmoothing: function(scale, canvasSize, sketchCanvasSize) {
          // The translation vector must have positive values, otherwise the image goes a bit off
          // the sketch canvas to the top and left and we must use negative coordinates to repaint it
          // to the main canvas. In that case, some browsers throw:
          // INDEX_SIZE_ERR: DOM Exception 1: Index or size was negative, or greater than the allowed value.
          var x = Math.max(1, Math.ceil((sketchCanvasSize.x - canvasSize.x) / 2));
          var y = Math.max(1, Math.ceil((sketchCanvasSize.y - canvasSize.y) / 2));
          return new $.Point(x, y).minus(
              this.position
                  .times($.pixelDensityRatio)
                  .times(scale || 1)
                  .apply(function(x) {
                      return x % 1;
                  })
          );
      },
  
      /**
       * Removes tile from its container.
       * @function
       */
      unload: function() {
          if ( this.imgElement && this.imgElement.parentNode ) {
              this.imgElement.parentNode.removeChild( this.imgElement );
          }
          if ( this.element && this.element.parentNode ) {
              this.element.parentNode.removeChild( this.element );
          }
  
          this.element    = null;
          this.imgElement = null;
          this.loaded     = false;
          this.loading    = false;
      }
  };
  
  }( OpenSeadragon ));
  