tile edge smoothing at high zoom - #755

This commit is contained in:
Petar Petrov 2015-11-04 17:04:50 +02:00
parent 9fa3136e78
commit 8c4fcc9ca9
5 changed files with 104 additions and 32 deletions

View File

@ -290,8 +290,9 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
* drawingHandler({context, tile, rendered}) * drawingHandler({context, tile, rendered})
* @param {Boolean} useSketch - Whether to use the sketch canvas or not. * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
* where <code>rendered</code> is the context with the pre-drawn image. * 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(tile, '[Drawer.drawTile] tile is required');
$.console.assert(drawingHandler, '[Drawer.drawTile] drawingHandler 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 // specifically, don't save,rotate,restore every time we draw a tile
if( this.viewport.degrees !== 0 ) { if( this.viewport.degrees !== 0 ) {
this._offsetForRotation( tile, this.viewport.degrees, useSketch ); this._offsetForRotation( tile, this.viewport.degrees, useSketch );
tile.drawCanvas( context, drawingHandler ); tile.drawCanvas( context, drawingHandler, scale );
this._restoreRotationChanges( tile, useSketch ); this._restoreRotationChanges( tile, useSketch );
} else { } else {
tile.drawCanvas( context, drawingHandler ); tile.drawCanvas( context, drawingHandler, scale );
} }
} else { } else {
tile.drawHTML( this.canvas ); tile.drawHTML( this.canvas );
@ -371,16 +372,29 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
/** /**
* Blends the sketch canvas in the main canvas. * Blends the sketch canvas in the main canvas.
* @param {Float} opacity The opacity of the blending. * @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} * @returns {undefined}
*/ */
blendSketch: function(opacity) { blendSketch: function(opacity, sketchScale) {
if (!this.useCanvas || !this.sketchCanvas) { if (!this.useCanvas || !this.sketchCanvas) {
return; return;
} }
sketchScale = sketchScale || 1;
this.context.save(); this.context.save();
this.context.globalAlpha = opacity; 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(); this.context.restore();
}, },

View File

@ -249,6 +249,11 @@
* image though it is less effective visually if the HTML5 Canvas is not * image though it is less effective visually if the HTML5 Canvas is not
* availble on the viewing device. * 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] * @property {Boolean} [autoResize=true]
* Set to false to prevent polling for viewer size changes. Useful for providing custom resize behavior. * 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, immediateRender: false,
minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity minZoomImageRatio: 0.9, //-> closer to 0 allows zoom out to infinity
maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels maxZoomPixelRatio: 1.1, //-> higher allows 'over zoom' into pixels
smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
pixelsPerWheelLine: 40, pixelsPerWheelLine: 40,
autoResize: true, autoResize: true,
preserveImageSizeOnResize: false, // requires autoResize=true preserveImageSizeOnResize: false, // requires autoResize=true

View File

