mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-22 13:16:10 +03:00
tiled image opacity works now with no overlapping regions at tile borders
This commit is contained in:
parent
24c4d2d2bc
commit
83ec2bb1f0
@ -36,7 +36,7 @@
|
|||||||
|
|
||||||
// internal class Mat3: implements matrix operations
|
// internal class Mat3: implements matrix operations
|
||||||
// Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
|
// Modified from https://webglfundamentals.org/webgl/lessons/webgl-2d-matrices.html
|
||||||
class Mat3{
|
class Mat3{
|
||||||
constructor(values){
|
constructor(values){
|
||||||
if(!values) {
|
if(!values) {
|
||||||
values = [
|
values = [
|
||||||
@ -118,9 +118,9 @@ class Mat3{
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class WebGLDrawer
|
* @class WebGLDrawer
|
||||||
* @memberof OpenSeadragon
|
* @memberof OpenSeadragon
|
||||||
* @classdesc Default implementation of WebGLDrawer for an {@link OpenSeadragon.Viewer}.
|
* @classdesc Default implementation of WebGLDrawer for an {@link OpenSeadragon.Viewer}.
|
||||||
@ -131,7 +131,7 @@ class Mat3{
|
|||||||
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
$.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
||||||
constructor(options){
|
constructor(options){
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
@ -144,7 +144,10 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
this._gl = null;
|
this._gl = null;
|
||||||
this._glLocs = null;
|
this._glLocs = null;
|
||||||
this._glProgram = null;
|
this._glProgram = null;
|
||||||
this._glPositionBuffer = null;
|
this._glUnitQuadBuffer = null;
|
||||||
|
this._glFrameBuffer = null;
|
||||||
|
this._glTiledImageTexture = null;
|
||||||
|
this._glFramebufferToCanvasTransform = null;
|
||||||
this._outputCanvas = null;
|
this._outputCanvas = null;
|
||||||
this._outputContext = null;
|
this._outputContext = null;
|
||||||
this._clippingCanvas = null;
|
this._clippingCanvas = null;
|
||||||
@ -193,8 +196,8 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Delete all our created resources
|
// Delete all our created resources
|
||||||
gl.deleteBuffer(this._glPositionBuffer);
|
gl.deleteBuffer(this._glUnitQuadBuffer);
|
||||||
|
gl.deleteBuffer(this._glFrameBuffer);
|
||||||
// TO DO: if/when render buffers or frame buffers are used, release them:
|
// TO DO: if/when render buffers or frame buffers are used, release them:
|
||||||
// gl.deleteRenderbuffer(someRenderbuffer);
|
// gl.deleteRenderbuffer(someRenderbuffer);
|
||||||
// gl.deleteFramebuffer(someFramebuffer);
|
// gl.deleteFramebuffer(someFramebuffer);
|
||||||
@ -262,6 +265,7 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
* @param {Array} tiledImages Array of TiledImage objects to draw
|
* @param {Array} tiledImages Array of TiledImage objects to draw
|
||||||
*/
|
*/
|
||||||
draw(tiledImages){
|
draw(tiledImages){
|
||||||
|
let gl = this._gl;
|
||||||
let viewport = {
|
let viewport = {
|
||||||
bounds: this.viewport.getBoundsNoRotate(true),
|
bounds: this.viewport.getBoundsNoRotate(true),
|
||||||
center: this.viewport.getCenter(true),
|
center: this.viewport.getCenter(true),
|
||||||
@ -275,21 +279,19 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
let rotMatrix = Mat3.makeRotation(-viewport.rotation);
|
let rotMatrix = Mat3.makeRotation(-viewport.rotation);
|
||||||
let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
let viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
||||||
|
|
||||||
//iterate over tiled imagesget the list of tiles to draw
|
// clear the output canvas
|
||||||
this._outputContext.clearRect(0, 0, this._outputCanvas.width, this._outputCanvas.height);
|
this._outputContext.clearRect(0, 0, this._outputCanvas.width, this._outputCanvas.height);
|
||||||
|
|
||||||
|
|
||||||
// TO DO: further optimization is possible.
|
// TO DO: further optimization is possible.
|
||||||
// If no clipping and no composite operation, the tiled images
|
// If no clipping and no composite operation, the tiled images
|
||||||
// can all be drawn onto the rendering canvas at the same time, avoiding
|
// can all be drawn onto the rendering canvas at the same time, avoiding
|
||||||
// unnecessary clearing and copying of the pixel data.
|
// unnecessary clearing and copying of the pixel data.
|
||||||
// For now, I'm doing it this way to replicate full functionality
|
// For now, I'm doing it this way to replicate full functionality
|
||||||
// of the context2d drawer
|
// of the context2d drawer
|
||||||
tiledImages.forEach( (tiledImage, i) => {
|
|
||||||
// clear the rendering canvas
|
|
||||||
this._gl.clear(this._gl.COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
// set opacity for this image
|
//iterate over tiled imagesget the list of tiles to draw
|
||||||
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, tiledImage.opacity);
|
tiledImages.forEach( (tiledImage, i) => {
|
||||||
|
|
||||||
//get the list of tiles to draw
|
//get the list of tiles to draw
|
||||||
let tilesToDraw = tiledImage.getTilesToDraw();
|
let tilesToDraw = tiledImage.getTilesToDraw();
|
||||||
@ -298,9 +300,21 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bind to the framebuffer for render-to-texture
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
||||||
|
|
||||||
|
// clear the buffer
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
// set opacity for this image
|
||||||
|
gl.uniform1f(this._glLocs.uOpacityMultiplier, tiledImage.opacity);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let overallMatrix = viewMatrix;
|
let overallMatrix = viewMatrix;
|
||||||
|
|
||||||
let imageRotation = tiledImage.getRotation(true);
|
let imageRotation = tiledImage.getRotation(true);
|
||||||
|
// if needed, handle the tiledImage being rotated
|
||||||
if( imageRotation % 360 !== 0){
|
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 imageCenter = tiledImage.getBoundsNoRotate(true).getCenter();
|
||||||
@ -312,54 +326,48 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
overallMatrix = viewMatrix.multiply(localMatrix);
|
overallMatrix = viewMatrix.multiply(localMatrix);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let i = 0; i < tilesToDraw.length; i++){
|
// iterate over tiles and draw each one to the buffer
|
||||||
let tile = tilesToDraw[i].tile;
|
for(let ti = 0; ti < tilesToDraw.length; ti++){
|
||||||
let texture = this._TextureMap.get(tile.getCanvasContext().canvas);
|
let tile = tilesToDraw[ti].tile;
|
||||||
if(texture){
|
let textureInfo = this._TextureMap.get(tile.getCanvasContext().canvas);
|
||||||
this._drawTile(tile, tiledImage, texture, overallMatrix, tiledImage.opacity);
|
if(textureInfo){
|
||||||
|
this._drawTile(tile, tiledImage, textureInfo, overallMatrix, tiledImage.opacity);
|
||||||
} else {
|
} else {
|
||||||
// console.log('No tile info', tile);
|
// console.log('No tile info', tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// composite onto the output canvas, clipping if necessary
|
|
||||||
this._outputContext.save();
|
|
||||||
|
|
||||||
// set composite operation; ignore for first image drawn
|
// Draw from the Framebuffer onto the rendering canvas buffer
|
||||||
this._outputContext.globalCompositeOperation = i === 0 ? null : tiledImage.compositeOperation || this.viewer.compositeOperation;
|
|
||||||
if(tiledImage._croppingPolygons || tiledImage._clip){
|
|
||||||
this._renderToClippingCanvas(tiledImage);
|
|
||||||
this._outputContext.drawImage(this._clippingCanvas, 0, 0);
|
|
||||||
|
|
||||||
} else {
|
gl.flush(); // finish drawing to the texture
|
||||||
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // null means bind to the backbuffer for drawing
|
||||||
}
|
gl.bindTexture(gl.TEXTURE_2D, this._glTiledImageTexture); // bind the rendered texture to use
|
||||||
this._outputContext.restore();
|
gl.clear(gl.COLOR_BUFFER_BIT); // clear the back buffer
|
||||||
if(tiledImage.debugMode){
|
|
||||||
let colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
|
||||||
let strokeStyle = this.debugGridColor[colorIndex];
|
|
||||||
let fillStyle = this.debugGridColor[colorIndex];
|
|
||||||
this._drawDebugInfo(tilesToDraw, tiledImage, strokeStyle, fillStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if( this.viewer ){
|
// set up the matrix to draw the whole framebuffer to the entire clip space
|
||||||
/**
|
gl.uniformMatrix3fv(this._glLocs.uMatrix, false, this._glFramebufferToCanvasTransform);
|
||||||
* Raised when a tiled image is drawn to the canvas. Only valid
|
|
||||||
* for webgl drawer.
|
// reset texturebuffer to unit quad
|
||||||
*
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._glTextureBuffer);
|
||||||
* @event tiled-image-drawn
|
gl.bufferData(gl.ARRAY_BUFFER, this._glUnitQuad, gl.DYNAMIC_DRAW);
|
||||||
* @memberof OpenSeadragon.Viewer
|
|
||||||
* @type {object}
|
// set opacity to the value for the current tiledImage
|
||||||
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, tiledImage.opacity);
|
||||||
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
* @property {Array} tiles - An array of Tile objects that were drawn.
|
|
||||||
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
// iterate over any filters - filters can use this._glTiledImageTexture to get rendered data if desired
|
||||||
*/
|
let filters = this.filters || [];
|
||||||
this.viewer.raiseEvent( 'tiled-image-drawn', {
|
for(let fi = 0; fi < filters.length; fi++){
|
||||||
tiledImage: tiledImage,
|
let filter = this.filters[fi];
|
||||||
tiles: tilesToDraw.map(info => info.tile),
|
if(filter.apply){
|
||||||
});
|
filter.apply(gl); // filter.apply should write data on top of the backbuffer (bound above)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
gl.flush(); //make sure drawing to the output buffer of the rendering canvas is complete. Is this necessary?
|
||||||
|
|
||||||
|
// draw from the rendering canvas onto the output canvas, clipping/cropping if needed.
|
||||||
|
this._renderToOutputCanvas(tiledImage, tilesToDraw, i);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -395,20 +403,83 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
// private
|
||||||
_getTextureDataFromTile(tile){
|
_getTextureDataFromTile(tile){
|
||||||
return tile.getCanvasContext().canvas;
|
return tile.getCanvasContext().canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods
|
/**
|
||||||
_drawTile(tile, tiledImage, texture, viewMatrix, imageOpacity){
|
* Draw data from the rendering canvas onto the output canvas, with clipping,
|
||||||
|
* cropping and/or debug info as requested.
|
||||||
|
* @private
|
||||||
|
* @param {OpenSeadragon.TiledImage} tiledImage - the tiledImage to draw
|
||||||
|
* @param {Array} tilesToDraw - array of objects containing tiles that were drawn
|
||||||
|
*/
|
||||||
|
_renderToOutputCanvas(tiledImage, tilesToDraw, tiledImageIndex){
|
||||||
|
// composite onto the output canvas, clipping if necessary
|
||||||
|
this._outputContext.save();
|
||||||
|
|
||||||
|
// set composite operation; ignore for first image drawn
|
||||||
|
this._outputContext.globalCompositeOperation = tiledImageIndex === 0 ? null : tiledImage.compositeOperation || this.viewer.compositeOperation;
|
||||||
|
if(tiledImage._croppingPolygons || tiledImage._clip){
|
||||||
|
this._renderToClippingCanvas(tiledImage);
|
||||||
|
this._outputContext.drawImage(this._clippingCanvas, 0, 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this._outputContext.drawImage(this._renderingCanvas, 0, 0);
|
||||||
|
}
|
||||||
|
this._outputContext.restore();
|
||||||
|
if(tiledImage.debugMode){
|
||||||
|
let colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
|
||||||
|
let strokeStyle = this.debugGridColor[colorIndex];
|
||||||
|
let fillStyle = this.debugGridColor[colorIndex];
|
||||||
|
this._drawDebugInfo(tilesToDraw, tiledImage, strokeStyle, fillStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire tiled-image-drawn event now that the data is on the output canvas
|
||||||
|
if( this.viewer ){
|
||||||
|
/**
|
||||||
|
* Raised when a tiled image is drawn to the canvas. Only valid
|
||||||
|
* for webgl drawer.
|
||||||
|
*
|
||||||
|
* @event tiled-image-drawn
|
||||||
|
* @memberof OpenSeadragon.Viewer
|
||||||
|
* @type {object}
|
||||||
|
* @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
|
||||||
|
* @property {OpenSeadragon.TiledImage} tiledImage - Which TiledImage is being drawn.
|
||||||
|
* @property {Array} tiles - An array of Tile objects that were drawn.
|
||||||
|
* @property {?Object} userData - Arbitrary subscriber-defined object.
|
||||||
|
*/
|
||||||
|
this.viewer.raiseEvent( 'tiled-image-drawn', {
|
||||||
|
tiledImage: tiledImage,
|
||||||
|
tiles: tilesToDraw.map(info => info.tile),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private
|
||||||
|
_drawTile(tile, tiledImage, textureInfo, viewMatrix, imageOpacity){
|
||||||
|
|
||||||
let gl = this._gl;
|
let gl = this._gl;
|
||||||
|
let texture = textureInfo.texture;
|
||||||
|
let textureQuad = textureInfo.position;
|
||||||
|
|
||||||
|
// set the vertices into the non-overlapped portion of the texture
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._glTextureBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, textureQuad, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
// compute offsets for overlap
|
||||||
|
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
|
||||||
|
let xOffset = tile.positionedBounds.width * overlapFraction.x;
|
||||||
|
let yOffset = tile.positionedBounds.height * overlapFraction.y;
|
||||||
|
|
||||||
// x, y, w, h in viewport coords
|
// x, y, w, h in viewport coords
|
||||||
let x = tile.positionedBounds.x;
|
let x = tile.positionedBounds.x + (tile.x === 0 ? 0 : xOffset);
|
||||||
let y = tile.positionedBounds.y;
|
let y = tile.positionedBounds.y + (tile.y === 0 ? 0 : yOffset);
|
||||||
let w = tile.positionedBounds.width;
|
let right = tile.positionedBounds.x + tile.positionedBounds.width - (tile.isRightMost ? 0 : xOffset);
|
||||||
let h = tile.positionedBounds.height;
|
let bottom = tile.positionedBounds.y + tile.positionedBounds.height - (tile.isBottomMost ? 0 : yOffset);
|
||||||
|
let w = right - x;
|
||||||
|
let h = bottom - y;
|
||||||
|
|
||||||
let matrix = new Mat3([
|
let matrix = new Mat3([
|
||||||
w, 0, 0,
|
w, 0, 0,
|
||||||
@ -416,7 +487,6 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
x, y, 1,
|
x, y, 1,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
if(tile.flipped){
|
if(tile.flipped){
|
||||||
// flip the tile around the center of the unit quad
|
// flip the tile around the center of the unit quad
|
||||||
let t1 = Mat3.makeTranslation(0.5, 0);
|
let t1 = Mat3.makeTranslation(0.5, 0);
|
||||||
@ -429,19 +499,17 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
let overallMatrix = viewMatrix.multiply(matrix);
|
let overallMatrix = viewMatrix.multiply(matrix);
|
||||||
|
|
||||||
if(tile.opacity !== 1 && tile.x === 0 && tile.y === 0){
|
|
||||||
// set opacity for this image
|
// set opacity for this image
|
||||||
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, imageOpacity * tile.opacity);
|
this._gl.uniform1f(this._glLocs.uOpacityMultiplier, tile.opacity); // imageOpacity *
|
||||||
}
|
|
||||||
|
|
||||||
gl.uniformMatrix3fv(this._glLocs.uMatrix, false, overallMatrix.values);
|
gl.uniformMatrix3fv(this._glLocs.uMatrix, false, overallMatrix.values);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
|
|
||||||
if(this.continuousTileRefresh){
|
if(this.continuousTileRefresh){
|
||||||
// Upload the image into the texture.
|
// Upload the image into the texture (already bound to TEXTURE_2D above)
|
||||||
let tileContext = tile.getCanvasContext();
|
let tileContext = tile.getCanvasContext();
|
||||||
this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext);
|
this._raiseTileDrawingEvent(tiledImage, this._outputContext, tile, tileContext);
|
||||||
this._uploadImageData(tileContext);
|
this._uploadImageData(tileContext, tile, tiledImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -455,18 +523,17 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
}
|
}
|
||||||
|
|
||||||
const vertexShaderProgram = `
|
const vertexShaderProgram = `
|
||||||
attribute vec2 a_position;
|
attribute vec2 a_output_position;
|
||||||
|
attribute vec2 a_texture_position;
|
||||||
|
|
||||||
uniform mat3 u_matrix;
|
uniform mat3 u_matrix;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = vec4(u_matrix * vec3(a_position, 1), 1);
|
gl_Position = vec4(u_matrix * vec3(a_output_position, 1), 1);
|
||||||
|
|
||||||
// because we're using a unit quad we can just use
|
v_texCoord = a_texture_position;
|
||||||
// the same data for our texcoords.
|
|
||||||
v_texCoord = a_position;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -484,7 +551,7 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_FragColor = texture2D(u_image, v_texCoord);
|
gl_FragColor = texture2D(u_image, v_texCoord);
|
||||||
// gl_FragColor *= u_opacity_multiplier;
|
gl_FragColor *= u_opacity_multiplier;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
let gl = this._gl;
|
let gl = this._gl;
|
||||||
@ -494,24 +561,69 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
this._glLocs = {
|
this._glLocs = {
|
||||||
aPosition: gl.getAttribLocation(this._glProgram, 'a_position'),
|
aOutputPosition: gl.getAttribLocation(this._glProgram, 'a_output_position'),
|
||||||
|
aTexturePosition: gl.getAttribLocation(this._glProgram, 'a_texture_position'),
|
||||||
uMatrix: gl.getUniformLocation(this._glProgram, 'u_matrix'),
|
uMatrix: gl.getUniformLocation(this._glProgram, 'u_matrix'),
|
||||||
uImage: gl.getUniformLocation(this._glProgram, 'u_image'),
|
uImage: gl.getUniformLocation(this._glProgram, 'u_image'),
|
||||||
uOpacityMultiplier: gl.getUniformLocation(this._glProgram, 'u_opacity_multiplier')
|
uOpacityMultiplier: gl.getUniformLocation(this._glProgram, 'u_opacity_multiplier')
|
||||||
};
|
};
|
||||||
|
|
||||||
// provide texture coordinates for the rectangle.
|
this._glUnitQuad = this._makeQuadVertexBuffer(0, 1, 0, 1);
|
||||||
this._glPositionBuffer = gl.createBuffer(); //keep reference to clear it later
|
// provide texture coordinates for the rectangle in output space.
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._glPositionBuffer);
|
this._glUnitQuadBuffer = gl.createBuffer(); //keep reference to clear it later
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._glUnitQuadBuffer);
|
||||||
0.0, 0.0,
|
gl.bufferData(gl.ARRAY_BUFFER, this._glUnitQuad, gl.STATIC_DRAW);
|
||||||
1.0, 0.0,
|
gl.enableVertexAttribArray(this._glLocs.aOutputPosition);
|
||||||
0., 1.0,
|
gl.vertexAttribPointer(this._glLocs.aOutputPosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
0.0, 1.0,
|
|
||||||
1.0, 0.0,
|
// provide texture coordinates for the rectangle in image (texture) space.
|
||||||
1.0, 1.0]), gl.STATIC_DRAW);
|
this._glTextureBuffer = gl.createBuffer(); //keep reference to clear it later
|
||||||
gl.enableVertexAttribArray(this._glLocs.aPosition);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this._glTextureBuffer);
|
||||||
gl.vertexAttribPointer(this._glLocs.aPosition, 2, gl.FLOAT, false, 0, 0);
|
gl.bufferData(gl.ARRAY_BUFFER, this._glUnitQuad, gl.DYNAMIC_DRAW); // use unit quad to start, will be updated per tile
|
||||||
|
gl.enableVertexAttribArray(this._glLocs.aTexturePosition);
|
||||||
|
gl.vertexAttribPointer(this._glLocs.aTexturePosition, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
// setup the framebuffer
|
||||||
|
this._glTiledImageTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this._glTiledImageTexture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this._renderingCanvas.width, this._renderingCanvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
this._glFrameBuffer = gl.createFramebuffer();
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
||||||
|
gl.framebufferTexture2D(
|
||||||
|
gl.FRAMEBUFFER,
|
||||||
|
gl.COLOR_ATTACHMENT0, // attach texture as COLOR_ATTACHMENT0
|
||||||
|
gl.TEXTURE_2D, // attach a 2D texture
|
||||||
|
this._glTiledImageTexture, // the texture to attach
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
this._glFramebufferToCanvasTransform = Mat3.makeScaling(2, 2).multiply(Mat3.makeTranslation(-0.5, -0.5)).values;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_resizeRenderer(){
|
||||||
|
let gl = this._gl;
|
||||||
|
let w = this._renderingCanvas.width;
|
||||||
|
let h = this._renderingCanvas.height;
|
||||||
|
gl.viewport(0, 0, w, h);
|
||||||
|
|
||||||
|
//release the old texture
|
||||||
|
gl.deleteTexture(this._glTiledImageTexture);
|
||||||
|
//create a new texture and set it up
|
||||||
|
this._glTiledImageTexture = gl.createTexture();
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, this._glTiledImageTexture);
|
||||||
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
|
//bind the frame buffer to the new texture
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this._glFrameBuffer);
|
||||||
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this._glTiledImageTexture, 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -549,25 +661,60 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
_this._renderingCanvas.style.height = _this._outputCanvas.clientHeight + 'px';
|
_this._renderingCanvas.style.height = _this._outputCanvas.clientHeight + 'px';
|
||||||
_this._renderingCanvas.width = _this._clippingCanvas.width = _this._outputCanvas.width;
|
_this._renderingCanvas.width = _this._clippingCanvas.width = _this._outputCanvas.width;
|
||||||
_this._renderingCanvas.height = _this._clippingCanvas.height = _this._outputCanvas.height;
|
_this._renderingCanvas.height = _this._clippingCanvas.height = _this._outputCanvas.height;
|
||||||
_this._gl.viewport(0, 0, _this._renderingCanvas.width, _this._renderingCanvas.height);
|
|
||||||
|
// important - update the size of the rendering viewport!
|
||||||
|
_this._resizeRenderer();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_makeQuadVertexBuffer(left, right, top, bottom){
|
||||||
|
return new Float32Array([
|
||||||
|
left, bottom,
|
||||||
|
right, bottom,
|
||||||
|
left, top,
|
||||||
|
left, top,
|
||||||
|
right, bottom,
|
||||||
|
right, top]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_tileReadyHandler(event){
|
_tileReadyHandler(event){
|
||||||
let tile = event.tile;
|
let tile = event.tile;
|
||||||
|
let tiledImage = event.tiledImage;
|
||||||
let tileContext = tile.getCanvasContext();
|
let tileContext = tile.getCanvasContext();
|
||||||
let canvas = tileContext.canvas;
|
let canvas = tileContext.canvas;
|
||||||
let texture = this._TextureMap.get(canvas);
|
let textureInfo = this._TextureMap.get(canvas);
|
||||||
|
|
||||||
// if this is a new image for us, create a texture
|
// if this is a new image for us, create a texture
|
||||||
if(!texture){
|
if(!textureInfo){
|
||||||
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
|
||||||
texture = gl.createTexture();
|
let texture = gl.createTexture();
|
||||||
|
let position;
|
||||||
|
let overlap = tiledImage.source.tileOverlap;
|
||||||
|
if( overlap > 0){
|
||||||
|
// calculate the normalized position of the rect to actually draw
|
||||||
|
// discarding overlap.
|
||||||
|
let overlapFraction = this._calculateOverlapFraction(tile, tiledImage);
|
||||||
|
|
||||||
|
let left = tile.x === 0 ? 0 : overlapFraction.x;
|
||||||
|
let top = tile.y === 0 ? 0 : overlapFraction.y;
|
||||||
|
let right = tile.isRightMost ? 1 : 1 - overlapFraction.x;
|
||||||
|
let bottom = tile.isBottomMost ? 1 : 1 - overlapFraction.y;
|
||||||
|
position = this._makeQuadVertexBuffer(left, right, top, bottom);
|
||||||
|
} else {
|
||||||
|
// no overlap: this texture can use the unit quad as it's position data
|
||||||
|
position = this._glUnitQuad;
|
||||||
|
}
|
||||||
|
|
||||||
|
let textureInfo = {
|
||||||
|
texture: texture,
|
||||||
|
position: position,
|
||||||
|
};
|
||||||
|
|
||||||
// add it to our _TextureMap
|
// add it to our _TextureMap
|
||||||
this._TextureMap.set(canvas, texture);
|
this._TextureMap.set(canvas, textureInfo);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||||
// Set the parameters so we can render any size image.
|
// Set the parameters so we can render any size image.
|
||||||
@ -583,17 +730,29 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_calculateOverlapFraction(tile, tiledImage){
|
||||||
|
let overlap = tiledImage.source.tileOverlap;
|
||||||
|
let nativeWidth = tile.sourceBounds.width; // in pixels
|
||||||
|
let nativeHeight = tile.sourceBounds.height; // in pixels
|
||||||
|
let overlapWidth = (tile.x === 0 ? 0 : overlap) + (tile.isRightMost ? 0 : overlap); // in pixels
|
||||||
|
let overlapHeight = (tile.y === 0 ? 0 : overlap) + (tile.isBottomMost ? 0 : overlap); // in pixels
|
||||||
|
let widthOverlapFraction = overlap / (nativeWidth + overlapWidth); // as a fraction of image including overlap
|
||||||
|
let heightOverlapFraction = overlap / (nativeHeight + overlapHeight); // as a fraction of image including overlap
|
||||||
|
return {
|
||||||
|
x: widthOverlapFraction,
|
||||||
|
y: heightOverlapFraction
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_uploadImageData(tileContext){
|
_uploadImageData(tileContext){
|
||||||
|
|
||||||
let gl = this._gl;
|
let gl = this._gl;
|
||||||
try{
|
|
||||||
let canvas = tileContext.canvas;
|
let canvas = tileContext.canvas;
|
||||||
if(!canvas){
|
|
||||||
throw('Tile context does not have a canvas', tileContext);
|
// This depends on gl.TEXTURE_2D being bound to the texture
|
||||||
}
|
// associated with this canvas before calling this function
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
||||||
} catch(e) {
|
|
||||||
$.console.error('Error uploading canvas data to webgl', e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageUnloadedHandler(event){
|
_imageUnloadedHandler(event){
|
||||||
@ -602,12 +761,17 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
}
|
}
|
||||||
|
|
||||||
_cleanupImageData(tileCanvas){
|
_cleanupImageData(tileCanvas){
|
||||||
let texture = this._TextureMap.get(tileCanvas);
|
let textureInfo = this._TextureMap.get(tileCanvas);
|
||||||
//remove from the map
|
//remove from the map
|
||||||
this._TextureMap.delete(tileCanvas);
|
this._TextureMap.delete(tileCanvas);
|
||||||
|
|
||||||
//release the texture from the GPU
|
//release the texture from the GPU
|
||||||
this._gl.deleteTexture(texture);
|
if(textureInfo){
|
||||||
|
this._gl.deleteTexture(textureInfo.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// release the position buffer from the GPU
|
||||||
|
// TO DO: do this!
|
||||||
}
|
}
|
||||||
// private
|
// private
|
||||||
// necessary for clip testing to pass (test uses spyOnce(drawer._setClip))
|
// necessary for clip testing to pass (test uses spyOnce(drawer._setClip))
|
||||||
@ -810,8 +974,8 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
// modified from https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
|
// modified from https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Adding_2D_content_to_a_WebGL_context
|
||||||
static initShaderProgram(gl, vsSource, fsSource) {
|
static initShaderProgram(gl, vsSource, fsSource) {
|
||||||
const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||||
const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||||
|
|
||||||
// Create the shader program
|
// Create the shader program
|
||||||
|
|
||||||
@ -832,13 +996,8 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return shaderProgram;
|
return shaderProgram;
|
||||||
}
|
|
||||||
|
|
||||||
//
|
function loadShader(gl, type, source) {
|
||||||
// creates a shader of the given type, uploads the source and
|
|
||||||
// compiles it.
|
|
||||||
//
|
|
||||||
static loadShader(gl, type, source) {
|
|
||||||
const shader = gl.createShader(type);
|
const shader = gl.createShader(type);
|
||||||
|
|
||||||
// Send the source to the shader object
|
// Send the source to the shader object
|
||||||
@ -861,7 +1020,18 @@ $.WebGLDrawer = class WebGLDrawer extends OpenSeadragon.DrawerBase{
|
|||||||
|
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// $.WebGLDrawer.Filter = class{
|
||||||
|
// constructor(gl){
|
||||||
|
// this.gl = gl;
|
||||||
|
// }
|
||||||
|
// apply(){
|
||||||
|
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
}( OpenSeadragon ));
|
}( OpenSeadragon ));
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>OSD-ThreeJS</title>
|
<title>Drawer Comparison Demo</title>
|
||||||
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
|
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
|
||||||
<script type="text/javascript" src='../lib/jquery-1.9.1.min.js'></script>
|
<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>
|
<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">
|
<link rel="stylesheet" href="../lib/jquery-ui-1.10.2/css/smoothness/jquery-ui-1.10.2.min.css">
|
||||||
|
|
||||||
</script>
|
<script type="module" src="./drawercomparison.js"></script>
|
||||||
<script type="module" src="./webgl.js"></script>
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.content{
|
.content{
|
||||||
max-width:960px;
|
max-width:960px;
|
||||||
@ -62,7 +61,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
||||||
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> (via three.js) drawers</h2>
|
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> drawers</h2>
|
||||||
<div class="mirrored">
|
<div class="mirrored">
|
||||||
<div>
|
<div>
|
||||||
<h3>Context2d drawer (default in OSD <= 4.1.0)</h3>
|
<h3>Context2d drawer (default in OSD <= 4.1.0)</h3>
|
@ -15,7 +15,7 @@ const labels = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Double viewer setup for comparison - Context2dDrawer and WebGLDrawer
|
//Double viewer setup for comparison - Context2dDrawer and WebGLDrawer
|
||||||
|
// viewer1: context2d drawer
|
||||||
let viewer1 = window.viewer1 = OpenSeadragon({
|
let viewer1 = window.viewer1 = OpenSeadragon({
|
||||||
id: "context2d",
|
id: "context2d",
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
@ -29,6 +29,7 @@ let viewer1 = window.viewer1 = OpenSeadragon({
|
|||||||
blendTime:0
|
blendTime:0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// viewer2: webgl drawer
|
||||||
let viewer2 = window.viewer2 = OpenSeadragon({
|
let viewer2 = window.viewer2 = OpenSeadragon({
|
||||||
id: "webgl",
|
id: "webgl",
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
@ -39,9 +40,26 @@ let viewer2 = window.viewer2 = OpenSeadragon({
|
|||||||
ajaxWithCredentials: false,
|
ajaxWithCredentials: false,
|
||||||
// maxImageCacheCount: 30,
|
// maxImageCacheCount: 30,
|
||||||
drawer:'webgl',
|
drawer:'webgl',
|
||||||
blendTime:0
|
blendTime:0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// viewer3: html drawer
|
||||||
|
var viewer3 = window.viewer3 = OpenSeadragon({
|
||||||
|
id: "htmldrawer",
|
||||||
|
drawer:'html',
|
||||||
|
blendTime:2,
|
||||||
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
|
minZoomImageRatio:0.01,
|
||||||
|
customDrawer: OpenSeadragon.HTMLDrawer,
|
||||||
|
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
||||||
|
sequenceMode: true,
|
||||||
|
crossOriginPolicy: 'Anonymous',
|
||||||
|
ajaxWithCredentials: false
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Sync navigation of viewer1 and viewer 2
|
// Sync navigation of viewer1 and viewer 2
|
||||||
var viewer1Leading = false;
|
var viewer1Leading = false;
|
||||||
var viewer2Leading = false;
|
var viewer2Leading = false;
|
||||||
@ -82,22 +100,6 @@ viewer1.addHandler('flip', viewer1Handler);
|
|||||||
viewer2.addHandler('flip', viewer2Handler);
|
viewer2.addHandler('flip', viewer2Handler);
|
||||||
|
|
||||||
|
|
||||||
// Single viewer showing how to use plugin Drawer via configuration
|
|
||||||
// Also shows sequence mode
|
|
||||||
var viewer3 = window.viewer3 = OpenSeadragon({
|
|
||||||
id: "htmldrawer",
|
|
||||||
drawer:'html',
|
|
||||||
blendTime:2,
|
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
|
||||||
minZoomImageRatio:0.01,
|
|
||||||
customDrawer: OpenSeadragon.HTMLDrawer,
|
|
||||||
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
|
||||||
sequenceMode: true,
|
|
||||||
crossOriginPolicy: 'Anonymous',
|
|
||||||
ajaxWithCredentials: false
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$('#image-picker').sortable({
|
$('#image-picker').sortable({
|
||||||
update: function(event, ui){
|
update: function(event, ui){
|
||||||
let thisItem = ui.item.find('.toggle').data('item1');
|
let thisItem = ui.item.find('.toggle').data('item1');
|
1046
test/demo/webgldemodrawer.js
Normal file
1046
test/demo/webgldemodrawer.js
Normal file
File diff suppressed because it is too large
Load Diff
105
test/demo/webglfiltering.html
Normal file
105
test/demo/webglfiltering.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>WebGL Demo</title>
|
||||||
|
<script type="text/javascript" src='../../build/openseadragon/openseadragon.js'></script>
|
||||||
|
<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="./webgldemodrawer.js"></script>
|
||||||
|
<script type="module" src="./webglfiltering.js"></script>
|
||||||
|
<style type="text/css">
|
||||||
|
.content{
|
||||||
|
max-width:960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.side-by-side{
|
||||||
|
display:grid;
|
||||||
|
grid-template-columns:50% 50%;
|
||||||
|
gap: 2em;
|
||||||
|
}
|
||||||
|
.viewer-container {
|
||||||
|
/* width: 600px;
|
||||||
|
height: 400px; */
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
border: thin gray solid;
|
||||||
|
position:relative;
|
||||||
|
}
|
||||||
|
.example-code{
|
||||||
|
background-color:tan;
|
||||||
|
border: thin black solid;
|
||||||
|
padding:10px;
|
||||||
|
display:inline-block;
|
||||||
|
width:95%;
|
||||||
|
}
|
||||||
|
.description pre{
|
||||||
|
display:inline-block;
|
||||||
|
background-color:gainsboro;
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-options{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 2em 9em 1fr;
|
||||||
|
padding:3px;
|
||||||
|
border: thin gray solid;
|
||||||
|
}
|
||||||
|
.option-grid{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 7em 7em 10em 10em 10em;
|
||||||
|
/* grid-template-columns: repeat(5, auto); */
|
||||||
|
}
|
||||||
|
.image-options input[type=number]{
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
.image-options select{
|
||||||
|
width: 5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<h2>WebGLDrawer demonstration</h2>
|
||||||
|
<div class="side-by-side">
|
||||||
|
<div>
|
||||||
|
<div id="webgl" class="viewer-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div id="image-picker">
|
||||||
|
<h3>Image options (drag and drop to re-order images)</h3>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Example code</h2>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div class="description">
|
||||||
|
Drawer options are shown below.
|
||||||
|
</div>
|
||||||
|
<pre class="example-code">
|
||||||
|
let viewer = OpenSeadragon({
|
||||||
|
...
|
||||||
|
drawer: 'webgl',
|
||||||
|
drawerOptions: {
|
||||||
|
webgl: {
|
||||||
|
programs: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
...
|
||||||
|
});
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
<div id="htmldrawer" class="viewer-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
298
test/demo/webglfiltering.js
Normal file
298
test/demo/webglfiltering.js
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
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 = $('<option>',{value:opt}).text(opt);
|
||||||
|
if(i===0){
|
||||||
|
el.attr('selected',true);
|
||||||
|
}
|
||||||
|
return el[0];
|
||||||
|
// $('.image-options select').append(el);
|
||||||
|
});
|
||||||
|
return $(elements);
|
||||||
|
}
|
||||||
|
function getCompositeOperationOptions(){
|
||||||
|
let opts = [null,'source-over','source-in','source-out','source-atop',
|
||||||
|
'destination-over','destination-in','destination-out','destination-atop',
|
||||||
|
'lighten','darken','copy','xor','multiply','screen','overlay','color-dodge',
|
||||||
|
'color-burn','hard-light','soft-light','difference','exclusion',
|
||||||
|
'hue','saturation','color','luminosity'];
|
||||||
|
let elements = opts.map((opt, i)=>{
|
||||||
|
let el = $('<option>',{value:opt}).text(opt);
|
||||||
|
if(i===0){
|
||||||
|
el.attr('selected',true);
|
||||||
|
}
|
||||||
|
return el[0];
|
||||||
|
// $('.image-options select').append(el);
|
||||||
|
});
|
||||||
|
return $(elements);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTileSource(viewer, image, checkbox){
|
||||||
|
let options = $(`#image-picker input[data-image=${image}][type=number]`).toArray().reduce((acc, input)=>{
|
||||||
|
let field = $(input).data('field');
|
||||||
|
if(field){
|
||||||
|
acc[field] = Number(input.value);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
options.flipped = $(`#image-picker input[data-image=${image}][data-type=flipped]`).prop('checked');
|
||||||
|
|
||||||
|
let items = $('#image-picker input.toggle:checked').toArray();
|
||||||
|
let insertionIndex = items.indexOf(checkbox);
|
||||||
|
|
||||||
|
let tileSource = sources[image];
|
||||||
|
if(tileSource){
|
||||||
|
viewer&&viewer.addTiledImage({tileSource: tileSource, ...options, index: insertionIndex});
|
||||||
|
viewer&&viewer.world.addOnceHandler('add-item',function(ev){
|
||||||
|
let item = ev.item;
|
||||||
|
let field = viewer === viewer1 ? 'item1' : 'item2';
|
||||||
|
$(checkbox).data(field,item);
|
||||||
|
item.source.hasTransparency = ()=>true; //simulate image with transparency, to show seams in default renderer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeImagePickerElement(key, label){
|
||||||
|
return $(`<div class="image-options">
|
||||||
|
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
||||||
|
<label><input type="checkbox" data-image="" class="toggle"> __title__</label>
|
||||||
|
<div class="option-grid">
|
||||||
|
<label>X: <input type="number" value="0" data-image="" data-field="x"> </label>
|
||||||
|
<label>Y: <input type="number" value="0" data-image="" data-field="y"> </label>
|
||||||
|
<label>Width: <input type="number" value="1" data-image="" data-field="width" min="0"> </label>
|
||||||
|
<label>Degrees: <input type="number" value="0" data-image="" data-field="degrees"> </label>
|
||||||
|
<label>Opacity: <input type="number" value="1" data-image="" data-field="opacity" min="0" max="1" step="0.2"> </label>
|
||||||
|
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
|
||||||
|
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
|
||||||
|
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
|
||||||
|
<label>Composite: <select data-image="" data-field="composite"></select></label>
|
||||||
|
<label>Wrapping: <select data-image="" data-field="wrapping"></select></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user