Fix tests: always fetch up-to-date pixel values, prevent adding loaded tile to the 'bestTiles' array. Enforce _needsDraw check to be based on lastDrawn - we are async now.

This commit is contained in:
Aiosa 2024-02-05 09:42:26 +01:00
parent fcf20be8ea
commit 9ef2d46e75
5 changed files with 68 additions and 46 deletions

View File

@ -352,7 +352,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
* @returns {Boolean} whether the item still needs to be drawn due to blending * @returns {Boolean} whether the item still needs to be drawn due to blending
*/ */
setDrawn: function(){ setDrawn: function(){
this._needsDraw = this._isBlending || this._wasBlending; this._needsDraw = this._isBlending || this._wasBlending ||
(this.opacity > 0 && this._lastDrawn.length < 1);
return this._needsDraw; return this._needsDraw;
}, },
@ -1825,7 +1826,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
if ( tile.loading ) { if ( tile.loading ) {
// the tile is already in the download queue // the tile is already in the download queue
this._tilesLoading++; this._tilesLoading++;
} else if (!loadingCoverage) { } else if (!tile.loaded && !loadingCoverage) {
// add tile to best tiles to load only when not loaded already
best = this._compareTiles( best, tile, this.maxTilesPerFrame ); best = this._compareTiles( best, tile, this.maxTilesPerFrame );
} }

View File

