From 90ce0669c535ffd0d26bffc9cf477c1ba17e951c Mon Sep 17 00:00:00 2001 From: Aiosa Date: Mon, 27 Nov 2023 12:12:54 +0100 Subject: [PATCH] Add auto recognition of the need for tiledImage draw call. Fix ajax-headers test: did not finish because we don't call tile-loaded on cached tiles by default. --- src/datatypeconvertor.js | 2 +- src/tile.js | 77 +++++++++++++++++++++++++-------- src/tilecache.js | 15 ++++++- test/modules/ajax-tiles.js | 10 ++++- test/modules/type-conversion.js | 8 ++-- 5 files changed, 86 insertions(+), 26 deletions(-) diff --git a/src/datatypeconvertor.js b/src/datatypeconvertor.js index 47af2eb0..438545c4 100644 --- a/src/datatypeconvertor.js +++ b/src/datatypeconvertor.js @@ -194,7 +194,7 @@ $.DataTypeConvertor = class { const canvas = document.createElement( 'canvas' ); canvas.width = imageData.width; canvas.height = imageData.height; - const context = canvas.getContext('2d'); + const context = canvas.getContext('2d', { willReadFrequently: true }); context.drawImage( imageData, 0, 0 ); return context; }; diff --git a/src/tile.js b/src/tile.js index af3777f1..97eab0c5 100644 --- a/src/tile.js +++ b/src/tile.js @@ -469,7 +469,7 @@ $.Tile.prototype = { }, /** - * Get the default data for this tile + * Get the data to render for this tile * @param {string} type data type to require * @param {boolean?} [copy=this.loaded] whether to force copy retrieval * @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing @@ -484,6 +484,22 @@ $.Tile.prototype = { return cache.getDataAs(type, copy); }, + /** + * Get the original data data for this tile + * @param {string} type data type to require + * @param {boolean?} [copy=this.loaded] whether to force copy retrieval + * @return {*|undefined} data in the desired type, or undefined if a conversion is ongoing + */ + getOriginalData: function(type, copy = true) { + //we return the data synchronously immediatelly (undefined if conversion happens) + const cache = this.getCache(this.originalCacheKey); + if (!cache) { + $.console.error("[Tile::getData] There is no cache available for tile with key " + this.originalCacheKey); + return undefined; + } + return cache.getDataAs(type, copy); + }, + /** * Set cache data * @param {*} value @@ -539,7 +555,8 @@ $.Tile.prototype = { type = $.convertor.guessType(data); } - if (_safely && key === this.cacheKey) { + const writesToRenderingCache = key === this.cacheKey; + if (writesToRenderingCache && _safely) { //todo later, we could have drawers register their supported rendering type // and OpenSeadragon would check compatibility automatically, now we render // using two main types so we check their ability @@ -609,25 +626,53 @@ $.Tile.prototype = { drawCanvas: function( context, drawingHandler, scale, translate, shouldRoundPositionAndSize, source) { var position = this.position.times($.pixelDensityRatio), - size = this.size.times($.pixelDensityRatio), - rendered = this.getCanvasContext(); + size = this.size.times($.pixelDensityRatio); - if (!rendered) { - $.console.warn( - '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', - this.toString()); - return; - } + const _this = this; + // This gives the application a chance to make image manipulation + // changes as we are rendering the image + drawingHandler({context: context, tile: this, get rendered() { + $.console.warn("[tile-drawing rendered] property is deprecated. Use Tile data API."); + const context = _this.getCanvasContext(); + if (!context) { + $.console.warn( + '[Tile.drawCanvas] attempting to draw tile %s when it\'s not cached', + _this.toString()); + return undefined; + } - if ( !this.loaded || !rendered ){ - $.console.warn( - "Attempting to draw tile %s when it's not yet loaded.", + if ( !_this.loaded || !context ){ + $.console.warn( + "Attempting to draw tile %s when it's not yet loaded.", + _this.toString() + ); + return undefined; + } + return _this.getCanvasContext(); + }}); + + //Now really get the tile data + const cache = this.getCache(this.cacheKey); + if (!cache) { + $.console.error( + "Attempting to draw tile %s when it's main cache key has no associated cache record!", this.toString() ); - return; } + if (cache.type !== "context2d") { + //cache not ready to render, wait + cache.transformTo("context2d"); + return; + } + + if ( !cache.loaded ){ + //cache not ready to render, wait + return; + } + const rendered = cache.data; + context.save(); context.globalAlpha = this.opacity; @@ -665,10 +710,6 @@ $.Tile.prototype = { ); } - // This gives the application a chance to make image manipulation - // changes as we are rendering the image - drawingHandler({context: context, tile: this, rendered: rendered}); - var sourceWidth, sourceHeight; if (this.sourceBounds) { sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width); diff --git a/src/tilecache.js b/src/tilecache.js index 181d8071..d409f08d 100644 --- a/src/tilecache.js +++ b/src/tilecache.js @@ -65,7 +65,7 @@ $.CacheRecord = class { * Might be undefined if this.loaded = false. * You can access the data in synchronous way, but the data might not be available. * If you want to access the data indirectly (await), use this.transformTo or this.getDataAs - * @return {any} + * @returns {any} */ get data() { return this._data; @@ -74,7 +74,7 @@ $.CacheRecord = class { /** * Read the cache type. The type can dynamically change, but should be consistent at * one point in the time. For available types see the OpenSeadragon.Convertor, or the tutorials. - * @return {string} + * @returns {string} */ get type() { return this._type; @@ -315,6 +315,12 @@ $.CacheRecord = class { }); } + _triggerNeedsDraw() { + for (let tile of this._tiles) { + tile.tiledImage._needsDraw = true; + } + } + /** * Safely overwrite the cache data and return the old data * @private @@ -330,6 +336,7 @@ $.CacheRecord = class { this._type = type; this._data = data; this._promise = $.Promise.resolve(data); + this._triggerNeedsDraw(); return this._promise; } return this._promise.then(x => { @@ -337,6 +344,7 @@ $.CacheRecord = class { this._type = type; this._data = data; this._promise = $.Promise.resolve(data); + this._triggerNeedsDraw(); return x; }); } @@ -481,6 +489,9 @@ $.TileCache = class { } cacheRecord.addTile(theTile, options.data, options.dataType); + if (cacheKey === theTile.cacheKey) { + theTile.tiledImage._needsDraw = true; + } // Note that just because we're unloading a tile doesn't necessarily mean // we're unloading its cache records. With repeated calls it should sort itself out, though. diff --git a/test/modules/ajax-tiles.js b/test/modules/ajax-tiles.js index 39a37876..37c3c167 100644 --- a/test/modules/ajax-tiles.js +++ b/test/modules/ajax-tiles.js @@ -49,7 +49,15 @@ loadTilesWithAjax: true, ajaxHeaders: { 'X-Viewer-Header': 'ViewerHeaderValue' - } + }, + // TODO: this test proves that tile cacheKey does not change + // with headers change, which by default are part of the key, + // but we cannot automatically update them since users might + // manually change it long before... or can we? The tile gets + // reloaded, so user code should get re-executed. IMHO, + // with tile propagation the best would be throw away old tile + // and start anew + callTileLoadedWithCachedData: true }); }, afterEach: function() { diff --git a/test/modules/type-conversion.js b/test/modules/type-conversion.js index 36787243..4f7616e5 100644 --- a/test/modules/type-conversion.js +++ b/test/modules/type-conversion.js @@ -213,7 +213,7 @@ const dummyRect = new OpenSeadragon.Rect(0, 0, 0, 0, 0); const dummyTile = new OpenSeadragon.Tile(0, 0, 0, dummyRect, true, "", undefined, true, null, dummyRect, "", "key"); - + dummyTile.tiledImage = {}; const cache = new OpenSeadragon.CacheRecord(); cache.addTile(dummyTile, "/test/data/A.png", "__TEST__url"); @@ -262,7 +262,7 @@ const dummyRect = new OpenSeadragon.Rect(0, 0, 0, 0, 0); const dummyTile = new OpenSeadragon.Tile(0, 0, 0, dummyRect, true, "", undefined, true, null, dummyRect, "", "key"); - + dummyTile.tiledImage = {}; const cache = new OpenSeadragon.CacheRecord(); cache.testGetSet = async function(type) { const value = await cache.getDataAs(type, false); @@ -330,7 +330,7 @@ const dummyRect = new OpenSeadragon.Rect(0, 0, 0, 0, 0); const dummyTile = new OpenSeadragon.Tile(0, 0, 0, dummyRect, true, "", undefined, true, null, dummyRect, "", "key"); - + dummyTile.tiledImage = {}; const cache = new OpenSeadragon.CacheRecord(); cache.addTile(dummyTile, "/test/data/A.png", "__TEST__url"); cache.getDataAs("__TEST__longConversionProcessForTesting").then(convertedData => { @@ -374,7 +374,7 @@ const dummyRect = new OpenSeadragon.Rect(0, 0, 0, 0, 0); const dummyTile = new OpenSeadragon.Tile(0, 0, 0, dummyRect, true, "", undefined, true, null, dummyRect, "", "key"); - + dummyTile.tiledImage = {}; const cache = new OpenSeadragon.CacheRecord(); cache.addTile(dummyTile, "/test/data/A.png", "__TEST__url"); cache.transformTo("__TEST__longConversionProcessForTesting").then(_ => {