diff --git a/Gruntfile.js b/Gruntfile.js index 82c0d0ca..c96e2e79 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -28,6 +28,7 @@ module.exports = function(grunt) { coverageDir = 'coverage/' + dateFormat(new Date(), 'yyyymmdd-HHMMss'), sources = [ "src/openseadragon.js", + "src/matrix.js", "src/fullscreen.js", "src/eventsource.js", "src/mousetracker.js", @@ -64,7 +65,15 @@ module.exports = function(grunt) { "src/viewport.js", "src/tiledimage.js", "src/tilecache.js", - "src/world.js" + "src/world.js", + + // Aoisa's webgl drawer - needs optimization, polishing, trimming + // "src/webgl/webGLWrapper.js", + // "src/webgl/visualisationLayer.js", + // "src/webgl/dataLoader.js", + // "src/webgl/webGLContext.js", + // "src/webgl/drawer.js", + // "src/webgl/plainShader.js", ]; var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" + @@ -195,7 +204,7 @@ module.exports = function(grunt) { } }, watch: { - files: [ "Gruntfile.js", "src/*.js", "images/*" ], + files: [ "Gruntfile.js", "src/*.js", "images/*" /*, "src/webgl/*.js" */ ], tasks: "watchTask" }, eslint: { diff --git a/src/canvasdrawer.js b/src/canvasdrawer.js index 6366e431..8d476286 100644 --- a/src/canvasdrawer.js +++ b/src/canvasdrawer.js @@ -246,7 +246,7 @@ class CanvasDrawer extends $.DrawerBase{ if (lastDrawn.length > 1 && imageZoom > tiledImage.smoothTileEdgesMinZoom && !tiledImage.iOSDevice && - tiledImage.getRotation(true) % 360 === 0 ){ // TO DO: support tile edge smoothing with tiled image rotation. + tiledImage.getRotation(true) % 360 === 0 ){ // TODO: support tile edge smoothing with tiled image rotation. // When zoomed in a lot (>100%) the tile edges are visible. // So we have to composite them at ~100% and scale them up together. // Note: Disabled on iOS devices per default as it causes a native crash diff --git a/src/drawerbase.js b/src/drawerbase.js index 8c32d6fd..e4df9068 100644 --- a/src/drawerbase.js +++ b/src/drawerbase.js @@ -84,7 +84,7 @@ $.DrawerBase = class DrawerBase{ */ this.container = $.getElement( options.element ); - // TO DO: Does this need to be in DrawerBase, or only in Drawer implementations? + // TODO: Does this need to be in DrawerBase, or only in Drawer implementations? // Original commment: // We force our container to ltr because our drawing math doesn't work in rtl. // This issue only affects our canvas renderer, but we do it always for consistency. diff --git a/src/matrix.js b/src/matrix.js new file mode 100644 index 00000000..b0b91ab0 --- /dev/null +++ b/src/matrix.js @@ -0,0 +1,124 @@ + +/* + * OpenSeadragon - Mat3 + * + * Copyright (C) 2010-2023 OpenSeadragon contributors + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of CodePlex Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +(function( $ ){ + +/** + * Matrix left-to-right system representation + * Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html + */ +$.Mat3 = class Mat3 { + constructor(values){ + if(!values) { + values = [ + 0, 0, 0, + 0, 0, 0, + 0, 0, 0 + ]; + } + + this.values = values; + } + + static makeIdentity(){ + return new Mat3([ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + ]); + } + + static makeTranslation(tx, ty) { + return new Mat3([ + 1, 0, 0, + 0, 1, 0, + tx, ty, 1, + ]); + } + + static makeRotation(angleInRadians) { + var c = Math.cos(angleInRadians); + var s = Math.sin(angleInRadians); + return new Mat3([ + c, -s, 0, + s, c, 0, + 0, 0, 1, + ]); + } + + static makeScaling(sx, sy) { + return new Mat3([ + sx, 0, 0, + 0, sy, 0, + 0, 0, 1, + ]); + } + + multiply(other) { + let a = this.values; + let b = other.values; + + var a00 = a[0 * 3 + 0]; + var a01 = a[0 * 3 + 1]; + var a02 = a[0 * 3 + 2]; + var a10 = a[1 * 3 + 0]; + var a11 = a[1 * 3 + 1]; + var a12 = a[1 * 3 + 2]; + var a20 = a[2 * 3 + 0]; + var a21 = a[2 * 3 + 1]; + var a22 = a[2 * 3 + 2]; + var b00 = b[0 * 3 + 0]; + var b01 = b[0 * 3 + 1]; + var b02 = b[0 * 3 + 2]; + var b10 = b[1 * 3 + 0]; + var b11 = b[1 * 3 + 1]; + var b12 = b[1 * 3 + 2]; + var b20 = b[2 * 3 + 0]; + var b21 = b[2 * 3 + 1]; + var b22 = b[2 * 3 + 2]; + return new Mat3([ + b00 * a00 + b01 * a10 + b02 * a20, + b00 * a01 + b01 * a11 + b02 * a21, + b00 * a02 + b01 * a12 + b02 * a22, + b10 * a00 + b11 * a10 + b12 * a20, + b10 * a01 + b11 * a11 + b12 * a21, + b10 * a02 + b11 * a12 + b12 * a22, + b20 * a00 + b21 * a10 + b22 * a20, + b20 * a01 + b21 * a11 + b22 * a21, + b20 * a02 + b21 * a12 + b22 * a22, + ]); + } +}; + +}( OpenSeadragon )); diff --git a/src/tiledimage.js b/src/tiledimage.js index 6c9c185b..9aaf561e 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -1473,7 +1473,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag * @param {Number} levelOpacity * @param {Number} currentTime * @param {Boolean} lowestLevel - * @returns {Boolean} whether the opacity of this tile has changed + * @returns {Boolean} true if blending did not yet finish */ _blendTile: function(tile, x, y, level, levelOpacity, currentTime, lowestLevel ){ let blendTimeMillis = 1000 * this.blendTime, diff --git a/src/viewer.js b/src/viewer.js index b39feea2..3b98cd74 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -455,35 +455,32 @@ $.Viewer = function( options ) { $.console.warn('No valid drawers were selected. Using the default value.'); } // extend the drawerOptions object with additional properties to pass to the Drawer implementation - this.drawer = null; // TO DO: how to deal with the possibility that none of the requested drawers are supported? + // TODO: how to deal with the possibility that none of the requested drawers are supported? + this.drawer = null; for(let i = 0; i < drawersToTry.length; i++){ - let Drawer = drawersToTry[i]; - let optsKey = null; - // replace text-based option with appropriate constructor - if (Drawer === 'canvas'){ - Drawer = $.CanvasDrawer; - optsKey = 'canvas'; - } else if (Drawer === 'html'){ - Drawer = $.HTMLDrawer; - optsKey = 'html'; - } else if (Drawer === 'webgl'){ - Drawer = $.WebGLDrawer; - optsKey = 'webgl'; - } else { + + //todo necessary? why not to use class names as drawer IDs + let optsKey = drawersToTry[i]; + let Drawer = $.determineDrawer(optsKey); + if (!Drawer) { optsKey = 'custom'; + //todo will raise error anyway... + } else { + // if the drawer is supported, create it and break the loop + if (Drawer.isSupported()){ + this.drawer = new Drawer({ + viewer: this, + viewport: this.viewport, + element: this.canvas, + debugGridColor: this.debugGridColor, + options: this.drawerOptions[optsKey], + }); + this.drawerOptions.constructor = Drawer; + break; + } } - // if the drawer is supported, create it and break the loop - if (Drawer.isSupported()){ - this.drawer = new Drawer({ - viewer: this, - viewport: this.viewport, - element: this.canvas, - debugGridColor: this.debugGridColor, - options: this.drawerOptions[optsKey], - }); - this.drawerOptions.constructor = Drawer; - break; - } + + } if(this.drawer === null){ $.console.error('No drawer could be created!'); @@ -4002,4 +3999,22 @@ function onFlip() { this.viewport.toggleFlip(); } +/** + * Find drawer + */ +$.determineDrawer = function( id ){ + for (let property in OpenSeadragon) { + const drawer = OpenSeadragon[ property ], + proto = drawer.prototype; + if( proto && + proto instanceof OpenSeadragon.DrawerBase && + $.isFunction( proto.getType ) && + proto.getType.call( drawer ) === id + ){ + return drawer; + } + } + return null; +}; + }( OpenSeadragon )); diff --git a/src/webgldrawer.js b/src/webgldrawer.js index 4b35e19f..2a9740fd 100644 --- a/src/webgldrawer.js +++ b/src/webgldrawer.js @@ -35,92 +35,6 @@ (function( $ ){ - // internal class Mat3: implements matrix operations - // Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html - class Mat3{ - constructor(values){ - if(!values) { - values = [ - 0, 0, 0, - 0, 0, 0, - 0, 0, 0 - ]; - } - - this.values = values; - } - - static makeIdentity(){ - return new Mat3([ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ]); - } - - static makeTranslation(tx, ty) { - return new Mat3([ - 1, 0, 0, - 0, 1, 0, - tx, ty, 1, - ]); - } - - static makeRotation(angleInRadians) { - var c = Math.cos(angleInRadians); - var s = Math.sin(angleInRadians); - return new Mat3([ - c, -s, 0, - s, c, 0, - 0, 0, 1, - ]); - } - - static makeScaling(sx, sy) { - return new Mat3([ - sx, 0, 0, - 0, sy, 0, - 0, 0, 1, - ]); - } - - multiply(other) { - let a = this.values; - let b = other.values; - - var a00 = a[0 * 3 + 0]; - var a01 = a[0 * 3 + 1]; - var a02 = a[0 * 3 + 2]; - var a10 = a[1 * 3 + 0]; - var a11 = a[1 * 3 + 1]; - var a12 = a[1 * 3 + 2]; - var a20 = a[2 * 3 + 0]; - var a21 = a[2 * 3 + 1]; - var a22 = a[2 * 3 + 2]; - var b00 = b[0 * 3 + 0]; - var b01 = b[0 * 3 + 1]; - var b02 = b[0 * 3 + 2]; - var b10 = b[1 * 3 + 0]; - var b11 = b[1 * 3 + 1]; - var b12 = b[1 * 3 + 2]; - var b20 = b[2 * 3 + 0]; - var b21 = b[2 * 3 + 1]; - var b22 = b[2 * 3 + 2]; - return new Mat3([ - b00 * a00 + b01 * a10 + b02 * a20, - b00 * a01 + b01 * a11 + b02 * a21, - b00 * a02 + b01 * a12 + b02 * a22, - b10 * a00 + b11 * a10 + b12 * a20, - b10 * a01 + b11 * a11 + b12 * a21, - b10 * a02 + b11 * a12 + b12 * a22, - b20 * a00 + b21 * a10 + b22 * a20, - b20 * a01 + b21 * a11 + b22 * a21, - b20 * a02 + b21 * a12 + b22 * a22, - ]); - } - - } - /** * @class WebGLDrawer * @memberof OpenSeadragon @@ -198,7 +112,7 @@ // Delete all our created resources gl.deleteBuffer(this._secondPass.bufferOutputPosition); gl.deleteFramebuffer(this._glFrameBuffer); - // TO DO: if/when render buffers or frame buffers are used, release them: + // TODO: if/when render buffers or frame buffers are used, release them: // gl.deleteRenderbuffer(someRenderbuffer); // gl.deleteFramebuffer(someFramebuffer); @@ -277,9 +191,9 @@ let flipMultiplier = this.viewport.flipped ? -1 : 1; // calculate view matrix for viewer - let posMatrix = Mat3.makeTranslation(-viewport.center.x, -viewport.center.y); - let scaleMatrix = Mat3.makeScaling(2 / viewport.bounds.width * flipMultiplier, -2 / viewport.bounds.height); - let rotMatrix = Mat3.makeRotation(-viewport.rotation); + let posMatrix = $.Mat3.makeTranslation(-viewport.center.x, -viewport.center.y); + let scaleMatrix = $.Mat3.makeScaling(2 / viewport.bounds.width * flipMultiplier, -2 / viewport.bounds.height); + let rotMatrix = $.Mat3.makeRotation(-viewport.rotation); let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix); gl.bindFramebuffer(gl.FRAMEBUFFER, null); @@ -300,7 +214,7 @@ tiledImage._croppingPolygons || tiledImage.debugMode ); - let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1); // TO DO: check hasTransparency in addition to opacity + let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1); // TODO: check hasTransparency in addition to opacity let tilesToDraw = tiledImage.getTilesToDraw(); @@ -343,10 +257,10 @@ let imageRotation = tiledImage.getRotation(true); // if needed, handle the tiledImage being rotated if( imageRotation % 360 !== 0){ - let imageRotationMatrix = Mat3.makeRotation(-imageRotation * Math.PI / 180); + let imageRotationMatrix = $.Mat3.makeRotation(-imageRotation * Math.PI / 180); let imageCenter = tiledImage.getBoundsNoRotate(true).getCenter(); - let t1 = Mat3.makeTranslation(imageCenter.x, imageCenter.y); - let t2 = Mat3.makeTranslation(-imageCenter.x, -imageCenter.y); + let t1 = $.Mat3.makeTranslation(imageCenter.x, imageCenter.y); + let t2 = $.Mat3.makeTranslation(-imageCenter.x, -imageCenter.y); // update the view matrix to account for this image's rotation let localMatrix = t1.multiply(imageRotationMatrix).multiply(t2); @@ -431,7 +345,7 @@ // Draw the quad (two triangles) gl.drawArrays(gl.TRIANGLES, 0, 6); - // TO DO: is this the mechanism we want to use here? + // TODO: is this the mechanism we want to use here? // iterate over any filters - filters can use this._renderToTexture to get rendered data if desired let filters = this.filters || []; for(let fi = 0; fi < filters.length; fi++){ @@ -456,7 +370,7 @@ } // Fire tiled-image-drawn event. - // TO DO: the image data may not be on the output canvas yet!! + // TODO: the image data may not be on the output canvas yet!! if( this.viewer ){ /** * Raised when a tiled image is drawn to the canvas. Only valid @@ -477,7 +391,7 @@ } }); - // TO DO: the line below is a test! + // TODO: the line below is a test! if(renderingBufferHasImageData){ this._outputContext.drawImage(this._renderingCanvas, 0, 0); } @@ -572,7 +486,7 @@ let w = right - x; let h = bottom - y; - let matrix = new Mat3([ + let matrix = new $.Mat3([ w, 0, 0, 0, h, 0, x, y, 1, @@ -580,11 +494,11 @@ if(tile.flipped){ // flip the tile around the center of the unit quad - let t1 = Mat3.makeTranslation(0.5, 0); - let t2 = Mat3.makeTranslation(-0.5, 0); + let t1 = $.Mat3.makeTranslation(0.5, 0); + let t2 = $.Mat3.makeTranslation(-0.5, 0); // update the view matrix to account for this image's rotation - let localMatrix = t1.multiply(Mat3.makeScaling(-1, 1)).multiply(t2); + let localMatrix = t1.multiply($.Mat3.makeScaling(-1, 1)).multiply(t2); matrix = matrix.multiply(localMatrix); } @@ -596,7 +510,7 @@ if(this.continuousTileRefresh){ // Upload the image into the texture - // TO DO: test if this works appropriately + // TODO: test if this works appropriately let tileContext = tile.getCanvasContext(); this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext); this._uploadImageData(tileContext, tile, tiledImage); @@ -798,7 +712,7 @@ gl.enableVertexAttribArray(this._secondPass.aTexturePosition); // set the matrix that transforms the framebuffer to clip space - let matrix = Mat3.makeScaling(2, 2).multiply(Mat3.makeTranslation(-0.5, -0.5)); + let matrix = $.Mat3.makeScaling(2, 2).multiply($.Mat3.makeTranslation(-0.5, -0.5)); gl.uniformMatrix3fv(this._secondPass.uMatrix, false, matrix.values); } @@ -957,8 +871,6 @@ } catch (e){ $.console.error('Error uploading image data to WebGL', e); } - - } _imageUnloadedHandler(event){ @@ -977,7 +889,7 @@ } // release the position buffer from the GPU - // TO DO: do this! + // TODO: do this! } // private // necessary for clip testing to pass (test uses spyOnce(drawer._setClip)) diff --git a/test/demo/drawerperformance.js b/test/demo/drawerperformance.js index 25cd52e5..0b6c9cdb 100644 --- a/test/demo/drawerperformance.js +++ b/test/demo/drawerperformance.js @@ -30,7 +30,10 @@ let viewer; $('#create-drawer').on('click',function(){ let drawerType = $('#select-drawer').val(); let num = Math.floor($('#input-number').val()); + run(drawerType, num); +}); +function run(drawerType, num) { if(viewer){ viewer.destroy(); } @@ -57,8 +60,7 @@ $('#create-drawer').on('click',function(){ viewer.viewport.panBy(new OpenSeadragon.Point( dist * m/2, 0)); }, 1000); - -}); +} function makeViewer(drawerType){ let viewer = OpenSeadragon({ diff --git a/test/demo/webgldemodrawer.js b/test/demo/webgldemodrawer.js index 1128e4a2..8978ab4a 100644 --- a/test/demo/webgldemodrawer.js +++ b/test/demo/webgldemodrawer.js @@ -200,7 +200,7 @@ // Delete all our created resources gl.deleteBuffer(this._secondPass.bufferOutputPosition); gl.deleteFramebuffer(this._glFrameBuffer); - // TO DO: if/when render buffers or frame buffers are used, release them: + // TODO: if/when render buffers or frame buffers are used, release them: // gl.deleteRenderbuffer(someRenderbuffer); // gl.deleteFramebuffer(someFramebuffer); @@ -302,7 +302,7 @@ tiledImage._croppingPolygons || tiledImage.debugMode ); - let useTwoPassRendering = useContext2dPipeline ||(tiledImage.opacity < 1); // TO DO: check hasTransparency in addition to opacity + let useTwoPassRendering = useContext2dPipeline ||(tiledImage.opacity < 1); // TODO: check hasTransparency in addition to opacity let tilesToDraw = tiledImage.getTilesToDraw(); @@ -433,7 +433,7 @@ // Draw the quad (two triangles) gl.drawArrays(gl.TRIANGLES, 0, 6); - // TO DO: is this the mechanism we want to use here? + // TODO: is this the mechanism we want to use here? // iterate over any filters - filters can use this._renderToTexture to get rendered data if desired let filters = this.filters || []; for(let fi = 0; fi < filters.length; fi++){ @@ -458,7 +458,7 @@ } // Fire tiled-image-drawn event. - // TO DO: the image data may not be on the output canvas yet!! + // TODO: the image data may not be on the output canvas yet!! if( this.viewer ){ /** * Raised when a tiled image is drawn to the canvas. Only valid @@ -479,7 +479,7 @@ } }); - // TO DO: the line below is a test! + // TODO: the line below is a test! if(renderingBufferHasImageData){ this._outputContext.drawImage(this._renderingCanvas, 0, 0); } @@ -598,7 +598,7 @@ if(this.continuousTileRefresh){ // Upload the image into the texture - // TO DO: test if this works appropriately + // TODO: test if this works appropriately let tileContext = tile.getCanvasContext(); this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext); this._uploadImageData(tileContext, tile, tiledImage); @@ -979,7 +979,7 @@ } // release the position buffer from the GPU - // TO DO: do this! + // TODO: do this! } // private // necessary for clip testing to pass (test uses spyOnce(drawer._setClip))