text: /*
   * OpenSeadragon - Drawer
   *
   * 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 Drawer
   * @memberof OpenSeadragon
   * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
   * @param {Object} options - Options for this Drawer.
   * @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
   * @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
   * @param {Element} options.element - Parent element.
   * @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
   */
  $.Drawer = function( options ) {
  
      $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
  
      //backward compatibility for positional args while preferring more
      //idiomatic javascript options object as the only argument
      var args  = arguments;
  
      if( !$.isPlainObject( options ) ){
          options = {
              source:     args[ 0 ], // Reference to Viewer tile source.
              viewport:   args[ 1 ], // Reference to Viewer viewport.
              element:    args[ 2 ]  // Parent element.
          };
      }
  
      $.console.assert( options.viewport, "[Drawer] options.viewport is required" );
      $.console.assert( options.element, "[Drawer] options.element is required" );
  
      if ( options.source ) {
          $.console.error( "[Drawer] options.source is no longer accepted; use TiledImage instead" );
      }
  
      this.viewer = options.viewer;
      this.viewport = options.viewport;
      this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
      if (options.opacity) {
          $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
      }
  
      this.useCanvas  = $.supportsCanvas && ( this.viewer ? this.viewer.useCanvas : true );
      /**
       * The parent element of this Drawer instance, passed in when the Drawer was created.
       * The parent of {@link OpenSeadragon.Drawer#canvas}.
       * @member {Element} container
       * @memberof OpenSeadragon.Drawer#
       */
      this.container  = $.getElement( options.element );
      /**
       * A &lt;canvas&gt; element if the browser supports them, otherwise a &lt;div&gt; element.
       * Child element of {@link OpenSeadragon.Drawer#container}.
       * @member {Element} canvas
       * @memberof OpenSeadragon.Drawer#
       */
      this.canvas     = $.makeNeutralElement( this.useCanvas ? "canvas" : "div" );
      /**
       * 2d drawing context for {@link OpenSeadragon.Drawer#canvas} if it's a &lt;canvas&gt; element, otherwise null.
       * @member {Object} context
       * @memberof OpenSeadragon.Drawer#
       */
      this.context    = this.useCanvas ? this.canvas.getContext( "2d" ) : null;
  
      /**
       * Sketch canvas used to temporarily draw tiles which cannot be drawn directly
       * to the main canvas due to opacity. Lazily initialized.
       */
      this.sketchCanvas = null;
      this.sketchContext = null;
  
      /**
       * @member {Element} element
       * @memberof OpenSeadragon.Drawer#
       * @deprecated Alias for {@link OpenSeadragon.Drawer#container}.
       */
      this.element    = this.container;
  
      // We force our container to ltr because our drawing math doesn't work in rtl.
      // This issue only affects our canvas renderer, but we do it always for consistency.
      // Note that this means overlays you want to be rtl need to be explicitly set to rtl.
      this.container.dir = 'ltr';
  
      // check canvas available width and height, set canvas width and height such that the canvas backing store is set to the proper pixel density
      if (this.useCanvas) {
          var viewportSize = this._calculateCanvasSize();
          this.canvas.width = viewportSize.x;
          this.canvas.height = viewportSize.y;
      }
  
      this.canvas.style.width     = "100%";
      this.canvas.style.height    = "100%";
      this.canvas.style.position  = "absolute";
      $.setElementOpacity( this.canvas, this.opacity, true );
      // Allow pointer events to pass through the canvas element so implicit
      //   pointer capture works on touch devices
      $.setElementPointerEventsNone( this.canvas );
      $.setElementTouchActionNone( this.canvas );
  
      // explicit left-align
      this.container.style.textAlign = "left";
      this.container.appendChild( this.canvas );
  
      // Image smoothing for canvas rendering (only if canvas is used).
      // Canvas default is "true", so this will only be changed if user specified "false".
      this._imageSmoothingEnabled = true;
  };
  
  /** @lends OpenSeadragon.Drawer.prototype */
  $.Drawer.prototype = {
      // deprecated
      addOverlay: function( element, location, placement, onDraw ) {
          $.console.error("drawer.addOverlay is deprecated. Use viewer.addOverlay instead.");
          this.viewer.addOverlay( element, location, placement, onDraw );
          return this;
      },
  
      // deprecated
      updateOverlay: function( element, location, placement ) {
          $.console.error("drawer.updateOverlay is deprecated. Use viewer.updateOverlay instead.");
          this.viewer.updateOverlay( element, location, placement );
          return this;
      },
  
      // deprecated
      removeOverlay: function( element ) {
          $.console.error("drawer.removeOverlay is deprecated. Use viewer.removeOverlay instead.");
          this.viewer.removeOverlay( element );
          return this;
      },
  
      // deprecated
      clearOverlays: function() {
          $.console.error("drawer.clearOverlays is deprecated. Use viewer.clearOverlays instead.");
          this.viewer.clearOverlays();
          return this;
      },
  
      /**
       * This function converts the given point from to the drawer coordinate by
       * multiplying it with the pixel density.
       * This function does not take rotation into account, thus assuming provided
       * point is at 0 degree.
       * @param {OpenSeadragon.Point} point - the pixel point to convert
       */
      viewportCoordToDrawerCoord: function(point) {
          var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
          return new $.Point(
              vpPoint.x * $.pixelDensityRatio,
              vpPoint.y * $.pixelDensityRatio
          );
      },
  
      /**
       * This function will create multiple polygon paths on the drawing context by provided polygons,
       * then clip the context to the paths.
       * @param {OpenSeadragon.Point[][]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
       * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
       */
      clipWithPolygons: function (polygons, useSketch) {
          if (!this.useCanvas) {
              return;
          }
          var context = this._getContext(useSketch);
          context.beginPath();
          polygons.forEach(function (polygon) {
              polygon.forEach(function (coord, i) {
                  context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
            });
          });
          context.clip();
      },
  
      /**
       * Set the opacity of the drawer.
       * @param {Number} opacity
       * @return {OpenSeadragon.Drawer} Chainable.
       */
      setOpacity: function( opacity ) {
          $.console.error("drawer.setOpacity is deprecated. Use tiledImage.setOpacity instead.");
          var world = this.viewer.world;
          for (var i = 0; i < world.getItemCount(); i++) {
              world.getItemAt( i ).setOpacity( opacity );
          }
          return this;
      },
  
      /**
       * Get the opacity of the drawer.
       * @returns {Number}
       */
      getOpacity: function() {
          $.console.error("drawer.getOpacity is deprecated. Use tiledImage.getOpacity instead.");
          var world = this.viewer.world;
          var maxOpacity = 0;
          for (var i = 0; i < world.getItemCount(); i++) {
              var opacity = world.getItemAt( i ).getOpacity();
              if ( opacity > maxOpacity ) {
                  maxOpacity = opacity;
              }
          }
          return maxOpacity;
      },
  
      // deprecated
      needsUpdate: function() {
          $.console.error( "[Drawer.needsUpdate] this function is deprecated. Use World.needsDraw instead." );
          return this.viewer.world.needsDraw();
      },
  
      // deprecated
      numTilesLoaded: function() {
          $.console.error( "[Drawer.numTilesLoaded] this function is deprecated. Use TileCache.numTilesLoaded instead." );
          return this.viewer.tileCache.numTilesLoaded();
      },
  
      // deprecated
      reset: function() {
          $.console.error( "[Drawer.reset] this function is deprecated. Use World.resetItems instead." );
          this.viewer.world.resetItems();
          return this;
      },
  
      // deprecated
      update: function() {
          $.console.error( "[Drawer.update] this function is deprecated. Use Drawer.clear and World.draw instead." );
          this.clear();
          this.viewer.world.draw();
          return this;
      },
  
      /**
       * @return {Boolean} True if rotation is supported.
       */
      canRotate: function() {
          return this.useCanvas;
      },
  
      /**
       * Destroy the drawer (unload current loaded tiles)
       */
      destroy: function() {
          //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
          this.canvas.width  = 1;
          this.canvas.height = 1;
          this.sketchCanvas = null;
          this.sketchContext = null;
      },
  
      /**
       * Clears the Drawer so it's ready to draw another frame.
       */
      clear: function() {
          this.canvas.innerHTML = "";
          if ( this.useCanvas ) {
              var viewportSize = this._calculateCanvasSize();
              if( this.canvas.width !== viewportSize.x ||
                  this.canvas.height !== viewportSize.y ) {
                  this.canvas.width = viewportSize.x;
                  this.canvas.height = viewportSize.y;
                  this._updateImageSmoothingEnabled(this.context);
                  if ( this.sketchCanvas !== null ) {
                      var sketchCanvasSize = this._calculateSketchCanvasSize();
                      this.sketchCanvas.width = sketchCanvasSize.x;
                      this.sketchCanvas.height = sketchCanvasSize.y;
                      this._updateImageSmoothingEnabled(this.sketchContext);
                  }
              }
              this._clear();
          }
      },
  
      _clear: function (useSketch, bounds) {
          if (!this.useCanvas) {
              return;
          }
          var context = this._getContext(useSketch);
          if (bounds) {
              context.clearRect(bounds.x, bounds.y, bounds.width, bounds.height);
          } else {
              var canvas = context.canvas;
              context.clearRect(0, 0, canvas.width, canvas.height);
          }
      },
  
      /**
       * Scale from OpenSeadragon viewer rectangle to drawer rectangle
       * (ignoring rotation)
       * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system.
       * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system.
       */
      viewportToDrawerRectangle: function(rectangle) {
          var topLeft = this.viewport.pixelFromPointNoRotate(rectangle.getTopLeft(), true);
          var size = this.viewport.deltaPixelsFromPointsNoRotate(rectangle.getSize(), true);
  
          return new $.Rect(
              topLeft.x * $.pixelDensityRatio,
              topLeft.y * $.pixelDensityRatio,
              size.x * $.pixelDensityRatio,
              size.y * $.pixelDensityRatio
          );
      },
  
      /**
       * Draws the given tile.
       * @param {OpenSeadragon.Tile} tile - The tile to draw.
       * @param {Function} drawingHandler - Method for firing the drawing event if using canvas.
       * drawingHandler({context, tile, rendered})
       * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
       * where <code>rendered</code> is the context with the pre-drawn image.
       * @param {Float} [scale=1] - Apply a scale to tile position and size. Defaults to 1.
       * @param {OpenSeadragon.Point} [translate] A translation vector to offset tile position
       * @param {Boolean} [shouldRoundPositionAndSize] - Tells whether to round
       * position and size of tiles supporting alpha channel in non-transparency
       * context.
       */
      drawTile: function(tile, drawingHandler, useSketch, scale, translate, shouldRoundPositionAndSize) {
          $.console.assert(tile, '[Drawer.drawTile] tile is required');
          $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
  
          if (this.useCanvas) {
              var context = this._getContext(useSketch);
              scale = scale || 1;
              tile.drawCanvas(context, drawingHandler, scale, translate, shouldRoundPositionAndSize);
          } else {
              tile.drawHTML( this.canvas );
          }
      },
  
      _getContext: function( useSketch ) {
          var context = this.context;
          if ( useSketch ) {
              if (this.sketchCanvas === null) {
                  this.sketchCanvas = document.createElement( "canvas" );
                  var sketchCanvasSize = this._calculateSketchCanvasSize();
                  this.sketchCanvas.width = sketchCanvasSize.x;
                  this.sketchCanvas.height = sketchCanvasSize.y;
                  this.sketchContext = this.sketchCanvas.getContext( "2d" );
  
                  // If the viewport is not currently rotated, the sketchCanvas
                  // will have the same size as the main canvas. However, if
                  // the viewport get rotated later on, we will need to resize it.
                  if (this.viewport.getRotation() === 0) {
                      var self = this;
                      this.viewer.addHandler('rotate', function resizeSketchCanvas() {
                          if (self.viewport.getRotation() === 0) {
                              return;
                          }
                          self.viewer.removeHandler('rotate', resizeSketchCanvas);
                          var sketchCanvasSize = self._calculateSketchCanvasSize();
                          self.sketchCanvas.width = sketchCanvasSize.x;
                          self.sketchCanvas.height = sketchCanvasSize.y;
                      });
                  }
                  this._updateImageSmoothingEnabled(this.sketchContext);
              }
              context = this.sketchContext;
          }
          return context;
      },
  
      // private
      saveContext: function( useSketch ) {
          if (!this.useCanvas) {
              return;
          }
  
          this._getContext( useSketch ).save();
      },
  
      // private
      restoreContext: function( useSketch ) {
          if (!this.useCanvas) {
              return;
          }
  
          this._getContext( useSketch ).restore();
      },
  
      // private
      setClip: function(rect, useSketch) {
          if (!this.useCanvas) {
              return;
          }
  
          var context = this._getContext( useSketch );
          context.beginPath();
          context.rect(rect.x, rect.y, rect.width, rect.height);
          context.clip();
      },
  
      // private
      drawRectangle: function(rect, fillStyle, useSketch) {
          if (!this.useCanvas) {
              return;
          }
  
          var context = this._getContext( useSketch );
          context.save();
          context.fillStyle = fillStyle;
          context.fillRect(rect.x, rect.y, rect.width, rect.height);
          context.restore();
      },
  
      /**
       * Blends the sketch canvas in the main canvas.
       * @param {Object} options The options
       * @param {Float} options.opacity The opacity of the blending.
       * @param {Float} [options.scale=1] The scale at which tiles were drawn on
       * the sketch. Default is 1.
       * Use scale to draw at a lower scale and then enlarge onto the main canvas.
       * @param {OpenSeadragon.Point} [options.translate] A translation vector
       * that was used to draw the tiles
       * @param {String} [options.compositeOperation] - How the image is
       * composited onto other images; see compositeOperation in
       * {@link OpenSeadragon.Options} for possible values.
       * @param {OpenSeadragon.Rect} [options.bounds] The part of the sketch
       * canvas to blend in the main canvas. If specified, options.scale and
       * options.translate get ignored.
       */
      blendSketch: function(opacity, scale, translate, compositeOperation) {
          var options = opacity;
          if (!$.isPlainObject(options)) {
              options = {
                  opacity: opacity,
                  scale: scale,
                  translate: translate,
                  compositeOperation: compositeOperation
              };
          }
          if (!this.useCanvas || !this.sketchCanvas) {
              return;
          }
          opacity = options.opacity;
          compositeOperation = options.compositeOperation;
          var bounds = options.bounds;
  
          this.context.save();
          this.context.globalAlpha = opacity;
          if (compositeOperation) {
              this.context.globalCompositeOperation = compositeOperation;
          }
          if (bounds) {
              // Internet Explorer, Microsoft Edge, and Safari have problems
              // when you call context.drawImage with negative x or y
              // or x + width or y + height greater than the canvas width or height respectively.
              if (bounds.x < 0) {
                  bounds.width += bounds.x;
                  bounds.x = 0;
              }
              if (bounds.x + bounds.width > this.canvas.width) {
                  bounds.width = this.canvas.width - bounds.x;
              }
              if (bounds.y < 0) {
                  bounds.height += bounds.y;
                  bounds.y = 0;
              }
              if (bounds.y + bounds.height > this.canvas.height) {
                  bounds.height = this.canvas.height - bounds.y;
              }
  
              this.context.drawImage(
                  this.sketchCanvas,
                  bounds.x,
                  bounds.y,
                  bounds.width,
                  bounds.height,
                  bounds.x,
                  bounds.y,
                  bounds.width,
                  bounds.height
              );
          } else {
              scale = options.scale || 1;
              translate = options.translate;
              var position = translate instanceof $.Point ?
                  translate : new $.Point(0, 0);
  
              var widthExt = 0;
              var heightExt = 0;
              if (translate) {
                  var widthDiff = this.sketchCanvas.width - this.canvas.width;
                  var heightDiff = this.sketchCanvas.height - this.canvas.height;
                  widthExt = Math.round(widthDiff / 2);
                  heightExt = Math.round(heightDiff / 2);
              }
              this.context.drawImage(
                  this.sketchCanvas,
                  position.x - widthExt * scale,
                  position.y - heightExt * scale,
                  (this.canvas.width + 2 * widthExt) * scale,
                  (this.canvas.height + 2 * heightExt) * scale,
                  -widthExt,
                  -heightExt,
                  this.canvas.width + 2 * widthExt,
                  this.canvas.height + 2 * heightExt
              );
          }
          this.context.restore();
      },
  
      // private
      drawDebugInfo: function(tile, count, i, tiledImage) {
          if ( !this.useCanvas ) {
              return;
          }
  
          var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
          var context = this.context;
          context.save();
          context.lineWidth = 2 * $.pixelDensityRatio;
          context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
          context.strokeStyle = this.debugGridColor[colorIndex];
          context.fillStyle = this.debugGridColor[colorIndex];
  
          if (this.viewport.getRotation(true) % 360 !== 0 ) {
              this._offsetForRotation({degrees: this.viewport.getRotation(true)});
          }
          if (tiledImage.getRotation(true) % 360 !== 0) {
              this._offsetForRotation({
                  degrees: tiledImage.getRotation(true),
                  point: tiledImage.viewport.pixelFromPointNoRotate(
                      tiledImage._getRotationPoint(true), true)
              });
          }
          if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
              tiledImage.getRotation(true) % 360 === 0) {
              if(tiledImage._drawer.viewer.viewport.getFlip()) {
                  tiledImage._drawer._flip();
              }
          }
  
          context.strokeRect(
              tile.position.x * $.pixelDensityRatio,
              tile.position.y * $.pixelDensityRatio,
              tile.size.x * $.pixelDensityRatio,
              tile.size.y * $.pixelDensityRatio
          );
  
          var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio;
          var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio;
  
          // Rotate the text the right way around.
          context.translate( tileCenterX, tileCenterY );
          context.rotate( Math.PI / 180 * -this.viewport.getRotation(true) );
          context.translate( -tileCenterX, -tileCenterY );
  
          if( tile.x === 0 && tile.y === 0 ){
              context.fillText(
                  "Zoom: " + this.viewport.getZoom(),
                  tile.position.x * $.pixelDensityRatio,
                  (tile.position.y - 30) * $.pixelDensityRatio
              );
              context.fillText(
                  "Pan: " + this.viewport.getBounds().toString(),
                  tile.position.x * $.pixelDensityRatio,
                  (tile.position.y - 20) * $.pixelDensityRatio
              );
          }
          context.fillText(
              "Level: " + tile.level,
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 20) * $.pixelDensityRatio
          );
          context.fillText(
              "Column: " + tile.x,
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 30) * $.pixelDensityRatio
          );
          context.fillText(
              "Row: " + tile.y,
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 40) * $.pixelDensityRatio
          );
          context.fillText(
              "Order: " + i + " of " + count,
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 50) * $.pixelDensityRatio
          );
          context.fillText(
              "Size: " + tile.size.toString(),
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 60) * $.pixelDensityRatio
          );
          context.fillText(
              "Position: " + tile.position.toString(),
              (tile.position.x + 10) * $.pixelDensityRatio,
              (tile.position.y + 70) * $.pixelDensityRatio
          );
  
          if (this.viewport.getRotation(true) % 360 !== 0 ) {
              this._restoreRotationChanges();
          }
          if (tiledImage.getRotation(true) % 360 !== 0) {
              this._restoreRotationChanges();
          }
  
          if (tiledImage.viewport.getRotation(true) % 360 === 0 &&
              tiledImage.getRotation(true) % 360 === 0) {
              if(tiledImage._drawer.viewer.viewport.getFlip()) {
                  tiledImage._drawer._flip();
              }
          }
  
          context.restore();
      },
  
      // private
      debugRect: function(rect) {
          if ( this.useCanvas ) {
              var context = this.context;
              context.save();
              context.lineWidth = 2 * $.pixelDensityRatio;
              context.strokeStyle = this.debugGridColor[0];
              context.fillStyle = this.debugGridColor[0];
  
              context.strokeRect(
                  rect.x * $.pixelDensityRatio,
                  rect.y * $.pixelDensityRatio,
                  rect.width * $.pixelDensityRatio,
                  rect.height * $.pixelDensityRatio
              );
  
              context.restore();
          }
      },
  
      /**
       * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
       *
       * @function
       * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
       * drawn smoothly on the canvas; see imageSmoothingEnabled in
       * {@link OpenSeadragon.Options} for more explanation.
       */
      setImageSmoothingEnabled: function(imageSmoothingEnabled){
          if ( this.useCanvas ) {
              this._imageSmoothingEnabled = imageSmoothingEnabled;
              this._updateImageSmoothingEnabled(this.context);
              this.viewer.forceRedraw();
          }
      },
  
      // private
      _updateImageSmoothingEnabled: function(context){
          context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
          context.imageSmoothingEnabled = this._imageSmoothingEnabled;
      },
  
      /**
       * Get the canvas size
       * @param {Boolean} sketch If set to true return the size of the sketch canvas
       * @returns {OpenSeadragon.Point} The size of the canvas
       */
      getCanvasSize: function(sketch) {
          var canvas = this._getContext(sketch).canvas;
          return new $.Point(canvas.width, canvas.height);
      },
  
      getCanvasCenter: function() {
          return new $.Point(this.canvas.width / 2, this.canvas.height / 2);
      },
  
      // private
      _offsetForRotation: function(options) {
          var point = options.point ?
              options.point.times($.pixelDensityRatio) :
              this.getCanvasCenter();
  
          var context = this._getContext(options.useSketch);
          context.save();
  
          context.translate(point.x, point.y);
          if(this.viewer.viewport.flipped){
            context.rotate(Math.PI / 180 * -options.degrees);
            context.scale(-1, 1);
          } else{
            context.rotate(Math.PI / 180 * options.degrees);
          }
          context.translate(-point.x, -point.y);
      },
  
      // private
      _flip: function(options) {
        options = options || {};
        var point = options.point ?
          options.point.times($.pixelDensityRatio) :
          this.getCanvasCenter();
        var context = this._getContext(options.useSketch);
  
        context.translate(point.x, 0);
        context.scale(-1, 1);
        context.translate(-point.x, 0);
      },
  
      // private
      _restoreRotationChanges: function(useSketch) {
          var context = this._getContext(useSketch);
          context.restore();
      },
  
      // private
      _calculateCanvasSize: function() {
          var pixelDensityRatio = $.pixelDensityRatio;
          var viewportSize = this.viewport.getContainerSize();
          return {
              // canvas width and height are integers
              x: Math.round(viewportSize.x * pixelDensityRatio),
              y: Math.round(viewportSize.y * pixelDensityRatio)
          };
      },
  
      // private
      _calculateSketchCanvasSize: function() {
          var canvasSize = this._calculateCanvasSize();
          if (this.viewport.getRotation() === 0) {
              return canvasSize;
          }
          // If the viewport is rotated, we need a larger sketch canvas in order
          // to support edge smoothing.
          var sketchCanvasSize = Math.ceil(Math.sqrt(
              canvasSize.x * canvasSize.x +
              canvasSize.y * canvasSize.y));
          return {
              x: sketchCanvasSize,
              y: sketchCanvasSize
          };
      }
  };
  
  }( OpenSeadragon ));
  