diff --git a/src/drawer.js b/src/drawer.js index 1f18f102..e14c14e3 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -232,6 +232,23 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ } }, + /** + * Translates from OpenSeadragon viewer rectangle to drawer rectangle. + * @param {OpenSeadragon.Rect} rectangle - The rectangle in viewport coordinate system. + * @return {OpenSeadragon.Rect} Rectangle in drawer coordinate system. + */ + viewportToDrawerRectangle: function(rectangle) { + var topLeft = this.viewport.pixelFromPoint(rectangle.getTopLeft(), true); + var size = this.viewport.deltaPixelsFromPoints(rectangle.getSize(), true); + + return new $.Rect( + topLeft.x * $.pixelDensityRatio, + topLeft.y * $.pixelDensityRatio, + size.x * $.pixelDensityRatio, + size.y * $.pixelDensityRatio + ); + }, + /** * Draws the given tile. * @param {OpenSeadragon.Tile} tile - The tile to draw. @@ -288,81 +305,95 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{ }, // private - drawDebugInfo: function( tile, count, i ){ - if ( this.useCanvas ) { - this.context.save(); - this.context.lineWidth = 2 * $.pixelDensityRatio; - this.context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial'; - this.context.strokeStyle = this.debugGridColor; - this.context.fillStyle = this.debugGridColor; - - if ( this.viewport.degrees !== 0 ) { - this._offsetForRotation( tile, this.canvas, this.context, this.viewport.degrees ); - } - - this.context.strokeRect( - tile.position.x * $.pixelDensityRatio, - tile.position.y * $.pixelDensityRatio, - tile.size.x * $.pixelDensityRatio, - tile.size.y * $.pixelDensityRatio - ); - - var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio; - var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio; - - // Rotate the text the right way around. - this.context.translate( tileCenterX, tileCenterY ); - this.context.rotate( Math.PI / 180 * -this.viewport.degrees ); - this.context.translate( -tileCenterX, -tileCenterY ); - - if( tile.x === 0 && tile.y === 0 ){ - this.context.fillText( - "Zoom: " + this.viewport.getZoom(), - tile.position.x * $.pixelDensityRatio, - (tile.position.y - 30) * $.pixelDensityRatio - ); - this.context.fillText( - "Pan: " + this.viewport.getBounds().toString(), - tile.position.x * $.pixelDensityRatio, - (tile.position.y - 20) * $.pixelDensityRatio - ); - } - this.context.fillText( - "Level: " + tile.level, - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 20) * $.pixelDensityRatio - ); - this.context.fillText( - "Column: " + tile.x, - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 30) * $.pixelDensityRatio - ); - this.context.fillText( - "Row: " + tile.y, - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 40) * $.pixelDensityRatio - ); - this.context.fillText( - "Order: " + i + " of " + count, - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 50) * $.pixelDensityRatio - ); - this.context.fillText( - "Size: " + tile.size.toString(), - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 60) * $.pixelDensityRatio - ); - this.context.fillText( - "Position: " + tile.position.toString(), - (tile.position.x + 10) * $.pixelDensityRatio, - (tile.position.y + 70) * $.pixelDensityRatio - ); - - if ( this.viewport.degrees !== 0 ) { - this._restoreRotationChanges( tile, this.canvas, this.context ); - } - this.context.restore(); + drawRectangle: function(rect, fillStyle) { + if (!this.useCanvas) { + return; } + + this.context.save(); + this.context.fillStyle = fillStyle; + this.context.fillRect(rect.x, rect.y, rect.width, rect.height); + this.context.restore(); + }, + + // private + drawDebugInfo: function( tile, count, i ){ + if ( !this.useCanvas ) { + return; + } + + this.context.save(); + this.context.lineWidth = 2 * $.pixelDensityRatio; + this.context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial'; + this.context.strokeStyle = this.debugGridColor; + this.context.fillStyle = this.debugGridColor; + + if ( this.viewport.degrees !== 0 ) { + this._offsetForRotation( tile, this.canvas, this.context, this.viewport.degrees ); + } + + this.context.strokeRect( + tile.position.x * $.pixelDensityRatio, + tile.position.y * $.pixelDensityRatio, + tile.size.x * $.pixelDensityRatio, + tile.size.y * $.pixelDensityRatio + ); + + var tileCenterX = (tile.position.x + (tile.size.x / 2)) * $.pixelDensityRatio; + var tileCenterY = (tile.position.y + (tile.size.y / 2)) * $.pixelDensityRatio; + + // Rotate the text the right way around. + this.context.translate( tileCenterX, tileCenterY ); + this.context.rotate( Math.PI / 180 * -this.viewport.degrees ); + this.context.translate( -tileCenterX, -tileCenterY ); + + if( tile.x === 0 && tile.y === 0 ){ + this.context.fillText( + "Zoom: " + this.viewport.getZoom(), + tile.position.x * $.pixelDensityRatio, + (tile.position.y - 30) * $.pixelDensityRatio + ); + this.context.fillText( + "Pan: " + this.viewport.getBounds().toString(), + tile.position.x * $.pixelDensityRatio, + (tile.position.y - 20) * $.pixelDensityRatio + ); + } + this.context.fillText( + "Level: " + tile.level, + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 20) * $.pixelDensityRatio + ); + this.context.fillText( + "Column: " + tile.x, + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 30) * $.pixelDensityRatio + ); + this.context.fillText( + "Row: " + tile.y, + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 40) * $.pixelDensityRatio + ); + this.context.fillText( + "Order: " + i + " of " + count, + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 50) * $.pixelDensityRatio + ); + this.context.fillText( + "Size: " + tile.size.toString(), + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 60) * $.pixelDensityRatio + ); + this.context.fillText( + "Position: " + tile.position.toString(), + (tile.position.x + 10) * $.pixelDensityRatio, + (tile.position.y + 70) * $.pixelDensityRatio + ); + + if ( this.viewport.degrees !== 0 ) { + this._restoreRotationChanges( tile, this.canvas, this.context ); + } + this.context.restore(); }, // private diff --git a/src/openseadragon.js b/src/openseadragon.js index 8d231cbe..3e3cffbd 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -206,6 +206,11 @@ * @property {Number} [opacity=1] * Opacity of the drawer (1=opaque, 0=transparent) * + * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null] + * Draws a colored rectangle behind the tile if it is not loaded yet. + * You can pass a CSS color value like "#FF8800". + * When passing a function the tiledImage and canvas context are available as argument which is useful when you draw a gradient or pattern. + * * @property {Number} [degrees=0] * Initial rotation. * @@ -262,7 +267,7 @@ * Possible subproperties (Numbers, in screen coordinates): left, top, right, bottom. * * @property {Number} [imageLoaderLimit=0] - * The maximum number of image requests to make concurrently. By default + * The maximum number of image requests to make concurrently. By default * it is set to 0 allowing the browser to make the maximum number of * image requests in parallel as allowed by the browsers policy. * @@ -1013,10 +1018,11 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){ navigatorRotate: true, // INITIAL ROTATION - degrees: 0, + degrees: 0, // APPEARANCE - opacity: 1, + opacity: 1, + placeholderFillStyle: null, //REFERENCE STRIP SETTINGS showReferenceStrip: false, diff --git a/src/tiledimage.js b/src/tiledimage.js index 405989b7..eebfa0af 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -65,6 +65,7 @@ * @param {Boolean} [options.alwaysBlend] - See {@link OpenSeadragon.Options}. * @param {Number} [options.minPixelRatio] - 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|Boolean} [options.crossOriginPolicy] - See {@link OpenSeadragon.Options}. */ $.TiledImage = function( options ) { @@ -126,21 +127,22 @@ $.TiledImage = function( options ) { coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean. lastDrawn: [], // An unordered list of Tiles drawn last frame. lastResetTime: 0, // Last time for which the tiledImage was reset. - _midDraw: false, // Is the tiledImage currently updating the viewport? - _needsDraw: true, // Does the tiledImage need to update the viewport again? + _midDraw: false, // Is the tiledImage currently updating the viewport? + _needsDraw: true, // Does the tiledImage need to update the viewport again? //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 + 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 }, options ); @@ -1148,7 +1150,7 @@ function compareTiles( previousBest, tile ) { return previousBest; } -function drawTiles( tiledImage, lastDrawn ){ +function drawTiles( tiledImage, lastDrawn ) { var i, tile, tileKey, @@ -1158,28 +1160,39 @@ function drawTiles( tiledImage, lastDrawn ){ tileSource; var usedClip = false; - if (tiledImage._clip) { + if ( tiledImage._clip ) { tiledImage._drawer.saveContext(); + var box = tiledImage.imageToViewportRectangle(tiledImage._clip, true); - var topLeft = tiledImage.viewport.pixelFromPoint(box.getTopLeft(), true); - var size = tiledImage.viewport.deltaPixelsFromPoints(box.getSize(), true); - box = new OpenSeadragon.Rect(topLeft.x * $.pixelDensityRatio, - topLeft.y * $.pixelDensityRatio, - size.x * $.pixelDensityRatio, - size.y * $.pixelDensityRatio); - tiledImage._drawer.setClip(box); + var clipRect = tiledImage._drawer.viewportToDrawerRectangle(box); + tiledImage._drawer.setClip(clipRect); + usedClip = true; } + if ( tiledImage.placeholderFillStyle && lastDrawn.length === 0 ) { + var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true)); + + var fillStyle = null; + if ( typeof tiledImage.placeholderFillStyle === "function" ) { + fillStyle = tiledImage.placeholderFillStyle(tiledImage, tiledImage._drawer.context); + } + else { + fillStyle = tiledImage.placeholderFillStyle; + } + + tiledImage._drawer.drawRectangle(placeholderRect, fillStyle); + } + for ( i = lastDrawn.length - 1; i >= 0; i-- ) { tile = lastDrawn[ i ]; tiledImage._drawer.drawTile( tile, tiledImage._drawingHandler ); tile.beingDrawn = true; - if( tiledImage.debugMode ){ - try{ + if( tiledImage.debugMode ) { + try { tiledImage._drawer.drawDebugInfo( tile, lastDrawn.length, i ); - }catch(e){ + } catch(e) { $.console.error(e); } } @@ -1203,7 +1216,7 @@ function drawTiles( tiledImage, lastDrawn ){ } } - if (usedClip) { + if ( usedClip ) { tiledImage._drawer.restoreContext(); } } diff --git a/src/viewer.js b/src/viewer.js index 7b269350..57ad0f1f 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -1206,6 +1206,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, * and "source" properties. * @param {Boolean} [options.collectionImmediately=false] If collectionMode is on, * specifies whether to snap to the new arrangement immediately or to animate to it. + * @param {String|CanvasGradient|CanvasPattern|Function} [options.placeholderFillStyle] - See {@link OpenSeadragon.Options}. * @fires OpenSeadragon.World.event:add-item * @fires OpenSeadragon.Viewer.event:add-item-failed */ @@ -1217,6 +1218,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, this._hideMessage(); + if (options.placeholderFillStyle === undefined) { + options.placeholderFillStyle = this.placeholderFillStyle; + } + var myQueueItem = { options: options }; @@ -1284,6 +1289,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, width: queueItem.options.width, height: queueItem.options.height, clip: queueItem.options.clip, + placeholderFillStyle: queueItem.options.placeholderFillStyle, springStiffness: _this.springStiffness, animationTime: _this.animationTime, minZoomImageRatio: _this.minZoomImageRatio,