@ -240,11 +240,12 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
* @param {Function} drawingHandler - Method for firing the drawing event. * @param {Function} drawingHandler - Method for firing the drawing event.
* drawingHandler({context, tile, rendered}) * drawingHandler({context, tile, rendered})
* where <code>rendered</code> is the context with the pre-drawn image. * 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, var position = this.position.times($.pixelDensityRatio),
size = this.size, size = this.size.times($.pixelDensityRatio),
rendered; rendered;
if (!this.cacheImageRecord) { if (!this.cacheImageRecord) {
@ -277,10 +278,10 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
//clearing only the inside of the rectangle occupied //clearing only the inside of the rectangle occupied
//by the png prevents edge flikering //by the png prevents edge flikering
context.clearRect( context.clearRect(
(position.x * $.pixelDensityRatio)+1, position.x + 1,
(position.y * $.pixelDensityRatio)+1, position.y + 1,
(size.x * $.pixelDensityRatio)-2, size.x - 2,
(size.y * $.pixelDensityRatio)-2 size.y - 2
); );
} }
@ -289,16 +290,52 @@ $.Tile.prototype = /** @lends OpenSeadragon.Tile.prototype */{
// changes as we are rendering the image // changes as we are rendering the image
drawingHandler({context: context, tile: this, rendered: rendered}); 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( context.drawImage(
rendered.canvas, rendered.canvas,
0, 0,
0, 0,
rendered.canvas.width, rendered.canvas.width,
rendered.canvas.height, rendered.canvas.height,
position.x * $.pixelDensityRatio, position.x,
position.y * $.pixelDensityRatio, position.y,
size.x * $.pixelDensityRatio, size.x,
size.y * $.pixelDensityRatio size.y
); );
context.restore(); context.restore();

View File

@ -133,19 +133,20 @@ $.TiledImage = function( options ) {
_hasOpaqueTile: false, // Do we have even one fully opaque tile? _hasOpaqueTile: false, // Do we have even one fully opaque tile?
//configurable settings //configurable settings
springStiffness: $.DEFAULT_SETTINGS.springStiffness, springStiffness: $.DEFAULT_SETTINGS.springStiffness,
animationTime: $.DEFAULT_SETTINGS.animationTime, animationTime: $.DEFAULT_SETTINGS.animationTime,
minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio, minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal, wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
wrapVertical: $.DEFAULT_SETTINGS.wrapVertical, wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
immediateRender: $.DEFAULT_SETTINGS.immediateRender, immediateRender: $.DEFAULT_SETTINGS.immediateRender,
blendTime: $.DEFAULT_SETTINGS.blendTime, blendTime: $.DEFAULT_SETTINGS.blendTime,
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend, alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio, minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
debugMode: $.DEFAULT_SETTINGS.debugMode, smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy, debugMode: $.DEFAULT_SETTINGS.debugMode,
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle, crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
opacity: $.DEFAULT_SETTINGS.opacity placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
opacity: $.DEFAULT_SETTINGS.opacity
}, options ); }, options );
@ -1302,6 +1303,19 @@ function drawTiles( tiledImage, lastDrawn ) {
return; return;
} }
var useSketch = tiledImage.opacity < 1; 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 ) { if ( useSketch ) {
tiledImage._drawer._clear( true ); tiledImage._drawer._clear( true );
} }
@ -1333,7 +1347,7 @@ function drawTiles( tiledImage, lastDrawn ) {
for ( i = lastDrawn.length - 1; i >= 0; i-- ) { for ( i = lastDrawn.length - 1; i >= 0; i-- ) {
tile = lastDrawn[ i ]; tile = lastDrawn[ i ];
tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch ); tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler, useSketch, sketchScale );
tile.beingDrawn = true; tile.beingDrawn = true;
if( tiledImage.viewer ){ if( tiledImage.viewer ){
@ -1360,7 +1374,7 @@ function drawTiles( tiledImage, lastDrawn ) {
} }
if ( useSketch ) { if ( useSketch ) {
tiledImage._drawer.blendSketch( tiledImage.opacity ); tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale );
} }
drawDebugInfo( tiledImage, lastDrawn ); drawDebugInfo( tiledImage, lastDrawn );
} }

View File

@ -604,7 +604,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
var originalSuccess = options.success; var originalSuccess = options.success;
options.success = function(event) { options.success = function(event) {
successes++; successes++;
// TODO: now that options has other things besides tileSource, the overlays // TODO: now that options has other things besides tileSource, the overlays
// should probably be at the options level, not the tileSource level. // should probably be at the options level, not the tileSource level.
if (options.tileSource.overlays) { if (options.tileSource.overlays) {
@ -1342,6 +1342,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
blendTime: _this.blendTime, blendTime: _this.blendTime,
alwaysBlend: _this.alwaysBlend, alwaysBlend: _this.alwaysBlend,
minPixelRatio: _this.minPixelRatio, minPixelRatio: _this.minPixelRatio,
smoothTileEdgesMinZoom: _this.smoothTileEdgesMinZoom,
crossOriginPolicy: _this.crossOriginPolicy, crossOriginPolicy: _this.crossOriginPolicy,
debugMode: _this.debugMode debugMode: _this.debugMode
}); });