mirror of
https://github.com/openseadragon/openseadragon.git
synced 2024-11-21 20:56:09 +03:00
Merge pull request #5 from Aiosa/rotation-seams
Small rendering bugfixes, URL params in demo pages.
This commit is contained in:
commit
a578b97d96
@ -66,14 +66,6 @@ module.exports = function(grunt) {
|
|||||||
"src/tiledimage.js",
|
"src/tiledimage.js",
|
||||||
"src/tilecache.js",
|
"src/tilecache.js",
|
||||||
"src/world.js",
|
"src/world.js",
|
||||||
|
|
||||||
//Aiosa's webgl drawer - needs optimization, polishing, trimming
|
|
||||||
"src/webgl/renderer.js",
|
|
||||||
"src/webgl/shaderLayer.js",
|
|
||||||
"src/webgl/dataLoader.js",
|
|
||||||
"src/webgl/webGLContext.js",
|
|
||||||
"src/webgl/drawer.js",
|
|
||||||
"src/webgl/plainShader.js",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" +
|
var banner = "//! <%= pkg.name %> <%= pkg.version %>\n" +
|
||||||
|
@ -38,6 +38,12 @@ $.DrawerOptions = class DrawerOptions{
|
|||||||
constructor(options){}
|
constructor(options){}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Point
|
||||||
|
* @property {number} x
|
||||||
|
* @property {number} y
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class DrawerBase
|
* @class DrawerBase
|
||||||
* @memberof OpenSeadragon
|
* @memberof OpenSeadragon
|
||||||
@ -297,7 +303,7 @@ $.DrawerBase = class DrawerBase{
|
|||||||
* @inner
|
* @inner
|
||||||
* Calculate width and height of the canvas based on viewport dimensions
|
* Calculate width and height of the canvas based on viewport dimensions
|
||||||
* and pixelDensityRatio
|
* and pixelDensityRatio
|
||||||
* @returns {Dictionary} {x, y} size of the canvas
|
* @returns {Point} {x, y} size of the canvas
|
||||||
*/
|
*/
|
||||||
_calculateCanvasSize() {
|
_calculateCanvasSize() {
|
||||||
var pixelDensityRatio = $.pixelDensityRatio;
|
var pixelDensityRatio = $.pixelDensityRatio;
|
||||||
|
@ -272,7 +272,8 @@ $.TileCache.prototype = {
|
|||||||
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
|
* @property {CanvasRenderingContext2D} context2D - The context that is being unloaded
|
||||||
*/
|
*/
|
||||||
tiledImage.viewer.raiseEvent("image-unloaded", {
|
tiledImage.viewer.raiseEvent("image-unloaded", {
|
||||||
context2D: context2D
|
context2D: context2D,
|
||||||
|
tile: tile
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,542 +0,0 @@
|
|||||||
|
|
||||||
(function($) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IDataLoader conforms to a specific texture type and WebGL version.
|
|
||||||
* It provides API for uniform handling of textures:
|
|
||||||
* - texture loading
|
|
||||||
* - GLSL texture handling
|
|
||||||
*/
|
|
||||||
$.WebGLModule.IDataLoader = class {
|
|
||||||
/**
|
|
||||||
* Creation
|
|
||||||
* @param {WebGLRenderingContextBase} gl
|
|
||||||
* @param {string} webglVersion
|
|
||||||
* @param {object} options
|
|
||||||
* @param {GLuint} options.wrap texture wrap parameteri
|
|
||||||
* @param {GLuint} options.magFilter texture filter parameteri
|
|
||||||
* @param {GLuint} options.minFilter texture filter parameteri
|
|
||||||
* */
|
|
||||||
constructor(gl, webglVersion, options) {
|
|
||||||
//texture cache to keep track of loaded GPU data
|
|
||||||
this.__cache = new Map();
|
|
||||||
this.wrap = options.wrap;
|
|
||||||
this.minFilter = options.minFilter;
|
|
||||||
this.magFilter = options.magFilter;
|
|
||||||
this.maxTextures = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loader strategy based on toString result, extend with your type if necessary.
|
|
||||||
* If your type cannot use the given version strategy (TEXTURE_2D_ARRAY UNIT), you have
|
|
||||||
* to re-define the whole API.
|
|
||||||
*
|
|
||||||
* When a data is sent to the shader for processing, `toString` method is called to
|
|
||||||
* get the data identifier. A typeLoaders key must be present to handle loading
|
|
||||||
* of that texture(s) data.
|
|
||||||
* @member typeLoaders
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule.IDataLoader
|
|
||||||
*
|
|
||||||
* @return {object} whatever you need to stare in the cache to later free the object
|
|
||||||
*/
|
|
||||||
this.typeLoaders = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} version
|
|
||||||
* @return {boolean} true if given webgl version is supported by the loader
|
|
||||||
*/
|
|
||||||
supportsWebglVersion(version) {
|
|
||||||
throw("::supportsWebglVersion must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get stored options under ID
|
|
||||||
* @param id
|
|
||||||
* @return {unknown} options stored by setLoaded
|
|
||||||
*/
|
|
||||||
getLoaded(id) {
|
|
||||||
return this.__cache.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Store options object
|
|
||||||
* @param id
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
setLoaded(id, options) {
|
|
||||||
this.__cache.set(id, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unload stored options
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
setUnloaded(id) {
|
|
||||||
this.__cache.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set texture sampling parameters
|
|
||||||
* @param {string} name one of 'minFilter', 'magFilter', 'wrap'
|
|
||||||
* @param {GLuint} value
|
|
||||||
*/
|
|
||||||
setTextureParam(name, value) {
|
|
||||||
if (!['minFilter', 'magFilter', 'wrap'].includes(name)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param renderer
|
|
||||||
* @param id
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
unloadTexture(renderer, id, options) {
|
|
||||||
throw("::unloadTexture must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {OpenSeadragon.WebGLModule} renderer renderer renderer reference
|
|
||||||
* @param id
|
|
||||||
* @param data
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
* @param {[number]} shaderDataIndexToGlobalDataIndex mapping of array indices to data indices, e.g. texture 0 for
|
|
||||||
* this shader corresponds to index shaderDataIndexToGlobalDataIndex[0] in the data array,
|
|
||||||
* -1 value used for textures not loaded
|
|
||||||
*/
|
|
||||||
load(renderer, id, data, width, height, shaderDataIndexToGlobalDataIndex) {
|
|
||||||
if (!data) {
|
|
||||||
$.console.warn("Attempt to draw nullable data!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const textureLoader = this.typeLoaders[toString.apply(data)];
|
|
||||||
if (!textureLoader) {
|
|
||||||
throw "WebGL Renderer cannot load data as texture: " + toString.apply(data);
|
|
||||||
}
|
|
||||||
this.setLoaded(id, textureLoader(this, renderer, data, width, height, shaderDataIndexToGlobalDataIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param renderer
|
|
||||||
* @param id
|
|
||||||
*/
|
|
||||||
free(renderer, id) {
|
|
||||||
this.unloadTexture(renderer, id, this.getLoaded(id));
|
|
||||||
this.setUnloaded(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the program is being loaded (set as active)
|
|
||||||
* @param {OpenSeadragon.WebGLModule} renderer
|
|
||||||
* @param {WebGLRenderingContextBase} gl WebGL context
|
|
||||||
* @param {WebGLProgram} program
|
|
||||||
* @param {object} specification reference to the specification object used
|
|
||||||
*/
|
|
||||||
programLoaded(renderer, gl, program, specification) {
|
|
||||||
//not needed
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when tile is processed
|
|
||||||
* @param {OpenSeadragon.WebGLModule} renderer renderer renderer reference
|
|
||||||
* @param {object} specification reference to the current active specification object
|
|
||||||
* @param {*} id data object present in the texture cache
|
|
||||||
* @param {WebGLProgram} program current WebGLProgram
|
|
||||||
* @param {WebGL2RenderingContext} gl
|
|
||||||
*/
|
|
||||||
programUsed(renderer, specification, id, program, gl) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sample texture
|
|
||||||
* @param {number|string} index texture index, must respect index re-mapping (see declare())
|
|
||||||
* @param {string} vec2coords GLSL expression that evaluates to vec2
|
|
||||||
* @return {string} GLSL expression (unterminated) that evaluates to vec4
|
|
||||||
*/
|
|
||||||
sample(index, vec2coords) {
|
|
||||||
return `texture(_vis_data_sampler_array, vec3(${vec2coords}, _vis_data_sampler_array_indices[${index}]))`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Declare GLSL texture logic (global scope) in the GLSL shader
|
|
||||||
* @param {[number]} shaderDataIndexToGlobalDataIndex mapping of array indices to data indices, e.g. texture 0 for
|
|
||||||
* this shader corresponds to index shaderDataIndexToGlobalDataIndex[0] in the data array,
|
|
||||||
* -1 value used for textures not loaded
|
|
||||||
* @return {string} GLSL declaration (terminated with semicolon) of necessary elements for textures
|
|
||||||
*/
|
|
||||||
declare(shaderDataIndexToGlobalDataIndex) {
|
|
||||||
return `
|
|
||||||
vec4 osd_texture(float index, vec2 coords) {
|
|
||||||
//This method must be implemented!
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: is this relevant?
|
|
||||||
// vec2 osd_texture_size() {
|
|
||||||
// //This method must be implemented!
|
|
||||||
// }
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data loading strategies for different WebGL versions.
|
|
||||||
* Should you have your own data format, change/re-define these
|
|
||||||
* to correctly load the textures to GPU, based on the WebGL version used.
|
|
||||||
*
|
|
||||||
* The processing accepts arrays of images to feed to the shader built from configuration.
|
|
||||||
* This implementation supports data as Image or Canvas objects. We will refer to them as <image*>
|
|
||||||
*
|
|
||||||
* Implemented texture loaders support
|
|
||||||
* - working with <image*> object - image data chunks are vertically concatenated
|
|
||||||
* - working with [<image*>] object - images are in array
|
|
||||||
*
|
|
||||||
* @namespace OpenSeadragon.WebGLModule.Loaders
|
|
||||||
*/
|
|
||||||
$.WebGLModule.Loaders = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* //TODO: ugly
|
|
||||||
* In case the system is fed by anything but 'Image' (or the like) data object,
|
|
||||||
* implement here conversion so that debug mode can draw it.
|
|
||||||
* @param {*} data
|
|
||||||
* @return {HTMLElement} Dom Element
|
|
||||||
*/
|
|
||||||
dataAsHtmlElement: function(data) {
|
|
||||||
return {
|
|
||||||
"[object HTMLImageElement]": () => data,
|
|
||||||
"[object HTMLCanvasElement]": () => data,
|
|
||||||
//Image objects in Array, we assume image objects only
|
|
||||||
"[object Array]": function() {
|
|
||||||
const node = document.createElement("div");
|
|
||||||
for (let image of data) {
|
|
||||||
node.append(image);
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}[toString.apply(data)]();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data loader for WebGL 2.0. Must load the data to a Texture2DArray.
|
|
||||||
* The name of the texture is a constant. The order od the textures in
|
|
||||||
* the z-stacking is defined in shaderDataIndexToGlobalDataIndex.
|
|
||||||
*
|
|
||||||
* For details, please, see the implementation.
|
|
||||||
* @class OpenSeadragon.WebGLModule.Loaders.TEXTURE_2D_ARRAY
|
|
||||||
*/
|
|
||||||
TEXTURE_2D_ARRAY: class /**@lends $.WebGLModule.Loaders.TEXTURE_2D_ARRAY */ extends OpenSeadragon.WebGLModule.IDataLoader {
|
|
||||||
unloadTexture(renderer, id, options) {
|
|
||||||
renderer.gl.deleteTexture(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creation
|
|
||||||
* @param {WebGL2RenderingContext} gl
|
|
||||||
* @param {string} webglVersion
|
|
||||||
* @param {object} options
|
|
||||||
* @param {GLuint} options.wrap texture wrap parameteri
|
|
||||||
* @param {GLuint} options.magFilter texture filter parameteri
|
|
||||||
* @param {GLuint} options.minFilter texture filter parameteri
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule.Loaders.TEXTURE_2D_ARRAY
|
|
||||||
* */
|
|
||||||
constructor(gl, webglVersion, options) {
|
|
||||||
super(gl, webglVersion, options);
|
|
||||||
|
|
||||||
if (webglVersion !== "2.0") {
|
|
||||||
throw "Incompatible WebGL version for TEXTURE_2D_ARRAY data loader!";
|
|
||||||
}
|
|
||||||
|
|
||||||
// this.batchSize = 5;
|
|
||||||
// let lastBatch = null;
|
|
||||||
|
|
||||||
this.typeLoaders["[object HTMLImageElement]"] =
|
|
||||||
this.typeLoaders["[object HTMLCanvasElement]"] = function (self, webglModule, data, width, height,
|
|
||||||
shaderDataIndexToGlobalDataIndex) {
|
|
||||||
|
|
||||||
const NUM_IMAGES = Math.round(data.height / height);
|
|
||||||
const gl = webglModule.gl;
|
|
||||||
|
|
||||||
// //todo different tile sizes are problems
|
|
||||||
// if (!lastBatch || lastBatch.length < NUM_IMAGES) {
|
|
||||||
// lastBatch = {
|
|
||||||
// texId: gl.createTexture(),
|
|
||||||
// length: this.batchSize,
|
|
||||||
// texCount: 0,
|
|
||||||
// };
|
|
||||||
// gl.bindTexture(gl.TEXTURE_2D_ARRAY, options.texId);
|
|
||||||
// gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, data[0].width, data[0].height, data.length + 1);
|
|
||||||
// } else {
|
|
||||||
// gl.bindTexture(gl.TEXTURE_2D_ARRAY, options.texId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const textureId = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D_ARRAY, textureId);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, self.magFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, self.minFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, self.wrap);
|
|
||||||
|
|
||||||
gl.texImage3D(
|
|
||||||
gl.TEXTURE_2D_ARRAY,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
NUM_IMAGES,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
return textureId;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Image objects in Array, we assume image objects only todo ugly, can be array of anything
|
|
||||||
this.typeLoaders["[object Array]"] = function (self, webglModule, data, options, width, height, shaderDataIndexToGlobalDataIndex) {
|
|
||||||
const gl = webglModule.gl;
|
|
||||||
const textureId = gl.createTexture();
|
|
||||||
|
|
||||||
//gl.activeTexture(gl.TEXTURE0);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D_ARRAY, textureId);
|
|
||||||
gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, data[0].width, data[0].height, data.length + 1);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAX_LEVEL, 0);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, self.minFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, self.magFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, self.wrap);
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
for (let image of data) {
|
|
||||||
gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 0, 0, 0, index++, image.width, image.height,
|
|
||||||
1, gl.RGBA, gl.UNSIGNED_BYTE, image);
|
|
||||||
}
|
|
||||||
return textureId;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsWebglVersion(version) {
|
|
||||||
return version === "2.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
programUsed(renderer, specification, id, program, gl) {
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
|
||||||
const tid = this.getLoaded(id);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D_ARRAY, tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
sample(index, vec2coords) {
|
|
||||||
return `texture(_vis_data_sampler_array, vec3(${vec2coords}, _vis_data_sampler_array_indices[${index}]))`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare(shaderDataIndexToGlobalDataIndex) {
|
|
||||||
return `uniform sampler2DArray _vis_data_sampler_array;
|
|
||||||
int _vis_data_sampler_array_indices[${shaderDataIndexToGlobalDataIndex.length}] = int[${shaderDataIndexToGlobalDataIndex.length}](
|
|
||||||
${shaderDataIndexToGlobalDataIndex.join(",")}
|
|
||||||
);
|
|
||||||
|
|
||||||
vec4 osd_texture(int index, vec2 coords) {
|
|
||||||
return ${this.sample("index", "coords")};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data loader for WebGL 2.0. Must load the data to a Texture2DArray.
|
|
||||||
* The name of the texture is a constant. The order od the textures in
|
|
||||||
* the z-stacking is defined in shaderDataIndexToGlobalDataIndex.
|
|
||||||
*
|
|
||||||
* For details, please, see the implementation.
|
|
||||||
* @class OpenSeadragon.WebGLModule.Loaders.TEXTURE_2D
|
|
||||||
*/
|
|
||||||
TEXTURE_2D: class /**@lends $.WebGLModule.Loaders.TEXTURE_2D */ extends OpenSeadragon.WebGLModule.IDataLoader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creation
|
|
||||||
* @param {WebGL2RenderingContext} gl
|
|
||||||
* @param {string} webglVersion
|
|
||||||
* @param {object} options
|
|
||||||
* @param {GLuint} options.wrap texture wrap parameteri
|
|
||||||
* @param {GLuint} options.magFilter texture filter parameteri
|
|
||||||
* @param {GLuint} options.minFilter texture filter parameteri
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule.Loaders.TEXTURE_2D
|
|
||||||
* */
|
|
||||||
constructor(gl, webglVersion, options) {
|
|
||||||
super(gl, webglVersion, options);
|
|
||||||
|
|
||||||
this._samples = webglVersion === "1.0" ? "texture2D" : "texture";
|
|
||||||
|
|
||||||
this.typeLoaders["[object HTMLImageElement]"] =
|
|
||||||
this.typeLoaders["[object HTMLCanvasElement]"] = function (self, webglModule, data, width, height,
|
|
||||||
shaderDataIndexToGlobalDataIndex) {
|
|
||||||
|
|
||||||
//Avoid canvas slicing if possible
|
|
||||||
const NUM_IMAGES = Math.round(data.height / height);
|
|
||||||
if (NUM_IMAGES === 1) {
|
|
||||||
const texture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, self.minFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, self.magFilter);
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
data);
|
|
||||||
return [texture];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!self._canvas) {
|
|
||||||
self._canvas = document.createElement('canvas');
|
|
||||||
self._canvasReader = self._canvas.getContext('2d', {willReadFrequently: true});
|
|
||||||
self._canvasConverter = document.createElement('canvas');
|
|
||||||
self._canvasConverterReader = self._canvasConverter.getContext('2d',
|
|
||||||
{willReadFrequently: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
width = Math.round(width);
|
|
||||||
height = Math.round(height);
|
|
||||||
|
|
||||||
const units = [];
|
|
||||||
|
|
||||||
//we read from here
|
|
||||||
self._canvas.width = data.width;
|
|
||||||
self._canvas.height = data.height;
|
|
||||||
self._canvasReader.drawImage(data, 0, 0);
|
|
||||||
|
|
||||||
//Allowed texture size dimension only 256+ and power of two...
|
|
||||||
|
|
||||||
//it worked for arbitrary size until we begun with image arrays... is it necessary?
|
|
||||||
const IMAGE_SIZE = data.width < 256 ? 256 : Math.pow(2, Math.ceil(Math.log2(data.width)));
|
|
||||||
self._canvasConverter.width = IMAGE_SIZE;
|
|
||||||
self._canvasConverter.height = IMAGE_SIZE;
|
|
||||||
|
|
||||||
//just load all images and let shaders reference them...
|
|
||||||
for (let i = 0; i < shaderDataIndexToGlobalDataIndex.length; i++) {
|
|
||||||
if (shaderDataIndexToGlobalDataIndex[i] < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (index >= NUM_IMAGES) {
|
|
||||||
console.warn("The visualisation contains less data than layers. Skipping layers ...");
|
|
||||||
return units;
|
|
||||||
}
|
|
||||||
|
|
||||||
units.push(gl.createTexture());
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, units[index]);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, self.minFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, self.magFilter);
|
|
||||||
|
|
||||||
let pixels;
|
|
||||||
if (width !== IMAGE_SIZE || height !== IMAGE_SIZE) {
|
|
||||||
self._canvasConverterReader.drawImage(self._canvas, 0,
|
|
||||||
shaderDataIndexToGlobalDataIndex[i] * height,
|
|
||||||
width, height, 0, 0, IMAGE_SIZE, IMAGE_SIZE);
|
|
||||||
|
|
||||||
pixels = self._canvasConverterReader.getImageData(0, 0, IMAGE_SIZE, IMAGE_SIZE);
|
|
||||||
} else {
|
|
||||||
//load data
|
|
||||||
pixels = self._canvasReader.getImageData(0,
|
|
||||||
shaderDataIndexToGlobalDataIndex[i] * height, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
pixels);
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
return units;
|
|
||||||
};
|
|
||||||
|
|
||||||
//Image objects in Array, we assume image objects only todo ugly, can be array of anything
|
|
||||||
this.typeLoaders["[object Array]"] = function (self, webglModule, data, options, width, height, shaderDataIndexToGlobalDataIndex) {
|
|
||||||
const gl = webglModule.gl;
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
const NUM_IMAGES = data.length;
|
|
||||||
const units = [];
|
|
||||||
|
|
||||||
//just load all images and let shaders reference them...
|
|
||||||
for (let i = 0; i < shaderDataIndexToGlobalDataIndex.length; i++) {
|
|
||||||
if (shaderDataIndexToGlobalDataIndex[i] < 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (index >= NUM_IMAGES) {
|
|
||||||
console.warn("The visualisation contains less data than layers. Skipping layers ...");
|
|
||||||
return units;
|
|
||||||
}
|
|
||||||
|
|
||||||
//create textures
|
|
||||||
units.push(gl.createTexture());
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, units[index]);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, self.wrap);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, self.minFilter);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, self.magFilter);
|
|
||||||
//do not check the image size, we render what wwe
|
|
||||||
gl.texImage2D(gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
data[index++]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return units;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
unloadTexture(renderer, id, options) {
|
|
||||||
for (let textureUnit of options) {
|
|
||||||
renderer.gl.deleteTexture(textureUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supportsWebglVersion(version) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
programUsed(renderer, specification, id, program, gl) {
|
|
||||||
const units = this.getLoaded(id);
|
|
||||||
for (let i = 0; i < units.length; i++) {
|
|
||||||
let textureUnit = units[i];
|
|
||||||
let bindConst = `TEXTURE${i}`;
|
|
||||||
gl.activeTexture(gl[bindConst]);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, textureUnit);
|
|
||||||
let location = gl.getUniformLocation(program, `vis_data_sampler_${i}`);
|
|
||||||
gl.uniform1i(location, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sample(index, vec2coords) {
|
|
||||||
return `${this._samples}(vis_data_sampler_${index}, ${vec2coords})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare(shaderDataIndexToGlobalDataIndex) {
|
|
||||||
let samplers = 'uniform vec2 sampler_size;';
|
|
||||||
for (let i = 0; i < shaderDataIndexToGlobalDataIndex.length; i++) {
|
|
||||||
if (shaderDataIndexToGlobalDataIndex[i] === -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
samplers += `uniform sampler2D vis_data_sampler_${i};`;
|
|
||||||
}
|
|
||||||
return samplers;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
})(OpenSeadragon);
|
|
@ -1,289 +0,0 @@
|
|||||||
|
|
||||||
/*
|
|
||||||
* OpenSeadragon - WebGLDrawer
|
|
||||||
*
|
|
||||||
* 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( $ ){
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class WebGLDrawer
|
|
||||||
* @memberof OpenSeadragon
|
|
||||||
* @classdesc Default implementation of WebGLDrawer for an {@link OpenSeadragon.Viewer}.
|
|
||||||
* @param {Object} options - Options for this Drawer.
|
|
||||||
* @param {OpenSeadragon.Viewer} options.viewer - The Viewer that owns this Drawer.
|
|
||||||
* @param {OpenSeadragon.Viewport} options.viewport - Reference to Viewer viewport.
|
|
||||||
* @param {Element} options.element - Parent element.
|
|
||||||
* @param {Number} [options.debugGridColor] - See debugGridColor in {@link OpenSeadragon.Options} for details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
$.WebGL = class WebGL extends OpenSeadragon.DrawerBase {
|
|
||||||
constructor(options){
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
this.destroyed = false;
|
|
||||||
// Add listeners for events that require modifying the scene or camera
|
|
||||||
this.viewer.addHandler("tile-ready", this._tileReadyHandler.bind(this));
|
|
||||||
this.viewer.addHandler("image-unloaded", this.renderer.freeData.bind(this.renderer));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API required by all Drawer implementations
|
|
||||||
/**
|
|
||||||
* Clean up the renderer, removing all resources
|
|
||||||
*/
|
|
||||||
destroy(){
|
|
||||||
if(this.destroyed){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//todo
|
|
||||||
this.destroyed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API required by all Drawer implementations
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns true if the drawer supports rotation
|
|
||||||
*/
|
|
||||||
canRotate(){
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API required by all Drawer implementations
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Boolean} returns true if canvas and webgl are supported
|
|
||||||
*/
|
|
||||||
static isSupported(){
|
|
||||||
return true; //todo
|
|
||||||
}
|
|
||||||
|
|
||||||
getType() {
|
|
||||||
return 'universal_webgl';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create the HTML element (canvas in this case) that the image will be drawn into
|
|
||||||
* @returns {Element} the canvas to draw into
|
|
||||||
*/
|
|
||||||
createDrawingElement(){
|
|
||||||
|
|
||||||
const engine = new $.WebGLModule($.extend(this.options, {
|
|
||||||
uniqueId: "openseadragon",
|
|
||||||
}));
|
|
||||||
|
|
||||||
engine.addRenderingSpecifications({
|
|
||||||
shaders: {
|
|
||||||
renderShader: {
|
|
||||||
type: "identity",
|
|
||||||
dataReferences: [0],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.prepare();
|
|
||||||
|
|
||||||
const size = this._calculateCanvasSize();
|
|
||||||
engine.init(size.x, size.y);
|
|
||||||
this.viewer.addHandler("resize", this._resizeRenderer.bind(this));
|
|
||||||
this.renderer = engine;
|
|
||||||
return engine.canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Array} tiledImages Array of TiledImage objects to draw
|
|
||||||
*/
|
|
||||||
draw(tiledImages){
|
|
||||||
let viewport = {
|
|
||||||
bounds: this.viewport.getBoundsNoRotate(true),
|
|
||||||
center: this.viewport.getCenter(true),
|
|
||||||
rotation: this.viewport.getRotation(true) * Math.PI / 180,
|
|
||||||
zoom: this.viewport.getZoom(true)
|
|
||||||
};
|
|
||||||
|
|
||||||
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 viewMatrix = scaleMatrix.multiply(rotMatrix).multiply(posMatrix);
|
|
||||||
|
|
||||||
this.renderer.clear();
|
|
||||||
this.renderer.setDataBlendingEnabled(false);
|
|
||||||
|
|
||||||
//iterate over tiled images and draw each one using a two-pass rendering pipeline if needed
|
|
||||||
|
|
||||||
let drawn = 0;
|
|
||||||
|
|
||||||
for (const tiledImage of tiledImages) {
|
|
||||||
let tilesToDraw = tiledImage.getTilesToDraw();
|
|
||||||
|
|
||||||
if (tilesToDraw.length === 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawn === 1) {
|
|
||||||
this.renderer.setDataBlendingEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let overallMatrix = viewMatrix;
|
|
||||||
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 imageCenter = tiledImage.getBoundsNoRotate(true).getCenter();
|
|
||||||
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);
|
|
||||||
overallMatrix = viewMatrix.multiply(localMatrix);
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo better access to the rendering context
|
|
||||||
const shader = this.renderer.specification(0).shaders.renderShader._renderContext;
|
|
||||||
|
|
||||||
// iterate over tiles and add data for each one to the buffers
|
|
||||||
for (let tileIndex = 0; tileIndex < tilesToDraw.length; tileIndex++){
|
|
||||||
const tile = tilesToDraw[tileIndex].tile;
|
|
||||||
const matrix = this._getTileMatrix(tile, tiledImage, overallMatrix);
|
|
||||||
shader.opacity.set(tile.opacity * tiledImage.opacity);
|
|
||||||
|
|
||||||
//todo pixelSize value (not yet memoized)
|
|
||||||
this.renderer.processData(tile.cacheKey, {
|
|
||||||
transform: matrix, zoom:
|
|
||||||
viewport.zoom,
|
|
||||||
pixelSize: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fire tiled-image-drawn event.
|
|
||||||
// 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
|
|
||||||
* 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),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
drawn++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the context2d imageSmoothingEnabled parameter
|
|
||||||
* @param {Boolean} enabled
|
|
||||||
*/
|
|
||||||
setImageSmoothingEnabled(enabled){
|
|
||||||
//todo
|
|
||||||
// this._clippingContext.imageSmoothingEnabled = enabled;
|
|
||||||
// this._outputContext.imageSmoothingEnabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private
|
|
||||||
_getTileMatrix(tile, tiledImage, viewMatrix){
|
|
||||||
// compute offsets that account for tile overlap; needed for calculating the transform matrix appropriately
|
|
||||||
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
|
|
||||||
let x = tile.positionedBounds.x + (tile.x === 0 ? 0 : xOffset);
|
|
||||||
let y = tile.positionedBounds.y + (tile.y === 0 ? 0 : yOffset);
|
|
||||||
let right = tile.positionedBounds.x + tile.positionedBounds.width - (tile.isRightMost ? 0 : xOffset);
|
|
||||||
let bottom = tile.positionedBounds.y + tile.positionedBounds.height - (tile.isBottomMost ? 0 : yOffset);
|
|
||||||
let w = right - x;
|
|
||||||
let h = bottom - y;
|
|
||||||
|
|
||||||
let matrix = new $.Mat3([
|
|
||||||
w, 0, 0,
|
|
||||||
0, h, 0,
|
|
||||||
x, y, 1,
|
|
||||||
]);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// update the view matrix to account for this image's rotation
|
|
||||||
let localMatrix = t1.multiply($.Mat3.makeScaling(-1, 1)).multiply(t2);
|
|
||||||
matrix = matrix.multiply(localMatrix);
|
|
||||||
}
|
|
||||||
let overallMatrix = viewMatrix.multiply(matrix);
|
|
||||||
return overallMatrix.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
_resizeRenderer(){
|
|
||||||
const size = this._calculateCanvasSize();
|
|
||||||
this.renderer.setDimensions(0, 0, size.x, size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
_imageUnloadedHandler(event){
|
|
||||||
this.renderer.freeData(event.tile.cacheKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
_tileReadyHandler(event){
|
|
||||||
//todo tile overlap
|
|
||||||
let tile = event.tile;
|
|
||||||
//todo fix cache system and then this line
|
|
||||||
//access by default raw tile data, and only access canvas if not cache set
|
|
||||||
let data = tile.cacheImageRecord ? tile.cacheImageRecord.getData() : tile.getCanvasContext().canvas;
|
|
||||||
this.renderer.loadData(tile.cacheKey, data, tile.sourceBounds.width, tile.sourceBounds.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
_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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}( OpenSeadragon ));
|
|
@ -1,40 +0,0 @@
|
|||||||
(function($) {
|
|
||||||
/**
|
|
||||||
* Identity shader
|
|
||||||
*
|
|
||||||
* data reference must contain one index to the data to render using identity
|
|
||||||
*/
|
|
||||||
$.WebGLModule.IdentityLayer = class extends $.WebGLModule.ShaderLayer {
|
|
||||||
|
|
||||||
static type() {
|
|
||||||
return "identity";
|
|
||||||
}
|
|
||||||
|
|
||||||
static name() {
|
|
||||||
return "Identity";
|
|
||||||
}
|
|
||||||
|
|
||||||
static description() {
|
|
||||||
return "shows the data AS-IS";
|
|
||||||
}
|
|
||||||
|
|
||||||
static sources() {
|
|
||||||
return [{
|
|
||||||
acceptsChannelCount: (x) => x === 4,
|
|
||||||
description: "4d texture to render AS-IS"
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShaderExecution() {
|
|
||||||
return `return ${this.sampleChannel("tile_texture_coords")};`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//todo why cannot be inside object :/
|
|
||||||
$.WebGLModule.IdentityLayer.defaultControls["use_channel0"] = {
|
|
||||||
required: "rgba"
|
|
||||||
};
|
|
||||||
|
|
||||||
$.WebGLModule.ShaderMediator.registerLayer($.WebGLModule.IdentityLayer);
|
|
||||||
|
|
||||||
})(OpenSeadragon);
|
|
@ -1,982 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapping the funcionality of WebGL to be suitable for tile processing and rendering.
|
|
||||||
* Written by Aiosa
|
|
||||||
* @class OpenSeadragon.WebGLModule
|
|
||||||
* @memberOf OpenSeadragon
|
|
||||||
*/
|
|
||||||
$.WebGLModule = class extends $.EventSource {
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* name: string,
|
|
||||||
* lossless: boolean,
|
|
||||||
* shaders: Object.<string, OpenSeadragon.WebGLModule.ShaderLayerConfig>
|
|
||||||
* }} OpenSeadragon.WebGLModule.RenderingConfig
|
|
||||||
*
|
|
||||||
* //use_channel[X] name
|
|
||||||
* @template {Object<string,any>} TUseChannel
|
|
||||||
* //use_[fitler_name]
|
|
||||||
* @template {Object<string,number>} TUseFilter
|
|
||||||
* @template {Object<string,(string|any)>} TIControlConfig
|
|
||||||
* @typedef OpenSeadragon.WebGLModule.ShaderLayerParams
|
|
||||||
* @type {{TUseChannel,TUseFilter,TIControlConfig}}
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* name: string,
|
|
||||||
* type: string,
|
|
||||||
* visible: boolean,
|
|
||||||
* dataReferences: number[],
|
|
||||||
* params: OpenSeadragon.WebGLModule.ShaderLayerParams
|
|
||||||
* }} OpenSeadragon.WebGLModule.ShaderLayerConfig
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @typedef OpenSeadragon.WebGLModule.UIControlsRenderer
|
|
||||||
* @type function
|
|
||||||
* @param {string} title
|
|
||||||
* @param {string} html
|
|
||||||
* @param {string} dataId
|
|
||||||
* @param {boolean} isVisible
|
|
||||||
* @param {OpenSeadragon.WebGLModule.ShaderLayer} layer
|
|
||||||
* @param {boolean} wasErrorWhenLoading
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} incomingOptions
|
|
||||||
* @param {string} incomingOptions.htmlControlsId: where to render html controls,
|
|
||||||
* @param {string} incomingOptions.webGlPreferredVersion prefered WebGL version, for now "1.0" or "2.0"
|
|
||||||
* @param {OpenSeadragon.WebGLModule.UIControlsRenderer} incomingOptions.htmlShaderPartHeader function that generates particular layer HTML
|
|
||||||
* @param {boolean} incomingOptions.debug debug mode default false
|
|
||||||
* @param {function} incomingOptions.ready function called when ready
|
|
||||||
* @param {function} incomingOptions.resetCallback function called when user input changed, e.g. changed output of the current rendering
|
|
||||||
* @param {function} incomingOptions.visualisationInUse function called when a specification is initialized and run
|
|
||||||
* @param {function} incomingOptions.visualisationChanged function called when a visualization swap is performed:
|
|
||||||
* signature f({Visualization} oldVisualisation,{Visualization} newVisualisation)
|
|
||||||
* @constructor
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
constructor(incomingOptions) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
///////////// Default values overrideable from incomingOptions /////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
this.uniqueId = "";
|
|
||||||
|
|
||||||
//todo events instead
|
|
||||||
this.ready = function() { };
|
|
||||||
this.htmlControlsId = null;
|
|
||||||
this.webGlPreferredVersion = "2.0";
|
|
||||||
this.htmlShaderPartHeader = function(title, html, dataId, isVisible, layer, isControllable = true) {
|
|
||||||
return `<div class="configurable-border"><div class="shader-part-name">${title}</div>${html}</div>`;
|
|
||||||
};
|
|
||||||
this.resetCallback = function() { };
|
|
||||||
//called once a visualisation is compiled and linked (might not happen)
|
|
||||||
this.visualisationReady = function(i, visualisation) { };
|
|
||||||
//called once a visualisation is switched to (including first run)
|
|
||||||
this.visualisationInUse = function(visualisation) { };
|
|
||||||
this.visualisationChanged = function(oldVis, newVis) { };
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Debug mode.
|
|
||||||
* @member {boolean}
|
|
||||||
*/
|
|
||||||
this.debug = false;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
///////////// Incoming Values ///////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Assign from incoming terms
|
|
||||||
for (let key in incomingOptions) {
|
|
||||||
if (incomingOptions[key]) {
|
|
||||||
this[key] = incomingOptions[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.constructor.idPattern.test(this.uniqueId)) {
|
|
||||||
throw "$.WebGLModule: invalid ID! Id can contain only letters, numbers and underscore. ID: " + this.uniqueId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Current rendering context
|
|
||||||
* @member {OpenSeadragon.WebGLModule.WebGLImplementation}
|
|
||||||
*/
|
|
||||||
this.webglContext = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebGL context
|
|
||||||
* @member {WebGLRenderingContextBase}
|
|
||||||
*/
|
|
||||||
this.gl = null;
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
///////////// Internals /////////////////////////////////////////////////////////
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
this.reset();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
for (let version of [this.webGlPreferredVersion, "2.0", "1.0"]) {
|
|
||||||
const Context = $.WebGLModule.determineContext(version);
|
|
||||||
let glContext = Context && Context.create(canvas);
|
|
||||||
|
|
||||||
if (glContext) {
|
|
||||||
this.gl = glContext;
|
|
||||||
const contextOpts = incomingOptions[version] || {};
|
|
||||||
const readGlProp = function(prop, defaultValue) {
|
|
||||||
return glContext[contextOpts[prop] || defaultValue] || glContext[defaultValue];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {object} options
|
|
||||||
* @param {string} options.wrap texture wrap parameteri
|
|
||||||
* @param {string} options.magFilter texture filter parameteri
|
|
||||||
* @param {string} options.minFilter texture filter parameteri
|
|
||||||
* @param {string|WebGLModule.IDataLoader} options.dataLoader class name or implementation of a given loader
|
|
||||||
*/
|
|
||||||
const options = {
|
|
||||||
wrap: readGlProp("wrap", "MIRRORED_REPEAT"),
|
|
||||||
magFilter: readGlProp("magFilter", "LINEAR"),
|
|
||||||
minFilter: readGlProp("minFilter", "NEAREST"),
|
|
||||||
dataLoader: contextOpts.dataLoader || "TEXTURE_2D"
|
|
||||||
};
|
|
||||||
this.webglContext = new Context(this, glContext, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
/**
|
|
||||||
* @event fatal-error
|
|
||||||
*/
|
|
||||||
this.raiseEvent('fatal-error', {message: "Unable to initialize the WebGL renderer.",
|
|
||||||
details: e});
|
|
||||||
console.error(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(`WebGL ${this.webglContext.getVersion()} Rendering module (ID ${this.uniqueId})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the engine to the initial state
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
this._unloadCurrentProgram();
|
|
||||||
this._programConfigurations = [];
|
|
||||||
this._dataSources = [];
|
|
||||||
this._shaderDataIndexToGlobalDataIndex = [];
|
|
||||||
this._origDataSources = [];
|
|
||||||
this._programs = {};
|
|
||||||
this._program = -1;
|
|
||||||
this._prepared = false;
|
|
||||||
this.running = false;
|
|
||||||
this._initialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if prepare() was called.
|
|
||||||
* @return {boolean}
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
get isPrepared() {
|
|
||||||
return this._prepared;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebGL target canvas
|
|
||||||
* @return {HTMLCanvasElement}
|
|
||||||
*/
|
|
||||||
get canvas() {
|
|
||||||
return this.gl.canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WebGL active program
|
|
||||||
* @return {WebGLProgram}
|
|
||||||
*/
|
|
||||||
get program() {
|
|
||||||
return this._programs[this._program];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if init() was called.
|
|
||||||
* @return {boolean}
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
get isInitialized() {
|
|
||||||
return this._initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the dimensions, useful for borders, used by openSeadragonGL
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
setDimensions(x, y, width, height) {
|
|
||||||
if (width === this.width && height === this.height) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.gl.canvas.width = width;
|
|
||||||
this.gl.canvas.height = height;
|
|
||||||
this.gl.viewport(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set program shaders. Vertex shader is set by default a square.
|
|
||||||
* @param {RenderingConfig} configurations - objects that define the what to render (see Readme)
|
|
||||||
* @return {boolean} true if loaded successfully
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
addRenderingSpecifications(...configurations) {
|
|
||||||
if (this._prepared) {
|
|
||||||
console.error("New specification cannot be introduced after the visualiser was prepared.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (let config of configurations) {
|
|
||||||
if (!config.shaders) {
|
|
||||||
console.warn("Invalid visualization: no shaders defined", config);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
for (let sid in config.shaders) {
|
|
||||||
const shader = config.shaders[sid];
|
|
||||||
if (!shader.params) {
|
|
||||||
shader.params = {};
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 0) {
|
|
||||||
console.warn("Invalid configualization: no shader configuration present!", config);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this._programConfigurations.push(config);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs a callback on each specification
|
|
||||||
* @param {function} call callback to perform on each specification (its object given as the only parameter)
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
foreachRenderingSpecification(call) {
|
|
||||||
this._programConfigurations.forEach(vis => call(vis));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rebuild specification and update scene
|
|
||||||
* @param {string[]|undefined} order of shaders, ID's of data as defined in setup JSON, last element
|
|
||||||
* is rendered last (top)
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
rebuildSpecification(order = undefined) {
|
|
||||||
let vis = this._programConfigurations[this._program];
|
|
||||||
|
|
||||||
if (order) {
|
|
||||||
vis.order = order;
|
|
||||||
}
|
|
||||||
this._unloadCurrentProgram();
|
|
||||||
this._specificationToProgram(vis, this._program);
|
|
||||||
this._forceSwitchShader(this._program);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get currently used specification
|
|
||||||
* @return {object} current specification
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
specification(index) {
|
|
||||||
return this._programConfigurations[Math.min(index, this._programConfigurations.length - 1)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get currently used specification ilayer.params,ndex
|
|
||||||
* @return {number} index of the current specification
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
currentSpecificationIndex() {
|
|
||||||
return this._program;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switch to program at index: this is the index (order) in which
|
|
||||||
* setShaders(...) was called. If you want to switch to shader that
|
|
||||||
* has been set with second setShaders(...) call, pass i=1.
|
|
||||||
* @param {Number} i program index or null if you wish to re-initialize the current one
|
|
||||||
* @instance
|
|
||||||
* @memberOf OpenSeadragon.WebGLModule
|
|
||||||
*/
|
|
||||||
useSpecification(i) {
|
|
||||||
if (!this._initialized) {
|
|
||||||
console.warn("$.WebGLModule::useSpecification(): not initialized.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._program === i) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let oldIndex = this._program;
|
|
||||||
this._forceSwitchShader(i);
|
|
||||||
this.visualisationChanged(this._programConfigurations[oldIndex], this._programConfigurations[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a list of image pyramids used to compose the current active specification
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
getSources() {
|
|
||||||
//return this._programConfigurations[this._program].dziExtendedUrl;
|
|
||||||
return this._dataSources;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported: 'wrap', 'minFilter', 'magFilter'
|
|
||||||
* @param {string} name WebGL name of the parameter
|
|
||||||
* @param {GLuint} value
|
|
||||||
*/
|
|
||||||
setTextureParam(name, value) {
|
|
||||||
this.webglContext.texture.setTextureParam(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param id
|
|
||||||
* @param data
|
|
||||||
* @param width
|
|
||||||
* @param height
|
|
||||||
*/
|
|
||||||
loadData(id, data, width, height) {
|
|
||||||
this.webglContext.texture.load(this, id, data, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders data using WebGL
|
|
||||||
* @param {string} id used in loadImage()
|
|
||||||
*
|
|
||||||
* @param {object} tileOpts
|
|
||||||
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
|
||||||
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
|
||||||
* @param {OpenSeadragon.Mat3} tileOpts.transform position of the rendered tile
|
|
||||||
*
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
processData(id, tileOpts) {
|
|
||||||
this.webglContext.programUsed(
|
|
||||||
this.program,
|
|
||||||
this._programConfigurations[this._program],
|
|
||||||
id,
|
|
||||||
tileOpts
|
|
||||||
);
|
|
||||||
|
|
||||||
// if (this.debug) {
|
|
||||||
// //todo
|
|
||||||
// this._renderDebugIO(data, result);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
freeData(id) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the output canvas
|
|
||||||
*/
|
|
||||||
clear() {
|
|
||||||
//todo: necessary?
|
|
||||||
this.gl.clearColor(0, 0, 0, 0);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the webgl module renders UI
|
|
||||||
* @return {boolean|boolean}
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
supportsHtmlControls() {
|
|
||||||
return typeof this.htmlControlsId === "string" && this.htmlControlsId.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute call on each visualization layer with no errors
|
|
||||||
* @param {object} vis current specification setup context
|
|
||||||
* @param {function} callback call to execute
|
|
||||||
* @param {function} onFail handle exception during execition
|
|
||||||
* @return {boolean} true if no exception occured
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
static eachValidShaderLayer(vis, callback,
|
|
||||||
onFail = (layer, e) => {
|
|
||||||
layer.error = e.message;
|
|
||||||
console.error(e);
|
|
||||||
}) {
|
|
||||||
let shaders = vis.shaders;
|
|
||||||
let noError = true;
|
|
||||||
for (let key in shaders) {
|
|
||||||
let shader = shaders[key];
|
|
||||||
|
|
||||||
if (shader && !shader.error) {
|
|
||||||
try {
|
|
||||||
callback(shader);
|
|
||||||
} catch (e) {
|
|
||||||
if (!onFail) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
onFail(shader, e);
|
|
||||||
noError = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute call on each _visible_ specification layer with no errors.
|
|
||||||
* Visible is subset of valid.
|
|
||||||
* @param {object} vis current specification setup context
|
|
||||||
* @param {function} callback call to execute
|
|
||||||
* @param {function} onFail handle exception during execition
|
|
||||||
* @return {boolean} true if no exception occured
|
|
||||||
* @instance
|
|
||||||
* @memberOf WebGLModule
|
|
||||||
*/
|
|
||||||
static eachVisibleShaderLayer(vis, callback,
|
|
||||||
onFail = (layer, e) => {
|
|
||||||
layer.error = e.message;
|
|
||||||
console.error(e);
|
|
||||||
}) {
|
|
||||||
|
|
||||||
let shaders = vis.shaders;
|
|
||||||
let noError = true;
|
|
||||||
for (let key in shaders) {
|
|
||||||
//rendering == true means no error
|
|
||||||
let shader = shaders[key];
|
|
||||||
if (shader && shader.rendering) {
|
|
||||||
try {
|
|
||||||
callback(shader);
|
|
||||||
} catch (e) {
|
|
||||||
if (!onFail) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
onFail(shader, e);
|
|
||||||
noError = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return noError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
//// YOU PROBABLY WANT TO READ FUNCTIONS BELOW SO YOU KNOW HOW TO SET UP YOUR SHADERS
|
|
||||||
//// BUT YOU SHOULD NOT CALL THEM DIRECTLY
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current program, reset if invalid
|
|
||||||
* @return {number} program index
|
|
||||||
*/
|
|
||||||
getCurrentProgramIndex() {
|
|
||||||
if (this._program < 0 || this._program >= this._programConfigurations.length) {
|
|
||||||
this._program = 0;
|
|
||||||
}
|
|
||||||
return this._program;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to JSON.stringify replacer
|
|
||||||
* @param key key to the value
|
|
||||||
* @param value value to be exported
|
|
||||||
* @return {*} value if key passes exportable condition, undefined otherwise
|
|
||||||
*/
|
|
||||||
static jsonReplacer(key, value) {
|
|
||||||
return key.startsWith("_") || ["eventSource"].includes(key) ? undefined : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For easy initialization, do both in once call.
|
|
||||||
* For separate initialization (prepare|init), see functions below.
|
|
||||||
* @param {string[]|undefined} dataSources a list of data identifiers available to the specifications
|
|
||||||
* - specification configurations should not reference data not present in this array
|
|
||||||
* - the module gives you current list of required subset of this list for particular active visualization goal
|
|
||||||
* @param width initialization width
|
|
||||||
* @param height initialization height
|
|
||||||
*/
|
|
||||||
prepareAndInit(dataSources = undefined, width = 1, height = 1) {
|
|
||||||
this.prepare(dataSources);
|
|
||||||
this.init(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares the WebGL wrapper for being initialized. It is separated from
|
|
||||||
* initialization as this must be finished before OSD is ready (we must be ready to draw when the data comes).
|
|
||||||
* The idea is to open the protocol for OSD in onPrepared.
|
|
||||||
* Shaders are fetched from `specification.url` parameter.
|
|
||||||
*
|
|
||||||
* @param {string[]|undefined} dataSources id's of data such that server can understand which image to send (usually paths)
|
|
||||||
* @param {number} visIndex index of the initial specification
|
|
||||||
*/
|
|
||||||
prepare(dataSources = undefined, visIndex = 0) {
|
|
||||||
if (this._prepared) {
|
|
||||||
console.error("Already prepared!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._programConfigurations.length < 1) {
|
|
||||||
console.error("No specification specified!");
|
|
||||||
/**
|
|
||||||
* @event fatal-error
|
|
||||||
*/
|
|
||||||
this.raiseEvent('fatal-error', {message: "No specification specified!",
|
|
||||||
details: "::prepare() called with no specification set."});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._origDataSources = dataSources || [];
|
|
||||||
this._program = visIndex;
|
|
||||||
|
|
||||||
this._prepared = true;
|
|
||||||
this.getCurrentProgramIndex(); //resets index
|
|
||||||
this._specificationToProgram(this._programConfigurations[this._program], this._program);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialization. It is separated from preparation as this actually initiates the rendering,
|
|
||||||
* sometimes this can happen only when other things are ready. Must be performed after
|
|
||||||
* all the prepare() strategy finished: e.g. as onPrepared. Or use prepareAndInit();
|
|
||||||
*
|
|
||||||
* @param {int} width width of the first tile going to be drawn
|
|
||||||
* @param {int} height height of the first tile going to be drawn
|
|
||||||
*/
|
|
||||||
init(width = 1, height = 1) {
|
|
||||||
if (!this._prepared) {
|
|
||||||
console.error("The viaGL was not yet prepared. Call prepare() before init()!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this._initialized) {
|
|
||||||
console.error("Already initialized!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._initialized = true;
|
|
||||||
this.setDimensions(width, height);
|
|
||||||
|
|
||||||
|
|
||||||
//todo rotate anticlockwise to cull backfaces
|
|
||||||
this.gl.enable(this.gl.CULL_FACE);
|
|
||||||
this.gl.cullFace(this.gl.FRONT);
|
|
||||||
|
|
||||||
this.running = true;
|
|
||||||
|
|
||||||
this._forceSwitchShader(null);
|
|
||||||
this.ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
setDataBlendingEnabled(enabled) {
|
|
||||||
if (enabled) {
|
|
||||||
this.gl.enable(this.gl.BLEND);
|
|
||||||
this.gl.blendEquation(this.gl.FUNC_ADD);
|
|
||||||
this.gl.blendFuncSeparate(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA, this.gl.ONE, this.gl.ONE);
|
|
||||||
} else {
|
|
||||||
this.gl.disable(this.gl.BLEND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported are two modes: show and blend
|
|
||||||
* show is the default option, stacking layers by generalized alpha blending
|
|
||||||
* blend is a custom alternative, default is a mask (remove background where foreground.a > 0.001)
|
|
||||||
*
|
|
||||||
* vec4 my_blend(vec4 foreground, vec4 background) {
|
|
||||||
* <<code>> //here goes your blending code
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param code GLSL code to blend - must return vec4() and can use
|
|
||||||
* two variables: background, foreground
|
|
||||||
*/
|
|
||||||
setLayerBlending(code) {
|
|
||||||
this.webglContext.setBlendEquation(code);
|
|
||||||
this.rebuildSpecification();
|
|
||||||
}
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
///////////// YOU PROBABLY DON'T WANT TO READ/CHANGE FUNCTIONS BELOW
|
|
||||||
//////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forward glLoaded event to the active layer
|
|
||||||
* @param gl
|
|
||||||
* @param program
|
|
||||||
* @param vis
|
|
||||||
*/
|
|
||||||
glLoaded(gl, program, vis) {
|
|
||||||
$.WebGLModule.eachVisibleShaderLayer(vis, layer => layer._renderContext.glLoaded(program, gl));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forward glDrawing event to the active layer
|
|
||||||
* @param gl
|
|
||||||
* @param program
|
|
||||||
* @param vis
|
|
||||||
* @param bounds
|
|
||||||
*/
|
|
||||||
glDrawing(gl, program, vis, bounds) {
|
|
||||||
$.WebGLModule.eachVisibleShaderLayer(vis, layer => layer._renderContext.glDrawing(program, gl));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force switch shader (program), will reset even if the specified
|
|
||||||
* program is currently active, good if you need 'gl-loaded' to be
|
|
||||||
* invoked (e.g. some uniform variables changed)
|
|
||||||
* @param {Number} i program index or null if you wish to re-initialize the current one
|
|
||||||
* @param _reset
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_forceSwitchShader(i, _reset = true) {
|
|
||||||
if (isNaN(i) || i === null || i === undefined) {
|
|
||||||
i = this._program;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i >= this._programConfigurations.length) {
|
|
||||||
console.error("Invalid specification index ", i, "trying to use index 0...");
|
|
||||||
if (i === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let target = this._programConfigurations[i];
|
|
||||||
if (!this._programs[i]) {
|
|
||||||
this._specificationToProgram(target, i);
|
|
||||||
} else if (i !== this._program) {
|
|
||||||
this._updateRequiredDataSources(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._program = i;
|
|
||||||
if (target.error) {
|
|
||||||
if (this.supportsHtmlControls()) {
|
|
||||||
this._loadHtml(i, this._program);
|
|
||||||
}
|
|
||||||
this._loadScript(i, this._program);
|
|
||||||
this.running = false;
|
|
||||||
if (this._programConfigurations.length < 2) {
|
|
||||||
/**
|
|
||||||
* @event fatal-error
|
|
||||||
*/
|
|
||||||
this.raiseEvent('fatal-error', {message: "The only rendering specification left is invalid!", target: target});
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* @event error
|
|
||||||
*/
|
|
||||||
this.raiseEvent('error', {message: "Currently chosen rendering specification is not valid!", target: target});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.running = true;
|
|
||||||
if (this.supportsHtmlControls()) {
|
|
||||||
this._loadHtml(i, this._program);
|
|
||||||
}
|
|
||||||
this._loadDebugInfo();
|
|
||||||
if (!this._loadScript(i, this._program)) {
|
|
||||||
if (!_reset) {
|
|
||||||
throw "Could not build visualization";
|
|
||||||
}
|
|
||||||
this._forceSwitchShader(i, false); //force reset in errors
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.webglContext.programLoaded(this._programs[i], target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_unloadCurrentProgram() {
|
|
||||||
let program = this._programs && this._programs[this._program];
|
|
||||||
if (program) {
|
|
||||||
//must remove before attaching new
|
|
||||||
this._detachShader(program, "VERTEX_SHADER");
|
|
||||||
this._detachShader(program, "FRAGMENT_SHADER");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadHtml(visId) {
|
|
||||||
let htmlControls = document.getElementById(this.htmlControlsId);
|
|
||||||
htmlControls.innerHTML = this._programConfigurations[visId]._built["html"];
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadScript(visId) {
|
|
||||||
return $.WebGLModule.eachValidShaderLayer(this._programConfigurations[visId], layer => layer._renderContext.init());
|
|
||||||
}
|
|
||||||
|
|
||||||
_getDebugInfoPanel() {
|
|
||||||
return `<div id="test-inner-${this.uniqueId}-webgl">
|
|
||||||
<b>WebGL Processing I/O (debug mode)</b>
|
|
||||||
<div id="test-${this.uniqueId}-webgl-log"></div>
|
|
||||||
Input: <br><div style="border: 1px solid;display: inline-block; overflow: auto;" id='test-${this.uniqueId}-webgl-input'>No input.</div><br>
|
|
||||||
Output:<br><div style="border: 1px solid;display: inline-block; overflow: auto;" id="test-${this.uniqueId}-webgl-output">No output.</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadDebugInfo() {
|
|
||||||
if (!this.debug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let container = document.getElementById(`test-${this.uniqueId}-webgl`);
|
|
||||||
if (!container) {
|
|
||||||
if (!this.htmlControlsId) {
|
|
||||||
document.body.innerHTML += `<div id="test-${this.uniqueId}-webgl" style="position:absolute; top:0; right:0; width: 250px">${this._getDebugInfoPanel()}</div>`;
|
|
||||||
} else {
|
|
||||||
//safe as we do this before handlers are attached
|
|
||||||
document.getElementById(this.htmlControlsId).parentElement.innerHTML += `<div id="test-${this.uniqueId}-webgl" style="width: 100%;">${this._getDebugInfoPanel()}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderDebugIO(inputData, outputData) {
|
|
||||||
let input = document.getElementById(`test-${this.uniqueId}-webgl-input`);
|
|
||||||
let output = document.getElementById(`test-${this.uniqueId}-webgl-output`);
|
|
||||||
|
|
||||||
input.innerHTML = "";
|
|
||||||
input.append($.WebGLModule.Loaders.dataAsHtmlElement(inputData));
|
|
||||||
|
|
||||||
if (outputData) {
|
|
||||||
output.innerHTML = "";
|
|
||||||
if (!this._ocanvas) {
|
|
||||||
this._ocanvas = document.createElement("canvas");
|
|
||||||
}
|
|
||||||
this._ocanvas.width = outputData.width;
|
|
||||||
this._ocanvas.height = outputData.height;
|
|
||||||
let octx = this._ocanvas.getContext('2d');
|
|
||||||
octx.drawImage(outputData, 0, 0);
|
|
||||||
output.append(this._ocanvas);
|
|
||||||
} else {
|
|
||||||
output.innerHTML = "No output!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildFailed(specification, error) {
|
|
||||||
console.error(error);
|
|
||||||
specification.error = "Failed to compose this specification.";
|
|
||||||
specification.desc = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
_buildSpecification(order, specification) {
|
|
||||||
try {
|
|
||||||
let data = this.webglContext.compileSpecification(order, specification,
|
|
||||||
this._shaderDataIndexToGlobalDataIndex, this.supportsHtmlControls());
|
|
||||||
|
|
||||||
if (data.usableShaders < 1) {
|
|
||||||
this._buildFailed(specification, `Empty specification: no valid specification has been specified.
|
|
||||||
<br><b>Specification setup:</b></br> <code>${JSON.stringify(specification, $.WebGLModule.jsonReplacer)}</code>
|
|
||||||
<br><b>Dynamic shader data:</b></br><code>${JSON.stringify(specification.data)}</code>`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
data.dziExtendedUrl = data.dataUrls.join(",");
|
|
||||||
specification._built = data;
|
|
||||||
|
|
||||||
//preventive
|
|
||||||
delete specification.error;
|
|
||||||
delete specification.desc;
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
this._buildFailed(specification, error);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
_detachShader(program, type) {
|
|
||||||
let shader = program[type];
|
|
||||||
if (shader) {
|
|
||||||
this.gl.detachShader(program, shader);
|
|
||||||
this.gl.deleteShader(shader);
|
|
||||||
program[type] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_specificationToProgram(vis, idx) {
|
|
||||||
if (!vis._built) {
|
|
||||||
vis._built = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
this._updateRequiredDataSources(vis);
|
|
||||||
this._processSpecification(vis, idx);
|
|
||||||
return idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
_initializeShaderFactory(ShaderFactoryClass, layer, idx) {
|
|
||||||
if (!ShaderFactoryClass) {
|
|
||||||
layer.error = "Unknown layer type.";
|
|
||||||
layer.desc = `The layer type '${layer.type}' has no associated factory. Missing in 'shaderSources'.`;
|
|
||||||
console.warn("Skipping layer " + layer.name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
layer._index = idx;
|
|
||||||
layer.visible = layer.visible === undefined ? true : layer.visible;
|
|
||||||
layer._renderContext = new ShaderFactoryClass(`${this.uniqueId}${idx}`, layer.params || {}, {
|
|
||||||
layer: layer,
|
|
||||||
webgl: this.webglContext,
|
|
||||||
invalidate: this.resetCallback,
|
|
||||||
rebuild: this.rebuildSpecification.bind(this, undefined)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateRequiredDataSources(specs) {
|
|
||||||
//for now just request all data, later decide in the context on what to really send
|
|
||||||
//might in the future decide to only request used data, now not supported
|
|
||||||
let usedIds = new Set();
|
|
||||||
for (let key in specs.shaders) {
|
|
||||||
let layer = specs.shaders[key];
|
|
||||||
if (layer) {
|
|
||||||
for (let x of layer.dataReferences) {
|
|
||||||
usedIds.add(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
usedIds = [...usedIds].sort();
|
|
||||||
this._dataSources = [];
|
|
||||||
|
|
||||||
while (usedIds[usedIds.length - 1] >= this._origDataSources.length) {
|
|
||||||
//make sure values are set if user did not provide
|
|
||||||
this._origDataSources.push("__generated_do_not_use__");
|
|
||||||
}
|
|
||||||
|
|
||||||
this._shaderDataIndexToGlobalDataIndex = new Array(
|
|
||||||
Math.max(this._origDataSources.length, usedIds[usedIds.length - 1])
|
|
||||||
).fill(-1);
|
|
||||||
|
|
||||||
for (let id of usedIds) {
|
|
||||||
this._shaderDataIndexToGlobalDataIndex[id] = this._dataSources.length;
|
|
||||||
this._dataSources.push(this._origDataSources[id]);
|
|
||||||
while (id > this._shaderDataIndexToGlobalDataIndex.length) {
|
|
||||||
this._shaderDataIndexToGlobalDataIndex.push(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_processSpecification(spec, idx) {
|
|
||||||
let gl = this.gl,
|
|
||||||
err = function(message, description) {
|
|
||||||
spec.error = message;
|
|
||||||
spec.desc = description;
|
|
||||||
};
|
|
||||||
|
|
||||||
let program;
|
|
||||||
|
|
||||||
if (!this._programs[idx]) {
|
|
||||||
program = gl.createProgram();
|
|
||||||
this._programs[idx] = program;
|
|
||||||
|
|
||||||
let index = 0;
|
|
||||||
//init shader factories and unique id's
|
|
||||||
for (let key in spec.shaders) {
|
|
||||||
let layer = spec.shaders[key];
|
|
||||||
if (layer) {
|
|
||||||
let ShaderFactoryClass = $.WebGLModule.ShaderMediator.getClass(layer.type);
|
|
||||||
if (layer.type === "none") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
this._initializeShaderFactory(ShaderFactoryClass, layer, index++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
program = this._programs[idx];
|
|
||||||
for (let key in spec.shaders) {
|
|
||||||
let layer = spec.shaders[key];
|
|
||||||
|
|
||||||
if (layer) {
|
|
||||||
if (!layer.error &&
|
|
||||||
layer._renderContext &&
|
|
||||||
layer._renderContext.constructor.type() === layer.type) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
delete layer.error;
|
|
||||||
delete layer.desc;
|
|
||||||
if (layer.type === "none") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let ShaderFactoryClass = $.WebGLModule.ShaderMediator.getClass(layer.type);
|
|
||||||
this._initializeShaderFactory(ShaderFactoryClass, layer, layer._index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(spec.order) || spec.order.length < 1) {
|
|
||||||
spec.order = Object.keys(spec.shaders);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._buildSpecification(spec.order, spec);
|
|
||||||
|
|
||||||
if (spec.error) {
|
|
||||||
this.visualisationReady(idx, spec);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.constructor.compileShader(gl, program,
|
|
||||||
spec._built.vertexShader, spec._built.fragmentShader, err, this.debug);
|
|
||||||
this.visualisationReady(idx, spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
static compileShader(gl, program, VS, FS, onError, isDebugMode) {
|
|
||||||
function ok (kind, status, value, sh) {
|
|
||||||
if (!gl['get' + kind + 'Parameter'](value, gl[status + '_STATUS'])) {
|
|
||||||
console.error((sh || 'LINK') + ':\n' + gl['get' + kind + 'InfoLog'](value));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function useShader(gl, program, data, type) {
|
|
||||||
let shader = gl.createShader(gl[type]);
|
|
||||||
gl.shaderSource(shader, data);
|
|
||||||
gl.compileShader(shader);
|
|
||||||
gl.attachShader(program, shader);
|
|
||||||
program[type] = shader;
|
|
||||||
return ok('Shader', 'COMPILE', shader, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
function numberLines(str) {
|
|
||||||
//https://stackoverflow.com/questions/49714971/how-to-add-line-numbers-to-beginning-of-each-line-in-string-in-javascript
|
|
||||||
return str.split('\n').map((line, index) => `${index + 1} ${line}`).join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useShader(gl, program, VS, 'VERTEX_SHADER') ||
|
|
||||||
!useShader(gl, program, FS, 'FRAGMENT_SHADER')) {
|
|
||||||
onError("Unable to use this specification.",
|
|
||||||
"Compilation of shader failed. For more information, see logs in the console.");
|
|
||||||
console.warn("VERTEX SHADER\n", numberLines( VS ));
|
|
||||||
console.warn("FRAGMENT SHADER\n", numberLines( FS ));
|
|
||||||
} else {
|
|
||||||
gl.linkProgram(program);
|
|
||||||
if (!ok('Program', 'LINK', program)) {
|
|
||||||
onError("Unable to use this specification.",
|
|
||||||
"Linking of shader failed. For more information, see logs in the console.");
|
|
||||||
} else { //if (isDebugMode) { //todo testing
|
|
||||||
console.info("FRAGMENT SHADER\n", numberLines( FS ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ID pattern allowed for module, ID's are used in GLSL
|
|
||||||
* to distinguish uniquely between static generated code parts
|
|
||||||
* @type {RegExp}
|
|
||||||
*/
|
|
||||||
$.WebGLModule.idPattern = /[0-9a-zA-Z_]*/;
|
|
||||||
|
|
||||||
})(OpenSeadragon);
|
|
File diff suppressed because it is too large
Load Diff
@ -1,391 +0,0 @@
|
|||||||
(function($) {
|
|
||||||
|
|
||||||
$.WebGLModule.determineContext = function( version ){
|
|
||||||
const namespace = OpenSeadragon.WebGLModule;
|
|
||||||
for (let property in namespace) {
|
|
||||||
const context = namespace[ property ],
|
|
||||||
proto = context.prototype;
|
|
||||||
if( proto &&
|
|
||||||
proto instanceof namespace.WebGLImplementation &&
|
|
||||||
$.isFunction( proto.getVersion ) &&
|
|
||||||
proto.getVersion.call( context ) === version
|
|
||||||
){
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface OpenSeadragon.WebGLModule.webglContext
|
|
||||||
* Interface for the visualisation rendering implementation which can run
|
|
||||||
* on various GLSL versions
|
|
||||||
*/
|
|
||||||
$.WebGLModule.WebGLImplementation = class {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a WebGL Renderer Context Implementation (version-dependent)
|
|
||||||
* @param {WebGLModule} renderer
|
|
||||||
* @param {WebGLRenderingContextBase} gl
|
|
||||||
* @param webglVersion
|
|
||||||
* @param {object} options
|
|
||||||
* @param {GLuint} options.wrap texture wrap parameteri
|
|
||||||
* @param {GLuint} options.magFilter texture filter parameteri
|
|
||||||
* @param {GLuint} options.minFilter texture filter parameteri
|
|
||||||
* @param {string|WebGLModule.IDataLoader} options.dataLoader class name or implementation of a given loader
|
|
||||||
*/
|
|
||||||
constructor(renderer, gl, webglVersion, options) {
|
|
||||||
//Set default blending to be MASK
|
|
||||||
this.renderer = renderer;
|
|
||||||
this.gl = gl;
|
|
||||||
this.glslBlendCode = "return background * (step(0.001, foreground.a));";
|
|
||||||
|
|
||||||
let Loader = options.dataLoader;
|
|
||||||
if (typeof Loader === "string") {
|
|
||||||
Loader = $.WebGLModule.Loaders[Loader];
|
|
||||||
}
|
|
||||||
if (!Loader) {
|
|
||||||
throw("Unknown data loader: " + options.dataLoader);
|
|
||||||
}
|
|
||||||
if (!(Loader.prototype instanceof $.WebGLModule.IDataLoader)) {
|
|
||||||
throw("Incompatible texture loader used: " + options.dataLoader);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._texture = new Loader(gl, webglVersion, options);
|
|
||||||
if (!this.texture.supportsWebglVersion(this.getVersion())) {
|
|
||||||
throw("Incompatible texture loader version to the renderer context version! Context WebGL" + this.getVersion());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static context creation (to avoid class instantiation in case of missing support)
|
|
||||||
* @param canvas
|
|
||||||
* @return {WebGLRenderingContextBase} //todo base is not common to all, remove from docs
|
|
||||||
*/
|
|
||||||
static create(canvas) {
|
|
||||||
throw("::create() must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string} WebGL version used
|
|
||||||
*/
|
|
||||||
getVersion() {
|
|
||||||
return "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get GLSL texture sampling code
|
|
||||||
* @return {string} GLSL code that is correct in texture sampling wrt. WebGL version used
|
|
||||||
*/
|
|
||||||
get texture() {
|
|
||||||
return this._texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a visualisation from the given JSON params
|
|
||||||
* @param {string[]} order keys of visualisation.shader in which order to build the visualization
|
|
||||||
* the order: painter's algorithm: the last drawn is the most visible
|
|
||||||
* @param {object} visualisation
|
|
||||||
* @param {[number]} shaderDataIndexToGlobalDataIndex
|
|
||||||
* @param {boolean} withHtml whether html should be also created (false if no UI controls are desired)
|
|
||||||
* @return {object} compiled specification object ready to be used by the wrapper, with the following keys:
|
|
||||||
{string} object.vertexShader vertex shader code
|
|
||||||
{string} object.fragmentShader fragment shader code
|
|
||||||
{string} object.html html for the UI
|
|
||||||
{number} object.usableShaders how many layers are going to be visualised
|
|
||||||
{(array|string[])} object.dataUrls ID's of data in use (keys of visualisation.shaders object) in desired order
|
|
||||||
the data is guaranteed to arrive in this order (images stacked below each other in imageElement)
|
|
||||||
*/
|
|
||||||
compileSpecification(order, visualisation, shaderDataIndexToGlobalDataIndex, withHtml) {
|
|
||||||
throw("::compileSpecification() must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called once program is switched to: initialize all necessary items
|
|
||||||
* @param {WebGLProgram} program used program
|
|
||||||
* @param {OpenSeadragon.WebGLModule.RenderingConfig} currentConfig JSON parameters used for this visualisation
|
|
||||||
*/
|
|
||||||
programLoaded(program, currentConfig) {
|
|
||||||
throw("::programLoaded() must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw on the canvas using given program
|
|
||||||
* @param {WebGLProgram} program used program
|
|
||||||
* @param {OpenSeadragon.WebGLModule.RenderingConfig} currentConfig JSON parameters used for this visualisation
|
|
||||||
*
|
|
||||||
* @param {string} id dataId
|
|
||||||
* @param {object} tileOpts
|
|
||||||
* @param {number} tileOpts.zoom value passed to the shaders as zoom_level
|
|
||||||
* @param {number} tileOpts.pixelSize value passed to the shaders as pixel_size_in_fragments
|
|
||||||
* @param {OpenSeadragon.Mat3} tileOpts.transform position of the rendered tile
|
|
||||||
*/
|
|
||||||
programUsed(program, currentConfig, id, tileOpts) {
|
|
||||||
throw("::programUsed() must be implemented!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Code to be included only once, required by given shader type (keys are considered global)
|
|
||||||
* @param {string} type shader type
|
|
||||||
* @returns {object} global-scope code used by the shader in <key: code> format
|
|
||||||
*/
|
|
||||||
globalCodeRequiredByShaderType(type) {
|
|
||||||
return $.WebGLModule.ShaderMediator.getClass(type).__globalIncludes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Blend equation sent from the outside, must be respected
|
|
||||||
* @param glslCode code for blending, using two variables: 'foreground', 'background'
|
|
||||||
* @example
|
|
||||||
* //The shader context must define the following:
|
|
||||||
*
|
|
||||||
* vec4 some_blending_name_etc(in vec4 background, in vec4 foreground) {
|
|
||||||
* // << glslCode >>
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void blend_clip(vec4 input) {
|
|
||||||
* //for details on clipping mask approach see show() below
|
|
||||||
* // <<use some_blending_name_etc() to blend input onto output color of the shader using a clipping mask>>
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void blend(vec4 input) { //must be called blend, API
|
|
||||||
* // <<use some_blending_name_etc() to blend input onto output color of the shader>>
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* //Also, default alpha blending equation 'show' must be implemented:
|
|
||||||
* void show(vec4 color) {
|
|
||||||
* //pseudocode
|
|
||||||
* //note that the blending output should not immediatelly work with 'color' but perform caching of the color,
|
|
||||||
* //render the color given in previous call and at the execution end of main call show(vec4(.0))
|
|
||||||
* //this way, the previous color is not yet blended for the next layer show/blend/blend_clip which can use it to create a clipping mask
|
|
||||||
*
|
|
||||||
* compute t = color.a + background.a - color.a*background.a;
|
|
||||||
* output vec4((color.rgb * color.a + background.rgb * background.a - background.rgb * (background.a * color.a)) / t, t)
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
setBlendEquation(glslCode) {
|
|
||||||
this.glslBlendCode = glslCode;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$.WebGLModule.WebGL20 = class extends $.WebGLModule.WebGLImplementation {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {OpenSeadragon.WebGLModule} renderer
|
|
||||||
* @param {WebGL2RenderingContext} gl
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
constructor(renderer, gl, options) {
|
|
||||||
super(renderer, gl, "2.0", options);
|
|
||||||
this.emptyBuffer = gl.createBuffer();
|
|
||||||
}
|
|
||||||
|
|
||||||
getVersion() {
|
|
||||||
return "2.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(canvas) {
|
|
||||||
return canvas.getContext('webgl2', { premultipliedAlpha: false, alpha: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo try to implement on the global scope version-independntly
|
|
||||||
compileSpecification(order, visualisation, shaderDataIndexToGlobalDataIndex, withHtml) {
|
|
||||||
var definition = "",
|
|
||||||
execution = "",
|
|
||||||
html = "",
|
|
||||||
_this = this,
|
|
||||||
usableShaders = 0,
|
|
||||||
globalScopeCode = {};
|
|
||||||
|
|
||||||
order.forEach(dataId => {
|
|
||||||
let layer = visualisation.shaders[dataId];
|
|
||||||
layer.rendering = false;
|
|
||||||
|
|
||||||
if (layer.type === "none") {
|
|
||||||
//prevents the layer from being accounted for
|
|
||||||
layer.error = "Not an error - layer type none.";
|
|
||||||
} else if (layer.error) {
|
|
||||||
if (withHtml) {
|
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name, layer.error, dataId, false, layer, false) + html;
|
|
||||||
}
|
|
||||||
console.warn(layer.error, layer["desc"]);
|
|
||||||
|
|
||||||
} else if (layer._renderContext && (layer._index || layer._index === 0)) {
|
|
||||||
let visible = false;
|
|
||||||
usableShaders++;
|
|
||||||
|
|
||||||
//make visible textures if 'visible' flag set
|
|
||||||
if (layer.visible) {
|
|
||||||
let renderCtx = layer._renderContext;
|
|
||||||
definition += renderCtx.getFragmentShaderDefinition() + `
|
|
||||||
vec4 lid_${layer._index}_xo() {
|
|
||||||
${renderCtx.getFragmentShaderExecution()}
|
|
||||||
}`;
|
|
||||||
if (renderCtx.opacity) {
|
|
||||||
execution += `
|
|
||||||
vec4 l${layer._index}_out = lid_${layer._index}_xo();
|
|
||||||
l${layer._index}_out.a *= ${renderCtx.opacity.sample()};
|
|
||||||
${renderCtx.__mode}(l${layer._index}_out);`;
|
|
||||||
} else {
|
|
||||||
execution += `
|
|
||||||
${renderCtx.__mode}(lid_${layer._index}_xo());`;
|
|
||||||
}
|
|
||||||
|
|
||||||
layer.rendering = true;
|
|
||||||
visible = true;
|
|
||||||
OpenSeadragon.extend(globalScopeCode, _this.globalCodeRequiredByShaderType(layer.type));
|
|
||||||
}
|
|
||||||
|
|
||||||
//reverse order append to show first the last drawn element (top)
|
|
||||||
if (withHtml) {
|
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
|
||||||
layer._renderContext.htmlControls(), dataId, visible, layer, true) + html;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (withHtml) {
|
|
||||||
html = _this.renderer.htmlShaderPartHeader(layer.name,
|
|
||||||
`The requested visualisation type does not work properly.`, dataId, false, layer, false) + html;
|
|
||||||
}
|
|
||||||
console.warn("Invalid shader part.", "Missing one of the required elements.", layer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
vertexShader: this.getVertexShader(),
|
|
||||||
fragmentShader: this.getFragmentShader(definition, execution, shaderDataIndexToGlobalDataIndex, globalScopeCode),
|
|
||||||
html: html,
|
|
||||||
usableShaders: usableShaders,
|
|
||||||
dataUrls: this.renderer._dataSources
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getFragmentShader(definition, execution, shaderDataIndexToGlobalDataIndex, globalScopeCode) {
|
|
||||||
return `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
precision mediump sampler2DArray;
|
|
||||||
|
|
||||||
${this.texture.declare(shaderDataIndexToGlobalDataIndex)}
|
|
||||||
uniform float pixel_size_in_fragments;
|
|
||||||
uniform float zoom_level;
|
|
||||||
uniform vec2 u_tile_size;
|
|
||||||
vec4 _last_rendered_color = vec4(.0);
|
|
||||||
|
|
||||||
in vec2 tile_texture_coords;
|
|
||||||
|
|
||||||
out vec4 final_color;
|
|
||||||
|
|
||||||
bool close(float value, float target) {
|
|
||||||
return abs(target - value) < 0.001;
|
|
||||||
}
|
|
||||||
|
|
||||||
void show(vec4 color) {
|
|
||||||
//premultiplied alpha blending
|
|
||||||
vec4 fg = _last_rendered_color;
|
|
||||||
_last_rendered_color = color;
|
|
||||||
vec4 pre_fg = vec4(fg.rgb * fg.a, fg.a);
|
|
||||||
final_color = pre_fg + final_color * (1.0-fg.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
void finalize() {
|
|
||||||
show(vec4(.0));
|
|
||||||
|
|
||||||
if (close(final_color.a, 0.0)) {
|
|
||||||
final_color = vec4(0.);
|
|
||||||
} else {
|
|
||||||
final_color = vec4(final_color.rgb/final_color.a, final_color.a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vec4 blend_equation(in vec4 foreground, in vec4 background) {
|
|
||||||
${this.glslBlendCode}
|
|
||||||
}
|
|
||||||
|
|
||||||
void blend_clip(vec4 foreground) {
|
|
||||||
_last_rendered_color = blend_equation(foreground, _last_rendered_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void blend(vec4 foreground) {
|
|
||||||
show(_last_rendered_color);
|
|
||||||
final_color = blend_equation(foreground, final_color);
|
|
||||||
_last_rendered_color = vec4(.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
${Object.values(globalScopeCode).join("\n")}
|
|
||||||
|
|
||||||
${definition}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
${execution}
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVertexShader() {
|
|
||||||
//UNPACK_FLIP_Y_WEBGL not supported with 3D textures so sample bottom up
|
|
||||||
return `#version 300 es
|
|
||||||
precision mediump float;
|
|
||||||
|
|
||||||
uniform mat3 transform_matrix;
|
|
||||||
out vec2 tile_texture_coords;
|
|
||||||
const vec3 quad[4] = vec3[4] (
|
|
||||||
vec3(0.0, 1.0, 1.0),
|
|
||||||
vec3(0.0, 0.0, 1.0),
|
|
||||||
vec3(1.0, 1.0, 1.0),
|
|
||||||
vec3(1.0, 0.0, 1.0)
|
|
||||||
);
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec3 vertex = quad[gl_VertexID];
|
|
||||||
tile_texture_coords = vec2(vertex.x, -vertex.y);
|
|
||||||
gl_Position = vec4(transform_matrix * vertex, 1);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
programLoaded(program, currentConfig) {
|
|
||||||
if (!this.renderer.running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = this.renderer,
|
|
||||||
gl = this.gl;
|
|
||||||
|
|
||||||
// Allow for custom loading
|
|
||||||
gl.useProgram(program);
|
|
||||||
context.visualisationInUse(currentConfig);
|
|
||||||
context.glLoaded(gl, program, currentConfig);
|
|
||||||
|
|
||||||
//Note that the drawing strategy is not to resize canvas, and simply draw everyhing on squares
|
|
||||||
this.texture.programLoaded(context, gl, program, currentConfig);
|
|
||||||
|
|
||||||
//Empty ARRAY: get the vertices directly from the shader
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.emptyBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
programUsed(program, currentConfig, id, tileOpts) {
|
|
||||||
if (!this.renderer.running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Allow for custom drawing in webGL and possibly avoid using webGL at all
|
|
||||||
|
|
||||||
let context = this.renderer,
|
|
||||||
gl = this.gl;
|
|
||||||
|
|
||||||
context.glDrawing(gl, program, currentConfig, tileOpts);
|
|
||||||
|
|
||||||
// Set Attributes for GLSL
|
|
||||||
gl.uniform1f(gl.getUniformLocation(program, "pixel_size_in_fragments"), tileOpts.pixelSize || 1);
|
|
||||||
gl.uniform1f(gl.getUniformLocation(program, "zoom_level"), tileOpts.zoom || 1);
|
|
||||||
gl.uniformMatrix3fv(gl.getUniformLocation(program, "transform_matrix"), false,
|
|
||||||
tileOpts.transform || OpenSeadragon.Mat3.makeIdentity());
|
|
||||||
|
|
||||||
// Upload textures
|
|
||||||
this.texture.programUsed(context, currentConfig, id, program, gl);
|
|
||||||
|
|
||||||
// Draw triangle strip (two triangles) from a static array defined in the vertex shader
|
|
||||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})(OpenSeadragon);
|
|
@ -20,12 +20,23 @@
|
|||||||
<div id="contentDiv" class="openseadragon1"></div>
|
<div id="contentDiv" class="openseadragon1"></div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
let drawer = url.searchParams.get("drawer");
|
||||||
|
if (!drawer) {
|
||||||
|
drawer = 'canvas';
|
||||||
|
url.searchParams.set('drawer', drawer);
|
||||||
|
if ("undefined" !== typeof history.replaceState) {
|
||||||
|
history.replaceState(null, window.location.title, url.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var viewer = OpenSeadragon({
|
var viewer = OpenSeadragon({
|
||||||
// debugMode: true,
|
// debugMode: true,
|
||||||
id: "contentDiv",
|
id: "contentDiv",
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
prefixUrl: "../../build/openseadragon/images/",
|
||||||
tileSources: "../data/testpattern.dzi",
|
tileSources: "../data/testpattern.dzi",
|
||||||
showNavigator: true
|
showNavigator: true,
|
||||||
|
drawer:drawer,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -65,12 +65,12 @@
|
|||||||
<h2>Compare behavior of <strong>Context2d</strong> and <strong>WebGL</strong> 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 id="title-w1">Loading...</h3>
|
||||||
<div id="canvasdrawer" class="viewer-container"></div>
|
<div id="canvasdrawer" class="viewer-container"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>New WebGL drawer</h3>
|
<h3 id="title-w2">Loading...</h3>
|
||||||
<div id="webgl" class="viewer-container"></div>
|
<div id="webgl" class="viewer-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,18 @@ const labels = {
|
|||||||
bblue: 'Blue B',
|
bblue: 'Blue B',
|
||||||
duomo: 'Duomo',
|
duomo: 'Duomo',
|
||||||
}
|
}
|
||||||
|
const drawers = {
|
||||||
|
canvas: "Context2d drawer (default in OSD <= 4.1.0)",
|
||||||
|
webgl: "New WebGL drawer"
|
||||||
|
}
|
||||||
|
|
||||||
|
//Support drawer type from the url
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const drawer1 = url.searchParams.get("left") || 'canvas';
|
||||||
|
const drawer2 = url.searchParams.get("right") || 'webgl';
|
||||||
|
|
||||||
|
$("#title-w1").html(drawers[drawer1]);
|
||||||
|
$("#title-w2").html(drawers[drawer2]);
|
||||||
|
|
||||||
//Double viewer setup for comparison - CanvasDrawer and WebGLDrawer
|
//Double viewer setup for comparison - CanvasDrawer and WebGLDrawer
|
||||||
// viewer1: canvas drawer
|
// viewer1: canvas drawer
|
||||||
@ -25,7 +37,7 @@ let viewer1 = window.viewer1 = OpenSeadragon({
|
|||||||
crossOriginPolicy: 'Anonymous',
|
crossOriginPolicy: 'Anonymous',
|
||||||
ajaxWithCredentials: false,
|
ajaxWithCredentials: false,
|
||||||
// maxImageCacheCount: 30,
|
// maxImageCacheCount: 30,
|
||||||
drawer:'canvas',
|
drawer:drawer1,
|
||||||
blendTime:0
|
blendTime:0
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -39,25 +51,23 @@ let viewer2 = window.viewer2 = OpenSeadragon({
|
|||||||
crossOriginPolicy: 'Anonymous',
|
crossOriginPolicy: 'Anonymous',
|
||||||
ajaxWithCredentials: false,
|
ajaxWithCredentials: false,
|
||||||
// maxImageCacheCount: 30,
|
// maxImageCacheCount: 30,
|
||||||
drawer:'webgl',
|
drawer:drawer2,
|
||||||
blendTime:0,
|
blendTime:0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// viewer3: html drawer
|
// // viewer3: html drawer, unused
|
||||||
var viewer3 = window.viewer3 = OpenSeadragon({
|
// var viewer3 = window.viewer3 = OpenSeadragon({
|
||||||
id: "htmldrawer",
|
// id: "htmldrawer",
|
||||||
drawer:'html',
|
// drawer:'html',
|
||||||
blendTime:2,
|
// blendTime:2,
|
||||||
prefixUrl: "../../build/openseadragon/images/",
|
// prefixUrl: "../../build/openseadragon/images/",
|
||||||
minZoomImageRatio:0.01,
|
// minZoomImageRatio:0.01,
|
||||||
customDrawer: OpenSeadragon.HTMLDrawer,
|
// customDrawer: OpenSeadragon.HTMLDrawer,
|
||||||
tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
// tileSources: [sources['leaves'], sources['rainbow'], sources['duomo']],
|
||||||
sequenceMode: true,
|
// sequenceMode: true,
|
||||||
crossOriginPolicy: 'Anonymous',
|
// crossOriginPolicy: 'Anonymous',
|
||||||
ajaxWithCredentials: false
|
// ajaxWithCredentials: false
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Sync navigation of viewer1 and viewer 2
|
// Sync navigation of viewer1 and viewer 2
|
||||||
@ -126,6 +136,8 @@ Object.keys(sources).forEach((key, index)=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
$('#image-picker').append(makeComparisonSwitcher());
|
||||||
|
|
||||||
$('#image-picker input.toggle').on('change',function(){
|
$('#image-picker input.toggle').on('change',function(){
|
||||||
let data = $(this).data();
|
let data = $(this).data();
|
||||||
if(this.checked){
|
if(this.checked){
|
||||||
@ -150,9 +162,10 @@ $('#image-picker input:not(.toggle)').on('change',function(){
|
|||||||
});
|
});
|
||||||
|
|
||||||
function updateTiledImage(tiledImage, data, value, item){
|
function updateTiledImage(tiledImage, data, value, item){
|
||||||
|
let field = data.field;
|
||||||
|
|
||||||
if(tiledImage){
|
if(tiledImage){
|
||||||
//item = tiledImage
|
//item = tiledImage
|
||||||
let field = data.field;
|
|
||||||
if(field == 'x'){
|
if(field == 'x'){
|
||||||
let bounds = tiledImage.getBoundsNoRotate();
|
let bounds = tiledImage.getBoundsNoRotate();
|
||||||
let position = new OpenSeadragon.Point(Number(value), bounds.y);
|
let position = new OpenSeadragon.Point(Number(value), bounds.y);
|
||||||
@ -185,14 +198,15 @@ function updateTiledImage(tiledImage, data, value, item){
|
|||||||
} else {
|
} else {
|
||||||
tiledImage.setClip(null);
|
tiledImage.setClip(null);
|
||||||
}
|
}
|
||||||
}
|
} else if (field == 'debug'){
|
||||||
else if (field == 'debug'){
|
|
||||||
if( $(item).prop('checked') ){
|
if( $(item).prop('checked') ){
|
||||||
tiledImage.debugMode = true;
|
tiledImage.debugMode = true;
|
||||||
} else {
|
} else {
|
||||||
tiledImage.debugMode = false;
|
tiledImage.debugMode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
//viewer-level option
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +302,30 @@ function addTileSource(viewer, image, checkbox){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAvailableDrawerSelect(name, selectedDrawer) {
|
||||||
|
return `
|
||||||
|
<select name="${name}">
|
||||||
|
${Object.entries(drawers).map(([k, v]) => {
|
||||||
|
const selected = selectedDrawer === k ? "selected" : "";
|
||||||
|
return `<option value="${k}" ${selected}>${v}</option>`;
|
||||||
|
}).join("\n")}
|
||||||
|
</select>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeComparisonSwitcher() {
|
||||||
|
const left = getAvailableDrawerSelect("left", drawer1),
|
||||||
|
right = getAvailableDrawerSelect("right", drawer2);
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
Note: you can run the comparison with desired drawers like this: drawercomparison.html?left=[type]&right=[type]
|
||||||
|
<form method="get">
|
||||||
|
${left}
|
||||||
|
${right}
|
||||||
|
<button>Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
function makeImagePickerElement(key, label){
|
function makeImagePickerElement(key, label){
|
||||||
return $(`<div class="image-options">
|
return $(`<div class="image-options">
|
||||||
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
|
||||||
@ -302,11 +340,11 @@ function makeImagePickerElement(key, label){
|
|||||||
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
|
<label>Flipped: <input type="checkbox" data-image="" data-field="flipped"></label>
|
||||||
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
|
<label>Cropped: <input type="checkbox" data-image="" data-field="cropped"></label>
|
||||||
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
|
<label>Clipped: <input type="checkbox" data-image="" data-field="clipped"></label>
|
||||||
|
<label>Chess Tile Opacity: <input type="checkbox" data-image="" data-field="tile-level-opecity"></label>
|
||||||
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
|
<label>Debug: <input type="checkbox" data-image="" data-field="debug"></label>
|
||||||
<label>Composite: <select data-image="" data-field="composite"></select></label>
|
<label>Composite: <select data-image="" data-field="composite"></select></label>
|
||||||
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
|
<label>Wrap: <select data-image="" data-field="wrapping"></select></label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
|
</div>`.replaceAll('data-image=""', `data-image="${key}"`).replace('__title__', label));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -27,9 +27,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<label>Select a drawer: </label>
|
<label>Select a drawer: </label>
|
||||||
<select id="select-drawer">
|
<select id="select-drawer">
|
||||||
<option value="canvas">Canvas</option>
|
|
||||||
<option value="webgl">WebGL</option>
|
|
||||||
<option value="html">HTML</option>
|
|
||||||
</select>
|
</select>
|
||||||
<label>Num images: </label>
|
<label>Num images: </label>
|
||||||
<input id="input-number" type="number" value="1" min="1" step="1">
|
<input id="input-number" type="number" value="1" min="1" step="1">
|
||||||
|
@ -13,6 +13,13 @@ const labels = {
|
|||||||
bblue: 'Blue B',
|
bblue: 'Blue B',
|
||||||
duomo: 'Duomo',
|
duomo: 'Duomo',
|
||||||
}
|
}
|
||||||
|
const drawers = {
|
||||||
|
canvas: "Context2d drawer (default in OSD <= 4.1.0)",
|
||||||
|
webgl: "New WebGL drawer",
|
||||||
|
html: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let viewer;
|
let viewer;
|
||||||
|
|
||||||
( function () {
|
( function () {
|
||||||
@ -408,6 +415,10 @@ function rStats ( settings ) {
|
|||||||
|
|
||||||
_base = document.createElement( 'div' );
|
_base = document.createElement( 'div' );
|
||||||
_base.className = 'rs-base';
|
_base.className = 'rs-base';
|
||||||
|
_base.style.bottom = '0px';
|
||||||
|
_base.style.right = '0px';
|
||||||
|
_base.style.top = 'initial';
|
||||||
|
_base.style.left = 'initial';
|
||||||
_div = document.createElement( 'div' );
|
_div = document.createElement( 'div' );
|
||||||
_div.className = 'rs-container';
|
_div.className = 'rs-container';
|
||||||
_div.style.height = 'auto';
|
_div.style.height = 'auto';
|
||||||
@ -509,6 +520,8 @@ function rStats ( settings ) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var glStats = function() {
|
var glStats = function() {
|
||||||
|
|
||||||
var _rS = null;
|
var _rS = null;
|
||||||
@ -898,16 +911,9 @@ Stats.Panel = function ( name, fg, bg ) {
|
|||||||
|
|
||||||
// })();
|
// })();
|
||||||
|
|
||||||
|
|
||||||
$('#create-drawer').on('click',function(){
|
|
||||||
let drawerType = $('#select-drawer').val();
|
|
||||||
let num = Math.floor($('#input-number').val());
|
|
||||||
rS('other').start();
|
|
||||||
run(drawerType, num);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function run(drawerType, num) {
|
function run(drawerType, num) {
|
||||||
|
rS('other').start();
|
||||||
|
|
||||||
if(viewer){
|
if(viewer){
|
||||||
viewer.destroy();
|
viewer.destroy();
|
||||||
}
|
}
|
||||||
@ -969,3 +975,28 @@ function makeTileSources(num){
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const drawer = url.searchParams.get("drawer");
|
||||||
|
const numberOfSources = Number.parseInt(url.searchParams.get("sources")) || 1;
|
||||||
|
|
||||||
|
$('#create-drawer').on('click',function(){
|
||||||
|
const drawer = $('#select-drawer').val();
|
||||||
|
let num = Math.floor($('#input-number').val());
|
||||||
|
|
||||||
|
url.searchParams.set("drawer", drawer);
|
||||||
|
url.searchParams.set("sources", num);
|
||||||
|
if ("undefined" !== typeof history.replaceState) {
|
||||||
|
history.replaceState(null, window.location.title, url.toString());
|
||||||
|
}
|
||||||
|
run(drawer, num);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#input-number').val(numberOfSources);
|
||||||
|
$("#select-drawer").html(Object.entries(drawers).map(([k, v]) => {
|
||||||
|
const selected = drawer === k ? "selected" : "";
|
||||||
|
return `<option value="${k}" ${selected}>${v}</option>`;
|
||||||
|
}).join("\n"));
|
||||||
|
if (drawer) {
|
||||||
|
run(drawer, numberOfSources);
|
||||||
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user