diff --git a/src/drawerbase.js b/src/drawerbase.js index ac7b3bd1..7232e614 100644 --- a/src/drawerbase.js +++ b/src/drawerbase.js @@ -237,6 +237,7 @@ $.DrawerBase = class DrawerBase{ * * NOTE: This event is only fired in certain drawing contexts: either the 'canvas' drawer is * being used, or the 'webgl' drawer with 'drawerOptions.webgl.continuousTileRefresh'. + * TODO: if we get rid of this in the webgl drawer, this can be moved to canvas drawer and the comment about continuousTileRefresh can be removed * * @event tile-drawing * @memberof OpenSeadragon.Viewer diff --git a/src/openseadragon.js b/src/openseadragon.js index b63aaf6f..49e3a364 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1346,22 +1346,20 @@ function OpenSeadragon( options ){ compositeOperation: null, // to be passed into each TiledImage // DRAWER SETTINGS - drawer: ['webgl', 'canvas', 'html'], // prefer using webgl, context2d, fallback to html + drawer: ['webgl', 'canvas', 'html'], // prefer using webgl, then canvas (i.e. context2d), then fallback to html /** * drawerOptions dictionary. * @type {Object} drawerOptions - * @property {Object} webgl - options if the WebGLDrawer is used. - * Set 'continuousTileFresh: true' if tile data is modified programmatically - * by filtering plugins or similar. - * @property {Object} context2d - options if the CanvasDrawer is used - * @property {Object} html - options if the HTMLDrawer is used - * @property {Object} custom - options if a custom drawer is used + * @property {Object} webgl - options if the WebGLDrawer is used. No options are currently supported. + * @property {Object} canvas - options if the CanvasDrawer is used. No options are currently supported. + * @property {Object} html - options if the HTMLDrawer is used. No options are currently supported. + * @property {Object} custom - options if a custom drawer is used. No options are currently supported. */ drawerOptions: { webgl: { - continuousTileRefresh: false, + }, - context2d: { + canvas: { }, html: { diff --git a/src/tiledimage.js b/src/tiledimage.js index 9aaf561e..a1ff089a 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -236,6 +236,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag return this._needsDraw; }, + /** + * Mark the tiled image as needing to be (re)drawn + */ + redraw: function() { + this._needsDraw = true; + }, + /** * @returns {Boolean} Whether all tiles necessary for this TiledImage to draw at the current view have been loaded. */ diff --git a/src/viewer.js b/src/viewer.js index b11233a6..b6280f37 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -438,7 +438,7 @@ $.Viewer = function( options ) { if (Object.prototype.hasOwnProperty.call(this.drawerOptions, 'useCanvas') ){ $.console.error('useCanvas is deprecated, use the "drawer" option to indicate preferred drawer(s)'); - // for backwards compatibility, use HTMLDrawer if useCanvas is defined an is falsey + // for backwards compatibility, use HTMLDrawer if useCanvas is defined and is falsey if (!this.drawerOptions.useCanvas){ this.drawer = $.HTMLDrawer; } @@ -450,8 +450,8 @@ $.Viewer = function( options ) { drawerCandidates = [$.DEFAULT_SETTINGS.drawer].flat(); // ensure it is a list $.console.warn('No valid drawers were selected. Using the default value.'); } - // extend the drawerOptions object with additional properties to pass to the Drawer implementation - // TODO: how to deal with the possibility that none of the requested drawers are supported? + + this.drawer = null; for (let i = 0; i < drawerCandidates.length; i++) { @@ -532,6 +532,7 @@ $.Viewer = function( options ) { displayRegionColor: this.navigatorDisplayRegionColor, crossOriginPolicy: this.crossOriginPolicy, animationTime: this.animationTime, + drawer: this.drawer.getType(), }); } diff --git a/src/webgldrawer.js b/src/webgldrawer.js index 0d34e299..d9eb672c 100644 --- a/src/webgldrawer.js +++ b/src/webgldrawer.js @@ -112,9 +112,6 @@ // Delete all our created resources gl.deleteBuffer(this._secondPass.bufferOutputPosition); gl.deleteFramebuffer(this._glFrameBuffer); - // TODO: if/when render buffers or frame buffers are used, release them: - // gl.deleteRenderbuffer(someRenderbuffer); - // gl.deleteFramebuffer(someFramebuffer); // make canvases 1 x 1 px and delete references this._renderingCanvas.width = this._renderingCanvas.height = 1; @@ -208,20 +205,21 @@ //iterate over tiled images and draw each one using a two-pass rendering pipeline if needed tiledImages.forEach( (tiledImage, tiledImageIndex) => { - let useContext2dPipeline = ( tiledImage.compositeOperation || - this.viewer.compositeOperation || - tiledImage._clip || - tiledImage._croppingPolygons || - tiledImage.debugMode - ); - - let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1); // TODO: check hasTransparency in addition to opacity - let tilesToDraw = tiledImage.getTilesToDraw(); if(tilesToDraw.length === 0){ return; } + let firstTile = tilesToDraw[0]; + + let useContext2dPipeline = ( tiledImage.compositeOperation || + this.viewer.compositeOperation || + tiledImage._clip || + tiledImage._croppingPolygons || + tiledImage.debugMode + ); + + let useTwoPassRendering = useContext2dPipeline || (tiledImage.opacity < 1) || firstTile.hasTransparency; // using the context2d pipeline requires a clean rendering (back) buffer to start if(useContext2dPipeline){ @@ -321,8 +319,6 @@ } } - // gl.flush(); // is this necessary? - if(useTwoPassRendering){ // Second rendering pass: Render the tiled image from the framebuffer into the back buffer gl.useProgram(this._secondPass.shaderProgram); @@ -346,21 +342,19 @@ // Draw the quad (two triangles) gl.drawArrays(gl.TRIANGLES, 0, 6); - // TODO: is this the mechanism we want to use here? + // TODO: Can we get rid of this entirely in this version of the webgl drawer? // 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++){ - let filter = this.filters[fi]; - if(filter.apply){ - filter.apply(gl); // filter.apply should write data on top of the backbuffer (bound above) - } - } + // let filters = this.filters || []; + // for(let fi = 0; fi < filters.length; fi++){ + // let filter = this.filters[fi]; + // if(filter.apply){ + // filter.apply(gl); // filter.apply should write data on top of the backbuffer (bound above) + // } + // } } renderingBufferHasImageData = true; - // gl.flush(); //make sure drawing to the output buffer of the rendering canvas is complete. Is this necessary? - if(useContext2dPipeline){ // draw from the rendering canvas onto the output canvas, clipping/cropping if needed. this._applyContext2dPipeline(tiledImage, tilesToDraw, tiledImageIndex); @@ -371,7 +365,7 @@ } // Fire tiled-image-drawn event. - // TODO: the image data may not be on the output canvas yet!! + // TODO: do we need to ensure the image has been drawn to the output canvas already? is it possible the image data may not be on the output canvas? if( this.viewer ){ /** * Raised when a tiled image is drawn to the canvas. Only valid @@ -505,17 +499,18 @@ let overallMatrix = viewMatrix.multiply(matrix); - opacityArray[index] = tile.opacity;// * tiledImage.opacity; + opacityArray[index] = tile.opacity; textureDataArray[index] = texture; matrixArray[index] = overallMatrix.values; - if(this.continuousTileRefresh){ - // Upload the image into the texture - // TODO: test if this works appropriately - let tileContext = tile.getCanvasContext(); - this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext); - this._uploadImageData(tileContext, tile, tiledImage); - } + // TODO: can we get rid of this in this version of the webgl drawer? + // if(this.continuousTileRefresh){ + // // Upload the image into the texture + // // TODO: test if this works appropriately + // let tileContext = tile.getCanvasContext(); + // this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext); + // this._uploadImageData(tileContext, tile, tiledImage); + // } } @@ -889,8 +884,6 @@ this._gl.deleteTexture(textureInfo.texture); } - // release the position buffer from the GPU - // TODO: do this! } // private // necessary for clip testing to pass (test uses spyOnce(drawer._setClip)) diff --git a/test/demo/drawercomparison.html b/test/demo/drawercomparison.html index d40c8ef8..e8ca6e48 100644 --- a/test/demo/drawercomparison.html +++ b/test/demo/drawercomparison.html @@ -6,7 +6,6 @@ - - - -
- -

WebGLDrawer demonstration

-
-
-
-
-
- - -
-

Image options (drag and drop to re-order images)

- - -
- -

Example code

-
-
-
- Drawer options are shown below. -
-
-    let viewer = OpenSeadragon({
-        ...
-        drawer: 'webgl',
-        drawerOptions: {
-            webgl: {
-                programs: [],
-            }
-        }
-        ...
-    });
-                
-
-
-
-
- - - - - - diff --git a/test/demo/webglfiltering.js b/test/demo/webglfiltering.js deleted file mode 100644 index c5357c0b..00000000 --- a/test/demo/webglfiltering.js +++ /dev/null @@ -1,298 +0,0 @@ -const sources = { - "rainbow":"../data/testpattern.dzi", - "leaves":"../data/iiif_2_0_sizes/info.json", - "bblue":{ - type:'image', - url: "../data/BBlue.png", - }, - "duomo":"https://openseadragon.github.io/example-images/duomo/duomo.dzi", -} -const labels = { - rainbow: 'Rainbow Grid', - leaves: 'Leaves', - bblue: 'Blue B', - duomo: 'Duomo', -} - -// viewer1: context2d drawer -let viewer1 = window.viewer1 = OpenSeadragon({ - id: "webgl", - prefixUrl: "../../build/openseadragon/images/", - minZoomImageRatio:0.01, - maxZoomPixelRatio:100, - smoothTileEdgesMinZoom:1.1, - crossOriginPolicy: 'Anonymous', - ajaxWithCredentials: false, - // maxImageCacheCount: 30, - drawer:OpenSeadragon.WebGL2Drawer, - blendTime:0, - drawerOptions:{ - webgl:{ - programs:[makeRedFilter()], - } - } -}); - -setupImageControls(); - - - -class ColorChanelFilter { - constructor() { - this.red = 1; - this.blue = 1; - this.green = 1; - - this.program = null; - } - createProgram(gl){ - this.program = OpenSeadragon.WebGLDrawer.initShaderProgram(gl, this.getVertexShaderCode(), this.getFragmentShaderCode()); - } - apply(gl, tiledImageTexture, tiledImage){ - - } - getVertexShaderCode(){ - return ` - attribute vec2 a_output_position; - attribute vec2 a_texture_position; - - uniform mat3 u_matrix; - - varying vec2 v_texCoord; - - void main() { - gl_Position = vec4(u_matrix * vec3(a_output_position, 1), 1); - - v_texCoord = a_texture_position; - } - `; - } - getFragmentShaderCode(){ - return ` - precision mediump float; - - // our texture - uniform sampler2D u_image; - - // the texCoords passed in from the vertex shader. - varying vec2 v_texCoord; - - // the opacity multiplier for the image - uniform vec3 color_multiplier; - - void main() { - gl_FragColor = texture2D(u_image, v_texCoord); - gl_FragColor *= vec4(color_multiplier, 1); - } - `; - } - -} - - - - - -function setupImageControls(){ - -$('#image-picker').sortable({ - update: function(event, ui){ - let thisItem = ui.item.find('.toggle').data('item1'); - let items = $('#image-picker input.toggle:checked').toArray().map(item=>$(item).data('item1')); - let newIndex = items.indexOf(thisItem); - if(thisItem){ - viewer1.world.setItemIndex(thisItem, newIndex); - } - } -}); - -Object.keys(sources).forEach((key, index)=>{ - let element = makeImagePickerElement(key, labels[key]) - $('#image-picker').append(element); - if(index === 0){ - element.find('.toggle').prop('checked',true); - } -}) - -$('#image-picker input.toggle').on('change',function(){ - let data = $(this).data(); - if(this.checked){ - addTileSource(viewer1, data.image, this); - } else { - if(data.item1){ - viewer1.world.removeItem(data.item1); - $(this).data({item1: null}); - } - } -}).trigger('change'); - -$('#image-picker input:not(.toggle)').on('change',function(){ - let data = $(this).data(); - let value = $(this).val(); - let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1'); - updateTiledImage(tiledImage1, data, value, this); -}); - -function updateTiledImage(tiledImage, data, value, item){ - if(tiledImage){ - //item = tiledImage - let field = data.field; - if(field == 'x'){ - let bounds = tiledImage.getBoundsNoRotate(); - let position = new OpenSeadragon.Point(Number(value), bounds.y); - tiledImage.setPosition(position); - } else if ( field == 'y'){ - let bounds = tiledImage.getBoundsNoRotate(); - let position = new OpenSeadragon.Point(bounds.x, Number(value)); - tiledImage.setPosition(position); - } else if (field == 'width'){ - tiledImage.setWidth(Number(value)); - } else if (field == 'degrees'){ - tiledImage.setRotation(Number(value)); - } else if (field == 'opacity'){ - tiledImage.setOpacity(Number(value)); - } else if (field == 'flipped'){ - tiledImage.setFlip($(item).prop('checked')); - } else if (field == 'cropped'){ - if( $(item).prop('checked') ){ - let croppingPolygons = [ [{x:200, y:200}, {x:800, y:200}, {x:500, y:800}] ]; - tiledImage.setCroppingPolygons(croppingPolygons); - } else { - tiledImage.resetCroppingPolygons(); - } - } else if (field == 'clipped'){ - if( $(item).prop('checked') ){ - let clipRect = new OpenSeadragon.Rect(2000, 0, 3000, 4000); - tiledImage.setClip(clipRect); - } else { - tiledImage.setClip(null); - } - } - else if (field == 'debug'){ - if( $(item).prop('checked') ){ - tiledImage.debugMode = true; - } else { - tiledImage.debugMode = false; - } - } - } -} - -$('.image-options select[data-field=composite]').append(getCompositeOperationOptions()).on('change',function(){ - let data = $(this).data(); - let tiledImage1 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1'); - if(tiledImage1){ - tiledImage1.setCompositeOperation(this.value == 'null' ? null : this.value); - } - let tiledImage2 = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2'); - if(tiledImage2){ - tiledImage2.setCompositeOperation(this.value == 'null' ? null : this.value); - } -}).trigger('change'); - -$('.image-options select[data-field=wrapping]').append(getWrappingOptions()).on('change',function(){ - let data = $(this).data(); - let tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item1'); - if(tiledImage){ - switch(this.value){ - case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break; - case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break; - case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break; - case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break; - } - tiledImage.viewer.raiseEvent('opacity-change');//trigger a redraw for the webgl renderer. TODO: fix this hack. - } - tiledImage = $(`#image-picker input.toggle[data-image=${data.image}]`).data('item2'); - if(tiledImage){ - switch(this.value){ - case "None": tiledImage.wrapHorizontal = tiledImage.wrapVertical = false; break; - case "Horizontal": tiledImage.wrapHorizontal = true; tiledImage.wrapVertical = false; break; - case "Vertical": tiledImage.wrapHorizontal = false; tiledImage.wrapVertical = true; break; - case "Both": tiledImage.wrapHorizontal = tiledImage.wrapVertical = true; break; - } - tiledImage.viewer.raiseEvent('opacity-change');//trigger a redraw for the webgl renderer. TODO: fix this hack. - } -}).trigger('change'); - -function getWrappingOptions(){ - let opts = ['None', 'Horizontal', 'Vertical', 'Both']; - let elements = opts.map((opt, i)=>{ - let el = $('