@ -87,10 +87,6 @@
this._clippingContext = null; this._clippingContext = null;
this._renderingCanvas = null; this._renderingCanvas = null;
// Unique type per drawer: uploads texture to unique webgl context.
this._dataType = `${Date.now()}_TEX_2D`;
this._setupTextureHandlers(this._dataType);
// Reject listening for the tile-drawing and tile-drawn events, which this drawer does not fire // Reject listening for the tile-drawing and tile-drawn events, which this drawer does not fire
this.viewer.rejectEventHandler("tile-drawn", "The WebGLDrawer does not raise the tile-drawn event"); this.viewer.rejectEventHandler("tile-drawn", "The WebGLDrawer does not raise the tile-drawn event");
this.viewer.rejectEventHandler("tile-drawing", "The WebGLDrawer does not raise the tile-drawing event"); this.viewer.rejectEventHandler("tile-drawing", "The WebGLDrawer does not raise the tile-drawing event");
@ -101,6 +97,10 @@
this._setupCanvases(); this._setupCanvases();
this._setupRenderer(); this._setupRenderer();
// Unique type per drawer: uploads texture to unique webgl context.
this._dataType = `${Date.now()}_TEX_2D`;
this._setupTextureHandlers(this._dataType);
this.context = this._outputContext; // API required by tests this.context = this._outputContext; // API required by tests
} }
@ -757,7 +757,6 @@
this._renderingCanvas.height = this._clippingCanvas.height = this._outputCanvas.height; this._renderingCanvas.height = this._clippingCanvas.height = this._outputCanvas.height;
this._gl = this._renderingCanvas.getContext('webgl'); this._gl = this._renderingCanvas.getContext('webgl');
//make the additional canvas elements mirror size changes to the output canvas //make the additional canvas elements mirror size changes to the output canvas
this.viewer.addHandler("resize", function(){ this.viewer.addHandler("resize", function(){
@ -784,12 +783,14 @@
} }
_setupTextureHandlers(thisType) { _setupTextureHandlers(thisType) {
const _this = this;
const tex2DCompatibleLoader = (tile, data) => { const tex2DCompatibleLoader = (tile, data) => {
let tiledImage = tile.tiledImage; let tiledImage = tile.tiledImage;
//todo verify we are calling conversion just right amount of time! //todo verify we are calling conversion just right amount of time!
// e.g. no upload of cpu-existing texture // e.g. no upload of cpu-existing texture
// also check textures are really getting destroyed (it is tested, but also do this with demos)
let gl = this._gl; let gl = _this._gl;
// create a gl Texture for this tile and bind the canvas with the image data // create a gl Texture for this tile and bind the canvas with the image data
let texture = gl.createTexture(); let texture = gl.createTexture();
@ -798,16 +799,16 @@
if( overlap > 0){ if( overlap > 0){
// calculate the normalized position of the rect to actually draw // calculate the normalized position of the rect to actually draw
// discarding overlap. // discarding overlap.
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage); let overlapFraction = _this._calculateOverlapFraction(tile, tiledImage);
let left = tile.x === 0 ? 0 : overlapFraction.x; let left = tile.x === 0 ? 0 : overlapFraction.x;
let top = tile.y === 0 ? 0 : overlapFraction.y; let top = tile.y === 0 ? 0 : overlapFraction.y;
let right = tile.isRightMost ? 1 : 1 - overlapFraction.x; let right = tile.isRightMost ? 1 : 1 - overlapFraction.x;
let bottom = tile.isBottomMost ? 1 : 1 - overlapFraction.y; let bottom = tile.isBottomMost ? 1 : 1 - overlapFraction.y;
position = this._makeQuadVertexBuffer(left, right, top, bottom); position = _this._makeQuadVertexBuffer(left, right, top, bottom);
} else { } else {
// no overlap: this texture can use the unit quad as its position data // no overlap: this texture can use the unit quad as its position data
position = this._unitQuad; position = _this._unitQuad;
} }
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
@ -848,11 +849,12 @@
this.declareSupportedDataFormats(imageTexType, c2dTexType); this.declareSupportedDataFormats(imageTexType, c2dTexType);
// We should be OK uploading any of these types. // We should be OK uploading any of these types. The complexity is selected to be O(3n), should be
$.convertor.learn("context2d", c2dTexType, tex2DCompatibleLoader, 1, 2); // more than linear pass over pixels
$.convertor.learn("image", imageTexType, tex2DCompatibleLoader, 1, 2); $.convertor.learn("context2d", c2dTexType, tex2DCompatibleLoader, 1, 3);
$.convertor.learn(c2dTexType, "context2d", dataRetrieval, 1, 2); $.convertor.learn("image", imageTexType, tex2DCompatibleLoader, 1, 3);
$.convertor.learn(imageTexType, "image", dataRetrieval, 1, 2); $.convertor.learn(c2dTexType, "context2d", dataRetrieval, 1, 3);
$.convertor.learn(imageTexType, "image", dataRetrieval, 1, 3);
$.convertor.learnDestroy(c2dTexType, tex2DCompatibleDestructor); $.convertor.learnDestroy(c2dTexType, tex2DCompatibleDestructor);
$.convertor.learnDestroy(imageTexType, tex2DCompatibleDestructor); $.convertor.learnDestroy(imageTexType, tex2DCompatibleDestructor);

View File

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

View File

@ -220,17 +220,23 @@
var done = assert.async(); var done = assert.async();
viewer.open('/test/data/testpattern.dzi'); viewer.open('/test/data/testpattern.dzi');
var density = OpenSeadragon.pixelDensityRatio; function getPixelFromViewerScreenCoords(x, y) {
const density = OpenSeadragon.pixelDensityRatio;
const imageData = viewer.drawer.context.getImageData(x * density, y * density, 1, 1);
return {
r: imageData.data[0],
g: imageData.data[1],
b: imageData.data[2],
a: imageData.data[3]
};
}
viewer.addHandler('open', function() { viewer.addHandler('open', function() {
var firstImage = viewer.world.getItemAt(0); var firstImage = viewer.world.getItemAt(0);
firstImage.addHandler('fully-loaded-change', function() { firstImage.addHandler('fully-loaded-change', function() {
viewer.addOnceHandler('update-viewport', function(){ viewer.addOnceHandler('update-viewport', function(){
var imageData = viewer.drawer.context.getImageData(0, 0,
500 * density, 500 * density);
// Pixel 250,250 will be in the hole of the A // Pixel 250,250 will be in the hole of the A
var expectedVal = getPixelValue(imageData, 250 * density, 250 * density); var expectedVal = getPixelFromViewerScreenCoords(250, 250);
assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0'); assert.notEqual(expectedVal.r, 0, 'Red channel should not be 0');
assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0'); assert.notEqual(expectedVal.g, 0, 'Green channel should not be 0');
@ -241,10 +247,9 @@
url: '/test/data/A.png', url: '/test/data/A.png',
success: function() { success: function() {
var secondImage = viewer.world.getItemAt(1); var secondImage = viewer.world.getItemAt(1);
secondImage.addHandler('fully-loaded-change', function() { secondImage.addHandler('fully-loaded-change', function() {
viewer.addOnceHandler('update-viewport',function(){ viewer.addOnceHandler('update-viewport', function(){
var imageData = viewer.drawer.context.getImageData(0, 0, 500 * density, 500 * density); var actualVal = getPixelFromViewerScreenCoords(250, 250);
var actualVal = getPixelValue(imageData, 250 * density, 250 * density);
assert.equal(actualVal.r, expectedVal.r, assert.equal(actualVal.r, expectedVal.r,
'Red channel should not change in transparent part of the A'); 'Red channel should not change in transparent part of the A');
@ -255,10 +260,10 @@
assert.equal(actualVal.a, expectedVal.a, assert.equal(actualVal.a, expectedVal.a,
'Alpha channel should not change in transparent part of the A'); 'Alpha channel should not change in transparent part of the A');
var onAVal = getPixelValue(imageData, 333 * density, 250 * density); var onAVal = getPixelFromViewerScreenCoords(333 , 250);
assert.equal(onAVal.r, 0, 'Red channel should be null on the A'); assert.equal(onAVal.r, 0, 'Red channel should be 0 on the A');
assert.equal(onAVal.g, 0, 'Green channel should be null on the A'); assert.equal(onAVal.g, 0, 'Green channel should be 0 on the A');
assert.equal(onAVal.b, 0, 'Blue channel should be null on the A'); assert.equal(onAVal.b, 0, 'Blue channel should be 0 on the A');
assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A'); assert.equal(onAVal.a, 255, 'Alpha channel should be 255 on the A');
done(); done();
@ -271,17 +276,6 @@
}); });
}); });
}); });
function getPixelValue(imageData, x, y) {
var offset = 4 * (y * imageData.width + x);
return {
r: imageData.data[offset],
g: imageData.data[offset + 1],
b: imageData.data[offset + 2],
a: imageData.data[offset + 3]
};
}
}); });
} }
})(); })();

View File

@ -123,7 +123,13 @@
QUnit.test('basics', function(assert) { QUnit.test('basics', function(assert) {
const done = assert.async(); const done = assert.async();
const fakeViewer = { const fakeViewer = {
raiseEvent: function() {} raiseEvent: function() {},
drawer: {
// tile in safe mode inspects the supported formats upon cache set
getSupportedDataFormats() {
return [T_A, T_B, T_C, T_D, T_E];
}
}
}; };
const fakeTiledImage0 = { const fakeTiledImage0 = {
viewer: fakeViewer, viewer: fakeViewer,
@ -170,7 +176,13 @@
QUnit.test('maxImageCacheCount', function(assert) { QUnit.test('maxImageCacheCount', function(assert) {
const done = assert.async(); const done = assert.async();
const fakeViewer = { const fakeViewer = {
raiseEvent: function() {} raiseEvent: function() {},
drawer: {
// tile in safe mode inspects the supported formats upon cache set
getSupportedDataFormats() {
return [T_A, T_B, T_C, T_D, T_E];
}
}
}; };
const fakeTiledImage0 = { const fakeTiledImage0 = {
viewer: fakeViewer, viewer: fakeViewer,
@ -218,7 +230,13 @@
QUnit.test('Tile API: basic conversion', function(test) { QUnit.test('Tile API: basic conversion', function(test) {
const done = test.async(); const done = test.async();
const fakeViewer = { const fakeViewer = {
raiseEvent: function() {} raiseEvent: function() {},
drawer: {
// tile in safe mode inspects the supported formats upon cache set
getSupportedDataFormats() {
return [T_A, T_B, T_C, T_D, T_E];
}
}
}; };
const tileCache = new OpenSeadragon.TileCache(); const tileCache = new OpenSeadragon.TileCache();
const fakeTiledImage0 = { const fakeTiledImage0 = {
@ -406,7 +424,13 @@
QUnit.test('Tile API Cache Interaction', function(test) { QUnit.test('Tile API Cache Interaction', function(test) {
const done = test.async(); const done = test.async();
const fakeViewer = { const fakeViewer = {
raiseEvent: function() {} raiseEvent: function() {},
drawer: {
// tile in safe mode inspects the supported formats upon cache set
getSupportedDataFormats() {
return [T_A, T_B, T_C, T_D, T_E];
}
}
}; };
const tileCache = new OpenSeadragon.TileCache(); const tileCache = new OpenSeadragon.TileCache();
const fakeTiledImage0 = { const fakeTiledImage0 = {