bruce-cesium 7.2.1 → 7.2.3
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/dist/bruce-cesium.es5.js +1784 -130
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +1782 -133
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +3 -1
- package/dist/lib/bruce-cesium.js.map +1 -1
- package/dist/lib/internal/image-utils.js +106 -1
- package/dist/lib/internal/image-utils.js.map +1 -1
- package/dist/lib/rendering/displaced-surface-primitive.js +87 -46
- package/dist/lib/rendering/displaced-surface-primitive.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +214 -26
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/texture-frame-series-animator.js +648 -51
- package/dist/lib/rendering/texture-frame-series-animator.js.map +1 -1
- package/dist/lib/rendering/texture-value-labels.js +729 -0
- package/dist/lib/rendering/texture-value-labels.js.map +1 -0
- package/dist/lib/rendering/visuals-register.js +8 -3
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +3 -1
- package/dist/types/internal/image-utils.d.ts +25 -0
- package/dist/types/rendering/displaced-surface-primitive.d.ts +7 -12
- package/dist/types/rendering/texture-frame-series-animator.d.ts +280 -14
- package/dist/types/rendering/texture-value-labels.d.ts +161 -0
- package/dist/types/rendering/visuals-register.d.ts +1 -0
- package/package.json +2 -2
|
@@ -6,26 +6,317 @@ const Cesium = require("cesium");
|
|
|
6
6
|
const image_utils_1 = require("../internal/image-utils");
|
|
7
7
|
var TextureFrameSeriesAnimator;
|
|
8
8
|
(function (TextureFrameSeriesAnimator) {
|
|
9
|
+
TextureFrameSeriesAnimator.LAYOUT_TILES = "tiles";
|
|
10
|
+
// The only generation this renderer draws. Anything else is refused rather than half read: the
|
|
11
|
+
// shapes differ enough that a partial reading produces a plausible wrong surface, which is worse
|
|
12
|
+
// than an empty one.
|
|
13
|
+
TextureFrameSeriesAnimator.SUPPORTED_VERSION = 3;
|
|
14
|
+
// Value in R at 8 bits, or high in R and low in G at 16.
|
|
15
|
+
TextureFrameSeriesAnimator.FORMAT_U8 = "u8";
|
|
16
|
+
TextureFrameSeriesAnimator.FORMAT_RG16 = "rg16";
|
|
17
|
+
// Declared as a field with one value, so a second mode can be added later without an absent
|
|
18
|
+
// Encoding having to mean anything.
|
|
19
|
+
TextureFrameSeriesAnimator.ENCODING_ABSOLUTE = "absolute";
|
|
20
|
+
TextureFrameSeriesAnimator.LAYER_VALUE = "Value";
|
|
21
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MIN = "ValueMin";
|
|
22
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_MAX = "ValueMax";
|
|
23
|
+
TextureFrameSeriesAnimator.LAYER_BASELINE = "Baseline";
|
|
24
|
+
// The per-texel correction that was applied, published only on an archive that was transformed.
|
|
25
|
+
TextureFrameSeriesAnimator.LAYER_VALUE_SHIFT = "ValueShift";
|
|
26
|
+
/**
|
|
27
|
+
* Turns a published value map into the flat shape this renderer works in, or null when it cannot be drawn.
|
|
28
|
+
* @param published the archive's Data.generation
|
|
29
|
+
*/
|
|
30
|
+
function Adapt(published) {
|
|
31
|
+
if (!published || published.Version !== TextureFrameSeriesAnimator.SUPPORTED_VERSION) {
|
|
32
|
+
const found = published && published.Version != null ? published.Version : "none";
|
|
33
|
+
const at = published && published.Identity ? published.Identity.Generated : undefined;
|
|
34
|
+
// In theory should be no files.
|
|
35
|
+
console.warn("bruce-cesium: value map version " + found + " is not supported (this build reads "
|
|
36
|
+
+ TextureFrameSeriesAnimator.SUPPORTED_VERSION + "), so nothing will be drawn for it."
|
|
37
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const geometry = published.Geometry || {};
|
|
41
|
+
const tiles = published.Tiles || [];
|
|
42
|
+
if (geometry.Layout !== TextureFrameSeriesAnimator.LAYOUT_TILES || tiles.length === 0) {
|
|
43
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
44
|
+
console.warn("bruce-cesium: value map declares no tiles, so nothing will be drawn for it."
|
|
45
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const value = published.Value || {};
|
|
49
|
+
if (value.Encoding && value.Encoding !== TextureFrameSeriesAnimator.ENCODING_ABSOLUTE) {
|
|
50
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
51
|
+
console.warn("bruce-cesium: value map encoding " + value.Encoding + " is not supported"
|
|
52
|
+
+ " (this build reads " + TextureFrameSeriesAnimator.ENCODING_ABSOLUTE + "), so nothing will be drawn for it."
|
|
53
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating." : ""));
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const series = published.Series || {};
|
|
57
|
+
const layerRange = declaredRange(published, TextureFrameSeriesAnimator.LAYER_VALUE_MIN);
|
|
58
|
+
const flat = {
|
|
59
|
+
Frames: [],
|
|
60
|
+
ValueMin: value.Min,
|
|
61
|
+
ValueMax: value.Max,
|
|
62
|
+
Format: value.Format || TextureFrameSeriesAnimator.FORMAT_U8,
|
|
63
|
+
Encoding: value.Encoding || TextureFrameSeriesAnimator.ENCODING_ABSOLUTE,
|
|
64
|
+
Units: value.Units,
|
|
65
|
+
West: geometry.West,
|
|
66
|
+
East: geometry.East,
|
|
67
|
+
South: geometry.South,
|
|
68
|
+
North: geometry.North,
|
|
69
|
+
ResolutionX: geometry.ResolutionX,
|
|
70
|
+
ResolutionY: geometry.ResolutionY,
|
|
71
|
+
PixelPlacement: geometry.PixelPlacement,
|
|
72
|
+
Times: series.Times,
|
|
73
|
+
ExtremesValueMin: layerRange ? layerRange.min : undefined,
|
|
74
|
+
ExtremesValueMax: layerRange ? layerRange.max : undefined,
|
|
75
|
+
// The delivered values are what gets drawn. What the source called them, and any shift
|
|
76
|
+
// applied on the way, travel for labels and analysis rather than for placement.
|
|
77
|
+
SourceVerticalDatum: published.ValueSource ? published.ValueSource.VerticalDatum : undefined,
|
|
78
|
+
Transform: published.ValueSource ? published.ValueSource.Transform : undefined,
|
|
79
|
+
SourceUnits: published.ValueSource ? published.ValueSource.Units : undefined,
|
|
80
|
+
SourceValueMin: published.ValueSource ? published.ValueSource.Min : undefined,
|
|
81
|
+
SourceValueMax: published.ValueSource ? published.ValueSource.Max : undefined
|
|
82
|
+
};
|
|
83
|
+
flat.Tiles = tiles.map((tile) => adaptTile(tile, series.Times));
|
|
84
|
+
// The coarsest tile covers the whole extent, so it is what a caller wanting one time list reads.
|
|
85
|
+
flat.Frames = flat.Tiles[0].Frames;
|
|
86
|
+
warnIfUnshifted(published, flat);
|
|
87
|
+
return flat;
|
|
88
|
+
}
|
|
89
|
+
TextureFrameSeriesAnimator.Adapt = Adapt;
|
|
90
|
+
/*
|
|
91
|
+
* Says so when an archive's heights are not on the ellipsoid they will be drawn against.
|
|
92
|
+
*/
|
|
93
|
+
function warnIfUnshifted(published, flat) {
|
|
94
|
+
const datum = flat.SourceVerticalDatum;
|
|
95
|
+
if (!datum || datum === TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL || flat.Transform) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const at = published.Identity ? published.Identity.Generated : undefined;
|
|
99
|
+
console.warn("bruce-cesium: value map heights are in " + datum + " and report no transform, so"
|
|
100
|
+
+ " they are not ellipsoidal and will draw at the wrong height when placed absolutely."
|
|
101
|
+
+ (at ? " The archive was generated at " + at + " and needs regenerating with the shift"
|
|
102
|
+
+ " applied." : ""));
|
|
103
|
+
}
|
|
104
|
+
/*
|
|
105
|
+
* The Min and Max a named layer declares, since a statistics layer is encoded over the true data
|
|
106
|
+
* range rather than over the frames' own.
|
|
107
|
+
*/
|
|
108
|
+
function declaredRange(published, name) {
|
|
109
|
+
const layers = published.Layers || [];
|
|
110
|
+
for (const layer of layers) {
|
|
111
|
+
if (layer.Name === name && typeof layer.Min === "number" && typeof layer.Max === "number") {
|
|
112
|
+
return { min: layer.Min, max: layer.Max };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/*
|
|
118
|
+
* One tile, with its per-time value rasters lined up against the shared time list.
|
|
119
|
+
*/
|
|
120
|
+
function adaptTile(tile, times) {
|
|
121
|
+
const layers = tile.Layers || {};
|
|
122
|
+
const perTime = layers[TextureFrameSeriesAnimator.LAYER_VALUE] || [];
|
|
123
|
+
const frames = perTime.map((entry, index) => ({
|
|
124
|
+
Timestamp: times && times[index] ? times[index] : "",
|
|
125
|
+
ByteOffset: entry.Bytes.Offset,
|
|
126
|
+
ByteLength: entry.Bytes.Length
|
|
127
|
+
}));
|
|
128
|
+
return {
|
|
129
|
+
Level: tile.Level,
|
|
130
|
+
X: tile.X,
|
|
131
|
+
Y: tile.Y,
|
|
132
|
+
West: tile.West,
|
|
133
|
+
East: tile.East,
|
|
134
|
+
South: tile.South,
|
|
135
|
+
North: tile.North,
|
|
136
|
+
TexelMetres: tile.TexelMetres,
|
|
137
|
+
ResolutionX: tile.ResolutionX,
|
|
138
|
+
ResolutionY: tile.ResolutionY,
|
|
139
|
+
Frames: frames,
|
|
140
|
+
Floor: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MIN]),
|
|
141
|
+
Ceiling: single(layers[TextureFrameSeriesAnimator.LAYER_VALUE_MAX]),
|
|
142
|
+
BaselineMask: single(layers[TextureFrameSeriesAnimator.LAYER_BASELINE])
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function single(layer) {
|
|
146
|
+
if (!layer || !layer.Bytes) {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
return { ByteOffset: layer.Bytes.Offset, ByteLength: layer.Bytes.Length };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* How to turn a delivered value into the number the source published, or null when the archive
|
|
153
|
+
* does not say enough to do it.
|
|
154
|
+
* @param metadata the archive's Data.generation
|
|
155
|
+
*/
|
|
156
|
+
function SourceMap(metadata) {
|
|
157
|
+
if (!metadata) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const { ValueMin, ValueMax, SourceValueMin, SourceValueMax } = metadata;
|
|
161
|
+
if (typeof ValueMin !== "number" || typeof ValueMax !== "number"
|
|
162
|
+
|| typeof SourceValueMin !== "number" || typeof SourceValueMax !== "number") {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const delivered = ValueMax - ValueMin;
|
|
166
|
+
// A single delivered value describes no line, so all that can be recovered is where it sits.
|
|
167
|
+
const scale = delivered !== 0 ? (SourceValueMax - SourceValueMin) / delivered : 1;
|
|
168
|
+
return { scale, offset: SourceValueMin - ValueMin * scale };
|
|
169
|
+
}
|
|
170
|
+
TextureFrameSeriesAnimator.SourceMap = SourceMap;
|
|
171
|
+
/**
|
|
172
|
+
* A delivered value in the source's own terms, or unchanged when it cannot be turned back.
|
|
173
|
+
* @param map from SourceMap(), null when the archive does not say enough
|
|
174
|
+
*/
|
|
175
|
+
function ToSource(map, value) {
|
|
176
|
+
return map ? value * map.scale + map.offset : value;
|
|
177
|
+
}
|
|
178
|
+
TextureFrameSeriesAnimator.ToSource = ToSource;
|
|
179
|
+
/**
|
|
180
|
+
* Whether the archive's frames carry a 16 bit value packed across R and G.
|
|
181
|
+
*
|
|
182
|
+
* Every consumer that reads a texel has to ask: taking R alone from a packed frame yields a
|
|
183
|
+
* plausible surface quantised to 255 steps of the full range rather than an obvious failure.
|
|
184
|
+
* @param metadata the archive's Data.generation
|
|
185
|
+
*/
|
|
186
|
+
function IsPackedValue(metadata) {
|
|
187
|
+
return Boolean(metadata && metadata.Format === TextureFrameSeriesAnimator.FORMAT_RG16);
|
|
188
|
+
}
|
|
189
|
+
TextureFrameSeriesAnimator.IsPackedValue = IsPackedValue;
|
|
190
|
+
/**
|
|
191
|
+
* The normalised 0 to 1 value at a texel, from whichever format the archive packed it in.
|
|
192
|
+
* @param metadata the archive's Data.generation
|
|
193
|
+
* @param pixels untinted RGBA value pixels
|
|
194
|
+
* @param at index of the texel's red channel
|
|
195
|
+
*/
|
|
196
|
+
function NormalisedAt(metadata, pixels, at) {
|
|
197
|
+
if (IsPackedValue(metadata)) {
|
|
198
|
+
return (pixels[at] * 256 + pixels[at + 1]) / 65535;
|
|
199
|
+
}
|
|
200
|
+
return pixels[at] / 255;
|
|
201
|
+
}
|
|
202
|
+
TextureFrameSeriesAnimator.NormalisedAt = NormalisedAt;
|
|
203
|
+
// Values already sit on the ellipsoid Cesium measures against, so nothing is added to place them.
|
|
204
|
+
TextureFrameSeriesAnimator.DATUM_ELLIPSOIDAL = "WGS84";
|
|
205
|
+
/**
|
|
206
|
+
* Whether the values are heights against a vertical datum rather than a thickness.
|
|
207
|
+
*
|
|
208
|
+
* A thickness rises from wherever the polygon sits and a height already knows where it belongs, so
|
|
209
|
+
* this is what decides whether the polygon's own altitude may be added underneath it.
|
|
210
|
+
* @param metadata the archive's Data.generation
|
|
211
|
+
*/
|
|
212
|
+
function IsDatumHeight(metadata) {
|
|
213
|
+
return Boolean(metadata && metadata.SourceVerticalDatum);
|
|
214
|
+
}
|
|
215
|
+
TextureFrameSeriesAnimator.IsDatumHeight = IsDatumHeight;
|
|
216
|
+
/**
|
|
217
|
+
* The archive's tiles, which Adapt() guarantees are present.
|
|
218
|
+
* @param metadata the archive's Data.generation
|
|
219
|
+
*/
|
|
220
|
+
function TilesOf(metadata) {
|
|
221
|
+
return (metadata && metadata.Tiles) || [];
|
|
222
|
+
}
|
|
223
|
+
TextureFrameSeriesAnimator.TilesOf = TilesOf;
|
|
224
|
+
/**
|
|
225
|
+
* Turns a value in the attribute's own units into the 0 to 1 position the ramp is indexed by.
|
|
226
|
+
*
|
|
227
|
+
* Stops are authored in real units, since "hide anything under 0.1 m" is the sentence a user
|
|
228
|
+
* actually has, and the archive's own published range is what makes that expressible.
|
|
229
|
+
* @param metadata the archive's Data.generation
|
|
230
|
+
* @param value in the attribute's units
|
|
231
|
+
*/
|
|
232
|
+
function NormalisePosition(metadata, value) {
|
|
233
|
+
const lo = metadata && typeof metadata.ValueMin === "number" ? metadata.ValueMin : 0;
|
|
234
|
+
const hi = metadata && typeof metadata.ValueMax === "number" ? metadata.ValueMax : 1;
|
|
235
|
+
const span = hi - lo;
|
|
236
|
+
if (!(span > 0)) {
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
return Math.min(1, Math.max(0, (value - lo) / span));
|
|
240
|
+
}
|
|
241
|
+
TextureFrameSeriesAnimator.NormalisePosition = NormalisePosition;
|
|
9
242
|
/**
|
|
10
243
|
* Detects whether a ClientFile's `Data.generation` metadata describes a frame archive rather than a single static image,
|
|
11
244
|
* so a caller can decide whether to construct an Animator or fall back to the existing static-texture path.
|
|
12
|
-
* @param data ClientFile.Data
|
|
245
|
+
* @param data ClientFile.Data
|
|
13
246
|
*/
|
|
14
247
|
function IsFrameArchiveMetadata(data) {
|
|
15
|
-
|
|
248
|
+
const generation = data && data.generation;
|
|
249
|
+
if (!generation) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return (typeof generation.Version === "number" ||
|
|
253
|
+
(Array.isArray(generation.Frames) && generation.Frames.length > 0));
|
|
16
254
|
}
|
|
17
255
|
TextureFrameSeriesAnimator.IsFrameArchiveMetadata = IsFrameArchiveMetadata;
|
|
18
|
-
//
|
|
256
|
+
// First resolvable static number out of a calculator field list, since that is all a border needs.
|
|
257
|
+
function resolveNumber(fields) {
|
|
258
|
+
if (!fields) {
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
for (const field of fields) {
|
|
262
|
+
const value = Number(field && field.value);
|
|
263
|
+
if (Number.isFinite(value)) {
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
// First resolvable static colour out of a calculator field list.
|
|
270
|
+
function resolveColor(fields) {
|
|
271
|
+
if (!fields) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
for (const field of fields) {
|
|
275
|
+
const parsed = typeof (field === null || field === void 0 ? void 0 : field.value) === "string"
|
|
276
|
+
? bruce_models_1.Color.ColorFromStr(field.value)
|
|
277
|
+
: null;
|
|
278
|
+
if (parsed) {
|
|
279
|
+
return parsed;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
/*
|
|
285
|
+
* Everything about an Animator that a style can change, as a comparable string.
|
|
286
|
+
*/
|
|
287
|
+
function AppearanceSignature(options) {
|
|
288
|
+
var _a, _b, _c;
|
|
289
|
+
return JSON.stringify([
|
|
290
|
+
(_a = options.textureColorMask) !== null && _a !== void 0 ? _a : null,
|
|
291
|
+
(_b = options.cellBorder) !== null && _b !== void 0 ? _b : null,
|
|
292
|
+
(_c = options.cellTexels) !== null && _c !== void 0 ? _c : null,
|
|
293
|
+
Boolean(options.maskBaseline)
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
TextureFrameSeriesAnimator.AppearanceSignature = AppearanceSignature;
|
|
297
|
+
const MAX_COMPOSITE_TEXELS = 2048;
|
|
19
298
|
const VALUE_DILATE_PASSES = 2;
|
|
20
299
|
/*
|
|
21
|
-
*
|
|
300
|
+
* Rewrites a packed 16 bit value as the 8 bit grey the colour ramps index on.
|
|
22
301
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
302
|
+
* All three channels, since a ramp is free to read any of them and a leftover low byte in G would
|
|
303
|
+
* show up as a green cast on the one that reads them all.
|
|
304
|
+
*/
|
|
305
|
+
function flattenPacked(pixels, packed) {
|
|
306
|
+
if (!packed) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
for (let i = 0; i < pixels.length; i += 4) {
|
|
310
|
+
const grey = Math.round((pixels[i] * 256 + pixels[i + 1]) / 65535 * 255);
|
|
311
|
+
pixels[i] = grey;
|
|
312
|
+
pixels[i + 1] = grey;
|
|
313
|
+
pixels[i + 2] = grey;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/*
|
|
317
|
+
* Bleeds covered values outward into uncovered texels, leaving alpha untouched.
|
|
27
318
|
*/
|
|
28
|
-
function dilateValues(value, width, height, passes) {
|
|
319
|
+
function dilateValues(value, width, height, passes, packed) {
|
|
29
320
|
const texels = width * height;
|
|
30
321
|
const filled = new Uint8Array(texels);
|
|
31
322
|
for (let p = 0; p < texels; p++) {
|
|
@@ -49,15 +340,25 @@ var TextureFrameSeriesAnimator;
|
|
|
49
340
|
if (nx < 0 || ny < 0 || nx >= width || ny >= height || !wasFilled[ny * width + nx]) {
|
|
50
341
|
continue;
|
|
51
342
|
}
|
|
52
|
-
|
|
343
|
+
const at = (ny * width + nx) * 4;
|
|
344
|
+
// Averaged as a value rather than per byte: averaging a packed low byte
|
|
345
|
+
// on its own wraps at every 256th step and speckles the bled edge.
|
|
346
|
+
sum += packed ? source[at] * 256 + source[at + 1] : source[at];
|
|
53
347
|
hits++;
|
|
54
348
|
}
|
|
55
349
|
}
|
|
56
350
|
if (hits > 0) {
|
|
57
351
|
const v = Math.round(sum / hits);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
352
|
+
if (packed) {
|
|
353
|
+
value[p * 4] = (v >> 8) & 255;
|
|
354
|
+
value[p * 4 + 1] = v & 255;
|
|
355
|
+
value[p * 4 + 2] = 0;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
value[p * 4] = v;
|
|
359
|
+
value[p * 4 + 1] = v;
|
|
360
|
+
value[p * 4 + 2] = v;
|
|
361
|
+
}
|
|
61
362
|
filled[p] = 1;
|
|
62
363
|
}
|
|
63
364
|
}
|
|
@@ -69,7 +370,7 @@ var TextureFrameSeriesAnimator;
|
|
|
69
370
|
const DEFAULT_HIGH_COLOR = { red: 21, green: 96, blue: 196, alpha: 0.92 };
|
|
70
371
|
class Animator {
|
|
71
372
|
constructor(options) {
|
|
72
|
-
var _a, _b;
|
|
373
|
+
var _a, _b, _c;
|
|
73
374
|
this.removeOnTick = null;
|
|
74
375
|
this.removeCrossfadeTick = null;
|
|
75
376
|
this.disposed = false;
|
|
@@ -97,6 +398,9 @@ var TextureFrameSeriesAnimator;
|
|
|
97
398
|
this.poolIdx = 0;
|
|
98
399
|
this.valueDims = null;
|
|
99
400
|
this.presentVersion = 0;
|
|
401
|
+
this.extremesLoad = null;
|
|
402
|
+
this.floorPixels = null;
|
|
403
|
+
this.ceilingPixels = null;
|
|
100
404
|
if (!options.entity.polygon) {
|
|
101
405
|
throw new Error("TextureFrameSeriesAnimator requires an entity with polygon graphics.");
|
|
102
406
|
}
|
|
@@ -108,16 +412,40 @@ var TextureFrameSeriesAnimator;
|
|
|
108
412
|
this.archiveUrl = options.archiveUrl;
|
|
109
413
|
this.frames = options.frames;
|
|
110
414
|
this.crossfadeMs = (_a = options.crossfadeMs) !== null && _a !== void 0 ? _a : DEFAULT_CROSSFADE_MS;
|
|
415
|
+
this.appearance = AppearanceSignature(options);
|
|
111
416
|
const mask = options.textureColorMask;
|
|
112
417
|
this.lowColor = (mask && bruce_models_1.Color.ColorFromStr(mask.minColor)) || DEFAULT_LOW_COLOR;
|
|
113
418
|
this.highColor = (mask && bruce_models_1.Color.ColorFromStr(mask.maxColor)) || DEFAULT_HIGH_COLOR;
|
|
419
|
+
// Positions are authored in the attribute's units and normalised once here, so the
|
|
420
|
+
// per-pixel loop stays a comparison against 0 to 1 like the two-colour path.
|
|
421
|
+
const points = mask && mask.points;
|
|
422
|
+
this.rampStops = (points && points.length > 0)
|
|
423
|
+
? points.map((p) => ({
|
|
424
|
+
position: NormalisePosition(options.metadata, p.position),
|
|
425
|
+
color: bruce_models_1.Color.ColorFromStr(p.color) || DEFAULT_LOW_COLOR
|
|
426
|
+
}))
|
|
427
|
+
: null;
|
|
428
|
+
const border = options.cellBorder;
|
|
429
|
+
const borderWidth = border ? resolveNumber(border.width) : 0;
|
|
430
|
+
const borderColor = border ? resolveColor(border.color) : null;
|
|
431
|
+
// Zero width or a fully transparent colour means no grid, matching how the polygon's own
|
|
432
|
+
// outline behaves, so an existing style that wants no border keeps getting none.
|
|
433
|
+
this.cellBorder = (border && borderWidth > 0 && borderColor && borderColor.alpha > 0)
|
|
434
|
+
? {
|
|
435
|
+
cellTexels: Math.max(1, Math.round((_b = options.cellTexels) !== null && _b !== void 0 ? _b : 1)),
|
|
436
|
+
widthTexels: borderWidth,
|
|
437
|
+
color: borderColor
|
|
438
|
+
}
|
|
439
|
+
: null;
|
|
114
440
|
this.frameDates = this.frames.map((f) => Cesium.JulianDate.fromIso8601(f.Timestamp));
|
|
115
441
|
this.frameCache = new Array(this.frames.length).fill(null);
|
|
116
442
|
this.frameDims = new Array(this.frames.length).fill(null);
|
|
117
443
|
this.valueCache = new Array(this.frames.length).fill(null);
|
|
118
444
|
this.produceValueCanvas = Boolean(options.produceValueCanvas);
|
|
119
|
-
this.
|
|
120
|
-
this.
|
|
445
|
+
this.metadata = options.metadata;
|
|
446
|
+
this.drapeExtent = options.drapeExtent;
|
|
447
|
+
this.tiles = TilesOf(options.metadata);
|
|
448
|
+
this.maskBaseline = (_c = options.maskBaseline) !== null && _c !== void 0 ? _c : this.tiles.some((tile) => Boolean(tile.BaselineMask));
|
|
121
449
|
this.valueCanvas = this.produceValueCanvas ? document.createElement("canvas") : null;
|
|
122
450
|
this.driveMaterial = options.driveMaterial !== false;
|
|
123
451
|
this.originalMaterial = options.entity.polygon.material;
|
|
@@ -153,6 +481,10 @@ var TextureFrameSeriesAnimator;
|
|
|
153
481
|
IsDisposed() {
|
|
154
482
|
return this.disposed;
|
|
155
483
|
}
|
|
484
|
+
// What this Animator was built to look like, for deciding whether it can be reused.
|
|
485
|
+
GetAppearanceSignature() {
|
|
486
|
+
return this.appearance;
|
|
487
|
+
}
|
|
156
488
|
/**
|
|
157
489
|
* The archive URL this instance was constructed with,
|
|
158
490
|
* lets a caller re-rendering the same entity tell whether an existing instance is already correct, without reaching into private state.
|
|
@@ -167,6 +499,15 @@ var TextureFrameSeriesAnimator;
|
|
|
167
499
|
GetImageProperty() {
|
|
168
500
|
return this.imageProperty;
|
|
169
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* The presented frame's TINTED pixels, where alpha is what the ramp actually painted.
|
|
504
|
+
*
|
|
505
|
+
* Distinct from the value canvas, whose alpha only says a texel has data. A cell can hold a
|
|
506
|
+
* reading and still be painted nothing, which is exactly what a hidden floor band does.
|
|
507
|
+
*/
|
|
508
|
+
GetDisplayedPixels() {
|
|
509
|
+
return this.displayedPixels;
|
|
510
|
+
}
|
|
170
511
|
/**
|
|
171
512
|
* The untinted canvas carrying the presented frame's value in RGB and its coverage in alpha,
|
|
172
513
|
* or null unless the instance was constructed with produceValueCanvas.
|
|
@@ -279,10 +620,20 @@ var TextureFrameSeriesAnimator;
|
|
|
279
620
|
});
|
|
280
621
|
}
|
|
281
622
|
async fetchAndTint(idx) {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
623
|
+
const tiles = this.tiles;
|
|
624
|
+
// Every tile's frame for one timestep sits contiguously, because the generator writes
|
|
625
|
+
// frame major. So a tiled frame is still ONE range request, not one per tile.
|
|
626
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
627
|
+
let hi = 0;
|
|
628
|
+
for (const tile of tiles) {
|
|
629
|
+
const at = tile.Frames[idx];
|
|
630
|
+
if (!at) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
634
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
635
|
+
}
|
|
636
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
286
637
|
const buffer = await response.arrayBuffer();
|
|
287
638
|
if (this.disposed) {
|
|
288
639
|
return;
|
|
@@ -293,7 +644,7 @@ var TextureFrameSeriesAnimator;
|
|
|
293
644
|
if (this.disposed) {
|
|
294
645
|
return;
|
|
295
646
|
}
|
|
296
|
-
const decoded = await this.
|
|
647
|
+
const decoded = await this.composeTiles(tiles, idx, buffer, lo);
|
|
297
648
|
if (this.disposed) {
|
|
298
649
|
return;
|
|
299
650
|
}
|
|
@@ -304,39 +655,292 @@ var TextureFrameSeriesAnimator;
|
|
|
304
655
|
this.beginCrossfadeTo(idx);
|
|
305
656
|
}
|
|
306
657
|
}
|
|
307
|
-
|
|
308
|
-
*
|
|
658
|
+
/*
|
|
659
|
+
* Draws every tile of one frame into a single raster covering the drape extent.
|
|
309
660
|
*/
|
|
310
|
-
async
|
|
311
|
-
const
|
|
312
|
-
const
|
|
313
|
-
let image;
|
|
314
|
-
try {
|
|
315
|
-
image = await (0, image_utils_1.loadImage)(objectUrl);
|
|
316
|
-
}
|
|
317
|
-
finally {
|
|
318
|
-
URL.revokeObjectURL(objectUrl);
|
|
319
|
-
}
|
|
661
|
+
async composeTiles(tiles, idx, buffer, spanStart) {
|
|
662
|
+
const target = this.compositeExtent();
|
|
663
|
+
const size = this.compositeSize(tiles, target);
|
|
320
664
|
const canvas = document.createElement("canvas");
|
|
321
|
-
canvas.width =
|
|
322
|
-
canvas.height =
|
|
665
|
+
canvas.width = size.width;
|
|
666
|
+
canvas.height = size.height;
|
|
323
667
|
const ctx = canvas.getContext("2d");
|
|
324
|
-
ctx.
|
|
325
|
-
|
|
668
|
+
ctx.imageSmoothingEnabled = false;
|
|
669
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
670
|
+
const images = await this.decodeSlices(buffer, spanStart, tiles.map((tile) => tile.Frames[idx]));
|
|
671
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
672
|
+
const image = images[i];
|
|
673
|
+
if (!image || this.disposed) {
|
|
674
|
+
continue;
|
|
675
|
+
}
|
|
676
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
677
|
+
ctx.drawImage(image, at.x, at.y, at.w, at.h);
|
|
678
|
+
}
|
|
679
|
+
return this.tint(ctx.getImageData(0, 0, size.width, size.height));
|
|
680
|
+
}
|
|
681
|
+
/*
|
|
682
|
+
* Where a composited frame is draped, which is the polygon's rectangle when there is one.
|
|
683
|
+
*/
|
|
684
|
+
compositeExtent() {
|
|
685
|
+
if (this.drapeExtent) {
|
|
686
|
+
return this.drapeExtent;
|
|
687
|
+
}
|
|
688
|
+
const m = this.metadata;
|
|
689
|
+
return {
|
|
690
|
+
West: m && m.West != null ? m.West : -180,
|
|
691
|
+
East: m && m.East != null ? m.East : 180,
|
|
692
|
+
South: m && m.South != null ? m.South : -90,
|
|
693
|
+
North: m && m.North != null ? m.North : 90
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
/*
|
|
697
|
+
* Composite resolution, capped by what can be uploaded as a texture every frame.
|
|
698
|
+
*/
|
|
699
|
+
compositeSize(tiles, target) {
|
|
700
|
+
let finest = Number.MAX_VALUE;
|
|
701
|
+
for (const tile of tiles) {
|
|
702
|
+
if (tile.TexelMetres > 0) {
|
|
703
|
+
finest = Math.min(finest, tile.TexelMetres);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (!(finest > 0) || finest === Number.MAX_VALUE) {
|
|
707
|
+
finest = 1;
|
|
708
|
+
}
|
|
709
|
+
const mid = (target.South + target.North) / 2 * Math.PI / 180;
|
|
710
|
+
const widthM = Math.max((target.East - target.West) * 111320 * Math.cos(mid), 1);
|
|
711
|
+
const heightM = Math.max((target.North - target.South) * 110574, 1);
|
|
712
|
+
let width = Math.ceil(widthM / finest);
|
|
713
|
+
let height = Math.ceil(heightM / finest);
|
|
714
|
+
const longest = Math.max(width, height);
|
|
715
|
+
if (longest > MAX_COMPOSITE_TEXELS) {
|
|
716
|
+
const shrink = MAX_COMPOSITE_TEXELS / longest;
|
|
717
|
+
width = Math.max(1, Math.round(width * shrink));
|
|
718
|
+
height = Math.max(1, Math.round(height * shrink));
|
|
719
|
+
}
|
|
720
|
+
return { width: Math.max(1, width), height: Math.max(1, height) };
|
|
721
|
+
}
|
|
722
|
+
/*
|
|
723
|
+
* Applies the ramp, borders and baseline mask to raw value pixels, whatever produced them.
|
|
724
|
+
*/
|
|
725
|
+
tint(imageData) {
|
|
726
|
+
const canvas = { width: imageData.width, height: imageData.height };
|
|
326
727
|
// Copied before tinting: the ramp is a lerp between two colours, so the value cannot be
|
|
327
728
|
// recovered from the tinted pixels afterwards.
|
|
328
729
|
const valuePixels = this.produceValueCanvas ? new Uint8ClampedArray(imageData.data) : undefined;
|
|
329
|
-
|
|
730
|
+
// The ramp indexes on R as an 8 bit grey, so a packed frame is flattened to one here
|
|
731
|
+
// rather than teaching every ramp function a second format.
|
|
732
|
+
// The copy above keeps the packed pair, which is what displacement and labels read.
|
|
733
|
+
flattenPacked(imageData.data, IsPackedValue(this.metadata));
|
|
734
|
+
// Stops win when supplied: they can express a band and a hidden floor, which a two
|
|
735
|
+
// colour ramp cannot. The pair stays the fallback so an older style still draws.
|
|
736
|
+
if (this.rampStops && this.rampStops.length > 0) {
|
|
737
|
+
(0, image_utils_1.ApplyGradientStops)(imageData, this.rampStops);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
(0, image_utils_1.ApplyGrayscaleColorMask)(imageData, this.lowColor, this.highColor);
|
|
741
|
+
}
|
|
742
|
+
if (this.cellBorder) {
|
|
743
|
+
(0, image_utils_1.ApplyCellBorders)(imageData, this.cellBorder.cellTexels, this.cellBorder.widthTexels, this.cellBorder.color);
|
|
744
|
+
}
|
|
330
745
|
this.applyBaselineMask(imageData.data, valuePixels, canvas.width, canvas.height);
|
|
331
746
|
if (valuePixels) {
|
|
332
|
-
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES);
|
|
747
|
+
dilateValues(valuePixels, canvas.width, canvas.height, VALUE_DILATE_PASSES, IsPackedValue(this.metadata));
|
|
333
748
|
}
|
|
334
749
|
return { pixels: imageData.data, width: canvas.width, height: canvas.height, valuePixels };
|
|
335
750
|
}
|
|
751
|
+
/*
|
|
752
|
+
* Paints one frame's composited pixels onto a canvas.
|
|
753
|
+
*/
|
|
754
|
+
writeInto(canvas, pixels, dims) {
|
|
755
|
+
canvas.width = dims.width;
|
|
756
|
+
canvas.height = dims.height;
|
|
757
|
+
const ctx = canvas.getContext("2d");
|
|
758
|
+
const image = ctx.createImageData(dims.width, dims.height);
|
|
759
|
+
image.data.set(pixels);
|
|
760
|
+
ctx.putImageData(image, 0, 0);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* The archive's per-cell floor and ceiling rasters, once EnsureExtremes has resolved.
|
|
764
|
+
*/
|
|
765
|
+
GetExtremes() {
|
|
766
|
+
return { floor: this.floorPixels, ceiling: this.ceilingPixels };
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Fetches and decodes the floor and ceiling rasters, at most once.
|
|
770
|
+
*/
|
|
771
|
+
EnsureExtremes() {
|
|
772
|
+
if (!this.extremesLoad) {
|
|
773
|
+
this.extremesLoad = this.loadTiledExtremes();
|
|
774
|
+
}
|
|
775
|
+
return this.extremesLoad;
|
|
776
|
+
}
|
|
777
|
+
/*
|
|
778
|
+
* Composites each tile's baseline mask into one covering the composited frames.
|
|
779
|
+
*/
|
|
780
|
+
async loadTiledBaselineMask() {
|
|
781
|
+
const tiles = this.tiles;
|
|
782
|
+
const entries = tiles.map((tile) => tile.BaselineMask);
|
|
783
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
784
|
+
let hi = 0;
|
|
785
|
+
for (const at of entries) {
|
|
786
|
+
if (!at) {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
790
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
791
|
+
}
|
|
792
|
+
if (!(hi > lo)) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
try {
|
|
796
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
797
|
+
const block = await response.arrayBuffer();
|
|
798
|
+
if (this.disposed) {
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const images = await this.decodeSlices(block, lo, entries);
|
|
802
|
+
if (this.disposed) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const target = this.compositeExtent();
|
|
806
|
+
const size = this.compositeSize(tiles, target);
|
|
807
|
+
const canvas = document.createElement("canvas");
|
|
808
|
+
canvas.width = size.width;
|
|
809
|
+
canvas.height = size.height;
|
|
810
|
+
const ctx = canvas.getContext("2d");
|
|
811
|
+
ctx.imageSmoothingEnabled = false;
|
|
812
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
813
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
814
|
+
if (!images[i]) {
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
818
|
+
ctx.drawImage(images[i], at.x, at.y, at.w, at.h);
|
|
819
|
+
}
|
|
820
|
+
const pixels = ctx.getImageData(0, 0, size.width, size.height).data;
|
|
821
|
+
const flags = new Uint8Array(size.width * size.height);
|
|
822
|
+
for (let p = 0; p < flags.length; p++) {
|
|
823
|
+
flags[p] = pixels[p * 4 + 3] >= 128 ? 1 : 0;
|
|
824
|
+
}
|
|
825
|
+
this.baselineFlags = flags;
|
|
826
|
+
this.baselineDims = { width: size.width, height: size.height };
|
|
827
|
+
}
|
|
828
|
+
catch (e) {
|
|
829
|
+
console.warn("TextureFrameSeriesAnimator: could not load the tiled baseline mask.", e);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
/*
|
|
833
|
+
* Composites each tile's floor and ceiling into rasters matching the composited frames.
|
|
834
|
+
*
|
|
835
|
+
* A label indexes them by the same texel as the value canvas, so they have to be laid out the
|
|
836
|
+
* same way rather than fetched as whole-extent rasters.
|
|
837
|
+
*/
|
|
838
|
+
async loadTiledExtremes() {
|
|
839
|
+
const tiles = this.tiles;
|
|
840
|
+
const target = this.compositeExtent();
|
|
841
|
+
const size = this.compositeSize(tiles, target);
|
|
842
|
+
// One request for the whole extremes block. They sit contiguously after the frames, so
|
|
843
|
+
// fetching per tile would be 164 round trips for an 82 tile pyramid.
|
|
844
|
+
let lo = Number.MAX_SAFE_INTEGER;
|
|
845
|
+
let hi = 0;
|
|
846
|
+
for (const tile of tiles) {
|
|
847
|
+
for (const at of [tile.Floor, tile.Ceiling]) {
|
|
848
|
+
if (!at) {
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
lo = Math.min(lo, at.ByteOffset);
|
|
852
|
+
hi = Math.max(hi, at.ByteOffset + at.ByteLength);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (!(hi > lo)) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const response = await fetch(this.archiveUrl, { headers: { Range: `bytes=${lo}-${hi - 1}` } });
|
|
859
|
+
const block = await response.arrayBuffer();
|
|
860
|
+
if (this.disposed) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const draw = async (pick) => {
|
|
864
|
+
const images = await this.decodeSlices(block, lo, tiles.map(pick));
|
|
865
|
+
if (this.disposed) {
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
const canvas = document.createElement("canvas");
|
|
869
|
+
canvas.width = size.width;
|
|
870
|
+
canvas.height = size.height;
|
|
871
|
+
const ctx = canvas.getContext("2d");
|
|
872
|
+
ctx.imageSmoothingEnabled = false;
|
|
873
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
874
|
+
let drew = false;
|
|
875
|
+
for (let i = 0; i < tiles.length; i++) {
|
|
876
|
+
const image = images[i];
|
|
877
|
+
if (!image) {
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
const at = this.tileRect(tiles[i], target, size);
|
|
881
|
+
ctx.drawImage(image, at.x, at.y, at.w, at.h);
|
|
882
|
+
drew = true;
|
|
883
|
+
}
|
|
884
|
+
return drew ? ctx.getImageData(0, 0, size.width, size.height) : null;
|
|
885
|
+
};
|
|
886
|
+
try {
|
|
887
|
+
this.floorPixels = await draw((tile) => tile.Floor);
|
|
888
|
+
this.ceilingPixels = await draw((tile) => tile.Ceiling);
|
|
889
|
+
}
|
|
890
|
+
catch (e) {
|
|
891
|
+
console.warn("TextureFrameSeriesAnimator: could not load the tiled extremes.", e);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
/*
|
|
895
|
+
* Where one tile lands on the composite, snapped to whole pixels.
|
|
896
|
+
*
|
|
897
|
+
* Rounding each edge rather than the origin and size is what makes neighbours meet exactly:
|
|
898
|
+
* a tile's right edge and the next tile's left edge round to the same pixel, so no seam of
|
|
899
|
+
* background shows between them.
|
|
900
|
+
*/
|
|
901
|
+
tileRect(tile, target, size) {
|
|
902
|
+
const spanLon = target.East - target.West;
|
|
903
|
+
const spanLat = target.North - target.South;
|
|
904
|
+
const x0 = Math.round((tile.West - target.West) / spanLon * size.width);
|
|
905
|
+
const x1 = Math.round((tile.East - target.West) / spanLon * size.width);
|
|
906
|
+
const y0 = Math.round((target.North - tile.North) / spanLat * size.height);
|
|
907
|
+
const y1 = Math.round((target.North - tile.South) / spanLat * size.height);
|
|
908
|
+
return { x: x0, y: y0, w: Math.max(1, x1 - x0), h: Math.max(1, y1 - y0) };
|
|
909
|
+
}
|
|
910
|
+
/*
|
|
911
|
+
* Decodes many tile images at once.
|
|
912
|
+
*
|
|
913
|
+
* Serially awaiting each decode makes a frame cost as many round trips through the image
|
|
914
|
+
* decoder as there are tiles, which for an 82 tile pyramid is seconds rather than one.
|
|
915
|
+
*/
|
|
916
|
+
decodeSlices(block, base, entries) {
|
|
917
|
+
return Promise.all(entries.map(async (at) => {
|
|
918
|
+
if (!at) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
const slice = block.slice(at.ByteOffset - base, at.ByteOffset - base + at.ByteLength);
|
|
922
|
+
const objectUrl = URL.createObjectURL(new Blob([slice], { type: "image/png" }));
|
|
923
|
+
try {
|
|
924
|
+
return await (0, image_utils_1.loadImage)(objectUrl);
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
finally {
|
|
930
|
+
URL.revokeObjectURL(objectUrl);
|
|
931
|
+
}
|
|
932
|
+
}));
|
|
933
|
+
}
|
|
336
934
|
/**
|
|
337
935
|
* Fetches and decodes the archive's static baseline mask, at most once.
|
|
338
936
|
*/
|
|
339
937
|
ensureBaselineMask() {
|
|
938
|
+
if (this.maskBaseline && this.tiles) {
|
|
939
|
+
if (!this.baselineLoad) {
|
|
940
|
+
this.baselineLoad = this.loadTiledBaselineMask();
|
|
941
|
+
}
|
|
942
|
+
return this.baselineLoad;
|
|
943
|
+
}
|
|
340
944
|
if (!this.maskBaseline || !this.baselineMaskEntry) {
|
|
341
945
|
return Promise.resolve();
|
|
342
946
|
}
|
|
@@ -449,24 +1053,17 @@ var TextureFrameSeriesAnimator;
|
|
|
449
1053
|
presentPixels(pixels, dims, valuePixels) {
|
|
450
1054
|
this.poolIdx = 1 - this.poolIdx;
|
|
451
1055
|
const target = this.pool[this.poolIdx];
|
|
452
|
-
target
|
|
453
|
-
target.height = dims.height;
|
|
454
|
-
const ctx = target.getContext("2d");
|
|
455
|
-
const imgData = ctx.createImageData(dims.width, dims.height);
|
|
456
|
-
imgData.data.set(pixels);
|
|
457
|
-
ctx.putImageData(imgData, 0, 0);
|
|
1056
|
+
this.writeInto(target, pixels, dims);
|
|
458
1057
|
this.displayedPixels = pixels;
|
|
459
1058
|
if (this.valueCanvas && valuePixels) {
|
|
460
|
-
this.valueCanvas
|
|
461
|
-
this.valueCanvas.height = dims.height;
|
|
462
|
-
const valueCtx = this.valueCanvas.getContext("2d");
|
|
463
|
-
const valueImgData = valueCtx.createImageData(dims.width, dims.height);
|
|
464
|
-
valueImgData.data.set(valuePixels);
|
|
465
|
-
valueCtx.putImageData(valueImgData, 0, 0);
|
|
1059
|
+
this.writeInto(this.valueCanvas, valuePixels, dims);
|
|
466
1060
|
this.displayedValuePixels = valuePixels;
|
|
467
1061
|
this.valueDims = dims;
|
|
468
1062
|
}
|
|
469
1063
|
this.presentVersion++;
|
|
1064
|
+
if (this.viewer && this.viewer.scene && this.viewer.scene.requestRenderMode) {
|
|
1065
|
+
this.viewer.scene.requestRender();
|
|
1066
|
+
}
|
|
470
1067
|
}
|
|
471
1068
|
}
|
|
472
1069
|
TextureFrameSeriesAnimator.Animator = Animator;
|