move tile update logic back to TiledImage to keep drawing logic cleaner

This commit is contained in:
Tom 2023-03-13 15:56:04 -04:00
parent 641951aaed
commit ade59513df
8 changed files with 301 additions and 162 deletions

View File

@ -82,9 +82,8 @@ $.extend( $.CanvasDrawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadr
this._prepareNewFrame(); // prepare to draw a new frame
tiledImages.forEach(function(tiledImage){
if (tiledImage.opacity !== 0 || tiledImage._preload) {
tiledImage._midDraw = true;
_this._updateViewportWithTiledImage(tiledImage);
tiledImage._midDraw = false;
// _this._updateViewportWithTiledImage(tiledImage);
_this._drawTiles(tiledImage);
}
else {
tiledImage._needsDraw = false;
@ -189,102 +188,102 @@ $.extend( $.CanvasDrawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadr
/* Methods from TiledImage */
/**
* @private
* @inner
* Handles drawing a single TiledImage to the canvas
*
*/
_updateViewportWithTiledImage: function(tiledImage) {
var _this = this;
tiledImage._needsDraw = false;
tiledImage._tilesLoading = 0;
tiledImage.loadingCoverage = {};
// /**
// * @private
// * @inner
// * Handles drawing a single TiledImage to the canvas
// *
// */
// _updateViewportWithTiledImage: function(tiledImage) {
// var _this = this;
// tiledImage._needsDraw = false;
// tiledImage._tilesLoading = 0;
// tiledImage.loadingCoverage = {};
// Reset tile's internal drawn state
while (tiledImage.lastDrawn.length > 0) {
var tile = tiledImage.lastDrawn.pop();
tile.beingDrawn = false;
}
// // Reset tile's internal drawn state
// while (tiledImage.lastDrawn.length > 0) {
// var tile = tiledImage.lastDrawn.pop();
// tile.beingDrawn = false;
// }
var drawArea = tiledImage.getDrawArea();
if(!drawArea){
return;
}
// var drawArea = tiledImage.getDrawArea();
// if(!drawArea){
// return;
// }
function updateTile(info){
var tile = info.tile;
if(tile && tile.loaded){
var needsDraw = _this._blendTile(
tiledImage,
tile,
tile.x,
tile.y,
info.level,
info.levelOpacity,
info.currentTime
);
if(needsDraw){
tiledImage._needsDraw = true;
}
}
}
// function updateTile(info){
// var tile = info.tile;
// if(tile && tile.loaded){
// var needsDraw = _this._blendTile(
// tiledImage,
// tile,
// tile.x,
// tile.y,
// info.level,
// info.levelOpacity,
// info.currentTime
// );
// if(needsDraw){
// tiledImage._needsDraw = true;
// }
// }
// }
var infoArray = tiledImage.getTileInfoForDrawing();
infoArray.forEach(updateTile);
// var infoArray = tiledImage.getTileInfoForDrawing();
// infoArray.forEach(updateTile);
this._drawTiles(tiledImage);
// this._drawTiles(tiledImage);
},
// },
/**
* @private
* @inner
* Updates the opacity of a tile according to the time it has been on screen
* to perform a fade-in.
* Updates coverage once a tile is fully opaque.
* Returns whether the fade-in has completed.
*
* @param {OpenSeadragon.Tile} tile
* @param {Number} x
* @param {Number} y
* @param {Number} level
* @param {Number} levelOpacity
* @param {Number} currentTime
* @returns {Boolean}
*/
_blendTile: function( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
var blendTimeMillis = 1000 * tiledImage.blendTime,
deltaTime,
opacity;
// /**
// * @private
// * @inner
// * Updates the opacity of a tile according to the time it has been on screen
// * to perform a fade-in.
// * Updates coverage once a tile is fully opaque.
// * Returns whether the fade-in has completed.
// *
// * @param {OpenSeadragon.Tile} tile
// * @param {Number} x
// * @param {Number} y
// * @param {Number} level
// * @param {Number} levelOpacity
// * @param {Number} currentTime
// * @returns {Boolean}
// */
// _blendTile: function( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
// var blendTimeMillis = 1000 * tiledImage.blendTime,
// deltaTime,
// opacity;
if ( !tile.blendStart ) {
tile.blendStart = currentTime;
}
// if ( !tile.blendStart ) {
// tile.blendStart = currentTime;
// }
deltaTime = currentTime - tile.blendStart;
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
// deltaTime = currentTime - tile.blendStart;
// opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
if ( tiledImage.alwaysBlend ) {
opacity *= levelOpacity;
}
// if ( tiledImage.alwaysBlend ) {
// opacity *= levelOpacity;
// }
tile.opacity = opacity;
// tile.opacity = opacity;
tiledImage.lastDrawn.push( tile );
// tiledImage.lastDrawn.push( tile );
if ( opacity === 1 ) {
tiledImage._setCoverage( tiledImage.coverage, level, x, y, true );
tiledImage._hasOpaqueTile = true;
} else if ( deltaTime < blendTimeMillis ) {
return true;
}
// if ( opacity === 1 ) {
// tiledImage._setCoverage( tiledImage.coverage, level, x, y, true );
// tiledImage._hasOpaqueTile = true;
// } else if ( deltaTime < blendTimeMillis ) {
// return true;
// }
return false;
},
// return false;
// },
/**
* @private

View File

@ -73,9 +73,8 @@ $.extend( $.HTMLDrawer.prototype, $.DrawerBase.prototype, /** @lends OpenSeadrag
this._prepareNewFrame(); // prepare to draw a new frame
tiledImages.forEach(function(tiledImage){
if (tiledImage.opacity !== 0 || tiledImage._preload) {
tiledImage._midDraw = true;
_this._updateViewportWithTiledImage(tiledImage);
tiledImage._midDraw = false;
// _this._updateViewportWithTiledImage(tiledImage);
_this._drawTiles(tiledImage);
}
else {
tiledImage._needsDraw = false;

View File

@ -156,7 +156,6 @@ $.TiledImage = function( options ) {
loadingCoverage: {}, // A '3d' dictionary [level][x][y] --> Boolean; shows what areas are loaded or are being loaded/blended.
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?
_hasOpaqueTile: false, // Do we have even one fully opaque tile?
_tilesLoading: 0, // The number of pending tile requests.
@ -291,8 +290,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
},
/**
* Updates the TiledImage's bounds, animating if needed.
* @returns {Boolean} Whether the TiledImage animated.
* Updates the TiledImage's bounds, animating if needed. Based on the new
* bounds, updates the levels and tiles to be drawn into the viewport.
* @returns {Boolean} Whether the TiledImage needs to be drawn.
*/
update: function() {
var xUpdated = this._xSpring.update();
@ -300,7 +300,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
var scaleUpdated = this._scaleSpring.update();
var degreesUpdated = this._degreesSpring.update();
this._updateTilesForViewport();
this._updateLevelsForViewport();
this._updateTilesInViewport();
if (xUpdated || yUpdated || scaleUpdated || degreesUpdated) {
this._updateForScale();
@ -312,6 +313,14 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return false;
},
/**
* Mark this TiledImage as having been drawn, so that it will only be drawn
* again if something changes about the image
*/
setDrawn: function(){
this._needsDraw = false;
},
/**
* Destroy the TiledImage (unload current loaded tiles).
*/
@ -897,7 +906,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @returns {Boolean} Whether the TiledImage should be flipped before rendering.
*/
getFlip: function() {
return !!this.flipped;
return this.flipped;
},
/**
@ -905,11 +914,26 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @fires OpenSeadragon.TiledImage.event:bounds-change
*/
setFlip: function(flip) {
this.flipped = !!flip;
this.flipped = flip;
},
get flipped(){
return this._flipped;
},
set flipped(flipped){
this._flipped = !!flipped;
this._needsDraw = true;
this._raiseBoundsChange();
},
get debugMode(){
return this._debugMode;
},
set debugMode(debug){
this._debugMode = !!debug;
this._needsDraw = true;
},
/**
* @returns {Number} The TiledImage's current opacity.
*/
@ -922,11 +946,19 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @fires OpenSeadragon.TiledImage.event:opacity-change
*/
setOpacity: function(opacity) {
this.opacity = opacity;
},
get opacity() {
return this._opacity;
},
set opacity(opacity) {
if (opacity === this.opacity) {
return;
}
this.opacity = opacity;
this._opacity = opacity;
this._needsDraw = true;
/**
* Raised when the TiledImage's opacity is changed.
@ -1008,6 +1040,14 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return drawArea;
},
/**
*
* @returns {Array} Array of Tiles that make up the current view
*/
getTilesToDraw: function(){
return this._tilesToDraw;
},
/**
* Get the point around which this tiled image is rotated
* @private
@ -1018,24 +1058,16 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
return this.getBoundsNoRotate(current).getCenter();
},
/**
* @returns {String} The TiledImage's current compositeOperation.
*/
getCompositeOperation: function() {
return this.compositeOperation;
get compositeOperation(){
return this._compositeOperation;
},
/**
* @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
* @fires OpenSeadragon.TiledImage.event:composite-operation-change
*/
setCompositeOperation: function(compositeOperation) {
var _this = this;
if (compositeOperation === this.compositeOperation) {
set compositeOperation(compositeOperation){
if (compositeOperation === this._compositeOperation) {
return;
}
this.compositeOperation = compositeOperation;
this._compositeOperation = compositeOperation;
this._needsDraw = true;
/**
* Raised when the TiledImage's opacity is changed.
@ -1048,23 +1080,24 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.raiseEvent('composite-operation-change', {
compositeOperation: this.compositeOperation
compositeOperation: this._compositeOperation
});
/**
* Raised when a TiledImage's opacity is changed.
* @event composite-operation-change
* @memberOf OpenSeadragon.TiledImage
* @type {object}
* @property {String} compositeOperation - The new compositeOperation value.
* @property {OpenSeadragon.Viewer} eventSource - A reference to the
* Viewer which raised the event.
* @property {?Object} userData - Arbitrary subscriber-defined object.
*/
this.viewer.raiseEvent('composite-operation-change', {
compositeOperation: _this.compositeOperation,
tiledImage: _this
});
},
/**
* @returns {String} The TiledImage's current compositeOperation.
*/
getCompositeOperation: function() {
return this._compositeOperation;
},
/**
* @param {String} compositeOperation the tiled image should be drawn with this globalCompositeOperation.
* @fires OpenSeadragon.TiledImage.event:composite-operation-change
*/
setCompositeOperation: function(compositeOperation) {
this.compositeOperation = compositeOperation; //invokes setter
},
// private
@ -1148,15 +1181,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
};
},
/**
*
* @returns {Array} Array of Tiles within the viewport which should be drawn
*/
getTileInfoForDrawing: function(){
return this._tilesToDraw;
},
_updateTilesForViewport: function(){
_updateLevelsForViewport: function(){
var levelsInterval = this._getLevelsInterval();
var lowestLevel = levelsInterval.lowestLevel;
var highestLevel = levelsInterval.highestLevel;
@ -1256,6 +1282,99 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
} else {
this._setFullyLoaded(this._tilesLoading === 0);
}
// Update
},
/**
* @private
* @inner
* Update all tiles that contribute to the current view
*
*/
_updateTilesInViewport: function() {
var _this = this;
this._tilesLoading = 0;
this.loadingCoverage = {};
// Reset tile's internal drawn state
while (this.lastDrawn.length > 0) {
var tile = this.lastDrawn.pop();
tile.beingDrawn = false;
}
var drawArea = this.getDrawArea();
if(!drawArea){
return;
}
function updateTile(info){
var tile = info.tile;
if(tile && tile.loaded){
var needsDraw = _this._blendTile(
tile,
tile.x,
tile.y,
info.level,
info.levelOpacity,
info.currentTime
);
if(needsDraw){
_this._needsDraw = true;
}
}
}
this._tilesToDraw.forEach(updateTile);
},
/**
* @private
* @inner
* Updates the opacity of a tile according to the time it has been on screen
* to perform a fade-in.
* Updates coverage once a tile is fully opaque.
* Returns whether the fade-in has completed.
*
* @param {OpenSeadragon.Tile} tile
* @param {Number} x
* @param {Number} y
* @param {Number} level
* @param {Number} levelOpacity
* @param {Number} currentTime
* @returns {Boolean}
*/
_blendTile: function(tile, x, y, level, levelOpacity, currentTime ){
var blendTimeMillis = 1000 * this.blendTime,
deltaTime,
opacity;
if ( !tile.blendStart ) {
tile.blendStart = currentTime;
}
deltaTime = currentTime - tile.blendStart;
opacity = blendTimeMillis ? Math.min( 1, deltaTime / ( blendTimeMillis ) ) : 1;
if ( this.alwaysBlend ) {
opacity *= levelOpacity;
}
tile.opacity = opacity;
this.lastDrawn.push( tile );
if ( opacity === 1 ) {
this._setCoverage( this.coverage, level, x, y, true );
this._hasOpaqueTile = true;
} else if ( deltaTime < blendTimeMillis ) {
return true;
}
return false;
},
/**
@ -1748,14 +1867,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
_this._setTileLoaded(tile, data, cutoff, tileRequest);
};
// Check if we're mid-update; this can happen on IE8 because image load events for
// cached images happen immediately there
if ( !this._midDraw ) {
finish();
} else {
// Wait until after the update, in case caching unloads any tiles
window.setTimeout( finish, 1);
}
finish();
},
/**
@ -1797,22 +1910,20 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
});
}
/**
* Triggered when a tile has just been loaded in memory. That means that the
* image has been downloaded and can be modified before being drawn to the canvas.
* Triggered when a tile is loaded and pre-processing is compelete,
* and the tile is ready to draw.
*
* @event tile-ready
* @memberof OpenSeadragon.Viewer
* @type {object}
* @property {*} data image data, the data sent to ImageJob.prototype.finish(), by default an Image object
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {OpenSeadragon.Tile} tile - The tile which has been loaded.
* @property {OpenSeadragon.TiledImage} tiledImage - The tiled image of the loaded tile.
* @property {XMLHttpRequest} tileRequest - The AJAX request that loaded this tile (if applicable).
*/
_this.viewer.raiseEvent("tile-ready", {
tile: tile,
tiledImage: _this,
tileRequest: tileRequest,
data: data
tileRequest: tileRequest
});
_this._needsDraw = true;
}

View File

@ -432,7 +432,7 @@ $.Viewer = function( options ) {
}
} else if(this.useCanvas && $.supportsCanvas) {
this.drawer = new $.Drawer({
this.drawer = new $.CanvasDrawer({
viewer: this,
viewport: this.viewport,
element: this.canvas,

View File

@ -257,6 +257,9 @@ $.extend( $.World.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.W
*/
draw: function() {
this.viewer.drawer.draw(this._items);
this._items.forEach(function(item){
item.setDrawn();
});
this._needsDraw = false;
},

View File

@ -7,6 +7,8 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
super(options);
let _this = this;
this._stats = options.stats; // optional input of stats.js object to enable performance testing
// this.viewer set by parent constructor
// this.canvas set by parent constructor, created and appended to the viewer container element
this._camera = null;
@ -55,12 +57,16 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
this._setupRenderer();
}
renderFrame(){
// this._stats && this._stats.begin();
if(this._animationFrame) {
cancelAnimationFrame(this._animationFrame);
}
this._animationFrame = requestAnimationFrame(()=>this.render());
// this._stats && this._stats.end();
}
render(){
// this._stats && this._stats.begin();
let numItems = this.viewer.world.getItemCount();
this._outputContext.clearRect(0, 0, this._outputCanvas.width, this._outputCanvas.height);
//iterate over items to draw
@ -94,6 +100,8 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
if(this._renderingContinuously){
this.renderFrame();
}
// this._stats && this._stats.end();
// console.log(this._renderer.info.memory, this._renderer.info.render.triangles);
}
renderContinuously(continuously){
@ -208,7 +216,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
this.viewer.addHandler("viewport-change", () => this._viewportChangeHandler());
this.viewer.addHandler("home", () => this._viewportChangeHandler());
this.viewer.addHandler("update-viewport", () => this.renderFrame());
this.viewer.addHandler("update-viewport", () => this.render());
this._viewportChangeHandler();
}
@ -341,6 +349,8 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
_updateTiledImageRendering(tiledImage, tile){
// this._stats && this._stats.begin();
let scene = this._tiledImageMap[tiledImage[this._uuid]];
let bounds = this._tileMap[tile.cacheKey].userData._tileBounds
@ -367,6 +377,8 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
}
}
// this._stats && this._stats.end();
this.renderFrame();
}
@ -472,6 +484,7 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
}
_viewportChangeHandler(){
//this._stats && this._stats.begin();
let viewer = this.viewer;
let viewerBounds = viewer.viewport.getBoundsNoRotate(true);
@ -493,9 +506,8 @@ export class ThreeJSDrawer extends OpenSeadragon.DrawerBase{
let tiledImage = viewer.world.getItemAt(i);
this._updateMeshIfNeeded(tiledImage);
}
this.renderFrame();
this.render();
// this.renderFrame(); //this._stats && this._stats.end();
}
_renderToClippingCanvas(item){

View File

@ -6,6 +6,7 @@
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
<script type="text/javascript" src='../lib/jquery-ui-1.10.2/js/jquery-ui-1.10.2.min.js'></script>
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
<!-- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js"></script> -->
<!-- <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script> -->
<!-- <script type="importmap">
{

View File

@ -1,6 +1,6 @@
//imports
import { ThreeJSDrawer } from './threejsdrawer.js';
import { default as Stats } from "https://cdnjs.cloudflare.com/ajax/libs/stats.js/17/Stats.js";
//globals
// const canvas = document.querySelector('#three-canvas');
const sources = {
@ -18,6 +18,15 @@ const labels = {
bblue: 'Blue B',
duomo: 'Duomo',
}
var stats = null;
// var stats = new Stats();
// stats.showPanel( 1 ); // 0: fps, 1: ms, 2: mb, 3+: custom
// document.body.appendChild( stats.dom );
//Double viewer setup for comparison - CanvasDrawer and ThreeJSDrawer
var viewer = window.viewer = OpenSeadragon({
id: "contentDiv",
prefixUrl: "../../build/openseadragon/images/",
@ -28,11 +37,26 @@ var viewer = window.viewer = OpenSeadragon({
smoothTileEdgesMinZoom:1.1,
crossOriginPolicy: 'Anonymous',
ajaxWithCredentials: false,
useCanvas:false,
useCanvas:true,
});
// let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element});
// Mirror the interactive viewer with CanvasDrawer onto a separate canvas using ThreeJSDrawer
let threeRenderer = window.threeRenderer = new ThreeJSDrawer({viewer, viewport: viewer.viewport, element:viewer.element, stats: stats});
//make the test canvas mirror all changes to the viewer canvas
let viewerCanvas = viewer.drawer.canvas;
let canvas = threeRenderer.canvas;
let canvasContainer = $('#three-canvas-container').append(canvas);
viewer.addHandler("resize", function(){
canvasContainer[0].style.width = viewerCanvas.clientWidth+'px';
canvasContainer[0].style.height = viewerCanvas.clientHeight+'px';
// canvas.width = viewerCanvas.width;
// canvas.height = viewerCanvas.height;
});
// Single viewer showing how to use plugin Drawer via configuration
// Also shows sequence mode
var viewer2 = window.viewer2 = OpenSeadragon({
id: "three-viewer",
prefixUrl: "../../build/openseadragon/images/",
@ -45,19 +69,9 @@ var viewer2 = window.viewer2 = OpenSeadragon({
ajaxWithCredentials: false
});
//make the test canvas mirror all changes to the viewer canvas
// let viewerCanvas = viewer.drawer.canvas;
// let canvas = threeRenderer.canvas;
// let canvasContainer = $('#three-canvas-container').append(canvas);
// viewer.addHandler("resize", function(){
// canvasContainer[0].style.width = viewerCanvas.clientWidth+'px';
// canvasContainer[0].style.height = viewerCanvas.clientHeight+'px';
// // canvas.width = viewerCanvas.width;
// // canvas.height = viewerCanvas.height;
// })
// viewer.addHandler("open", () => viewer.world.getItemAt(0).source.hasTransparency = function(){ return true; });
$('#three-viewer').resizable(true);
$('#contentDiv').resizable(true);
$('#image-picker').sortable({