mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-24 22:26:10 +03:00
Merge pull request #764 from picturae/master
tile edge smoothing at high zoom - #755
This commit is contained in:
commit
f07c9a7026
@ -290,21 +290,24 @@ $.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=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 ) {
|
drawTile: function( tile, drawingHandler, useSketch, scale, translate ) {
|
||||||
$.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');
|
||||||
|
|
||||||
if ( this.useCanvas ) {
|
if ( this.useCanvas ) {
|
||||||
var context = this._getContext( useSketch );
|
var context = this._getContext( useSketch );
|
||||||
|
scale = scale || 1;
|
||||||
// TODO do this in a more performant way
|
// TODO do this in a more performant way
|
||||||
// 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, translate );
|
||||||
this._restoreRotationChanges( tile, useSketch );
|
this._restoreRotationChanges( tile, useSketch );
|
||||||
} else {
|
} else {
|
||||||
tile.drawCanvas( context, drawingHandler );
|
tile.drawCanvas( context, drawingHandler, scale, translate );
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tile.drawHTML( this.canvas );
|
tile.drawHTML( this.canvas );
|
||||||
@ -371,16 +374,33 @@ $.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} [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
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
blendSketch: function(opacity) {
|
blendSketch: function(opacity, scale, translate) {
|
||||||
if (!this.useCanvas || !this.sketchCanvas) {
|
if (!this.useCanvas || !this.sketchCanvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
scale = scale || 1;
|
||||||
|
var position = translate instanceof $.Point ?
|
||||||
|
translate :
|
||||||
|
new $.Point(0, 0);
|
||||||
|
|
||||||
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,
|
||||||
|
position.x,
|
||||||
|
position.y,
|
||||||
|
this.sketchCanvas.width * scale,
|
||||||
|
this.sketchCanvas.height * scale,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this.canvas.width,
|
||||||
|
this.canvas.height
|
||||||
|
);
|
||||||
this.context.restore();
|
this.context.restore();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -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 ( 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]
|
* @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.
|
||||||
*
|
*
|
||||||
@ -1022,6 +1027,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
|
||||||
|
@ -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.
|
* Returns the smallest rectangle that will contain this and the given rectangle.
|
||||||
* @param {OpenSeadragon.Rect} rect
|
* @param {OpenSeadragon.Rect} rect
|
||||||
|
73
src/tile.js
73
src/tile.js
@ -248,11 +248,13 @@ $.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=1] - Apply a scale to position and size
|
||||||
|
* @param {OpenSeadragon.Point} [translate] - A translation vector
|
||||||
*/
|
*/
|
||||||
drawCanvas: function( context, drawingHandler ) {
|
drawCanvas: function( context, drawingHandler, scale, translate ) {
|
||||||
|
|
||||||
var position = this.position,
|
var position = this.position.times($.pixelDensityRatio),
|
||||||
size = this.size,
|
size = this.size.times($.pixelDensityRatio),
|
||||||
rendered;
|
rendered;
|
||||||
|
|
||||||
if (!this.context2D && !this.cacheImageRecord) {
|
if (!this.context2D && !this.cacheImageRecord) {
|
||||||
@ -286,10 +288,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
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -298,21 +300,70 @@ $.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 (translate instanceof $.Point) {
|
||||||
|
// shift tile position slightly
|
||||||
|
position = position.plus(translate);
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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=1] - Scale to be applied to position.
|
||||||
|
* @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($.pixelDensityRatio)
|
||||||
|
.times(scale || 1)
|
||||||
|
.apply(function(x) {
|
||||||
|
return x % 1;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes tile from its container.
|
* Removes tile from its container.
|
||||||
* @function
|
* @function
|
||||||
|
@ -64,6 +64,7 @@
|
|||||||
* @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
|
* @param {Number} [options.blendTime] - See {@link OpenSeadragon.Options}.
|
||||||
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
|
* @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}.
|
||||||
* @param {Number} [options.minPixelRatio] - 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 {Number} [options.opacity=1] - Opacity the tiled image should be drawn at.
|
||||||
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
|
* @param {Boolean} [options.debugMode] - See {@link OpenSeadragon.Options}.
|
||||||
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
|
* @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}.
|
||||||
@ -142,6 +143,7 @@ $.TiledImage = function( options ) {
|
|||||||
blendTime: $.DEFAULT_SETTINGS.blendTime,
|
blendTime: $.DEFAULT_SETTINGS.blendTime,
|
||||||
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
|
alwaysBlend: $.DEFAULT_SETTINGS.alwaysBlend,
|
||||||
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
|
minPixelRatio: $.DEFAULT_SETTINGS.minPixelRatio,
|
||||||
|
smoothTileEdgesMinZoom: $.DEFAULT_SETTINGS.smoothTileEdgesMinZoom,
|
||||||
debugMode: $.DEFAULT_SETTINGS.debugMode,
|
debugMode: $.DEFAULT_SETTINGS.debugMode,
|
||||||
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
|
crossOriginPolicy: $.DEFAULT_SETTINGS.crossOriginPolicy,
|
||||||
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
|
placeholderFillStyle: $.DEFAULT_SETTINGS.placeholderFillStyle,
|
||||||
@ -1305,13 +1307,26 @@ function compareTiles( previousBest, tile ) {
|
|||||||
|
|
||||||
function drawTiles( tiledImage, lastDrawn ) {
|
function drawTiles( tiledImage, lastDrawn ) {
|
||||||
var i,
|
var i,
|
||||||
tile;
|
tile = lastDrawn[0];
|
||||||
|
|
||||||
if ( tiledImage.opacity <= 0 ) {
|
if ( tiledImage.opacity <= 0 ) {
|
||||||
drawDebugInfo( tiledImage, lastDrawn );
|
drawDebugInfo( tiledImage, lastDrawn );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var useSketch = tiledImage.opacity < 1;
|
var useSketch = tiledImage.opacity < 1;
|
||||||
|
var sketchScale;
|
||||||
|
var sketchTranslate;
|
||||||
|
|
||||||
|
var zoom = tiledImage.viewport.getZoom(true);
|
||||||
|
var imageZoom = tiledImage.viewportToImageZoom(zoom);
|
||||||
|
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;
|
||||||
|
sketchScale = tile.getScaleForEdgeSmoothing();
|
||||||
|
sketchTranslate = tile.getTranslationForEdgeSmoothing(sketchScale);
|
||||||
|
}
|
||||||
|
|
||||||
if ( useSketch ) {
|
if ( useSketch ) {
|
||||||
tiledImage._drawer._clear( true );
|
tiledImage._drawer._clear( true );
|
||||||
}
|
}
|
||||||
@ -1322,6 +1337,12 @@ function drawTiles( tiledImage, lastDrawn ) {
|
|||||||
|
|
||||||
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
|
var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true);
|
||||||
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
|
var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box);
|
||||||
|
if (sketchScale) {
|
||||||
|
clipRect = clipRect.times(sketchScale);
|
||||||
|
}
|
||||||
|
if (sketchTranslate) {
|
||||||
|
clipRect = clipRect.translate(sketchTranslate);
|
||||||
|
}
|
||||||
tiledImage._drawer.setClip(clipRect, useSketch);
|
tiledImage._drawer.setClip(clipRect, useSketch);
|
||||||
|
|
||||||
usedClip = true;
|
usedClip = true;
|
||||||
@ -1329,6 +1350,12 @@ function drawTiles( tiledImage, lastDrawn ) {
|
|||||||
|
|
||||||
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
|
||||||
var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
|
var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
|
||||||
|
if (sketchScale) {
|
||||||
|
placeholderRect = placeholderRect.times(sketchScale);
|
||||||
|
}
|
||||||
|
if (sketchTranslate) {
|
||||||
|
placeholderRect = placeholderRect.translate(sketchTranslate);
|
||||||
|
}
|
||||||
|
|
||||||
var fillStyle = null;
|
var fillStyle = null;
|
||||||
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
if ( typeof tiledImage.placeholderFillStyle === "function" ) {
|
||||||
@ -1343,7 +1370,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, sketchTranslate );
|
||||||
tile.beingDrawn = true;
|
tile.beingDrawn = true;
|
||||||
|
|
||||||
if( tiledImage.viewer ){
|
if( tiledImage.viewer ){
|
||||||
@ -1370,7 +1397,7 @@ function drawTiles( tiledImage, lastDrawn ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ( useSketch ) {
|
if ( useSketch ) {
|
||||||
tiledImage._drawer.blendSketch( tiledImage.opacity );
|
tiledImage._drawer.blendSketch( tiledImage.opacity, sketchScale, sketchTranslate );
|
||||||
}
|
}
|
||||||
drawDebugInfo( tiledImage, lastDrawn );
|
drawDebugInfo( tiledImage, lastDrawn );
|
||||||
}
|
}
|
||||||
|
@ -1346,6 +1346,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
|
||||||
});
|
});
|
||||||
|
@ -322,6 +322,7 @@
|
|||||||
asyncTest( 'CrossOriginPolicyMissing', function () {
|
asyncTest( 'CrossOriginPolicyMissing', function () {
|
||||||
|
|
||||||
viewer.crossOriginPolicy = false;
|
viewer.crossOriginPolicy = false;
|
||||||
|
viewer.smoothTileEdgesMinZoom = Infinity;
|
||||||
viewer.open( {
|
viewer.open( {
|
||||||
type: 'legacy-image-pyramid',
|
type: 'legacy-image-pyramid',
|
||||||
levels: [ {
|
levels: [ {
|
||||||
|
Loading…
Reference in New Issue
Block a user