From 6d4d7cc8c6bc74c28d0a98cc863718d91b9bb229 Mon Sep 17 00:00:00 2001 From: thatcher Date: Fri, 1 Mar 2013 08:14:35 -0500 Subject: [PATCH 1/8] niave implementation of prerender for canvas, the first optimization discussed here: http://www.html5rocks.com/en/tutorials/canvas/performance/ --- src/drawer.js | 2 ++ src/tile.js | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index ed9e588c..56d883de 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -1153,6 +1153,7 @@ function drawTiles( drawer, lastDrawn ){ function drawDebugInfo( drawer, tile, count, i ){ if ( USE_CANVAS ) { + drawer.context.save(); drawer.context.lineWidth = 2; drawer.context.font = 'small-caps bold 13px ariel'; drawer.context.strokeStyle = drawer.debugGridColor; @@ -1205,6 +1206,7 @@ function drawDebugInfo( drawer, tile, count, i ){ tile.position.x + 10, tile.position.y + 70 ); + drawer.context.restore(); } } diff --git a/src/tile.js b/src/tile.js index 4624ff01..8add9ede 100644 --- a/src/tile.js +++ b/src/tile.js @@ -1,5 +1,7 @@ (function( $ ){ - + var TILE_CACHE = {}, + TILE_CACHE_STACK = [], + TILE_CACHE_MAX = 256; /** * @class * @param {Number} level The zoom level this tile belongs to. @@ -140,7 +142,9 @@ $.Tile.prototype = { drawCanvas: function( context ) { var position = this.position, - size = this.size; + size = this.size, + rendered, + canvas; if ( !this.loaded || !this.image ) { $.console.warn( @@ -151,9 +155,9 @@ $.Tile.prototype = { } context.globalAlpha = this.opacity; - context.save(); + //context.save(); - //if we are supposed to b rendering fully opaque rectangle, + //if we are supposed to be rendering fully opaque rectangle, //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way //to avoid seeing the tile underneath is to clear the rectangle @@ -168,10 +172,32 @@ $.Tile.prototype = { ); } - - context.drawImage( this.image, position.x, position.y, size.x, size.y ); - context.restore(); + if( !TILE_CACHE[ this.image.src ] ){ + canvas = document.createElement( 'canvas' ); + canvas.width = this.image.width; + canvas.height = this.image.height; + rendered = canvas.getContext('2d'); + rendered.drawImage( this.image, 0, 0 ); + TILE_CACHE[ this.image.src ] = rendered; + } + + rendered = TILE_CACHE[ this.image.src ]; + //rendered.save(); + context.drawImage( + rendered.canvas, + 0, + 0, + rendered.canvas.width, + rendered.canvas.height, + position.x, + position.y, + size.x, + size.y + ); + //rendered.restore(); + + //context.restore(); }, /** @@ -183,7 +209,7 @@ $.Tile.prototype = { this.element.parentNode.removeChild( this.element ); } - this.element = null; + this.element = null; this.image = null; this.loaded = false; this.loading = false; From 2fc6cc3876b1686f2d08b9089981b2897263a87a Mon Sep 17 00:00:00 2001 From: thatcher Date: Fri, 1 Mar 2013 08:14:35 -0500 Subject: [PATCH 2/8] niave implementation of prerender for canvas, the first optimization discussed here: http://www.html5rocks.com/en/tutorials/canvas/performance/ --- src/drawer.js | 2 ++ src/tile.js | 42 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index 2fe5c4ba..102085f9 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -1153,6 +1153,7 @@ function drawTiles( drawer, lastDrawn ){ function drawDebugInfo( drawer, tile, count, i ){ if ( USE_CANVAS ) { + drawer.context.save(); drawer.context.lineWidth = 2; drawer.context.font = 'small-caps bold 13px ariel'; drawer.context.strokeStyle = drawer.debugGridColor; @@ -1205,6 +1206,7 @@ function drawDebugInfo( drawer, tile, count, i ){ tile.position.x + 10, tile.position.y + 70 ); + drawer.context.restore(); } } diff --git a/src/tile.js b/src/tile.js index 4624ff01..8add9ede 100644 --- a/src/tile.js +++ b/src/tile.js @@ -1,5 +1,7 @@ (function( $ ){ - + var TILE_CACHE = {}, + TILE_CACHE_STACK = [], + TILE_CACHE_MAX = 256; /** * @class * @param {Number} level The zoom level this tile belongs to. @@ -140,7 +142,9 @@ $.Tile.prototype = { drawCanvas: function( context ) { var position = this.position, - size = this.size; + size = this.size, + rendered, + canvas; if ( !this.loaded || !this.image ) { $.console.warn( @@ -151,9 +155,9 @@ $.Tile.prototype = { } context.globalAlpha = this.opacity; - context.save(); + //context.save(); - //if we are supposed to b rendering fully opaque rectangle, + //if we are supposed to be rendering fully opaque rectangle, //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way //to avoid seeing the tile underneath is to clear the rectangle @@ -168,10 +172,32 @@ $.Tile.prototype = { ); } - - context.drawImage( this.image, position.x, position.y, size.x, size.y ); - context.restore(); + if( !TILE_CACHE[ this.image.src ] ){ + canvas = document.createElement( 'canvas' ); + canvas.width = this.image.width; + canvas.height = this.image.height; + rendered = canvas.getContext('2d'); + rendered.drawImage( this.image, 0, 0 ); + TILE_CACHE[ this.image.src ] = rendered; + } + + rendered = TILE_CACHE[ this.image.src ]; + //rendered.save(); + context.drawImage( + rendered.canvas, + 0, + 0, + rendered.canvas.width, + rendered.canvas.height, + position.x, + position.y, + size.x, + size.y + ); + //rendered.restore(); + + //context.restore(); }, /** @@ -183,7 +209,7 @@ $.Tile.prototype = { this.element.parentNode.removeChild( this.element ); } - this.element = null; + this.element = null; this.image = null; this.loaded = false; this.loading = false; From 11a0f3b315bcc731412587af0ce00d05b166ddc1 Mon Sep 17 00:00:00 2001 From: thatcher Date: Tue, 5 Mar 2013 07:30:37 -0500 Subject: [PATCH 3/8] improved caching follows existing use of tile.unload to delete prerendered canvas --- Gruntfile.js | 2 +- src/tile.js | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index bd21d089..4c8f8006 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -136,7 +136,7 @@ module.exports = function(grunt) { grunt.registerTask("copy:release", function() { grunt.file.recurse("build", function(abspath, rootdir, subdir, filename) { var dest = "../site-build/" - + (subdir ? subdir + "/" : "") + + (subdir ? subdir + "/" : '/') + filename; grunt.file.copy(abspath, dest); diff --git a/src/tile.js b/src/tile.js index 8add9ede..4cb6d22e 100644 --- a/src/tile.js +++ b/src/tile.js @@ -146,7 +146,7 @@ $.Tile.prototype = { rendered, canvas; - if ( !this.loaded || !this.image ) { + if ( !this.loaded || !( this.image || TILE_CACHE[ this.url ] ) ){ $.console.warn( "Attempting to draw tile %s when it's not yet loaded.", this.toString() @@ -161,7 +161,7 @@ $.Tile.prototype = { //ie its done fading or fading is turned off, and if we are drawing //an image with an alpha channel, then the only way //to avoid seeing the tile underneath is to clear the rectangle - if( context.globalAlpha == 1 && this.image.src.match('.png') ){ + if( context.globalAlpha == 1 && this.url.match('.png') ){ //clearing only the inside of the rectangle occupied //by the png prevents edge flikering context.clearRect( @@ -173,16 +173,19 @@ $.Tile.prototype = { } - if( !TILE_CACHE[ this.image.src ] ){ + if( !TILE_CACHE[ this.url ] ){ canvas = document.createElement( 'canvas' ); canvas.width = this.image.width; canvas.height = this.image.height; rendered = canvas.getContext('2d'); rendered.drawImage( this.image, 0, 0 ); - TILE_CACHE[ this.image.src ] = rendered; + TILE_CACHE[ this.url ] = rendered; + //since we are caching the prerendered image on a canvas + //allow the image to not be held in memory + this.image = null; } - rendered = TILE_CACHE[ this.image.src ]; + rendered = TILE_CACHE[ this.url ]; //rendered.save(); context.drawImage( rendered.canvas, @@ -207,6 +210,9 @@ $.Tile.prototype = { unload: function() { if ( this.element && this.element.parentNode ) { this.element.parentNode.removeChild( this.element ); + } + if ( TILE_CACHE[ this.url ]){ + delete TILE_CACHE[ this.url ]; } this.element = null; From 61a844bdc0fce6e430c3f738de1aa00e217a9ca6 Mon Sep 17 00:00:00 2001 From: thatcher Date: Wed, 6 Mar 2013 05:51:31 -0500 Subject: [PATCH 4/8] what was initally a feature branch to work on just canvas prerender, evolved into a feature branch focused on broader ideas discussed in issue #4 - I've basically been able to reduce time spent in drawTile by half. good stuff, thanks to dustmoo for getting us looking into this --- src/drawer.js | 28 ++++++++++++++++++---------- src/navigator.js | 19 ++++++++++++++++++- src/openseadragon.js | 14 +++++++------- src/referencestrip.js | 6 ++++-- src/tile.js | 4 +--- src/tilesource.js | 18 ++++++++++++++++++ 6 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/drawer.js b/src/drawer.js index 102085f9..0792e2e6 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -515,9 +515,15 @@ function updateViewport( drawer ) { ).x; zeroRatioT = drawer.viewport.deltaPixelsFromPoints( - drawer.source.getPixelRatio( 0 ), + drawer.source.getPixelRatio( + Math.max( + drawer.source.getClosestLevel( drawer.viewport.containerSize ) - 1, + 0 + ) + ), false ).x; + console.log( "ZERO RATIO T %s", drawer.source.getClosestLevel( drawer.viewport.containerSize ) ); optimalRatio = drawer.immediateRender ? 1 : @@ -850,7 +856,7 @@ function blendTile( drawer, tile, x, y, level, levelOpacity, currentTime ){ } deltaTime = currentTime - tile.blendStart; - opacity = Math.min( 1, deltaTime / ( blendTimeMillis || 1 ) ); + opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1; if ( drawer.alwaysBlend ) { opacity *= levelOpacity; @@ -1080,14 +1086,16 @@ function drawTiles( drawer, lastDrawn ){ //$.console.log("Rendering collection tile %s | %s | %s", tile.y, tile.y, position); if( tileSource ){ drawer.collectionOverlays[ tileKey ] = viewer = new $.Viewer({ - element: $.makeNeutralElement( "div" ), - mouseNavEnabled: false, - showNavigator: false, - showSequenceControl: false, - showNavigationControl: false, - //visibilityRatio: 1, - //debugMode: true, - //debugGridColor: 'red', + element: $.makeNeutralElement( "div" ), + mouseNavEnabled: false, + showNavigator: false, + showSequenceControl: false, + showNavigationControl: false, + //visibilityRatio: 1, + //debugMode: true, + //debugGridColor: 'red', + animationTime: 0, + blentTime: 0.1, tileSources: [ tileSource ] diff --git a/src/navigator.js b/src/navigator.js index 04d17a4d..1a5defdd 100644 --- a/src/navigator.js +++ b/src/navigator.js @@ -37,7 +37,10 @@ $.Navigator = function( options ){ showNavigator: false, mouseNavEnabled: false, showNavigationControl: false, - showSequenceControl: false + showSequenceControl: false, + immediateRender: true, + blendTime: 0, + animationTime: 0 }); options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio; @@ -228,6 +231,20 @@ $.extend( $.Navigator.prototype, $.EventHandler.prototype, $.Viewer.prototype, { }( this.displayRegion.style )); } + }, + + open: function( source ){ + var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio ); + if( source.tileSize > containerSize.x || + source.tileSize > containerSize.y ){ + this.minPixelRatio = Math.min( + containerSize.x, + containerSize.y + ) / source.tileSize; + } else { + this.minPixelRatio = thie.viewer.minPixelRatio; + } + return $.Viewer.prototype.open.apply( this, [ source ] ); } }); diff --git a/src/openseadragon.js b/src/openseadragon.js index c70d7a7b..497f006b 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -473,25 +473,25 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ wrapHorizontal: false, wrapVertical: false, visibilityRatio: 0.5, - minPixelRatio: 0.5, - minZoomImageRatio: 0.8, - maxZoomPixelRatio: 2, + minPixelRatio: 0.9, defaultZoomLevel: 0, minZoomLevel: null, maxZoomLevel: null, //UI RESPONSIVENESS AND FEEL - springStiffness: 5.0, + springStiffness: 7.0, clickTimeThreshold: 300, clickDistThreshold: 5, zoomPerClick: 2.0, zoomPerScroll: 1.2, zoomPerSecond: 2.0, - animationTime: 1.5, - blendTime: 1.5, + animationTime: 1.0, + blendTime: 0, alwaysBlend: false, autoHideControls: true, immediateRender: false, + minZoomImageRatio: 0.8, + maxZoomPixelRatio: 1.2, //DEFAULT CONTROL SETTINGS showSequenceControl: true, //SEQUENCE @@ -530,7 +530,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ //PERFORMANCE SETTINGS imageLoaderLimit: 0, maxImageCacheCount: 200, - timeout: 5000, + timeout: 30000, //INTERFACE RESOURCE SETTINGS prefixUrl: "/images/", diff --git a/src/referencestrip.js b/src/referencestrip.js index 20f8ff03..80bf10d1 100644 --- a/src/referencestrip.js +++ b/src/referencestrip.js @@ -377,11 +377,13 @@ function loadPanels(strip, viewerSize, scroll){ tileSources: [ strip.viewer.tileSources[ i ] ], element: element, navigatorSizeRatio: strip.sizeRatio, - minPixelRatio: strip.minPixelRatio, showNavigator: false, mouseNavEnabled: false, showNavigationControl: false, - showSequenceControl: false + showSequenceControl: false, + immediateRender: true, + blendTime: 0, + animationTime: 0 } ); miniViewer.displayRegion = $.makeNeutralElement( "textarea" ); diff --git a/src/tile.js b/src/tile.js index 4cb6d22e..35e6821b 100644 --- a/src/tile.js +++ b/src/tile.js @@ -1,7 +1,5 @@ (function( $ ){ - var TILE_CACHE = {}, - TILE_CACHE_STACK = [], - TILE_CACHE_MAX = 256; + var TILE_CACHE = {}; /** * @class * @param {Number} level The zoom level this tile belongs to. diff --git a/src/tilesource.js b/src/tilesource.js index 51b0f03f..5f59f84a 100644 --- a/src/tilesource.js +++ b/src/tilesource.js @@ -180,6 +180,24 @@ $.TileSource.prototype = { return new $.Point(rx, ry); }, + + /** + * @function + * @param {Number} level + */ + getClosestLevel: function( rect ) { + var i, + tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.tileSize ), + tiles; + for( i = this.minLevel; i < this.maxLevel; i++ ){ + tiles = this.getNumTiles( i ); + if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){ + break; + } + } + return Math.max( 0, i - 1 ); + }, + /** * @function * @param {Number} level From e04813e47746df09fa0be2738403cfeb580828ae Mon Sep 17 00:00:00 2001 From: thatcher Date: Wed, 6 Mar 2013 06:44:55 -0500 Subject: [PATCH 5/8] minZoomImageRatio should not be less than minPixelRatio to avoid blurry full zoom out. Its worth noting now that several options are 'coupled' but dont have logic which addresses the coupling so as to avoid ill affects from settings that dont make sense for how they are coupled. --- src/openseadragon.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 497f006b..9ee4c011 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -490,8 +490,8 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ alwaysBlend: false, autoHideControls: true, immediateRender: false, - minZoomImageRatio: 0.8, - maxZoomPixelRatio: 1.2, + minZoomImageRatio: 0.9, + maxZoomPixelRatio: 1.1, //DEFAULT CONTROL SETTINGS showSequenceControl: true, //SEQUENCE From 0bdc4383bde2e0fe8243d39f7715cb76895f6ed6 Mon Sep 17 00:00:00 2001 From: thatcher Date: Wed, 6 Mar 2013 07:35:47 -0500 Subject: [PATCH 6/8] simplifying zoom settings so scroll and click arent significantly different --- src/openseadragon.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 9ee4c011..1f45198d 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -482,9 +482,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ springStiffness: 7.0, clickTimeThreshold: 300, clickDistThreshold: 5, - zoomPerClick: 2.0, - zoomPerScroll: 1.2, - zoomPerSecond: 2.0, + zoomPerClick: 1.5, + zoomPerScroll: 1.5, + zoomPerSecond: 1.5, animationTime: 1.0, blendTime: 0, alwaysBlend: false, From 75f7589312e2d2472614bce6a322a486c49a423e Mon Sep 17 00:00:00 2001 From: thatcher Date: Wed, 6 Mar 2013 07:43:12 -0500 Subject: [PATCH 7/8] simplifying zoom settings so scroll and click arent significantly different --- src/openseadragon.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 1f45198d..7c7a84f3 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -482,9 +482,9 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ springStiffness: 7.0, clickTimeThreshold: 300, clickDistThreshold: 5, - zoomPerClick: 1.5, - zoomPerScroll: 1.5, - zoomPerSecond: 1.5, + zoomPerClick: 2, + zoomPerScroll: 1.1, + zoomPerSecond: 1.0, animationTime: 1.0, blendTime: 0, alwaysBlend: false, From f107aaf0e85d89b90787591aacac031da4cfc664 Mon Sep 17 00:00:00 2001 From: thatcher Date: Wed, 6 Mar 2013 21:53:20 -0500 Subject: [PATCH 8/8] removing debug console log statement per ventero's review notes --- src/drawer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/drawer.js b/src/drawer.js index 0792e2e6..bb1d2a95 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -523,7 +523,6 @@ function updateViewport( drawer ) { ), false ).x; - console.log( "ZERO RATIO T %s", drawer.source.getClosestLevel( drawer.viewport.containerSize ) ); optimalRatio = drawer.immediateRender ? 1 :