@alleninstitute/vis-dzi 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2024 Allen Institute
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. 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.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ 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 HOLDER 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.
package/dist/main.js ADDED
@@ -0,0 +1,294 @@
1
+ import {logger as $ls06s$logger, buildAsyncRenderer as $ls06s$buildAsyncRenderer} from "@alleninstitute/vis-core";
2
+ import {Box2D as $ls06s$Box2D, Vec2 as $ls06s$Vec2} from "@alleninstitute/vis-geometry";
3
+
4
+
5
+
6
+ const $f3100f971d08a9cd$var$isDziFormat = (format)=>[
7
+ 'jpeg',
8
+ 'png',
9
+ 'jpg',
10
+ 'JPG',
11
+ 'PNG'
12
+ ].includes(format);
13
+ async function $f3100f971d08a9cd$export$fcea0dd904d012d9(url) {
14
+ return fetch(url).then((response)=>response.text()).then((xmlString)=>$f3100f971d08a9cd$var$decodeDzi(xmlString, url));
15
+ }
16
+ function $f3100f971d08a9cd$var$decodeDzi(xmlString, url) {
17
+ const parser = new DOMParser();
18
+ const doc = parser.parseFromString(xmlString, 'text/xml');
19
+ const err = doc.querySelector('parsererror');
20
+ if (err) {
21
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url} with content:`, xmlString);
22
+ return undefined;
23
+ }
24
+ const img = doc.getElementsByTagName('Image')[0];
25
+ const size = doc.getElementsByTagName('Size')?.[0];
26
+ const [format, overlap, tileSize] = [
27
+ img.getAttribute('Format'),
28
+ img.getAttribute('Overlap'),
29
+ img.getAttribute('TileSize')
30
+ ];
31
+ if (!size || !format || !overlap || !tileSize) {
32
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Missing required attributes`);
33
+ return undefined;
34
+ }
35
+ const width = size.getAttribute('Width');
36
+ const height = size.getAttribute('Height');
37
+ const splits = url.split('.dzi');
38
+ if (!width || !height || !splits || splits.length < 1) {
39
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Missing size or URL splits`);
40
+ return undefined;
41
+ }
42
+ if (!$f3100f971d08a9cd$var$isDziFormat(format)) {
43
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Invalid format "${format}"`);
44
+ return undefined;
45
+ }
46
+ return {
47
+ imagesUrl: `${splits[0]}_files/`,
48
+ format: format,
49
+ overlap: Number.parseInt(overlap, 10),
50
+ tileSize: Number.parseInt(tileSize, 10),
51
+ size: {
52
+ width: Number.parseInt(width, 10),
53
+ height: Number.parseInt(height, 10)
54
+ }
55
+ };
56
+ }
57
+ function $f3100f971d08a9cd$var$tileUrl(dzi, level, tile) {
58
+ return `${dzi.imagesUrl}${level.toFixed(0)}/${tile.col.toFixed(0)}_${tile.row.toFixed(0)}.${dzi.format}`;
59
+ }
60
+ function $f3100f971d08a9cd$export$2957f72eb8eb81d4(dzi, camera) {
61
+ const viewWidth = (0, $ls06s$Box2D).size(camera.view)[0];
62
+ const layer = $f3100f971d08a9cd$export$8d84f69083f6d537(dzi.size.width, camera.screenSize[0] / viewWidth);
63
+ const layerResolution = $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer);
64
+ const availableTiles = $f3100f971d08a9cd$export$28da4732af8bb035(dzi, layer);
65
+ const baseLayer = $f3100f971d08a9cd$var$findLargestSingleTileLayer(dzi);
66
+ const baseIndex = {
67
+ col: 0,
68
+ row: 0
69
+ };
70
+ const baseTile = {
71
+ index: baseIndex,
72
+ layer: baseLayer,
73
+ relativeLocation: (0, $ls06s$Box2D).create([
74
+ 0,
75
+ 0
76
+ ], [
77
+ 1,
78
+ 1
79
+ ]),
80
+ url: $f3100f971d08a9cd$var$tileUrl(dzi, baseLayer, baseIndex)
81
+ };
82
+ // note that the tile boxes are in pixels relative to the layer in which they reside
83
+ // the given view is assumed to be a parameter (in the space [0:1]) of the image as a whole
84
+ // so, we must convert literal pixel boxes into their relative position in the image as a whole:
85
+ const tileBoxAsParameter = (tile)=>(0, $ls06s$Box2D).create((0, $ls06s$Vec2).div(tile.minCorner, layerResolution), (0, $ls06s$Vec2).div(tile.maxCorner, layerResolution));
86
+ const tiles = availableTiles.flatMap((row, rowIndex)=>{
87
+ return row.map((tile, colIndex)=>{
88
+ const index = {
89
+ col: colIndex,
90
+ row: rowIndex
91
+ };
92
+ return {
93
+ index: index,
94
+ layer: layer,
95
+ relativeLocation: tileBoxAsParameter(tile),
96
+ url: $f3100f971d08a9cd$var$tileUrl(dzi, layer, index)
97
+ };
98
+ });
99
+ // filter out tiles which are not in view
100
+ }).filter((t)=>!!(0, $ls06s$Box2D).intersection(t.relativeLocation, camera.view));
101
+ return baseLayer < layer ? [
102
+ baseTile,
103
+ ...tiles
104
+ ] : tiles;
105
+ }
106
+ function $f3100f971d08a9cd$export$8d84f69083f6d537(imageWidth, screenWidth) {
107
+ const idealLayer = Math.ceil(Math.log2(screenWidth));
108
+ const biggestRealLayer = Math.ceil(Math.log2(imageWidth));
109
+ return Math.max(0, Math.min(biggestRealLayer, idealLayer));
110
+ }
111
+ /**
112
+ *
113
+ * @param dzi
114
+ * @returns the index of the largest layer which contains only a single tile
115
+ *
116
+ */ function $f3100f971d08a9cd$var$findLargestSingleTileLayer(dzi) {
117
+ return Math.floor(Math.log2(dzi.tileSize));
118
+ }
119
+ function $f3100f971d08a9cd$export$80d9f82e562c6ccd(total, step, overlap) {
120
+ const blocks = [];
121
+ let start = 0;
122
+ while(start < total){
123
+ const next = Math.min(total, start + step + overlap + (start > 0 ? overlap : 0));
124
+ blocks.push({
125
+ min: start,
126
+ max: next
127
+ });
128
+ if (next >= total) return blocks;
129
+ start = next - 2 * overlap;
130
+ }
131
+ return blocks;
132
+ }
133
+ function $f3100f971d08a9cd$var$boxFromRowCol(row, col) {
134
+ return (0, $ls06s$Box2D).create([
135
+ col.min,
136
+ row.min
137
+ ], [
138
+ col.max,
139
+ row.max
140
+ ]);
141
+ }
142
+ const $f3100f971d08a9cd$var$logBaseHalf = (x)=>Math.log2(x) / Math.log2(0.5);
143
+ function $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer) {
144
+ const { size: dim } = dzi;
145
+ const layerMaxSize = 2 ** (Number.isFinite(layer) ? Math.max(0, layer) : 0);
146
+ const size = [
147
+ dim.width,
148
+ dim.height
149
+ ];
150
+ // the question is how many times do we need to divide size
151
+ // by 2 to make it less than layerMaxSize?
152
+ // solve for N, X = the larger the image dimensions:
153
+ // X * (0.5^N) <= maxLayerSize ...
154
+ // 0.5^N = maxLayerSize/X ...
155
+ // log_0.5(maxLayerSize/X) = N
156
+ const bigger = Math.max(size[0], size[1]);
157
+ const N = Math.ceil($f3100f971d08a9cd$var$logBaseHalf(layerMaxSize / bigger));
158
+ return (0, $ls06s$Vec2).ceil((0, $ls06s$Vec2).scale(size, 0.5 ** N));
159
+ }
160
+ function $f3100f971d08a9cd$export$28da4732af8bb035(dzi, layer) {
161
+ const { overlap: overlap, tileSize: tileSize } = dzi;
162
+ // figure out the effective size of a layer by dividing the total size by 2 until its less than our layerMax
163
+ // note: if this all feels weird, its because I can find no reference implementation or specification, its a bit of reverse engineering
164
+ const total = $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer);
165
+ const rows = $f3100f971d08a9cd$export$80d9f82e562c6ccd(Math.ceil(total[1]), tileSize, overlap);
166
+ const cols = $f3100f971d08a9cd$export$80d9f82e562c6ccd(Math.ceil(total[0]), tileSize, overlap);
167
+ return rows.map((r)=>cols.map((c)=>$f3100f971d08a9cd$var$boxFromRowCol(r, c)));
168
+ }
169
+
170
+
171
+
172
+
173
+
174
+ /* ======================== VERTEX SHADER ======================= */ const $86022f707876cb5b$var$vert = /*glsl*/ `
175
+ precision highp float;
176
+ uniform vec4 view;
177
+ uniform vec4 tile;
178
+ uniform float depth;
179
+ attribute vec2 position;
180
+ varying vec2 uv;
181
+
182
+ void main(){
183
+ uv = position;
184
+ vec2 size = view.zw - view.xy;
185
+ vec2 tileSize = tile.zw - tile.xy;
186
+ vec2 tilePosition = (position * tileSize) + tile.xy;
187
+ vec2 pos = (tilePosition - view.xy)/size;
188
+ // to clip space:
189
+ pos = (pos * 2.0) - 1.0;
190
+ gl_Position = vec4(pos.x, pos.y, depth, 1);
191
+ }
192
+ `;
193
+ /* -------------------------------------------------------------- */ /* ======================= FRAGMENT SHADER ====================== */ const $86022f707876cb5b$var$frag = /*glsl*/ `
194
+ precision highp float;
195
+ varying vec2 uv;
196
+ uniform sampler2D img;
197
+
198
+ void main(){
199
+ gl_FragColor = texture2D(img, uv);
200
+ }
201
+ `;
202
+ function $86022f707876cb5b$export$640f994018c81008(regl, blend) {
203
+ const cmd = regl({
204
+ vert: $86022f707876cb5b$var$vert,
205
+ frag: $86022f707876cb5b$var$frag,
206
+ depth: {
207
+ enable: true
208
+ },
209
+ blend: blend,
210
+ count: 4,
211
+ primitive: 'triangle fan',
212
+ attributes: {
213
+ position: [
214
+ 0,
215
+ 0,
216
+ 1,
217
+ 0,
218
+ 1,
219
+ 1,
220
+ 0,
221
+ 1
222
+ ]
223
+ },
224
+ uniforms: {
225
+ img: regl.prop('img'),
226
+ view: regl.prop('view'),
227
+ tile: regl.prop('tile'),
228
+ depth: regl.prop('depth')
229
+ },
230
+ framebuffer: regl.prop('target')
231
+ });
232
+ return (p)=>cmd(p);
233
+ }
234
+
235
+
236
+ function $c6cbba1e8cad1f93$export$e530ae442eb0196c(regl) {
237
+ const renderCmd = (0, $86022f707876cb5b$export$640f994018c81008)(regl, {
238
+ enable: false
239
+ });
240
+ const fetchDziTile = (tile, _img, _settings, _abort)=>{
241
+ return {
242
+ pixels: ()=>{
243
+ return new Promise((resolve, reject)=>{
244
+ try {
245
+ const img = new Image();
246
+ img.crossOrigin = 'anonymous';
247
+ img.onload = ()=>{
248
+ resolve({
249
+ type: 'texture',
250
+ texture: regl.texture(img),
251
+ bytes: img.width * img.height * 4
252
+ }); // close enough
253
+ };
254
+ img.src = tile.url;
255
+ } catch (err) {
256
+ reject(err);
257
+ }
258
+ });
259
+ }
260
+ };
261
+ };
262
+ return {
263
+ destroy: ()=>{},
264
+ cacheKey: (item, _requestKey, _data, _settings)=>`${item.url}`,
265
+ fetchItemContent: fetchDziTile,
266
+ getVisibleItems: (dzi, settings)=>{
267
+ return (0, $f3100f971d08a9cd$export$2957f72eb8eb81d4)(dzi, settings.camera);
268
+ },
269
+ isPrepared: (cacheData)=>{
270
+ const pixels = cacheData.pixels;
271
+ return !!pixels && pixels.type === 'texture';
272
+ },
273
+ renderItem: (target, tile, _dzi, settings, gpuData)=>{
274
+ const { pixels: pixels } = gpuData;
275
+ const { camera: camera } = settings;
276
+ renderCmd({
277
+ target: target,
278
+ depth: -tile.layer / 1000,
279
+ img: pixels.texture,
280
+ tile: (0, $ls06s$Box2D).toFlatArray(tile.relativeLocation),
281
+ view: (0, $ls06s$Box2D).toFlatArray(camera.view)
282
+ });
283
+ }
284
+ };
285
+ }
286
+ function $c6cbba1e8cad1f93$export$a56de096ea731ba6(regl) {
287
+ return (0, $ls06s$buildAsyncRenderer)($c6cbba1e8cad1f93$export$e530ae442eb0196c(regl));
288
+ }
289
+
290
+
291
+
292
+
293
+ export {$f3100f971d08a9cd$export$fcea0dd904d012d9 as fetchDziMetadata, $f3100f971d08a9cd$export$2957f72eb8eb81d4 as getVisibleTiles, $c6cbba1e8cad1f93$export$e530ae442eb0196c as buildDziRenderer, $c6cbba1e8cad1f93$export$a56de096ea731ba6 as buildAsyncDziRenderer};
294
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/dzi/src/index.ts","packages/dzi/src/loader.ts","packages/dzi/src/renderer.ts","packages/dzi/src/tile-renderer.ts"],"sourcesContent":["export { fetchDziMetadata, getVisibleTiles, type DziImage, type DziTile } from './loader';\nexport {\n buildDziRenderer,\n buildAsyncDziRenderer,\n type RenderSettings as DziRenderSettings,\n} from './renderer';\n","import { logger } from '@alleninstitute/vis-core';\nimport { Box2D, type Interval, Vec2, type box2D, type vec2 } from '@alleninstitute/vis-geometry';\n\ntype DziTilesRoot = `${string}_files/`;\ntype DziFormat = 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG';\nconst isDziFormat = (format: string): format is DziFormat => ['jpeg', 'png', 'jpg', 'JPG', 'PNG'].includes(format);\n\n// see https://learn.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/cc645077(v=vs.95)?redirectedfrom=MSDN\n// TODO find a less ancient spec...\nexport type DziImage = {\n imagesUrl: DziTilesRoot; // lets say you found a dzi at http://blah.com/deepzoom.dzi\n // imagesUrl would be the path which contains all the files for the actual image tiles:\n // in this example:\n // http://blah.com/deepzoom_files/\n format: DziFormat;\n overlap: number; // in pixels, ADDED every side of any given tile (for example, with overlap=1 and tilesize=256, you could see a jpeg of size 258x258).\n // note that tiles on the edge wont have padding (on a per edge basis!)\n tileSize: number;\n size: {\n width: number;\n height: number;\n };\n};\ntype TileIndex = {\n row: number;\n col: number;\n};\nexport type DziTile = {\n url: string;\n index: TileIndex;\n relativeLocation: box2D;\n layer: number;\n};\n\n/**\n * Fetches the metadata for a Deep Zoom Image (DZI) from a given URL.\n *\n * @param url The URL to a DZI metadata file, which should be an XML file containing the metadata for a Deep Zoom Image\n * @returns A DZI image object containing the metadata for the Deep Zoom Image\n */\nexport async function fetchDziMetadata(url: string): Promise<DziImage | undefined> {\n return fetch(url)\n .then((response) => response.text())\n .then((xmlString) => decodeDzi(xmlString, url));\n}\n\nfunction decodeDzi(xmlString: string, url: string): DziImage | undefined {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xmlString, 'text/xml');\n const err = doc.querySelector('parsererror');\n if (err) {\n logger.error(`Failed to parse DZI XML from ${url} with content:`, xmlString);\n return undefined;\n }\n\n const img = doc.getElementsByTagName('Image')[0];\n const size = doc.getElementsByTagName('Size')?.[0];\n const [format, overlap, tileSize] = [\n img.getAttribute('Format'),\n img.getAttribute('Overlap'),\n img.getAttribute('TileSize'),\n ];\n\n if (!size || !format || !overlap || !tileSize) {\n logger.error(`Failed to parse DZI XML from ${url}: Missing required attributes`);\n return undefined;\n }\n\n const width = size.getAttribute('Width');\n const height = size.getAttribute('Height');\n const splits = url.split('.dzi');\n\n if (!width || !height || !splits || splits.length < 1) {\n logger.error(`Failed to parse DZI XML from ${url}: Missing size or URL splits`);\n return undefined;\n }\n\n if (!isDziFormat(format)) {\n logger.error(`Failed to parse DZI XML from ${url}: Invalid format \"${format}\"`);\n return undefined;\n }\n\n return {\n imagesUrl: `${splits[0]}_files/`,\n format: format,\n overlap: Number.parseInt(overlap, 10),\n tileSize: Number.parseInt(tileSize, 10),\n size: {\n width: Number.parseInt(width, 10),\n height: Number.parseInt(height, 10),\n },\n };\n}\n\nfunction tileUrl(dzi: DziImage, level: number, tile: TileIndex): string {\n return `${dzi.imagesUrl}${level.toFixed(0)}/${tile.col.toFixed(0)}_${tile.row.toFixed(0)}.${dzi.format}`;\n}\n// some quick notes on this deep zoom image format:\n// 1. image / tile names are given by {column}_{row}.{format}\n// 2. a layer (which may contain multiple tiles) is a folder\n// 2.1 that folder contains all the tiles for that layer.\n// layer 0 should contain a single image, 0_0, which is a single pixel!\n// the origin of this tile indexing system is the top left of the image.\n// the spec says that the \"size\" of a layer is 2*layer... but its closer to pow(2, layer).\n// note also that is more of a maximum size... for example I've seen 9/0_0.jpeg have a size of 421x363, both of those are lower than pow(2,9)=512\n// note also that overlap is ADDED to the tile-size, which is a weird choice, as tileSize seems like it must be a power of 2...🤷‍♀️\n\n/**\n *\n * @param dzi the dzi image to read tiles from\n * @param camera.view a parametric box [0:1] relative the the image as a whole. note that 0 is the TOP of the image.\n * @param camera.screenSize the size, in output pixels, at which the requested region will be displayed.\n * @return a list of tiles at the most appropriate resolution which may be fetched and displayed\n */\nexport function getVisibleTiles(dzi: DziImage, camera: { view: box2D; screenSize: vec2 }): DziTile[] {\n const viewWidth = Box2D.size(camera.view)[0];\n const layer = firstSuitableLayer(dzi.size.width, camera.screenSize[0] / viewWidth);\n const layerResolution = imageSizeAtLayer(dzi, layer);\n\n const availableTiles = tilesInLayer(dzi, layer);\n const baseLayer = findLargestSingleTileLayer(dzi);\n const baseIndex: TileIndex = { col: 0, row: 0 };\n const baseTile: DziTile = {\n index: baseIndex,\n layer: baseLayer,\n relativeLocation: Box2D.create([0, 0], [1, 1]),\n url: tileUrl(dzi, baseLayer, baseIndex),\n };\n\n // note that the tile boxes are in pixels relative to the layer in which they reside\n // the given view is assumed to be a parameter (in the space [0:1]) of the image as a whole\n // so, we must convert literal pixel boxes into their relative position in the image as a whole:\n const tileBoxAsParameter = (tile: box2D) =>\n Box2D.create(Vec2.div(tile.minCorner, layerResolution), Vec2.div(tile.maxCorner, layerResolution));\n\n const tiles: DziTile[] = availableTiles\n .flatMap((row, rowIndex) => {\n return row.map((tile, colIndex) => {\n const index = { col: colIndex, row: rowIndex };\n return {\n index,\n layer,\n relativeLocation: tileBoxAsParameter(tile),\n url: tileUrl(dzi, layer, index),\n };\n });\n // filter out tiles which are not in view\n })\n .filter((t) => !!Box2D.intersection(t.relativeLocation, camera.view));\n return baseLayer < layer ? [baseTile, ...tiles] : tiles;\n}\n/**\n * NOTE: THE REMAINDER OF THIS FILE IS EXPORTED ONLY FOR TESTING PURPOSES\n * **/\n\n// starting with the width of an image, and the width of the screen on which to display that image\n// return the highest-numbered (aka highest resolution) dzi-layer folder which has a size that would be lower than the screen resolution\nexport function firstSuitableLayer(imageWidth: number, screenWidth: number) {\n const idealLayer = Math.ceil(Math.log2(screenWidth));\n const biggestRealLayer = Math.ceil(Math.log2(imageWidth));\n return Math.max(0, Math.min(biggestRealLayer, idealLayer));\n}\n\n/**\n *\n * @param dzi\n * @returns the index of the largest layer which contains only a single tile\n *\n */\nfunction findLargestSingleTileLayer(dzi: DziImage): number {\n return Math.floor(Math.log2(dzi.tileSize));\n}\nexport function tileWithOverlap(total: number, step: number, overlap: number): Interval[] {\n const blocks: Interval[] = [];\n let start = 0;\n while (start < total) {\n const next = Math.min(total, start + step + overlap + (start > 0 ? overlap : 0));\n blocks.push({ min: start, max: next });\n if (next >= total) {\n return blocks;\n }\n start = next - 2 * overlap;\n }\n return blocks;\n}\nfunction boxFromRowCol(row: Interval, col: Interval) {\n return Box2D.create([col.min, row.min], [col.max, row.max]);\n}\n\nconst logBaseHalf = (x: number) => Math.log2(x) / Math.log2(0.5);\n\nexport function imageSizeAtLayer(dzi: DziImage, layer: number) {\n const { size: dim } = dzi;\n const layerMaxSize = 2 ** (Number.isFinite(layer) ? Math.max(0, layer) : 0);\n const size: vec2 = [dim.width, dim.height];\n // the question is how many times do we need to divide size\n // by 2 to make it less than layerMaxSize?\n // solve for N, X = the larger the image dimensions:\n // X * (0.5^N) <= maxLayerSize ...\n // 0.5^N = maxLayerSize/X ...\n // log_0.5(maxLayerSize/X) = N\n const bigger = Math.max(size[0], size[1]);\n const N = Math.ceil(logBaseHalf(layerMaxSize / bigger));\n return Vec2.ceil(Vec2.scale(size, 0.5 ** N));\n}\nexport function tilesInLayer(dzi: DziImage, layer: number): box2D[][] {\n const { overlap, tileSize } = dzi;\n // figure out the effective size of a layer by dividing the total size by 2 until its less than our layerMax\n // note: if this all feels weird, its because I can find no reference implementation or specification, its a bit of reverse engineering\n const total: vec2 = imageSizeAtLayer(dzi, layer);\n const rows = tileWithOverlap(Math.ceil(total[1]), tileSize, overlap);\n const cols = tileWithOverlap(Math.ceil(total[0]), tileSize, overlap);\n return rows.map((r) => cols.map((c) => boxFromRowCol(r, c)));\n}\n","import { Box2D, type box2D, type vec2 } from '@alleninstitute/vis-geometry';\nimport { type CachedTexture, type ReglCacheEntry, type Renderer, buildAsyncRenderer } from '@alleninstitute/vis-core';\nimport type REGL from 'regl';\nimport { type DziImage, type DziTile, getVisibleTiles } from './loader';\nimport { buildTileRenderCommand } from './tile-renderer';\n\nexport type RenderSettings = {\n camera: {\n /**\n * a region of a dzi image, expressed as a relative parameter (eg. [0,0],[1,1] means the whole image)\n */\n view: box2D;\n /**\n * the resolution of the output screen on which to project the region of source pixels given by view\n */\n screenSize: vec2;\n };\n};\n\ntype GpuProps = {\n pixels: CachedTexture;\n};\n/**\n *\n * @param regl a valid REGL context (https://github.com/regl-project/regl)\n * @returns an object which can fetch tiles from a DeepZoomImage, determine the visibility of those tiles given a simple camera, and render said tiles\n * using regl (which uses webGL)\n */\nexport function buildDziRenderer(regl: REGL.Regl): Renderer<DziImage, DziTile, RenderSettings, GpuProps> {\n const renderCmd = buildTileRenderCommand(regl, { enable: false });\n const fetchDziTile = (\n tile: DziTile,\n _img: DziImage,\n _settings: RenderSettings,\n _abort?: AbortSignal,\n ): Record<string, () => Promise<ReglCacheEntry>> => {\n return {\n pixels: () => {\n return new Promise<ReglCacheEntry>((resolve, reject) => {\n try {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n resolve({\n type: 'texture',\n texture: regl.texture(img),\n bytes: img.width * img.height * 4,\n }); // close enough\n };\n img.src = tile.url;\n } catch (err) {\n reject(err);\n }\n });\n },\n };\n };\n return {\n destroy: () => {}, // no private resources to destroy\n cacheKey: (item, _requestKey, _data, _settings) => `${item.url}`,\n fetchItemContent: fetchDziTile,\n getVisibleItems: (dzi, settings) => {\n return getVisibleTiles(dzi, settings.camera);\n },\n isPrepared: (cacheData): cacheData is GpuProps => {\n const pixels = cacheData.pixels;\n return !!pixels && pixels.type === 'texture';\n },\n renderItem: (target, tile, _dzi, settings, gpuData) => {\n const { pixels } = gpuData;\n const { camera } = settings;\n renderCmd({\n target,\n depth: -tile.layer / 1000,\n img: pixels.texture,\n tile: Box2D.toFlatArray(tile.relativeLocation),\n view: Box2D.toFlatArray(camera.view),\n });\n },\n };\n}\n/**\n *\n * @param regl a valid REGL context (https://github.com/regl-project/regl)\n * @returns a function which creates a \"Frame\" of actions. each action represents loading\n * and subsequently rendering a tile of the image as requested via its configuration -\n * @see RenderSettings\n */\nexport function buildAsyncDziRenderer(regl: REGL.Regl) {\n return buildAsyncRenderer(buildDziRenderer(regl));\n}\n","import type { vec4 } from '@alleninstitute/vis-geometry';\nimport type REGL from 'regl';\ntype Props = {\n img: REGL.Texture2D;\n view: vec4;\n tile: vec4;\n depth: number;\n target: REGL.Framebuffer2D | null;\n};\n\n/* ======================== VERTEX SHADER ======================= */\nconst vert = /*glsl*/ `\nprecision highp float;\nuniform vec4 view;\nuniform vec4 tile;\nuniform float depth;\nattribute vec2 position;\nvarying vec2 uv;\n\nvoid main(){\n uv = position;\n vec2 size = view.zw - view.xy;\n vec2 tileSize = tile.zw - tile.xy;\n vec2 tilePosition = (position * tileSize) + tile.xy;\n vec2 pos = (tilePosition - view.xy)/size;\n // to clip space:\n pos = (pos * 2.0) - 1.0;\n gl_Position = vec4(pos.x, pos.y, depth, 1);\n}\n`;\n/* -------------------------------------------------------------- */\n\n/* ======================= FRAGMENT SHADER ====================== */\nconst frag = /*glsl*/ `\nprecision highp float;\nvarying vec2 uv;\nuniform sampler2D img;\n\nvoid main(){\n gl_FragColor = texture2D(img, uv);\n}\n`;\n/* -------------------------------------------------------------- */\n\nexport function buildTileRenderCommand(regl: REGL.Regl, blend: REGL.BlendingOptions) {\n const cmd = regl({\n vert,\n frag,\n depth: {\n enable: true,\n },\n blend,\n count: 4,\n primitive: 'triangle fan',\n attributes: {\n position: [0, 0, 1, 0, 1, 1, 0, 1],\n },\n uniforms: {\n img: regl.prop<Props, 'img'>('img'),\n view: regl.prop<Props, 'view'>('view'),\n tile: regl.prop<Props, 'tile'>('tile'),\n depth: regl.prop<Props, 'depth'>('depth'),\n },\n framebuffer: regl.prop<Props, 'target'>('target'),\n });\n return (p: Props) => cmd(p);\n}\n"],"names":[],"version":3,"file":"main.js.map"}
package/dist/module.js ADDED
@@ -0,0 +1,292 @@
1
+ import {logger as $ls06s$logger, buildAsyncRenderer as $ls06s$buildAsyncRenderer} from "@alleninstitute/vis-core";
2
+ import {Box2D as $ls06s$Box2D, Vec2 as $ls06s$Vec2} from "@alleninstitute/vis-geometry";
3
+
4
+
5
+
6
+ const $f3100f971d08a9cd$var$isDziFormat = (format)=>[
7
+ 'jpeg',
8
+ 'png',
9
+ 'jpg',
10
+ 'JPG',
11
+ 'PNG'
12
+ ].includes(format);
13
+ async function $f3100f971d08a9cd$export$fcea0dd904d012d9(url) {
14
+ return fetch(url).then((response)=>response.text()).then((xmlString)=>$f3100f971d08a9cd$var$decodeDzi(xmlString, url));
15
+ }
16
+ function $f3100f971d08a9cd$var$decodeDzi(xmlString, url) {
17
+ const parser = new DOMParser();
18
+ const doc = parser.parseFromString(xmlString, 'text/xml');
19
+ const err = doc.querySelector('parsererror');
20
+ if (err) {
21
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url} with content:`, xmlString);
22
+ return undefined;
23
+ }
24
+ const img = doc.getElementsByTagName('Image')[0];
25
+ const size = doc.getElementsByTagName('Size')?.[0];
26
+ const [format, overlap, tileSize] = [
27
+ img.getAttribute('Format'),
28
+ img.getAttribute('Overlap'),
29
+ img.getAttribute('TileSize')
30
+ ];
31
+ if (!size || !format || !overlap || !tileSize) {
32
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Missing required attributes`);
33
+ return undefined;
34
+ }
35
+ const width = size.getAttribute('Width');
36
+ const height = size.getAttribute('Height');
37
+ const splits = url.split('.dzi');
38
+ if (!width || !height || !splits || splits.length < 1) {
39
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Missing size or URL splits`);
40
+ return undefined;
41
+ }
42
+ if (!$f3100f971d08a9cd$var$isDziFormat(format)) {
43
+ (0, $ls06s$logger).error(`Failed to parse DZI XML from ${url}: Invalid format "${format}"`);
44
+ return undefined;
45
+ }
46
+ return {
47
+ imagesUrl: `${splits[0]}_files/`,
48
+ format: format,
49
+ overlap: Number.parseInt(overlap, 10),
50
+ tileSize: Number.parseInt(tileSize, 10),
51
+ size: {
52
+ width: Number.parseInt(width, 10),
53
+ height: Number.parseInt(height, 10)
54
+ }
55
+ };
56
+ }
57
+ function $f3100f971d08a9cd$var$tileUrl(dzi, level, tile) {
58
+ return `${dzi.imagesUrl}${level.toFixed(0)}/${tile.col.toFixed(0)}_${tile.row.toFixed(0)}.${dzi.format}`;
59
+ }
60
+ function $f3100f971d08a9cd$export$2957f72eb8eb81d4(dzi, camera) {
61
+ const viewWidth = (0, $ls06s$Box2D).size(camera.view)[0];
62
+ const layer = $f3100f971d08a9cd$export$8d84f69083f6d537(dzi.size.width, camera.screenSize[0] / viewWidth);
63
+ const layerResolution = $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer);
64
+ const availableTiles = $f3100f971d08a9cd$export$28da4732af8bb035(dzi, layer);
65
+ const baseLayer = $f3100f971d08a9cd$var$findLargestSingleTileLayer(dzi);
66
+ const baseIndex = {
67
+ col: 0,
68
+ row: 0
69
+ };
70
+ const baseTile = {
71
+ index: baseIndex,
72
+ layer: baseLayer,
73
+ relativeLocation: (0, $ls06s$Box2D).create([
74
+ 0,
75
+ 0
76
+ ], [
77
+ 1,
78
+ 1
79
+ ]),
80
+ url: $f3100f971d08a9cd$var$tileUrl(dzi, baseLayer, baseIndex)
81
+ };
82
+ // note that the tile boxes are in pixels relative to the layer in which they reside
83
+ // the given view is assumed to be a parameter (in the space [0:1]) of the image as a whole
84
+ // so, we must convert literal pixel boxes into their relative position in the image as a whole:
85
+ const tileBoxAsParameter = (tile)=>(0, $ls06s$Box2D).create((0, $ls06s$Vec2).div(tile.minCorner, layerResolution), (0, $ls06s$Vec2).div(tile.maxCorner, layerResolution));
86
+ const tiles = availableTiles.flatMap((row, rowIndex)=>{
87
+ return row.map((tile, colIndex)=>{
88
+ const index = {
89
+ col: colIndex,
90
+ row: rowIndex
91
+ };
92
+ return {
93
+ index: index,
94
+ layer: layer,
95
+ relativeLocation: tileBoxAsParameter(tile),
96
+ url: $f3100f971d08a9cd$var$tileUrl(dzi, layer, index)
97
+ };
98
+ });
99
+ // filter out tiles which are not in view
100
+ }).filter((t)=>!!(0, $ls06s$Box2D).intersection(t.relativeLocation, camera.view));
101
+ return baseLayer < layer ? [
102
+ baseTile,
103
+ ...tiles
104
+ ] : tiles;
105
+ }
106
+ function $f3100f971d08a9cd$export$8d84f69083f6d537(imageWidth, screenWidth) {
107
+ const idealLayer = Math.ceil(Math.log2(screenWidth));
108
+ const biggestRealLayer = Math.ceil(Math.log2(imageWidth));
109
+ return Math.max(0, Math.min(biggestRealLayer, idealLayer));
110
+ }
111
+ /**
112
+ *
113
+ * @param dzi
114
+ * @returns the index of the largest layer which contains only a single tile
115
+ *
116
+ */ function $f3100f971d08a9cd$var$findLargestSingleTileLayer(dzi) {
117
+ return Math.floor(Math.log2(dzi.tileSize));
118
+ }
119
+ function $f3100f971d08a9cd$export$80d9f82e562c6ccd(total, step, overlap) {
120
+ const blocks = [];
121
+ let start = 0;
122
+ while(start < total){
123
+ const next = Math.min(total, start + step + overlap + (start > 0 ? overlap : 0));
124
+ blocks.push({
125
+ min: start,
126
+ max: next
127
+ });
128
+ if (next >= total) return blocks;
129
+ start = next - 2 * overlap;
130
+ }
131
+ return blocks;
132
+ }
133
+ function $f3100f971d08a9cd$var$boxFromRowCol(row, col) {
134
+ return (0, $ls06s$Box2D).create([
135
+ col.min,
136
+ row.min
137
+ ], [
138
+ col.max,
139
+ row.max
140
+ ]);
141
+ }
142
+ const $f3100f971d08a9cd$var$logBaseHalf = (x)=>Math.log2(x) / Math.log2(0.5);
143
+ function $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer) {
144
+ const { size: dim } = dzi;
145
+ const layerMaxSize = 2 ** (Number.isFinite(layer) ? Math.max(0, layer) : 0);
146
+ const size = [
147
+ dim.width,
148
+ dim.height
149
+ ];
150
+ // the question is how many times do we need to divide size
151
+ // by 2 to make it less than layerMaxSize?
152
+ // solve for N, X = the larger the image dimensions:
153
+ // X * (0.5^N) <= maxLayerSize ...
154
+ // 0.5^N = maxLayerSize/X ...
155
+ // log_0.5(maxLayerSize/X) = N
156
+ const bigger = Math.max(size[0], size[1]);
157
+ const N = Math.ceil($f3100f971d08a9cd$var$logBaseHalf(layerMaxSize / bigger));
158
+ return (0, $ls06s$Vec2).ceil((0, $ls06s$Vec2).scale(size, 0.5 ** N));
159
+ }
160
+ function $f3100f971d08a9cd$export$28da4732af8bb035(dzi, layer) {
161
+ const { overlap: overlap, tileSize: tileSize } = dzi;
162
+ // figure out the effective size of a layer by dividing the total size by 2 until its less than our layerMax
163
+ // note: if this all feels weird, its because I can find no reference implementation or specification, its a bit of reverse engineering
164
+ const total = $f3100f971d08a9cd$export$4b1b4ec58947b14e(dzi, layer);
165
+ const rows = $f3100f971d08a9cd$export$80d9f82e562c6ccd(Math.ceil(total[1]), tileSize, overlap);
166
+ const cols = $f3100f971d08a9cd$export$80d9f82e562c6ccd(Math.ceil(total[0]), tileSize, overlap);
167
+ return rows.map((r)=>cols.map((c)=>$f3100f971d08a9cd$var$boxFromRowCol(r, c)));
168
+ }
169
+
170
+
171
+
172
+
173
+
174
+ const $86022f707876cb5b$var$vert = `
175
+ precision highp float;
176
+ uniform vec4 view;
177
+ uniform vec4 tile;
178
+ uniform float depth;
179
+ attribute vec2 position;
180
+ varying vec2 uv;
181
+ void main(){
182
+ uv = position;
183
+ vec2 size = view.zw-view.xy;
184
+ vec2 tileSize = tile.zw-tile.xy;
185
+ vec2 tilePosition = (position * tileSize)+tile.xy;
186
+ vec2 pos =(tilePosition-view.xy)/size;
187
+ // to clip space:
188
+ pos = (pos*2.0)-1.0;
189
+ gl_Position = vec4(pos.x,pos.y,depth,1);
190
+ }`;
191
+ const $86022f707876cb5b$var$frag = `
192
+ precision highp float;
193
+ varying vec2 uv;
194
+ uniform sampler2D img;
195
+
196
+ void main(){
197
+ gl_FragColor = texture2D(img, uv);
198
+ }
199
+ `;
200
+ function $86022f707876cb5b$export$ecfa859e3c9f4021(regl, blend) {
201
+ const cmd = regl({
202
+ vert: $86022f707876cb5b$var$vert,
203
+ frag: $86022f707876cb5b$var$frag,
204
+ depth: {
205
+ enable: true
206
+ },
207
+ blend: blend,
208
+ count: 4,
209
+ primitive: 'triangle fan',
210
+ attributes: {
211
+ position: [
212
+ 0,
213
+ 0,
214
+ 1,
215
+ 0,
216
+ 1,
217
+ 1,
218
+ 0,
219
+ 1
220
+ ]
221
+ },
222
+ uniforms: {
223
+ img: regl.prop('img'),
224
+ view: regl.prop('view'),
225
+ tile: regl.prop('tile'),
226
+ depth: regl.prop('depth')
227
+ },
228
+ framebuffer: regl.prop('target')
229
+ });
230
+ return (p)=>cmd(p);
231
+ }
232
+
233
+
234
+ function $c6cbba1e8cad1f93$export$e530ae442eb0196c(regl) {
235
+ const renderCmd = (0, $86022f707876cb5b$export$ecfa859e3c9f4021)(regl, {
236
+ enable: false
237
+ });
238
+ const fetchDziTile = (tile, _img, _settings, _abort)=>{
239
+ return {
240
+ pixels: ()=>{
241
+ return new Promise((resolve, reject)=>{
242
+ try {
243
+ const img = new Image();
244
+ img.crossOrigin = 'anonymous';
245
+ img.onload = ()=>{
246
+ resolve({
247
+ type: 'texture',
248
+ texture: regl.texture(img),
249
+ bytes: img.width * img.height * 4
250
+ }); // close enough
251
+ };
252
+ img.src = tile.url;
253
+ } catch (err) {
254
+ reject(err);
255
+ }
256
+ });
257
+ }
258
+ };
259
+ };
260
+ return {
261
+ destroy: ()=>{},
262
+ cacheKey: (item, _requestKey, _data, _settings)=>`${item.url}`,
263
+ fetchItemContent: fetchDziTile,
264
+ getVisibleItems: (dzi, settings)=>{
265
+ return (0, $f3100f971d08a9cd$export$2957f72eb8eb81d4)(dzi, settings.camera);
266
+ },
267
+ isPrepared: (cacheData)=>{
268
+ const pixels = cacheData.pixels;
269
+ return !!pixels && pixels.type === 'texture';
270
+ },
271
+ renderItem: (target, tile, _dzi, settings, gpuData)=>{
272
+ const { pixels: pixels } = gpuData;
273
+ const { camera: camera } = settings;
274
+ renderCmd({
275
+ target: target,
276
+ depth: -tile.layer / 1000,
277
+ img: pixels.texture,
278
+ tile: (0, $ls06s$Box2D).toFlatArray(tile.relativeLocation),
279
+ view: (0, $ls06s$Box2D).toFlatArray(camera.view)
280
+ });
281
+ }
282
+ };
283
+ }
284
+ function $c6cbba1e8cad1f93$export$a56de096ea731ba6(regl) {
285
+ return (0, $ls06s$buildAsyncRenderer)($c6cbba1e8cad1f93$export$e530ae442eb0196c(regl));
286
+ }
287
+
288
+
289
+
290
+
291
+ export {$f3100f971d08a9cd$export$fcea0dd904d012d9 as fetchDziMetadata, $f3100f971d08a9cd$export$2957f72eb8eb81d4 as getVisibleTiles, $c6cbba1e8cad1f93$export$e530ae442eb0196c as buildDziRenderer, $c6cbba1e8cad1f93$export$a56de096ea731ba6 as buildAsyncDziRenderer};
292
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","sources":["packages/dzi/src/index.ts","packages/dzi/src/loader.ts","packages/dzi/src/renderer.ts","packages/dzi/src/tile-renderer.ts"],"sourcesContent":["export { fetchDziMetadata, getVisibleTiles, type DziImage, type DziTile } from './loader';\nexport {\n buildDziRenderer,\n buildAsyncDziRenderer,\n type RenderSettings as DziRenderSettings,\n} from './renderer';\n","import { logger } from '@alleninstitute/vis-core';\nimport { Box2D, type Interval, Vec2, type box2D, type vec2 } from '@alleninstitute/vis-geometry';\n\ntype DziTilesRoot = `${string}_files/`;\ntype DziFormat = 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG';\nconst isDziFormat = (format: string): format is DziFormat => ['jpeg', 'png', 'jpg', 'JPG', 'PNG'].includes(format);\n\n// see https://learn.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/cc645077(v=vs.95)?redirectedfrom=MSDN\n// TODO find a less ancient spec...\nexport type DziImage = {\n imagesUrl: DziTilesRoot; // lets say you found a dzi at http://blah.com/deepzoom.dzi\n // imagesUrl would be the path which contains all the files for the actual image tiles:\n // in this example:\n // http://blah.com/deepzoom_files/\n format: DziFormat;\n overlap: number; // in pixels, ADDED every side of any given tile (for example, with overlap=1 and tilesize=256, you could see a jpeg of size 258x258).\n // note that tiles on the edge wont have padding (on a per edge basis!)\n tileSize: number;\n size: {\n width: number;\n height: number;\n };\n};\ntype TileIndex = {\n row: number;\n col: number;\n};\nexport type DziTile = {\n url: string;\n index: TileIndex;\n relativeLocation: box2D;\n layer: number;\n};\n\n/**\n * Fetches the metadata for a Deep Zoom Image (DZI) from a given URL.\n *\n * @param url The URL to a DZI metadata file, which should be an XML file containing the metadata for a Deep Zoom Image\n * @returns A DZI image object containing the metadata for the Deep Zoom Image\n */\nexport async function fetchDziMetadata(url: string): Promise<DziImage | undefined> {\n return fetch(url)\n .then((response) => response.text())\n .then((xmlString) => decodeDzi(xmlString, url));\n}\n\nfunction decodeDzi(xmlString: string, url: string): DziImage | undefined {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xmlString, 'text/xml');\n const err = doc.querySelector('parsererror');\n if (err) {\n logger.error(`Failed to parse DZI XML from ${url} with content:`, xmlString);\n return undefined;\n }\n\n const img = doc.getElementsByTagName('Image')[0];\n const size = doc.getElementsByTagName('Size')?.[0];\n const [format, overlap, tileSize] = [\n img.getAttribute('Format'),\n img.getAttribute('Overlap'),\n img.getAttribute('TileSize'),\n ];\n\n if (!size || !format || !overlap || !tileSize) {\n logger.error(`Failed to parse DZI XML from ${url}: Missing required attributes`);\n return undefined;\n }\n\n const width = size.getAttribute('Width');\n const height = size.getAttribute('Height');\n const splits = url.split('.dzi');\n\n if (!width || !height || !splits || splits.length < 1) {\n logger.error(`Failed to parse DZI XML from ${url}: Missing size or URL splits`);\n return undefined;\n }\n\n if (!isDziFormat(format)) {\n logger.error(`Failed to parse DZI XML from ${url}: Invalid format \"${format}\"`);\n return undefined;\n }\n\n return {\n imagesUrl: `${splits[0]}_files/`,\n format: format,\n overlap: Number.parseInt(overlap, 10),\n tileSize: Number.parseInt(tileSize, 10),\n size: {\n width: Number.parseInt(width, 10),\n height: Number.parseInt(height, 10),\n },\n };\n}\n\nfunction tileUrl(dzi: DziImage, level: number, tile: TileIndex): string {\n return `${dzi.imagesUrl}${level.toFixed(0)}/${tile.col.toFixed(0)}_${tile.row.toFixed(0)}.${dzi.format}`;\n}\n// some quick notes on this deep zoom image format:\n// 1. image / tile names are given by {column}_{row}.{format}\n// 2. a layer (which may contain multiple tiles) is a folder\n// 2.1 that folder contains all the tiles for that layer.\n// layer 0 should contain a single image, 0_0, which is a single pixel!\n// the origin of this tile indexing system is the top left of the image.\n// the spec says that the \"size\" of a layer is 2*layer... but its closer to pow(2, layer).\n// note also that is more of a maximum size... for example I've seen 9/0_0.jpeg have a size of 421x363, both of those are lower than pow(2,9)=512\n// note also that overlap is ADDED to the tile-size, which is a weird choice, as tileSize seems like it must be a power of 2...🤷‍♀️\n\n/**\n *\n * @param dzi the dzi image to read tiles from\n * @param camera.view a parametric box [0:1] relative the the image as a whole. note that 0 is the TOP of the image.\n * @param camera.screenSize the size, in output pixels, at which the requested region will be displayed.\n * @return a list of tiles at the most appropriate resolution which may be fetched and displayed\n */\nexport function getVisibleTiles(dzi: DziImage, camera: { view: box2D; screenSize: vec2 }): DziTile[] {\n const viewWidth = Box2D.size(camera.view)[0];\n const layer = firstSuitableLayer(dzi.size.width, camera.screenSize[0] / viewWidth);\n const layerResolution = imageSizeAtLayer(dzi, layer);\n\n const availableTiles = tilesInLayer(dzi, layer);\n const baseLayer = findLargestSingleTileLayer(dzi);\n const baseIndex: TileIndex = { col: 0, row: 0 };\n const baseTile: DziTile = {\n index: baseIndex,\n layer: baseLayer,\n relativeLocation: Box2D.create([0, 0], [1, 1]),\n url: tileUrl(dzi, baseLayer, baseIndex),\n };\n\n // note that the tile boxes are in pixels relative to the layer in which they reside\n // the given view is assumed to be a parameter (in the space [0:1]) of the image as a whole\n // so, we must convert literal pixel boxes into their relative position in the image as a whole:\n const tileBoxAsParameter = (tile: box2D) =>\n Box2D.create(Vec2.div(tile.minCorner, layerResolution), Vec2.div(tile.maxCorner, layerResolution));\n\n const tiles: DziTile[] = availableTiles\n .flatMap((row, rowIndex) => {\n return row.map((tile, colIndex) => {\n const index = { col: colIndex, row: rowIndex };\n return {\n index,\n layer,\n relativeLocation: tileBoxAsParameter(tile),\n url: tileUrl(dzi, layer, index),\n };\n });\n // filter out tiles which are not in view\n })\n .filter((t) => !!Box2D.intersection(t.relativeLocation, camera.view));\n return baseLayer < layer ? [baseTile, ...tiles] : tiles;\n}\n/**\n * NOTE: THE REMAINDER OF THIS FILE IS EXPORTED ONLY FOR TESTING PURPOSES\n * **/\n\n// starting with the width of an image, and the width of the screen on which to display that image\n// return the highest-numbered (aka highest resolution) dzi-layer folder which has a size that would be lower than the screen resolution\nexport function firstSuitableLayer(imageWidth: number, screenWidth: number) {\n const idealLayer = Math.ceil(Math.log2(screenWidth));\n const biggestRealLayer = Math.ceil(Math.log2(imageWidth));\n return Math.max(0, Math.min(biggestRealLayer, idealLayer));\n}\n\n/**\n *\n * @param dzi\n * @returns the index of the largest layer which contains only a single tile\n *\n */\nfunction findLargestSingleTileLayer(dzi: DziImage): number {\n return Math.floor(Math.log2(dzi.tileSize));\n}\nexport function tileWithOverlap(total: number, step: number, overlap: number): Interval[] {\n const blocks: Interval[] = [];\n let start = 0;\n while (start < total) {\n const next = Math.min(total, start + step + overlap + (start > 0 ? overlap : 0));\n blocks.push({ min: start, max: next });\n if (next >= total) {\n return blocks;\n }\n start = next - 2 * overlap;\n }\n return blocks;\n}\nfunction boxFromRowCol(row: Interval, col: Interval) {\n return Box2D.create([col.min, row.min], [col.max, row.max]);\n}\n\nconst logBaseHalf = (x: number) => Math.log2(x) / Math.log2(0.5);\n\nexport function imageSizeAtLayer(dzi: DziImage, layer: number) {\n const { size: dim } = dzi;\n const layerMaxSize = 2 ** (Number.isFinite(layer) ? Math.max(0, layer) : 0);\n const size: vec2 = [dim.width, dim.height];\n // the question is how many times do we need to divide size\n // by 2 to make it less than layerMaxSize?\n // solve for N, X = the larger the image dimensions:\n // X * (0.5^N) <= maxLayerSize ...\n // 0.5^N = maxLayerSize/X ...\n // log_0.5(maxLayerSize/X) = N\n const bigger = Math.max(size[0], size[1]);\n const N = Math.ceil(logBaseHalf(layerMaxSize / bigger));\n return Vec2.ceil(Vec2.scale(size, 0.5 ** N));\n}\nexport function tilesInLayer(dzi: DziImage, layer: number): box2D[][] {\n const { overlap, tileSize } = dzi;\n // figure out the effective size of a layer by dividing the total size by 2 until its less than our layerMax\n // note: if this all feels weird, its because I can find no reference implementation or specification, its a bit of reverse engineering\n const total: vec2 = imageSizeAtLayer(dzi, layer);\n const rows = tileWithOverlap(Math.ceil(total[1]), tileSize, overlap);\n const cols = tileWithOverlap(Math.ceil(total[0]), tileSize, overlap);\n return rows.map((r) => cols.map((c) => boxFromRowCol(r, c)));\n}\n","import { Box2D, type box2D, type vec2 } from '@alleninstitute/vis-geometry';\nimport { type CachedTexture, type ReglCacheEntry, type Renderer, buildAsyncRenderer } from '@alleninstitute/vis-core';\nimport type REGL from 'regl';\nimport { type DziImage, type DziTile, getVisibleTiles } from './loader';\nimport { buildTileRenderer } from './tile-renderer';\n\nexport type RenderSettings = {\n camera: {\n /**\n * a region of a dzi image, expressed as a relative parameter (eg. [0,0],[1,1] means the whole image)\n */\n view: box2D;\n /**\n * the resolution of the output screen on which to project the region of source pixels given by view\n */\n screenSize: vec2;\n };\n};\n\ntype GpuProps = {\n pixels: CachedTexture;\n};\n/**\n *\n * @param regl a valid REGL context (https://github.com/regl-project/regl)\n * @returns an object which can fetch tiles from a DeepZoomImage, determine the visibility of those tiles given a simple camera, and render said tiles\n * using regl (which uses webGL)\n */\nexport function buildDziRenderer(regl: REGL.Regl): Renderer<DziImage, DziTile, RenderSettings, GpuProps> {\n const renderCmd = buildTileRenderer(regl, { enable: false });\n const fetchDziTile = (\n tile: DziTile,\n _img: DziImage,\n _settings: RenderSettings,\n _abort?: AbortSignal,\n ): Record<string, () => Promise<ReglCacheEntry>> => {\n return {\n pixels: () => {\n return new Promise<ReglCacheEntry>((resolve, reject) => {\n try {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n resolve({\n type: 'texture',\n texture: regl.texture(img),\n bytes: img.width * img.height * 4,\n }); // close enough\n };\n img.src = tile.url;\n } catch (err) {\n reject(err);\n }\n });\n },\n };\n };\n return {\n destroy: () => {}, // no private resources to destroy\n cacheKey: (item, _requestKey, _data, _settings) => `${item.url}`,\n fetchItemContent: fetchDziTile,\n getVisibleItems: (dzi, settings) => {\n return getVisibleTiles(dzi, settings.camera);\n },\n isPrepared: (cacheData): cacheData is GpuProps => {\n const pixels = cacheData.pixels;\n return !!pixels && pixels.type === 'texture';\n },\n renderItem: (target, tile, _dzi, settings, gpuData) => {\n const { pixels } = gpuData;\n const { camera } = settings;\n renderCmd({\n target,\n depth: -tile.layer / 1000,\n img: pixels.texture,\n tile: Box2D.toFlatArray(tile.relativeLocation),\n view: Box2D.toFlatArray(camera.view),\n });\n },\n };\n}\n/**\n *\n * @param regl a valid REGL context (https://github.com/regl-project/regl)\n * @returns a function which creates a \"Frame\" of actions. each action represents loading\n * and subsequently rendering a tile of the image as requested via its configuration -\n * @see RenderSettings\n */\nexport function buildAsyncDziRenderer(regl: REGL.Regl) {\n return buildAsyncRenderer(buildDziRenderer(regl));\n}\n","import type { vec4 } from '@alleninstitute/vis-geometry';\nimport type REGL from 'regl';\ntype Props = {\n img: REGL.Texture2D;\n view: vec4;\n tile: vec4;\n depth: number;\n target: REGL.Framebuffer2D | null;\n};\nconst vert = `\nprecision highp float;\nuniform vec4 view;\nuniform vec4 tile;\nuniform float depth;\nattribute vec2 position;\nvarying vec2 uv;\nvoid main(){\n uv = position;\n vec2 size = view.zw-view.xy;\n vec2 tileSize = tile.zw-tile.xy;\n vec2 tilePosition = (position * tileSize)+tile.xy;\n vec2 pos =(tilePosition-view.xy)/size;\n // to clip space:\n pos = (pos*2.0)-1.0;\n gl_Position = vec4(pos.x,pos.y,depth,1);\n}`;\n\nconst frag = `\nprecision highp float;\nvarying vec2 uv;\nuniform sampler2D img;\n\nvoid main(){\n gl_FragColor = texture2D(img, uv);\n}\n`;\nexport function buildTileRenderer(regl: REGL.Regl, blend: REGL.BlendingOptions) {\n const cmd = regl({\n vert,\n frag,\n depth: {\n enable: true,\n },\n blend,\n count: 4,\n primitive: 'triangle fan',\n attributes: {\n position: [0, 0, 1, 0, 1, 1, 0, 1],\n },\n uniforms: {\n img: regl.prop<Props, 'img'>('img'),\n view: regl.prop<Props, 'view'>('view'),\n tile: regl.prop<Props, 'tile'>('tile'),\n depth: regl.prop<Props, 'depth'>('depth'),\n },\n framebuffer: regl.prop<Props, 'target'>('target'),\n });\n return (p: Props) => cmd(p);\n}\n"],"names":[],"version":3,"file":"module.js.map","sourceRoot":"../../../"}
@@ -0,0 +1,75 @@
1
+ import { box2D, vec2 } from "@alleninstitute/vis-geometry";
2
+ import { CachedTexture, ReglCacheEntry, Renderer, _RenderCallback1, AsyncDataCache, _FrameLifecycle1 } from "@alleninstitute/vis-core";
3
+ import REGL from "regl";
4
+ type DziTilesRoot = `${string}_files/`;
5
+ type DziFormat = 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG';
6
+ export type DziImage = {
7
+ imagesUrl: DziTilesRoot;
8
+ format: DziFormat;
9
+ overlap: number;
10
+ tileSize: number;
11
+ size: {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ };
16
+ type TileIndex = {
17
+ row: number;
18
+ col: number;
19
+ };
20
+ export type DziTile = {
21
+ url: string;
22
+ index: TileIndex;
23
+ relativeLocation: box2D;
24
+ layer: number;
25
+ };
26
+ /**
27
+ * Fetches the metadata for a Deep Zoom Image (DZI) from a given URL.
28
+ *
29
+ * @param url The URL to a DZI metadata file, which should be an XML file containing the metadata for a Deep Zoom Image
30
+ * @returns A DZI image object containing the metadata for the Deep Zoom Image
31
+ */
32
+ export function fetchDziMetadata(url: string): Promise<DziImage | undefined>;
33
+ /**
34
+ *
35
+ * @param dzi the dzi image to read tiles from
36
+ * @param camera.view a parametric box [0:1] relative the the image as a whole. note that 0 is the TOP of the image.
37
+ * @param camera.screenSize the size, in output pixels, at which the requested region will be displayed.
38
+ * @return a list of tiles at the most appropriate resolution which may be fetched and displayed
39
+ */
40
+ export function getVisibleTiles(dzi: DziImage, camera: {
41
+ view: box2D;
42
+ screenSize: vec2;
43
+ }): DziTile[];
44
+ export type DziRenderSettings = {
45
+ camera: {
46
+ /**
47
+ * a region of a dzi image, expressed as a relative parameter (eg. [0,0],[1,1] means the whole image)
48
+ */
49
+ view: box2D;
50
+ /**
51
+ * the resolution of the output screen on which to project the region of source pixels given by view
52
+ */
53
+ screenSize: vec2;
54
+ };
55
+ };
56
+ type GpuProps = {
57
+ pixels: CachedTexture;
58
+ };
59
+ /**
60
+ *
61
+ * @param regl a valid REGL context (https://github.com/regl-project/regl)
62
+ * @returns an object which can fetch tiles from a DeepZoomImage, determine the visibility of those tiles given a simple camera, and render said tiles
63
+ * using regl (which uses webGL)
64
+ */
65
+ export function buildDziRenderer(regl: REGL.Regl): Renderer<DziImage, DziTile, DziRenderSettings, GpuProps>;
66
+ /**
67
+ *
68
+ * @param regl a valid REGL context (https://github.com/regl-project/regl)
69
+ * @returns a function which creates a "Frame" of actions. each action represents loading
70
+ * and subsequently rendering a tile of the image as requested via its configuration -
71
+ * @see RenderSettings
72
+ */
73
+ export function buildAsyncDziRenderer(regl: REGL.Regl): (data: DziImage, settings: DziRenderSettings, callback: _RenderCallback1<DziImage, DziTile>, target: REGL.Framebuffer2D | null, cache: AsyncDataCache<string, string, ReglCacheEntry>) => _FrameLifecycle1;
74
+
75
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"mappings":";;;AAGA,oBAAoB,GAAG,MAAM,SAAS,CAAC;AACvC,iBAAiB,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAKxD,uBAAuB;IACnB,SAAS,EAAE,YAAY,CAAC;IAIxB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAEhB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE;QACF,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAClB,CAAC;CACL,CAAC;AACF,iBAAiB;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACf,CAAC;AACF,sBAAsB;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,CAAC;IACjB,gBAAgB,EAAE,KAAK,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,iCAAuC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAIjF;AA+DD;;;;;;GAMG;AACH,gCAAgC,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,EAAE,CAoCnG;AEhJD,gCAA6B;IACzB,MAAM,EAAE;QACJ;;WAEG;QACH,IAAI,EAAE,KAAK,CAAC;QACZ;;WAEG;QACH,UAAU,EAAE,IAAI,CAAC;KACpB,CAAC;CACL,CAAC;AAEF,gBAAgB;IACZ,MAAM,EAAE,aAAa,CAAC;CACzB,CAAC;AACF;;;;;GAKG;AACH,iCAAiC,IAAI,EAAE,KAAK,IAAI,GAAG,SAAS,QAAQ,EAAE,OAAO,EAAE,iBAAc,EAAE,QAAQ,CAAC,CAoDvG;AACD;;;;;;GAMG;AACH,sCAAsC,IAAI,EAAE,KAAK,IAAI,8MAEpD","sources":["packages/dzi/src/src/loader.ts","packages/dzi/src/src/tile-renderer.ts","packages/dzi/src/src/renderer.ts","packages/dzi/src/src/index.ts","packages/dzi/src/index.ts"],"sourcesContent":[null,null,null,null,"export { fetchDziMetadata, getVisibleTiles, type DziImage, type DziTile } from './loader';\nexport {\n buildDziRenderer,\n buildAsyncDziRenderer,\n type RenderSettings as DziRenderSettings,\n} from './renderer';\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@alleninstitute/vis-dzi",
3
+ "version": "0.0.12",
4
+ "contributors": [
5
+ {
6
+ "name": "Lane Sawyer",
7
+ "email": "lane.sawyer@alleninstitute.org"
8
+ },
9
+ {
10
+ "name": "Noah Shepard",
11
+ "email": "noah.shepard@alleninstitute.org"
12
+ },
13
+ {
14
+ "name": "Skyler Moosman",
15
+ "email": "skyler.moosman@alleninstitute.org"
16
+ },
17
+ {
18
+ "name": "Su Li",
19
+ "email": "su.li@alleninstitute.org"
20
+ },
21
+ {
22
+ "name": "Joel Arbuckle",
23
+ "email": "joel.arbuckle@alleninstitute.org"
24
+ }
25
+ ],
26
+ "license": "BSD-3-Clause",
27
+ "source": "src/index.ts",
28
+ "main": "dist/main.js",
29
+ "types": "dist/types.d.ts",
30
+ "type": "module",
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/AllenInstitute/vis.git"
37
+ },
38
+ "publishConfig": {
39
+ "registry": "https://registry.npmjs.org",
40
+ "access": "public"
41
+ },
42
+ "dependencies": {
43
+ "regl": "2.1.1",
44
+ "@alleninstitute/vis-core": "0.0.6",
45
+ "@alleninstitute/vis-geometry": "0.0.8"
46
+ },
47
+ "scripts": {
48
+ "typecheck": "tsc --noEmit",
49
+ "build": "parcel build --no-cache",
50
+ "dev": "parcel watch --port 1236",
51
+ "test": "vitest --watch",
52
+ "test:ci": "vitest run",
53
+ "coverage": "vitest run --coverage",
54
+ "changelog": "git-cliff -o changelog.md"
55
+ }
56
+ }