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 rendered 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 rendered 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: [ {