From 8c4fcc9ca9289b54f3dd77155ff0b3888a3847e7 Mon Sep 17 00:00:00 2001
From: Petar Petrov <ppetrov@bluebuffstudio.com>
Date: Wed, 4 Nov 2015 17:04:50 +0200
Subject: [PATCH 1/5] tile edge smoothing at high zoom - #755

---
 src/drawer.js        | 24 ++++++++++++++----
 src/openseadragon.js |  6 +++++
 src/tile.js          | 59 +++++++++++++++++++++++++++++++++++---------
 src/tiledimage.js    | 44 ++++++++++++++++++++++-----------
 src/viewer.js        |  3 ++-
 5 files changed, 104 insertions(+), 32 deletions(-)

diff --git a/src/drawer.js b/src/drawer.js
index 9b74b981..5666370d 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -290,8 +290,9 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
      * 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 - Apply a scale to tile position and size
      */
-    drawTile: function( tile, drawingHandler, useSketch ) {
+    drawTile: function( tile, drawingHandler, useSketch, scale ) {
         $.console.assert(tile, '[Drawer.drawTile] tile is required');
         $.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler is required');
 
@@ -301,10 +302,10 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
             // specifically, don't save,rotate,restore every time we draw a tile
             if( this.viewport.degrees !== 0 ) {
                 this._offsetForRotation( tile, this.viewport.degrees, useSketch );
-                tile.drawCanvas( context, drawingHandler );
+                tile.drawCanvas( context, drawingHandler, scale );
                 this._restoreRotationChanges( tile, useSketch );
             } else {
-                tile.drawCanvas( context, drawingHandler );
+                tile.drawCanvas( context, drawingHandler, scale );
             }
         } else {
             tile.drawHTML( this.canvas );
@@ -371,16 +372,29 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
     /**
      * Blends the sketch canvas in the main canvas.
      * @param {Float} opacity The opacity of the blending.
+     * @param {Float} sketchScale The scale at which tiles were drawn on the sketch. Default is 1.
+     *   Use sketchScale to draw at a lower scale and then enlarge onto the main canvas.
      * @returns {undefined}
      */
-    blendSketch: function(opacity) {
+    blendSketch: function(opacity, sketchScale) {
         if (!this.useCanvas || !this.sketchCanvas) {
             return;
         }
+        sketchScale = sketchScale || 1;
 
         this.context.save();
         this.context.globalAlpha = opacity;
-        this.context.drawImage(this.sketchCanvas, 0, 0);
+        this.context.drawImage(
+            this.sketchCanvas,
+            0,
+            0,
+            this.sketchCanvas.width * sketchScale,
+            this.sketchCanvas.height * sketchScale,
+            0,
+            0,
+            this.canvas.width,
+            this.canvas.height
+        );
         this.context.restore();
     },
 
diff --git a/src/openseadragon.js b/src/openseadragon.js
index 6b36327b..df4dd55f 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -249,6 +249,11 @@
   *     image though it is less effective visually if the HTML5 Canvas is not
   *     availble on the viewing device.
   *
+  * @property {Number} [smoothTileEdgesMinZoom=1.1]
+  *     A zoom percentage ( expressed as a number between 0 and 1 ) of the highest
+  *     resolution level. When zoomed in beyond this value alternative compositing will
+  *     be used to smooth out the edges between tiles. This WILL have a performance impact.
+  *
   * @property {Boolean} [autoResize=true]
   *     Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
   *
@@ -1000,6 +1005,7 @@ if (typeof define === 'function' && define.amd) {
             immediateRender:        false,
             minZoomImageRatio:      0.9, //-> closer to 0 allows zoom out to infinity
             maxZoomPixelRatio:      1.1, //-> higher allows 'over zoom' into pixels
+            smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
             pixelsPerWheelLine:     40,
             autoResize:             true,
             preserveImageSizeOnResize: false, // requires autoResize=true
diff --git a/src/tile.js b/src/tile.js
index ad018ba7..9a7fde55 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -240,11 +240,12 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
      * @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 - Apply a scale to position and size
      */
-    drawCanvas: function( context, drawingHandler ) {
+    drawCanvas: function( context, drawingHandler, scale ) {
 
-        var position = this.position,
-            size     = this.size,
+        var position = this.position.times($.pixelDensityRatio),
+            size     = this.size.times($.pixelDensityRatio),
             rendered;
 
         if (!this.cacheImageRecord) {
@@ -277,10 +278,10 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
             //clearing only the inside of the rectangle occupied
             //by the png prevents edge flikering
             context.clearRect(
-                (position.x * $.pixelDensityRatio)+1,
-                (position.y * $.pixelDensityRatio)+1,
-                (size.x * $.pixelDensityRatio)-2,
-                (size.y * $.pixelDensityRatio)-2
+                position.x + 1,
+                position.y + 1,
+                size.x - 2,
+                size.y - 2
             );
 
         }
@@ -289,16 +290,52 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
         // changes as we are rendering the image
         drawingHandler({context: context, tile: this, rendered: rendered});
 
+        if (typeof scale === 'number' && scale !== 1) {
+            // draw tile at a different scale
+            position = position.times(scale);
+            size = size.times(scale);
+
+            if (scale < 1 && $.Browser.vendor == $.BROWSERS.FIREFOX) {
+                // In firefox edges are very visible because there seems to be
+                // empty space between tiles caused by float coordinates.
+                // Adding partial overlap fixes this.
+                // These will be covered by the top and left tiles.
+                context.drawImage( // duplicate first column to the left
+                    rendered.canvas,
+                    0,
+                    0,
+                    1,
+                    rendered.canvas.height,
+                    Math.floor(position.x),
+                    position.y,
+                    1,
+                    size.y
+                );
+                context.drawImage( // duplicate first row up
+                    rendered.canvas,
+                    0,
+                    0,
+                    rendered.canvas.width,
+                    1,
+                    position.x,
+                    Math.floor(position.y),
+                    size.x,
+                    1
+                );
+            }
+        }
+
+        // context.globalCompositeOperation = 'source-out';
         context.drawImage(
             rendered.canvas,
             0,
             0,
             rendered.canvas.width,
             rendered.canvas.height,
-            position.x * $.pixelDensityRatio,
-            position.y * $.pixelDensityRatio,
-            size.x * $.pixelDensityRatio,
-            size.y * $.pixelDensityRatio
+            position.x,
+            position.y,
+            size.x,
+            size.y
         );
 
         context.restore();
diff --git a/src/tiledimage.js b/src/tiledimage.js
index 1731d472..815d052c 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -133,19 +133,20 @@ $.TiledImage = function( options ) {
         _hasOpaqueTile: false,  // Do we have even one fully opaque tile?
 
         //configurable settings
-        springStiffness:      $.DEFAULT_SETTINGS.springStiffness,
-        animationTime:        $.DEFAULT_SETTINGS.animationTime,
-        minZoomImageRatio:    $.DEFAULT_SETTINGS.minZoomImageRatio,
-        wrapHorizontal:       $.DEFAULT_SETTINGS.wrapHorizontal,
-        wrapVertical:         $.DEFAULT_SETTINGS.wrapVertical,
-        immediateRender:      $.DEFAULT_SETTINGS.immediateRender,
-        blendTime:            $.DEFAULT_SETTINGS.blendTime,
-        alwaysBlend:          $.DEFAULT_SETTINGS.alwaysBlend,
-        minPixelRatio:        $.DEFAULT_SETTINGS.minPixelRatio,
-        debugMode:            $.DEFAULT_SETTINGS.debugMode,
-        crossOriginPolicy:    $.DEFAULT_SETTINGS.crossOriginPolicy,
-        placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
-        opacity:              $.DEFAULT_SETTINGS.opacity
+        springStiffness:        $.DEFAULT_SETTINGS.springStiffness,
+        animationTime:          $.DEFAULT_SETTINGS.animationTime,
+        minZoomImageRatio:      $.DEFAULT_SETTINGS.minZoomImageRatio,
+        wrapHorizontal:         $.DEFAULT_SETTINGS.wrapHorizontal,
+        wrapVertical:           $.DEFAULT_SETTINGS.wrapVertical,
+        immediateRender:        $.DEFAULT_SETTINGS.immediateRender,
+        blendTime:              $.DEFAULT_SETTINGS.blendTime,
+        alwaysBlend:            $.DEFAULT_SETTINGS.alwaysBlend,
+        minPixelRatio:          $.DEFAULT_SETTINGS.minPixelRatio,
+        smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
+        debugMode:              $.DEFAULT_SETTINGS.debugMode,
+        crossOriginPolicy:      $.DEFAULT_SETTINGS.crossOriginPolicy,
+        placeholderFillStyle:   $.DEFAULT_SETTINGS.placeholderFillStyle,
+        opacity:                $.DEFAULT_SETTINGS.opacity
 
     }, options );
 
@@ -1302,6 +1303,19 @@ function drawTiles( tiledImage, lastDrawn ) {
         return;
     }
     var useSketch = tiledImage.opacity < 1;
+    var sketchScale = 1;
+
+    var zoom = tiledImage.viewport.getZoom();
+    var imageZoom = tiledImage.viewportToImageZoom(zoom);
+    if ( imageZoom > tiledImage.smoothTileEdgesMinZoom ) {
+        // When zoomed in a lot (>100%) the tile edges are visible.
+        // So we have to composite them at ~100% and scale them up together.
+        useSketch = true;
+        // Compositing at 100% is not precise and causes weird twithing.
+        // So we composite at 101% zoom
+        sketchScale = 1.01 / imageZoom;
+    }
+
     if ( useSketch ) {
         tiledImage._drawer._clear( true );
     }
@@ -1333,7 +1347,7 @@ function drawTiles( tiledImage, lastDrawn ) {
 
     for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
         tile = lastDrawn[ i ];
-        tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch );
+        tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale );
         tile.beingDrawn = true;
 
         if( tiledImage.viewer ){
@@ -1360,7 +1374,7 @@ function drawTiles( tiledImage, lastDrawn ) {
     }
 
     if ( useSketch ) {
-        tiledImage._drawer.blendSketch( tiledImage.opacity );
+        tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale );
     }
     drawDebugInfo( tiledImage, lastDrawn );
 }
diff --git a/src/viewer.js b/src/viewer.js
index c66556f1..0851f0e6 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -604,7 +604,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
             var originalSuccess = options.success;
             options.success = function(event) {
                 successes++;
-                
+
                 // TODO: now that options has other things besides tileSource, the overlays
                 // should probably be at the options level, not the tileSource level.
                 if (options.tileSource.overlays) {
@@ -1342,6 +1342,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
                     blendTime: _this.blendTime,
                     alwaysBlend: _this.alwaysBlend,
                     minPixelRatio: _this.minPixelRatio,
+                    smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
                     crossOriginPolicy: _this.crossOriginPolicy,
                     debugMode: _this.debugMode
                 });

From 515c15bf9851061112400b7c159005faec8e21bd Mon Sep 17 00:00:00 2001
From: Petar Petrov <ppetrov@bluebuffstudio.com>
Date: Thu, 5 Nov 2015 16:19:56 +0200
Subject: [PATCH 2/5] various fixes for edge smoothing - #755

---
 src/drawer.js         | 30 ++++++++++-------
 src/openseadragon.js  |  6 ++--
 src/tile.js           | 75 +++++++++++++++++++++++++------------------
 src/tiledimage.js     | 19 +++++------
 test/modules/basic.js |  1 +
 5 files changed, 76 insertions(+), 55 deletions(-)

diff --git a/src/drawer.js b/src/drawer.js
index 5666370d..a28606b1 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -290,22 +290,24 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
      * 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 - Apply a scale to tile position and size
+     * @param {Float} scale - Apply a scale to tile position and size. Defaults to 1.
+     * @param {OpenSeadragon.Point} translate Optional. A translation vector to offset tile position
      */
-    drawTile: function( tile, drawingHandler, useSketch, scale ) {
+    drawTile: function( tile, drawingHandler, useSketch, scale, translate ) {
         $.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;
             // TODO do this in a more performant way
             // specifically, don't save,rotate,restore every time we draw a tile
             if( this.viewport.degrees !== 0 ) {
                 this._offsetForRotation( tile, this.viewport.degrees, useSketch );
-                tile.drawCanvas( context, drawingHandler, scale );
+                tile.drawCanvas( context, drawingHandler, scale, translate );
                 this._restoreRotationChanges( tile, useSketch );
             } else {
-                tile.drawCanvas( context, drawingHandler, scale );
+                tile.drawCanvas( context, drawingHandler, scale, translate );
             }
         } else {
             tile.drawHTML( this.canvas );
@@ -372,24 +374,28 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
     /**
      * Blends the sketch canvas in the main canvas.
      * @param {Float} opacity The opacity of the blending.
-     * @param {Float} sketchScale The scale at which tiles were drawn on the sketch. Default is 1.
-     *   Use sketchScale to draw at a lower scale and then enlarge onto the main canvas.
+     * @param {Float} scale 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} translate A translation vector that was used to draw the tiles
      * @returns {undefined}
      */
-    blendSketch: function(opacity, sketchScale) {
+    blendSketch: function(opacity, scale, translate) {
         if (!this.useCanvas || !this.sketchCanvas) {
             return;
         }
-        sketchScale = sketchScale || 1;
+        scale = scale || 1;
+        var position = translate instanceof $.Point ?
+            translate :
+            new $.Point(0, 0);
 
         this.context.save();
         this.context.globalAlpha = opacity;
         this.context.drawImage(
             this.sketchCanvas,
-            0,
-            0,
-            this.sketchCanvas.width * sketchScale,
-            this.sketchCanvas.height * sketchScale,
+            position.x,
+            position.y,
+            this.sketchCanvas.width * scale,
+            this.sketchCanvas.height * scale,
             0,
             0,
             this.canvas.width,
diff --git a/src/openseadragon.js b/src/openseadragon.js
index df4dd55f..b3b1bd6e 100644
--- a/src/openseadragon.js
+++ b/src/openseadragon.js
@@ -250,9 +250,9 @@
   *     availble on the viewing device.
   *
   * @property {Number} [smoothTileEdgesMinZoom=1.1]
-  *     A zoom percentage ( expressed as a number between 0 and 1 ) of the highest
-  *     resolution level. When zoomed in beyond this value alternative compositing will
-  *     be used to smooth out the edges between tiles. This WILL have a performance impact.
+  *     A zoom percentage ( where 1 is 100% ) of the highest resolution level.
+  *     When zoomed in beyond this value alternative compositing will be used to
+  *     smooth out the edges between tiles. This will have a performance impact.
   *
   * @property {Boolean} [autoResize=true]
   *     Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior.
diff --git a/src/tile.js b/src/tile.js
index 9a7fde55..e5d5be50 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -241,8 +241,9 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
      * drawingHandler({context, tile, rendered})
      * where <code>rendered</code> is the context with the pre-drawn image.
      * @param {Number} scale - Apply a scale to position and size
+     * @param {OpenSeadragon.Point} translate - A translation vector
      */
-    drawCanvas: function( context, drawingHandler, scale ) {
+    drawCanvas: function( context, drawingHandler, scale, translate ) {
 
         var position = this.position.times($.pixelDensityRatio),
             size     = this.size.times($.pixelDensityRatio),
@@ -294,38 +295,13 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
             // draw tile at a different scale
             position = position.times(scale);
             size = size.times(scale);
-
-            if (scale < 1 && $.Browser.vendor == $.BROWSERS.FIREFOX) {
-                // In firefox edges are very visible because there seems to be
-                // empty space between tiles caused by float coordinates.
-                // Adding partial overlap fixes this.
-                // These will be covered by the top and left tiles.
-                context.drawImage( // duplicate first column to the left
-                    rendered.canvas,
-                    0,
-                    0,
-                    1,
-                    rendered.canvas.height,
-                    Math.floor(position.x),
-                    position.y,
-                    1,
-                    size.y
-                );
-                context.drawImage( // duplicate first row up
-                    rendered.canvas,
-                    0,
-                    0,
-                    rendered.canvas.width,
-                    1,
-                    position.x,
-                    Math.floor(position.y),
-                    size.x,
-                    1
-                );
-            }
         }
 
-        // context.globalCompositeOperation = 'source-out';
+        if (translate instanceof $.Point) {
+            // shift tile position slightly
+            position = position.plus(translate);
+        }
+
         context.drawImage(
             rendered.canvas,
             0,
@@ -341,6 +317,43 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
         context.restore();
     },
 
+    /**
+     * Get the ratio between current and original size.
+     * @function
+     * @return {Float}
+     */
+    getScaleForEdgeSmoothing: function() {
+        if (!this.cacheImageRecord) {
+            $.console.warn(
+                '[Tile.drawCanvas] attempting to get tile scale %s when tile\'s not cached',
+                this.toString());
+            return 1;
+        }
+
+        var rendered = this.cacheImageRecord.getRenderedContext();
+        return rendered.canvas.width / this.size.times($.pixelDensityRatio).x;
+    },
+
+    /**
+     * Get a translation vector that when applied to the tile position produces integer coordinates.
+     * Needed to avoid swimming and twitching.
+     * @function
+     * @param {Number} scale - Scale to be applied to position. Defaults to 1.
+     * @return {OpenSeadragon.Point}
+     */
+    getTranslationForEdgeSmoothing: function(scale) {
+        // 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. And FF does not like it. It crashes the viewer.
+        return new $.Point(1, 1).minus(
+            this.position
+                .times(scale || 1)
+                .apply(function(x) {
+                    return x % 1;
+                })
+        );
+    },
+
     /**
      * Removes tile from its container.
      * @function
diff --git a/src/tiledimage.js b/src/tiledimage.js
index 815d052c..d349658e 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -64,6 +64,7 @@
  * @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
  * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
  * @param {Number} [options.minPixelRatio] - See {@link OpenSeadragon.Options}.
+ * @param {Number} [options.smoothTileEdgesMinZoom] - See {@link OpenSeadragon.Options}.
  * @param {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
  * @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
  * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
@@ -1296,24 +1297,24 @@ function compareTiles( previousBest, tile ) {
 
 function drawTiles( tiledImage, lastDrawn ) {
     var i,
-        tile;
+        tile = lastDrawn[0];
 
     if ( tiledImage.opacity <= 0 ) {
         drawDebugInfo( tiledImage, lastDrawn );
         return;
     }
     var useSketch = tiledImage.opacity < 1;
-    var sketchScale = 1;
+    var sketchScale;
+    var sketchTranslate;
 
-    var zoom = tiledImage.viewport.getZoom();
+    var zoom = tiledImage.viewport.getZoom(true);
     var imageZoom = tiledImage.viewportToImageZoom(zoom);
-    if ( imageZoom > tiledImage.smoothTileEdgesMinZoom ) {
+    if ( imageZoom > tiledImage.smoothTileEdgesMinZoom && tile) {
         // When zoomed in a lot (>100%) the tile edges are visible.
         // So we have to composite them at ~100% and scale them up together.
         useSketch = true;
-        // Compositing at 100% is not precise and causes weird twithing.
-        // So we composite at 101% zoom
-        sketchScale = 1.01 / imageZoom;
+        sketchScale = tile.getScaleForEdgeSmoothing();
+        sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale);
     }
 
     if ( useSketch ) {
@@ -1347,7 +1348,7 @@ function drawTiles( tiledImage, lastDrawn ) {
 
     for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
         tile = lastDrawn[ i ];
-        tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale );
+        tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale, sketchTranslate );
         tile.beingDrawn = true;
 
         if( tiledImage.viewer ){
@@ -1374,7 +1375,7 @@ function drawTiles( tiledImage, lastDrawn ) {
     }
 
     if ( useSketch ) {
-        tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale );
+        tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale, sketchTranslate );
     }
     drawDebugInfo( tiledImage, lastDrawn );
 }
diff --git a/test/modules/basic.js b/test/modules/basic.js
index 7f095777..eae33474 100644
--- a/test/modules/basic.js
+++ b/test/modules/basic.js
@@ -334,6 +334,7 @@
     asyncTest( 'CrossOriginPolicyMissing', function () {
 
         viewer.crossOriginPolicy = false;
+        viewer.smoothTileEdgesMinZoom = Infinity;
         viewer.open( {
             type: 'legacy-image-pyramid',
             levels: [ {

From 7eda39c9a94b890cabcc06d01443bf69d31db127 Mon Sep 17 00:00:00 2001
From: Petar Petrov <ppetrov@bluebuffstudio.com>
Date: Thu, 5 Nov 2015 16:31:13 +0200
Subject: [PATCH 3/5] fixed edge smoothing to work with clipping and
 placeholderFillRect - #755

---
 src/rectangle.js  | 15 +++++++++++++++
 src/tiledimage.js | 12 ++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/src/rectangle.js b/src/rectangle.js
index 5d3495af..abf1c947 100644
--- a/src/rectangle.js
+++ b/src/rectangle.js
@@ -201,6 +201,21 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
         );
     },
 
+    /**
+    * Translate/move this Rect by a vector and return new Rect.
+    * @function
+    * @param {OpenSeadragon.Point} delta The translation vector.
+    * @returns {OpenSeadragon.Rect} A new rect with altered position
+    */
+    translate: function( delta ) {
+        return new OpenSeadragon.Rect(
+            this.x + delta.x,
+            this.y + delta.y,
+            this.width,
+            this.height
+        );
+    },
+
     /**
      * Returns the smallest rectangle that will contain this and the given rectangle.
      * @param {OpenSeadragon.Rect} rect
diff --git a/src/tiledimage.js b/src/tiledimage.js
index d349658e..f2059eba 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -1327,6 +1327,12 @@ function drawTiles( tiledImage, lastDrawn ) {
 
         var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
         var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
+        if (sketchScale) {
+            clipRect = clipRect.times(sketchScale);
+        }
+        if (sketchTranslate) {
+            clipRect = clipRect.translate(sketchTranslate);
+        }
         tiledImage._drawer.setClip(clipRect, useSketch);
 
         usedClip = true;
@@ -1334,6 +1340,12 @@ function drawTiles( tiledImage, lastDrawn ) {
 
     if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
         var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
+        if (sketchScale) {
+            placeholderRect = placeholderRect.times(sketchScale);
+        }
+        if (sketchTranslate) {
+            placeholderRect = placeholderRect.translate(sketchTranslate);
+        }
 
         var fillStyle = null;
         if ( typeof tiledImage.placeholderFillStyle === "function" ) {

From 8bee1e7b8b16518b70bcfc368c6a4071c44840c3 Mon Sep 17 00:00:00 2001
From: Petar Petrov <ppetrov@bluebuffstudio.com>
Date: Fri, 6 Nov 2015 08:55:30 +0200
Subject: [PATCH 4/5] doc tweak for edge smoothing changes - #755

---
 src/drawer.js | 8 ++++----
 src/tile.js   | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/drawer.js b/src/drawer.js
index a28606b1..9e928bb6 100644
--- a/src/drawer.js
+++ b/src/drawer.js
@@ -290,8 +290,8 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
      * 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 - Apply a scale to tile position and size. Defaults to 1.
-     * @param {OpenSeadragon.Point} translate Optional. A translation vector to offset tile position
+     * @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
      */
     drawTile: function( tile, drawingHandler, useSketch, scale, translate ) {
         $.console.assert(tile, '[Drawer.drawTile] tile is required');
@@ -374,9 +374,9 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
     /**
      * Blends the sketch canvas in the main canvas.
      * @param {Float} opacity The opacity of the blending.
-     * @param {Float} scale The scale at which tiles were drawn on the sketch. Default is 1.
+     * @param {Float} [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} translate A translation vector that was used to draw the tiles
+     * @param OpenSeadragon.Point} [translate] A translation vector that was used to draw the tiles
      * @returns {undefined}
      */
     blendSketch: function(opacity, scale, translate) {
diff --git a/src/tile.js b/src/tile.js
index e5d5be50..12a33a22 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -240,8 +240,8 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
      * @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 - Apply a scale to position and size
-     * @param {OpenSeadragon.Point} translate - A translation vector
+     * @param {Number} [scale=1] - Apply a scale to position and size
+     * @param {OpenSeadragon.Point} [translate] - A translation vector
      */
     drawCanvas: function( context, drawingHandler, scale, translate ) {
 
@@ -338,7 +338,7 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
      * Get a translation vector that when applied to the tile position produces integer coordinates.
      * Needed to avoid swimming and twitching.
      * @function
-     * @param {Number} scale - Scale to be applied to position. Defaults to 1.
+     * @param {Number} [scale=1] - Scale to be applied to position.
      * @return {OpenSeadragon.Point}
      */
     getTranslationForEdgeSmoothing: function(scale) {

From 9922d2a4bb5761b62792f7024dafe8880fa403a0 Mon Sep 17 00:00:00 2001
From: Petar Petrov <ppetrov@bluebuffstudio.com>
Date: Thu, 19 Nov 2015 10:06:43 +0200
Subject: [PATCH 5/5] edge smoothing fix for Mac

---
 src/tile.js | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/tile.js b/src/tile.js
index 7f598ef2..a07bec74 100644
--- a/src/tile.js
+++ b/src/tile.js
@@ -356,6 +356,7 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
         // to the main canvas. And FF does not like it. It crashes the viewer.
         return new $.Point(1, 1).minus(
             this.position
+                .times($.pixelDensityRatio)
                 .times(scale || 1)
                 .apply(function(x) {
                     return x % 1;