@tomtom-org/maps-sdk-plugin-map-effects 0.4.1 → 0.4.2
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/CHANGELOG.md +225 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.es.js +1 -1
- package/package.json +5 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#2823](https://github.com/tomtom-internal/maps-sdk-js/pull/2823) [`d1b906f`](https://github.com/tomtom-internal/maps-sdk-js/commit/d1b906f460c33607bf9789475ad85bff339b7c3b) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package ships its `CHANGELOG.md`, as the SDK does, so you can read what changed between two releases from `node_modules`
|
|
8
|
+
|
|
9
|
+
- [#2813](https://github.com/tomtom-internal/maps-sdk-js/pull/2813) [`c186703`](https://github.com/tomtom-internal/maps-sdk-js/commit/c1867038e9a38e300ea72e8812419204bf008e73) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Captures run one at a time and wait for loading tiles, and a failed frame hides the artwork and the bloom and warns once
|
|
10
|
+
|
|
11
|
+
- `onlyTolerance: 0` lights the key colours, and a negative value counts as 0
|
|
12
|
+
- The depth of field warns when WebGL2 is missing
|
|
13
|
+
- `capture()` rejects with a `RangeError` for a `pixelRatio` that isn't a positive number
|
|
14
|
+
- Render targets are freed when the effects chain drops to one stage or none
|
|
15
|
+
- App layers keep drawing while the effects chain fails
|
|
16
|
+
- A layer whose `draw()` throws part-way is left empty rather than half drawn
|
|
17
|
+
- `capture()` skips a tint whose colour the live overlay can't paint either, rather than filling with the colour drawn before it
|
|
18
|
+
|
|
19
|
+
## 0.4.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- [#2803](https://github.com/tomtom-internal/maps-sdk-js/pull/2803) [`1a62c72`](https://github.com/tomtom-internal/maps-sdk-js/commit/1a62c72eb372d037397bb88485ea731763f3e554) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The README links the repository's security policy page, which sends a vulnerability report to TomTom's HackerOne program
|
|
24
|
+
|
|
25
|
+
## 0.4.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- [#2761](https://github.com/tomtom-internal/maps-sdk-js/pull/2761) [`287bf72`](https://github.com/tomtom-internal/maps-sdk-js/commit/287bf728c9a1d1b7eb632b9af9afd8865160abf6) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** Give the fog an air colour of your own with `fog.color`, and push a blur or a grade past its slider's end
|
|
30
|
+
|
|
31
|
+
- `fog.color` washes the rim towards your colour, and its lightness brightens or darkens the rim with it. A translucent colour thins both. If you don't set it, the fog keeps the air of the map's light/dark theme
|
|
32
|
+
- `fog.color` has no `default` in `effectKnobCatalogue`, so a `color` knob's `default` is now optional in `EffectKnob`. Code that reads it as a `string` handles `undefined` too
|
|
33
|
+
- `bloom.radius`, `bloom.onlyTolerance`, `glare.length`, `glare.onlyTolerance`, `grade.brightness`, `grade.contrast`, `grade.saturation`, `edgeBlur.intensity` and `depthOfField.intensity` now have `hard-min` bounds: `setKnob` still refuses a value below the minimum, and a value above the maximum applies
|
|
34
|
+
- The map effects playground adds a milky distance use case, and its panel shows `fog.color` following the theme until you pick a colour
|
|
35
|
+
|
|
36
|
+
- [#2722](https://github.com/tomtom-internal/maps-sdk-js/pull/2722) [`9eff3f9`](https://github.com/tomtom-internal/maps-sdk-js/commit/9eff3f9f11ae3c358e0df83e9241cfb4a00f7282) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** the `maplibre-gl` peer dependency moves up to `^6.15.0`
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- [#2742](https://github.com/tomtom-internal/maps-sdk-js/pull/2742) [`2fcc1ab`](https://github.com/tomtom-internal/maps-sdk-js/commit/2fcc1abc07bcdf89aff3942ff1207637ed00f419) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `license` field reads `SEE LICENSE IN LICENSE.txt`, the form npm and license scanners recognise, so they point to the package's own `LICENSE.txt` instead of reporting an unknown license
|
|
41
|
+
|
|
42
|
+
- [#2731](https://github.com/tomtom-internal/maps-sdk-js/pull/2731) [`d54e042`](https://github.com/tomtom-internal/maps-sdk-js/commit/d54e0422c5e903c96352098693f5c789b3a840e6) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Glare makes the colours you name shine like street lamps through a lens, with `glare.*` knobs for the streaks' strength, length, ray count and angle
|
|
43
|
+
|
|
44
|
+
- `glare.only` names the lights as `bloom.only` does, by style colour knob, group or CSS colour, and defaults to `colors.roadMajor`
|
|
45
|
+
- The glare runs on the GPU, after the depth of field and before a scoped bloom, so a bloom on the same colour glows along the streaks
|
|
46
|
+
- A `bloom.only` scope set before the map's first style loads now resolves once the style is in, where it lit the whole frame
|
|
47
|
+
- The plugin ships its shaders without their comments, so its bundle is smaller with the glare than it was without it
|
|
48
|
+
- The map effects playground adds a street lights use case
|
|
49
|
+
|
|
50
|
+
- [#2725](https://github.com/tomtom-internal/maps-sdk-js/pull/2725) [`8bb231b`](https://github.com/tomtom-internal/maps-sdk-js/commit/8bb231b6f969316fea4fc0bed4d83711d2de9486) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - A bloom scoped with `bloom.only` keys and blurs the map on the GPU, so it no longer reads the frame back to the CPU on every render
|
|
51
|
+
|
|
52
|
+
- The scoped bloom runs as the last stages of the WebGL2 chain, after your own passes and the depth of field. Without WebGL2 it draws no glow and warns once
|
|
53
|
+
- `bloom.radius` blurs in CSS pixels on a high-DPI screen, as its doc and `capture()` already did, where the live bloom blurred in device pixels
|
|
54
|
+
|
|
55
|
+
- [#2741](https://github.com/tomtom-internal/maps-sdk-js/pull/2741) [`afa67a1`](https://github.com/tomtom-internal/maps-sdk-js/commit/afa67a13e1efcd08d4c65fd966a3c5426d37297d) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - HTML markers sit above every effect, so a marker stays sharp over the edge blur, the tint, the vignette and the depth of field, whether you add it before or after the effects
|
|
56
|
+
|
|
57
|
+
- To have a pin take the effects, draw it as a symbol layer instead
|
|
58
|
+
|
|
59
|
+
- [#2728](https://github.com/tomtom-internal/maps-sdk-js/pull/2728) [`7b97756`](https://github.com/tomtom-internal/maps-sdk-js/commit/7b9775608f380d2c36c8ee2668153f3545ed896e) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `MapEffects` passes take `vec3` and `vec4` uniforms, textures and mip levels, and `addLayer` puts your own 2D layer between the overlays
|
|
60
|
+
|
|
61
|
+
- A pass uniform is a number, or an array of two to four numbers bound as `vec2` to `vec4`, so a colour goes in as one `vec3`
|
|
62
|
+
- `MapEffectsPass.textures` binds images, canvases, videos or any other `TexImageSource` as named `sampler2D`s, up to 15 per pass. A video or a canvas uploads on every draw, and an image or a bitmap once per object. A source that hasn't loaded skips the pass for that draw
|
|
63
|
+
- `MapEffectsPass.mipmapped` builds the picture's mip levels before your pass runs, for a pass that reads coarser levels with `textureLod`
|
|
64
|
+
- `addLayer({ id, placement, draw })` draws a 2D canvas layer of yours right above the overlay its `placement` names, from `after-bloom` to `after-vignette`, and `capture()` draws it in the same place. `removeLayer(id)` takes it off
|
|
65
|
+
- examples: `mixed-map-effects` paints its watercolour on a paper texture in a `vec3` paper colour, and leaves a bare paper margin around it as a layer
|
|
66
|
+
|
|
67
|
+
- [#2764](https://github.com/tomtom-internal/maps-sdk-js/pull/2764) [`74472b5`](https://github.com/tomtom-internal/maps-sdk-js/commit/74472b5c5daaef4e07862356815673c03acdf23d) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Map effects draw in the same frame as the map, so the map no longer needs `preserveDrawingBuffer: true`, and the browser stops copying its frame every frame
|
|
68
|
+
|
|
69
|
+
- Bloom, the glare, the depth of field, your own passes, your layers and `capture()` read the map canvas inside its `render` event. The plugin no longer warns when `preserveDrawingBuffer` is off, and a map that still sets it keeps working
|
|
70
|
+
- A bloom, glare or depth-of-field knob change, or a pass you add or remove, asks the map for one frame and draws in it, so an idle map redraws once. A grade, tint, fog, edge blur or vignette change asks for none
|
|
71
|
+
- A frame the effects fail to draw, such as with a pass texture from another origin loaded without CORS, shows the map without them and warns once, and `capture()` rejects with the error
|
|
72
|
+
- examples: the map effects examples create their maps without `preserveDrawingBuffer`
|
|
73
|
+
|
|
74
|
+
- [#2734](https://github.com/tomtom-internal/maps-sdk-js/pull/2734) [`9a12d95`](https://github.com/tomtom-internal/maps-sdk-js/commit/9a12d95c22e60e69fb1891237344ca259aa236a5) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Installs beside `@tomtom-org/maps-sdk` 1.0.0 and every later 1.x release: the peer range on the SDK runs up to `<2.0.0` instead of stopping below 1.0.0
|
|
75
|
+
|
|
76
|
+
- [#2697](https://github.com/tomtom-internal/maps-sdk-js/pull/2697) [`3d9c9a3`](https://github.com/tomtom-internal/maps-sdk-js/commit/3d9c9a34b897cf8eb791096620ed3e18dead1966) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package page on npm and the API reference of its entry points say the plugin is in Public Preview until its 1.0.0 release, as its guides already do
|
|
77
|
+
|
|
78
|
+
## 0.3.1
|
|
79
|
+
|
|
80
|
+
### Patch Changes
|
|
81
|
+
|
|
82
|
+
- [#2596](https://github.com/tomtom-internal/maps-sdk-js/pull/2596) [`037a4ca`](https://github.com/tomtom-internal/maps-sdk-js/commit/037a4ca4d730d40847e978938806084cbe7bb7ca) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `MapEffects` stops paying for the WebGL chain while nothing runs on it
|
|
83
|
+
- With the depth of field off, or on over a flat map, a map frame no longer measures the map canvas, so it stops forcing a page layout on every frame
|
|
84
|
+
- The chain takes its WebGL2 context on the first pass or depth of field it runs rather than in the constructor, so a `MapEffects` with only bloom or CSS overlays on holds no context
|
|
85
|
+
|
|
86
|
+
- [#2693](https://github.com/tomtom-internal/maps-sdk-js/pull/2693) [`db69794`](https://github.com/tomtom-internal/maps-sdk-js/commit/db69794e00011a820e2046a161e5390d07b8bb7a) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Plugins are side-effect free, so your bundler drops every part of a plugin your app doesn't use
|
|
87
|
+
|
|
88
|
+
- Each plugin declares `"sideEffects": false`, and its knob catalogue and other module-level tables are dropped when nothing reads them
|
|
89
|
+
- flyover takes `@turf/turf` as a peer dependency, the copy the SDK already requires, instead of bundling its own
|
|
90
|
+
- The plugins' bundles keep their module-level names, as the SDK's do, so Sandpack and builds that transpile `node_modules` with Babel run their loops correctly
|
|
91
|
+
|
|
92
|
+
## 0.3.0
|
|
93
|
+
|
|
94
|
+
### Minor Changes
|
|
95
|
+
|
|
96
|
+
- [#2548](https://github.com/tomtom-internal/maps-sdk-js/pull/2548) [`89660b0`](https://github.com/tomtom-internal/maps-sdk-js/commit/89660b065154b0211cb7234fa5d2da6f74ba9086) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** map: a knob id is the path of its setting in the configuration on every module, and `setKnobs` sets several knobs in one checked change
|
|
97
|
+
- map: `setKnobs(module, catalogue, values)` checks every value against its knob, then applies them all in one change and one `config-change`. If it refuses one value, it sets none. `KnobValues<CONFIG, ID>` types the values by id
|
|
98
|
+
- map: `StylingFoundationsModuleConfig` nests each setting along its id, as every other module does: `{ labels: { sizeFactor: 1.2 } }` instead of `{ 'labels.sizeFactor': 1.2 }`. `setKnob`, `setKnobs`, `getKnob`, `resetKnob` and `MapPresetSettings` keep the ids
|
|
99
|
+
- map: `StylingFoundationsModule`'s `updateConfig` merges one level deep, as on every module, so `updateConfig({ colors: { water } })` replaces the colours set before. Set a single colour with `setKnob`. Its `applyConfig` and `updateConfig` no longer check values at run time, as on every module. `setKnob` and `setKnobs` do
|
|
100
|
+
- map: a `KnobTarget` implements `_setKnobValues(values)` instead of `_setKnobValue(id, value)`, and a subclass of `AbstractMapModule` inherits it. For a plugin's own target, `knobValueIn`, `configWithKnobs` and `configWithoutKnob` implement the hooks
|
|
101
|
+
- map-effects: `MapEffectsSettings` is `MapEffectsConfig`, and nests each knob along its id: `new MapEffects(map, { bloom: { intensity: 0.5 } })`. `updateConfig` replaces each effect it names, while `setKnob` and `setKnobs` keep the rest of an effect. The constructor, `applyConfig` and `updateConfig` no longer check values at run time, as on a map module. `setKnob` and `setKnobs` do. The plugin needs SDK 1.0.0-rc.2
|
|
102
|
+
- agent-toolkit: `setMapStyling` reads the styling knobs at their paths, and the plugin needs SDK 1.0.0-rc.2
|
|
103
|
+
- examples: the map effects playground restores its panel from `localStorage` with `setKnobs`
|
|
104
|
+
|
|
105
|
+
### Patch Changes
|
|
106
|
+
|
|
107
|
+
- [#2590](https://github.com/tomtom-internal/maps-sdk-js/pull/2590) [`75fb3ea`](https://github.com/tomtom-internal/maps-sdk-js/commit/75fb3eab4d38b2aad8f4f671c3fd84f78ffc19f3) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - license: the privacy clause of `LICENSE.txt` now links the TomTom Privacy Policy, https://www.tomtom.com/privacy/
|
|
108
|
+
|
|
109
|
+
- [#2594](https://github.com/tomtom-internal/maps-sdk-js/pull/2594) [`470a75c`](https://github.com/tomtom-internal/maps-sdk-js/commit/470a75c881d652fd8145fccf0d6b01ad5bcd1609) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - Fog takes the new style's air colour when `setStyle()` switches between a light and a dark style, as `capture()` already did, with no config call needed after the switch
|
|
110
|
+
|
|
111
|
+
- [#2627](https://github.com/tomtom-internal/maps-sdk-js/pull/2627) [`e09550e`](https://github.com/tomtom-internal/maps-sdk-js/commit/e09550ebbd9a46d464bb186fb4ec220cafc55170) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `maplibre-gl` peer dependency moves up to `^6.13.0`
|
|
112
|
+
|
|
113
|
+
- [#2614](https://github.com/tomtom-internal/maps-sdk-js/pull/2614) [`fe596fc`](https://github.com/tomtom-internal/maps-sdk-js/commit/fe596fcb2d1c5a05dddb77547f48debc1b856a12) Thanks [@JulianChinAFoeng-TomTom](https://github.com/JulianChinAFoeng-TomTom)! - tests: each package's unit test run now fails when its coverage drops below the goals in `TESTING.md`
|
|
114
|
+
|
|
115
|
+
## 0.2.5
|
|
116
|
+
|
|
117
|
+
### Patch Changes
|
|
118
|
+
|
|
119
|
+
- [#2522](https://github.com/tomtom-internal/maps-sdk-js/pull/2522) [`9f01f52`](https://github.com/tomtom-internal/maps-sdk-js/commit/9f01f52ee6f5a4ca23a2a44586c6cfac3595937f) Thanks [@AlvaroGraca-TomTom](https://github.com/AlvaroGraca-TomTom)! - No code change: this version is on the `latest` npm tag, which the previous one missed
|
|
120
|
+
|
|
121
|
+
## 0.2.4
|
|
122
|
+
|
|
123
|
+
### Patch Changes
|
|
124
|
+
|
|
125
|
+
- [#2519](https://github.com/tomtom-internal/maps-sdk-js/pull/2519) [`7b854b3`](https://github.com/tomtom-internal/maps-sdk-js/commit/7b854b30bc25fdac5d9afe1975d6961a0bb137f5) Thanks [@AlvaroGraca-TomTom](https://github.com/AlvaroGraca-TomTom)! - No code change: this version is on the `latest` npm tag, which the previous one missed
|
|
126
|
+
|
|
127
|
+
## 0.2.3
|
|
128
|
+
|
|
129
|
+
### Patch Changes
|
|
130
|
+
|
|
131
|
+
- [#2517](https://github.com/tomtom-internal/maps-sdk-js/pull/2517) [`49611d0`](https://github.com/tomtom-internal/maps-sdk-js/commit/49611d0ac91dc0b584e3d059d1f654208bdead6c) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The README's Project setup link opens the page again
|
|
132
|
+
|
|
133
|
+
## 0.2.2
|
|
134
|
+
|
|
135
|
+
### Patch Changes
|
|
136
|
+
|
|
137
|
+
- [#2500](https://github.com/tomtom-internal/maps-sdk-js/pull/2500) [`d19d11d`](https://github.com/tomtom-internal/maps-sdk-js/commit/d19d11d41546bac2dd870560b9508bb0b3a2ce2a) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `maplibre-gl` peer dependency moves up to `^6.12.0`
|
|
138
|
+
|
|
139
|
+
- [#2489](https://github.com/tomtom-internal/maps-sdk-js/pull/2489) [`640b53b`](https://github.com/tomtom-internal/maps-sdk-js/commit/640b53bf1c0ea20ce996053ee2f1a664310a381d) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `MapEffects` docs and README stop naming custom styles, which the SDK no longer loads
|
|
140
|
+
|
|
141
|
+
- [#2506](https://github.com/tomtom-internal/maps-sdk-js/pull/2506) [`12099bd`](https://github.com/tomtom-internal/maps-sdk-js/commit/12099bdc040f3ce2d3990266bfb42024cda7956c) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The package ships without source maps, so a stack trace through the plugin points into its minified bundle
|
|
142
|
+
- An app's own source maps still lead a trace to the plugin's frames (`dist/index.es.js:1:2345`); share such a trace in a report and TomTom maps it back to the source
|
|
143
|
+
|
|
144
|
+
## 0.2.1
|
|
145
|
+
|
|
146
|
+
### Patch Changes
|
|
147
|
+
|
|
148
|
+
- [#2479](https://github.com/tomtom-internal/maps-sdk-js/pull/2479) [`b6ec343`](https://github.com/tomtom-internal/maps-sdk-js/commit/b6ec3438fb953ba344dbdc5a9c3cf5c0c4190e03) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - map-effects: the TSDoc of `EffectKnobId` and `EffectKnob` names the SDK's `setKnob` and `getKnob` in place of the removed `MapEffects.set` and `MapEffects.get`
|
|
149
|
+
- Updated dependencies:
|
|
150
|
+
- @tomtom-org/maps-sdk@0.64.0
|
|
151
|
+
|
|
152
|
+
## 0.2.0
|
|
153
|
+
|
|
154
|
+
### Minor Changes
|
|
155
|
+
|
|
156
|
+
- [#2466](https://github.com/tomtom-internal/maps-sdk-js/pull/2466) [`9959fff`](https://github.com/tomtom-internal/maps-sdk-js/commit/9959fffc945a70012a8f8c6b2cbd69ce46cbf274) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** map-effects: `MapEffects` loses `set`, `get` and `reset`: one knob goes through the SDK's `setKnob`, `getKnob` and `resetKnob` with `effectKnobCatalogue`, several through `updateConfig(settings)`, and `resetConfig()` turns everything off
|
|
157
|
+
- map-effects: `applyConfig` checks every value before it replaces any, so a refused value leaves the effects as they were instead of clearing them
|
|
158
|
+
- map-effects: the peer range starts at `@tomtom-org/maps-sdk` 0.63.0
|
|
159
|
+
- agent-toolkit: `setMapStyling` sets and resets knobs through the SDK's `setKnob` and `resetKnob`, and the peer range starts at `@tomtom-org/maps-sdk` 0.63.0
|
|
160
|
+
|
|
161
|
+
- [#2466](https://github.com/tomtom-internal/maps-sdk-js/pull/2466) [`9959fff`](https://github.com/tomtom-internal/maps-sdk-js/commit/9959fffc945a70012a8f8c6b2cbd69ce46cbf274) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** map-effects: `bloom.only` names the style colour knobs by their map-wide ids (`'traffic.incidents.colors.major'`, `'pois.label.color'`), and `StylingColorKnobGroup` is `StyleColorKnobGroup`
|
|
162
|
+
- map-effects: the scope reads the colours with the SDK's `getStyleColor`, so it no longer needs a `StylingFoundationsModule` on the map
|
|
163
|
+
- agent-toolkit: `setMapStyling` sets the look knobs on the module owning each (Styling, BaseMap road markings, POIs, traffic flow and incidents, the terrain hillshade) by its map-wide id, and its `preset` applies a map preset with `applyMapPreset`
|
|
164
|
+
|
|
165
|
+
### Patch Changes
|
|
166
|
+
|
|
167
|
+
- Updated dependencies:
|
|
168
|
+
- @tomtom-org/maps-sdk@0.63.0
|
|
169
|
+
|
|
170
|
+
## 0.1.2
|
|
171
|
+
|
|
172
|
+
### Patch Changes
|
|
173
|
+
|
|
174
|
+
- [#2416](https://github.com/tomtom-internal/maps-sdk-js/pull/2416) [`4d2acd0`](https://github.com/tomtom-internal/maps-sdk-js/commit/4d2acd0eba34f2c8386a85b771261f4d1b9b9a10) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - agent-toolkit and map-effects check knob values with the SDK's `validateKnobValue`.
|
|
175
|
+
|
|
176
|
+
- agent-toolkit: `toggleTilesPOIs` checks `filterCategories.values` with the SDK's `validateKnobValue`, so its error names every refused value the way the map does
|
|
177
|
+
- map-effects: `set` checks values with the SDK's `validateKnobValue`, so a `color` knob refuses a string that is no CSS colour, and the peer range starts at `@tomtom-org/maps-sdk` 0.61.0
|
|
178
|
+
- map-effects: every `effectKnobCatalogue` range has `bounds: 'hard'`
|
|
179
|
+
|
|
180
|
+
- [#2413](https://github.com/tomtom-internal/maps-sdk-js/pull/2413) [`94e5baa`](https://github.com/tomtom-internal/maps-sdk-js/commit/94e5baaf8399ac8acf62266a8b707f45ac6e4db0) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `LICENSE.txt` is the SDK's current license agreement, the same file `@tomtom-org/maps-sdk` ships, in place of the 29 May 2026 version
|
|
181
|
+
- Updated dependencies:
|
|
182
|
+
- @tomtom-org/maps-sdk@0.61.0
|
|
183
|
+
|
|
184
|
+
## 0.1.1
|
|
185
|
+
|
|
186
|
+
### Patch Changes
|
|
187
|
+
|
|
188
|
+
- [#2336](https://github.com/tomtom-internal/maps-sdk-js/pull/2336) [`a117660`](https://github.com/tomtom-internal/maps-sdk-js/commit/a11766044b72a7a8128e882fc58b55f151e7db7c) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `BloomScopeEntry` docs name `RoutingModule`'s route colour by its `color` option ([#2336](https://github.com/tomtom-internal/maps-sdk-js/issues/2336))
|
|
189
|
+
- Updated dependencies:
|
|
190
|
+
- @tomtom-org/maps-sdk@0.60.0
|
|
191
|
+
|
|
192
|
+
## 0.1.0
|
|
193
|
+
|
|
194
|
+
### Minor Changes
|
|
195
|
+
|
|
196
|
+
- [#2223](https://github.com/tomtom-internal/maps-sdk-js/pull/2223) [`e0417a9`](https://github.com/tomtom-internal/maps-sdk-js/commit/e0417a900d9954567180fe96325160f484563c7e) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - **Breaking:** `MapEffects.describe()` is removed, together with the `MapEffectsCatalogue` and `MapEffectsKnobDescriptor` types. `effectKnobCatalogue` lists every effect knob's kind, description, default, range or options and use case as static data, and `effects.get(id)` reads the value in force.
|
|
197
|
+
|
|
198
|
+
### Patch Changes
|
|
199
|
+
|
|
200
|
+
- [#2217](https://github.com/tomtom-internal/maps-sdk-js/pull/2217) [`8eff899`](https://github.com/tomtom-internal/maps-sdk-js/commit/8eff899c713586296529fb22a4c84f3663fa0859) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - The `maplibre-gl` peer dependency requires `^6.11.2`
|
|
201
|
+
|
|
202
|
+
- [#2194](https://github.com/tomtom-internal/maps-sdk-js/pull/2194) [`151b0b5`](https://github.com/tomtom-internal/maps-sdk-js/commit/151b0b581496a4fd6740416de2b29c7f3e5a84b8) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)!
|
|
203
|
+
|
|
204
|
+
- `addPass` / `removePass` run a GLSL fragment pass of your own (`MapEffectsPass`: its `fragment`, `float` / `vec2` `uniforms` and a `placement` before or after the depth of field) inside the effects chain, so bloom, grade, fog, edge blur, tint, vignette and `capture()` all work on what it drew
|
|
205
|
+
- A shader that fails to compile is reported once, with the compiler's log, instead of on every frame
|
|
206
|
+
- Updated dependencies:
|
|
207
|
+
- @tomtom-org/maps-sdk@0.58.0
|
|
208
|
+
|
|
209
|
+
## 0.0.3
|
|
210
|
+
|
|
211
|
+
### Patch Changes
|
|
212
|
+
|
|
213
|
+
- [#2175](https://github.com/tomtom-internal/maps-sdk-js/pull/2175) [`1bea71f`](https://github.com/tomtom-internal/maps-sdk-js/commit/1bea71fe79f2875ff3c78cfef136eeb492e8acc0) Thanks [@DanielForniessoria-TomTom](https://github.com/DanielForniessoria-TomTom)! - `bloom.only` scopes bloom to styling colour knobs, groups of them (`traffic`) or CSS colours, resolved against the loaded style; `bloom.onlyTolerance` sets how close a pixel's colour must be
|
|
214
|
+
Requires `@tomtom-org/maps-sdk` 0.56.0 or later, for `stylingColorKnobIds`.
|
|
215
|
+
|
|
216
|
+
- [#2188](https://github.com/tomtom-internal/maps-sdk-js/pull/2188) [`9e1d080`](https://github.com/tomtom-internal/maps-sdk-js/commit/9e1d080397ee2d16b6185873661890e5a81f7b65) Thanks [@AlvaroGraca-TomTom](https://github.com/AlvaroGraca-TomTom)! - Accept every SDK release below 1.0: the peer dependency on `@tomtom-org/maps-sdk` is the range `>=0.55.1 <1.0.0` instead of an exact version.
|
|
217
|
+
- Updated dependencies:
|
|
218
|
+
- @tomtom-org/maps-sdk@0.56.0
|
|
219
|
+
|
|
220
|
+
## [0.0.2](https://github.com/tomtom-internal/maps-sdk-js/compare/maps-sdk-plugin-map-effects-v0.0.1...maps-sdk-plugin-map-effects-v0.0.2) (2026-09-21)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
### Features
|
|
224
|
+
|
|
225
|
+
* **map:** styling GA phase 2 - view knobs, presets, map-effects plugin ([#2090](https://github.com/tomtom-internal/maps-sdk-js/issues/2090)) ([a672ffb](https://github.com/tomtom-internal/maps-sdk-js/commit/a672ffbe5f9d2c61e009674e44ac49a4b9e0c980))
|
package/dist/index.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ import { KnobConfigOf, KnobKind, KnobRange, StyleColorKnobId, TomTomMap } from "
|
|
|
20
20
|
*/
|
|
21
21
|
/**
|
|
22
22
|
* The uniforms a {@link MapEffectsPass} reads, by the name its shader declares. A number sets a
|
|
23
|
-
* `float`, and an array of two to four numbers a `vec2` to `vec4`.
|
|
24
|
-
* WebGL silently refuses a float for an `int`
|
|
23
|
+
* `float`, and an array of two to four numbers a `vec2` to `vec4`. A number sets a `bool` too, 0 as
|
|
24
|
+
* false, but WebGL silently refuses a float for an `int` uniform, which then stays 0.
|
|
25
25
|
*
|
|
26
26
|
* @group Map Effects
|
|
27
27
|
*/
|
|
@@ -157,7 +157,7 @@ declare const effectKnobDefinitions: {
|
|
|
157
157
|
};
|
|
158
158
|
readonly 'glare.rays': {
|
|
159
159
|
readonly kind: "number";
|
|
160
|
-
readonly description: "How many streaks each light casts: 2 is an anamorphic lens’s line, 4 a cross, 6 and 8 a star.";
|
|
160
|
+
readonly description: "How many streaks each light casts: 2 is an anamorphic lens’s line, 4 a cross, 6 and 8 a star. An odd count rounds up.";
|
|
161
161
|
readonly default: 4;
|
|
162
162
|
readonly range: {
|
|
163
163
|
readonly min: 2;
|
|
@@ -327,7 +327,7 @@ declare const effectKnobDefinitions: {
|
|
|
327
327
|
*/
|
|
328
328
|
type EffectKnobId = keyof typeof effectKnobDefinitions;
|
|
329
329
|
/**
|
|
330
|
-
* Every effect knob id, in
|
|
330
|
+
* Every effect knob id, in the order of {@link effectKnobCatalogue}.
|
|
331
331
|
*
|
|
332
332
|
* @group Map Effects
|
|
333
333
|
*/
|
|
@@ -352,7 +352,8 @@ type EffectKnob = EffectKnobDefinition & {
|
|
|
352
352
|
};
|
|
353
353
|
/**
|
|
354
354
|
* Every effect knob with its kind, description, default, range or accepted names and the use case
|
|
355
|
-
* it serves, in
|
|
355
|
+
* it serves, grouped by effect in the order the overlays stack (the glare and the depth of field, which
|
|
356
|
+
* run under every overlay, sit beside the bloom and the edge blur). It's static data, readable without a map.
|
|
356
357
|
*
|
|
357
358
|
* @group Map Effects
|
|
358
359
|
*/
|
|
@@ -539,6 +540,7 @@ type CaptureOptions = {
|
|
|
539
540
|
export declare class MapEffects {
|
|
540
541
|
private readonly map;
|
|
541
542
|
private config;
|
|
543
|
+
private values;
|
|
542
544
|
private passes;
|
|
543
545
|
private layers;
|
|
544
546
|
private readonly container;
|
|
@@ -551,6 +553,7 @@ export declare class MapEffects {
|
|
|
551
553
|
private readonly warnedScopes;
|
|
552
554
|
private warnedNoWebGL2;
|
|
553
555
|
private warnedFrame;
|
|
556
|
+
private capturing;
|
|
554
557
|
private readonly warnedThrows;
|
|
555
558
|
private readonly passSources;
|
|
556
559
|
private readonly onStyleData;
|
|
@@ -596,7 +599,7 @@ export declare class MapEffects {
|
|
|
596
599
|
*/
|
|
597
600
|
updateConfig(partial: MapEffectsConfig): void;
|
|
598
601
|
/**
|
|
599
|
-
* Resets every knob to its default,
|
|
602
|
+
* Resets every knob to its default, which turns every effect off.
|
|
600
603
|
*/
|
|
601
604
|
resetConfig(): void;
|
|
602
605
|
/**
|
|
@@ -672,14 +675,15 @@ export declare class MapEffects {
|
|
|
672
675
|
/**
|
|
673
676
|
* Renders the current view, with its effects, into a canvas, optionally at a higher pixel ratio
|
|
674
677
|
* for print. The map is re-rendered at that ratio (never resampled), read back once every tile is
|
|
675
|
-
* drawn, and then set back to the ratio it had.
|
|
678
|
+
* drawn or 15 seconds have passed, whichever comes first, and then set back to the ratio it had.
|
|
676
679
|
*
|
|
677
680
|
* @remarks
|
|
678
681
|
* - Viewers cannot download from a sandboxed page, so hand the canvas to your own upload or `toBlob` flow.
|
|
679
682
|
* - It rejects when the frame fails to draw, such as with a pass texture from another origin that
|
|
680
|
-
* was loaded without CORS.
|
|
683
|
+
* was loaded without CORS, and with a `RangeError` for a `pixelRatio` that isn't a positive number.
|
|
681
684
|
*/
|
|
682
685
|
capture(options?: CaptureOptions): Promise<HTMLCanvasElement>;
|
|
686
|
+
private captureAtRatio;
|
|
683
687
|
/**
|
|
684
688
|
* Detaches the effects from the map, removes their elements and releases the WebGL context the
|
|
685
689
|
* chain holds. A page with several maps needs this, since a browser caps how many contexts it
|
|
@@ -717,11 +721,11 @@ export declare class MapEffects {
|
|
|
717
721
|
private drawChain;
|
|
718
722
|
private drawBloom;
|
|
719
723
|
private currentScopeMask;
|
|
720
|
-
private buildScopeMaskFor;
|
|
721
724
|
private warnScopeEmpty;
|
|
722
725
|
private readBack;
|
|
723
726
|
private compositeFrame;
|
|
724
727
|
private settled;
|
|
728
|
+
private skipFrame;
|
|
725
729
|
private warnNoWebGL2;
|
|
726
730
|
}
|
|
727
731
|
//#endregion
|
package/dist/index.es.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{configWithKnobs,configWithoutKnob,getStyleColor,knobValueIn,parseCssColor,styleColorKnobIds,toHsl}from"@tomtom-org/maps-sdk/map";var everyColor,groupsOf=e=>e.split(".").slice(0,-1).map((e,t,a)=>a.slice(0,t+1).join(".")),NAMED_KNOBS=/* @__PURE__ */(()=>{const e=/* @__PURE__ */new Map;for(const t of styleColorKnobIds)for(const a of[...groupsOf(t),t])e.set(a,[...e.get(a)??[],t]);return e})(),bloomScopeNames=/* @__PURE__ */(()=>[...NAMED_KNOBS.keys()])(),cubeIndex=(e,t,a)=>32*(32*(e>>3)+(t>>3))+(a>>3),chromaticity=([e,t,a])=>{const r=Math.max(e,t,a);if(0!==r)return[e/r,t/r,a/r]},FRACTION={min:0,max:1,step:.05,bounds:"hard"},TOLERANCE={min:0,max:1,step:.05,bounds:"hard-min"},GRADE_TERM={min:.5,max:1.5,step:.05,bounds:"hard-min"},BLUR_CSS_PX={min:0,max:30,step:1,bounds:"hard-min"},USE_CASE_bloom="Emphasis on dark and driving styles.",USE_CASE_glare="Lights that read as lights on dark styles: lit roads, icons and routes cast streaks, as lamps do through a lens.",USE_CASE_grade="Muting the base map under a data overlay.",USE_CASE_fog="Depth cue: the far context recedes as if seen through air.",USE_CASE_edgeBlur="Focus: the centre stays sharp and the edges lose their detail.",USE_CASE_depthOfField="Depth on a tilted map: one band of distance stays sharp, the rest defocuses with distance.",USE_CASE_tint="Muting the base map under a data overlay; matching a brand.",effectKnobDefinitions={"bloom.intensity":{kind:"number",description:"How strongly bright features (lit roads, traffic tubes, route lines) glow. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_bloom},"bloom.radius":{kind:"number",description:"How far the glow spreads, in CSS pixels.",default:12,range:{min:2,max:40,step:1,bounds:"hard-min"},useCase:USE_CASE_bloom},"bloom.threshold":{kind:"number",description:"Brightness below which nothing glows. Raise it so only the brightest features light up.",default:.55,range:FRACTION,useCase:USE_CASE_bloom},"bloom.only":{kind:"colors",description:"The colours bloom may light: CSS colours, style colour knobs or groups of them (`traffic`, `traffic.incidents.colors.major`), read from the loaded style. Only the pixels wearing one of them glow; empty lights the whole frame.",default:[],options:bloomScopeNames,useCase:USE_CASE_bloom},"bloom.onlyTolerance":{kind:"number",description:"How far a pixel may sit from one of the scope's colours and still glow. Raise it when antialiasing has mixed the colour with the map underneath; lower it when a neighbouring severity is glowing too.",default:.15,range:TOLERANCE,useCase:USE_CASE_bloom},"glare.intensity":{kind:"number",description:"How strongly the lights cast streaks, as a star filter or an anamorphic lens draws them from a lamp. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_glare},"glare.length":{kind:"number",description:"How far each streak reaches from its light, in CSS pixels.",default:32,range:{min:4,max:120,step:1,bounds:"hard-min"},useCase:USE_CASE_glare},"glare.rays":{kind:"number",description:"How many streaks each light casts: 2 is an anamorphic lens’s line, 4 a cross, 6 and 8 a star.",default:4,range:{min:2,max:8,step:2,bounds:"hard"},useCase:USE_CASE_glare},"glare.angle":{kind:"number",description:"The first streak’s direction, in degrees anticlockwise from the horizontal; the others share the turn evenly.",default:45,range:{min:0,max:180,step:5,bounds:"hard"},useCase:USE_CASE_glare},"glare.threshold":{kind:"number",description:"Brightness below which a light casts nothing.",default:.3,range:FRACTION,useCase:USE_CASE_glare},"glare.only":{kind:"colors",description:"The colours that count as lights, as `bloom.only` names them. By default the major roads, read from the loaded style; empty lets every bright pixel streak, labels included.",default:["colors.roadMajor"],options:bloomScopeNames,useCase:USE_CASE_glare},"glare.onlyTolerance":{kind:"number",description:"How far a pixel may sit from a light's colour and still streak.",default:.15,range:TOLERANCE,useCase:USE_CASE_glare},"grade.brightness":{kind:"number",description:"Brightness of the whole base map (1 = unchanged).",default:1,range:GRADE_TERM,useCase:USE_CASE_grade},"grade.contrast":{kind:"number",description:"Contrast of the whole base map (1 = unchanged).",default:1,range:GRADE_TERM,useCase:USE_CASE_grade},"grade.saturation":{kind:"number",description:"Colour saturation of the whole base map (1 = unchanged, 0 = greyscale).",default:1,range:{min:0,max:1.5,step:.05,bounds:"hard-min"},useCase:USE_CASE_grade},"fog.intensity":{kind:"number",description:"Haze over the edges of the view: blur, plus a wash towards the colour of the air (`fog.color`). Reads as distance. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_fog},"fog.reach":{kind:"number",description:"How far the fog reaches in from the edges (1 = to the centre).",default:.4,range:FRACTION,useCase:USE_CASE_fog},"fog.color":{kind:"color",description:"The colour of the air the fog washes towards; its lightness brightens or darkens the rim with it, and a translucent colour does both less. Unset, it follows the map's light/dark theme: pale on a light map, dark on a dark one.",useCase:USE_CASE_fog},"edgeBlur.intensity":{kind:"number",description:"Blur over the edges of the view, in CSS pixels, leaving their colour untouched. Reads as a lens, not as distance — `fog` is the effect that also washes the rim out. 0 = off.",default:0,range:BLUR_CSS_PX,useCase:USE_CASE_edgeBlur},"edgeBlur.reach":{kind:"number",description:"How far the edge blur reaches in from the edges (1 = to the centre).",default:.5,range:FRACTION,useCase:USE_CASE_edgeBlur},"depthOfField.intensity":{kind:"number",description:"The widest circle of confusion, in CSS pixels — the blur the most out-of-focus distance gets. Needs a tilted camera: a map seen from straight above has no depth, and the effect ends there on its own. 0 = off.",default:0,range:BLUR_CSS_PX,useCase:USE_CASE_depthOfField},"depthOfField.focus":{kind:"number",description:"Which distance is sharp, across what the frame shows: 0 the nearest ground at the bottom of the view, 1 the farthest at the top.",default:0,range:FRACTION,useCase:USE_CASE_depthOfField},"depthOfField.band":{kind:"number",description:"How much of that depth stays sharp — the field itself. Thin is the miniature; wide holds a whole run of the map. A gentle tilt has little depth, so the same value covers more of the view.",default:.3,range:FRACTION,useCase:USE_CASE_depthOfField},"depthOfField.bokeh":{kind:"number",description:"Whether an out-of-focus highlight fades or comes back as a disc of light. 0 averages the light away, as a blur does; turned up, a lit road or a traffic tube keeps its brightness across the whole circle it is spread over.",default:.5,range:FRACTION,useCase:USE_CASE_depthOfField},"tint.color":{kind:"color",description:"A flat colour laid over the whole map.",default:"#1f2937",useCase:USE_CASE_tint},"tint.opacity":{kind:"number",description:"Opacity of the tint. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_tint},"vignette.intensity":{kind:"number",description:"Darkening (negative) or lightening (positive) towards the edges. 0 = off.",default:0,range:{min:-1,max:1,step:.05,bounds:"hard"},useCase:"Focus on the centre of the view."},"vignette.reach":{kind:"number",description:"How far the vignette reaches in from the edges (1 = to the centre).",default:.45,range:FRACTION,useCase:"Focus on the centre of the view."}},effectKnobIds=/* @__PURE__ */Object.keys(effectKnobDefinitions),effectKnobCatalogue=/* @__PURE__ */effectKnobIds.map(e=>({id:e,...effectKnobDefinitions[e]})),clamp=(e,t,a)=>Math.min(Math.max(e,t),a),asFraction=e=>clamp(e,0,1),bloomThreshold=e=>{const t=.92*asFraction(e);return{brightness:1/(1+t),contrast:(1+t)/(1-t)}},bloomFilter=(e,t,a=1)=>{const{brightness:r,contrast:s}=bloomThreshold(t);return`brightness(${r.toFixed(4)}) contrast(${s.toFixed(3)}) blur(${e*a}px) saturate(1.35)`},texelOf=e=>[1/Math.max(e.width,1),1/Math.max(e.height,1)],lightUniforms=(e,t)=>{const{brightness:a,contrast:r}=bloomThreshold(e.threshold);return{uBrightness:a,uContrast:r,uTexel:texelOf(t),uIntensity:asFraction(e.intensity),uSaturation:1.35}},toRadians=e=>e*Math.PI/180,gradeFilter=(e,t,a)=>[1===e?"":`brightness(${e})`,1===t?"":`contrast(${t})`,1===a?"":`saturate(${a})`].filter(Boolean).join(" "),rimGradient=(e,t,a)=>`radial-gradient(ellipse farthest-corner at center, ${e} ${Math.round(100*(1-asFraction(a)))}%, ${t} 100%)`,vignetteGradient=(e,t)=>{const{inner:a,outer:r}=vignetteStops(e);return rimGradient(a,r,t)},vignetteStops=e=>{const t=e<0?"0, 0, 0":"255, 255, 255";return{inner:`rgba(${t}, 0)`,outer:`rgba(${t}, ${Math.min(Math.abs(e),1)})`}},rimMask=e=>rimGradient("transparent","black",e),depthRamp=({pitch:e,verticalFieldOfView:t})=>{const a=Math.tan(toRadians(clamp(e,0,89))),r=2*Math.tan(toRadians(clamp(t,1,175))/2)*a;return r/(1+r/2)},THEME_AIR={light:{rgb:[238,240,244],brightness:1.08},dark:{rgb:[16,22,33],brightness:.92}},lightnessOf=e=>toHsl(`rgb(${e.join(", ")})`)?.l??0,fogAir=(e,t)=>{const a=THEME_AIR[t],r=void 0===e?void 0:parseCssColor(e),s=r?[Math.round(255*r[0]),Math.round(255*r[1]),Math.round(255*r[2])]:a.rgb,n=r?.[3]??1,i=a.brightness+n*((e=>{const t=lightnessOf(THEME_AIR.dark.rgb),a=(lightnessOf(e)-t)/(lightnessOf(THEME_AIR.light.rgb)-t);return THEME_AIR.dark.brightness+a*(THEME_AIR.light.brightness-THEME_AIR.dark.brightness)})(s)-a.brightness);return{rgb:s.join(", "),alpha:n,grade:`brightness(${Math.round(1e3*i)/1e3}) contrast(0.82) saturate(0.7)`}},fogBlurPx=e=>2+6*e,fogFilter=(e,t)=>`blur(${fogBlurPx(e)}px) ${t.grade}`,fogVeilStops=(e,t)=>({inner:`rgba(${t.rgb}, 0)`,outer:`rgba(${t.rgb}, ${.65*e*t.alpha})`}),fogVeil=(e,t,a)=>{const{inner:r,outer:s}=fogVeilStops(e,a);return rimGradient(r,s,t)},filteredCopy=(e,t,a,r)=>{const s=document.createElement("canvas");s.width=t,s.height=a;const n=s.getContext("2d");if(n)return n.filter=r,n.drawImage(e.canvas,0,0),n.filter="none",n},drawOver=(e,t,a,r=1)=>{e.save(),e.globalCompositeOperation=a,e.globalAlpha=r,e.drawImage(t.canvas,0,0),e.restore()},fillEllipticalRadial=(e,t,a,r,s,n)=>{const i=t/Math.SQRT2,o=a/Math.SQRT2,l=e.createRadialGradient(0,0,1-r,0,0,1);l.addColorStop(0,s),l.addColorStop(1,n),e.save(),e.translate(t/2,a/2),e.scale(i,o),e.fillStyle=l,e.fillRect(-t/2/i,-a/2/o,t/i,a/o),e.restore()},applyMaskedBlur=(e,t,a,r,s,n,i)=>{const o=filteredCopy(e,t,a,`blur(${s*r}px) ${n}`.trim());o&&(o.globalCompositeOperation="destination-in",i(o),e.drawImage(o.canvas,0,0))},OVERLAYS=["bloom","grade","fog","edgeBlur","tint","vignette"],effectValueOf=(e,t)=>{const a=effectKnobDefinitions[t];return knobValueIn(e,t)??a.default},withDefaults=e=>Object.fromEntries(effectKnobIds.map(t=>[t,effectValueOf(e,t)])),bloomBlur_default="uniform float uSigma;\nuniform vec2 uTexel;\nuniform vec2 uDirection;\nconst int BLUR_TAPS = 12;\nvec3 blurAlong(vec2 uv) {\nfloat spacing = max(3.0 * uSigma / float(BLUR_TAPS), 1.0);\nfloat level = log2(spacing);\nvec3 total = vec3(0.0);\nfloat weights = 0.0;\nfor (int index = -BLUR_TAPS; index <= BLUR_TAPS; index++) {\nfloat offset = float(index) * spacing;\nfloat weight = exp(-0.5 * offset * offset / (uSigma * uSigma));\nvec2 at = uv + uDirection * offset * uTexel;\nfloat inside = float(all(greaterThanEqual(at, vec2(0.0))) && all(lessThanEqual(at, vec2(1.0))));\ntotal += textureLod(uSource, at, level).rgb * weight * inside;\nweights += weight;\n}\nreturn total / weights;\n}\n",shared_default="float luma(vec3 colour) {\nreturn dot(colour, vec3(0.2126, 0.7152, 0.0722));\n}\nvec4 screenLight(vec4 picture, vec3 light, float intensity, float saturation) {\nlight = clamp(mix(vec3(luma(light)), light, saturation), 0.0, 1.0);\nreturn vec4(picture.rgb + intensity * light * (1.0 - picture.rgb), picture.a);\n}\nfloat hash(vec2 at) {\nvec3 spread = fract(vec3(at.xyx) * 0.1031);\nspread += dot(spread, spread.yzx + 33.33);\nreturn fract((spread.x + spread.y) * spread.z);\n}\n",BLOOM_KEY_SHADER="uniform highp usampler3D uLookup;\nuniform float uBrightness;\nuniform float uContrast;\nvoid main() {\nvec4 colour = texture(uSource, vUv);\nivec3 cell = ivec3(colour.bgr * 255.0 + 0.5) >> 3;\nfloat lit = float(texelFetch(uLookup, cell, 0).r);\nvec3 cut = clamp((colour.rgb * lit * uBrightness - 0.5) * uContrast + 0.5, 0.0, 1.0);\nfragColor = vec4(cut, 1.0);\n}\n",BLOOM_BLUR_SHADER=/* @__PURE__ */(()=>`${bloomBlur_default}void main() {\nfragColor = vec4(blurAlong(vUv), 1.0);\n}\n`)(),BLOOM_COMPOSITE_SHADER=/* @__PURE__ */(()=>`${shared_default}${bloomBlur_default}uniform sampler2D uKept;\nuniform float uIntensity;\nuniform float uSaturation;\nvoid main() {\nfragColor = screenLight(texture(uKept, vUv), blurAlong(vUv), uIntensity, uSaturation);\n}\n`)(),DEFOCUS_SHADER=/* @__PURE__ */(()=>`${shared_default}uniform float uDepth;\nuniform float uFocus;\nuniform float uBand;\nuniform float uBlur;\nuniform float uBokeh;\nuniform vec2 uTexel;\nconst int MAX_TAPS = 48;\nconst float GOLDEN_ANGLE = 2.39996323;\nfloat depthAt(float v) {\nreturn uDepth * v;\n}\nfloat circleOfConfusion(float v) {\nfloat defocus = abs(depthAt(v) - uFocus) - uBand;\nreturn uBlur * clamp(defocus / max(1.0 - uBand, 0.001), 0.0, 1.0);\n}\nfloat bokehWeight(vec3 colour) {\nfloat bright = luma(colour);\nfloat fourth = bright * bright * bright * bright;\nreturn 1.0 + uBokeh * 8.0 * fourth;\n}\nvec4 defocus(vec2 uv, float coc) {\nfloat radius = coc * 0.5;\nif (radius < 0.75) return textureLod(uSource, uv, 0.0);\nint taps = int(clamp(radius * 1.5, 12.0, float(MAX_TAPS)));\nfloat spacing = radius * sqrt(3.14159265 / float(taps));\nfloat level = max(log2(spacing), 0.0);\nfloat spin = hash(gl_FragCoord.xy) * 6.28318531;\nvec4 centre = textureLod(uSource, uv, level);\nfloat weight = bokehWeight(centre.rgb);\nvec4 total = vec4(centre.rgb * centre.a, centre.a) * weight;\nfloat carried = weight;\nfor (int index = 0; index < MAX_TAPS; index++) {\nif (index >= taps) break;\nfloat fraction = (float(index) + 0.5) / float(taps);\nfloat distance = radius * sqrt(fraction);\nfloat angle = float(index) * GOLDEN_ANGLE + spin;\nvec2 offset = vec2(cos(angle), sin(angle)) * distance;\nvec4 sampled = textureLod(uSource, uv + offset * uTexel, level);\nfloat tapWeight = bokehWeight(sampled.rgb);\ntotal += vec4(sampled.rgb * sampled.a, sampled.a) * tapWeight;\ncarried += tapWeight;\n}\nreturn vec4(total.rgb / max(total.a, 0.0001), total.a / carried);\n}\nvoid main() {\nfragColor = defocus(vUv, circleOfConfusion(vUv.y));\n}\n`)(),GLARE_SHADER=/* @__PURE__ */(()=>`${shared_default}uniform sampler2D uKept;\nuniform float uIntensity;\nuniform float uLength;\nuniform float uAxes;\nuniform float uAngle;\nuniform float uGain;\nuniform float uSaturation;\nuniform vec2 uTexel;\nconst int MAX_AXES = 4;\nconst int RAY_TAPS = 16;\nconst float PI = 3.14159265;\nfloat inFrame(vec2 at) {\nvec2 inside = step(vec2(0.0), at) * step(at, vec2(1.0));\nreturn inside.x * inside.y;\n}\nvec3 streakAlong(vec2 uv, vec2 direction, float jitter) {\nfloat spacing = max(uLength / float(RAY_TAPS), 1.0);\nfloat level = max(log2(spacing), 0.0);\nvec3 total = vec3(0.0);\nfor (int index = 0; index < RAY_TAPS; index++) {\nfloat distance = (float(index) + jitter) * spacing;\nfloat remaining = max(1.0 - distance / uLength, 0.0);\nvec2 offset = direction * distance * uTexel;\nvec3 both = textureLod(uSource, uv + offset, level).rgb * inFrame(uv + offset) +\ntextureLod(uSource, uv - offset, level).rgb * inFrame(uv - offset);\ntotal += both * remaining * remaining;\n}\nreturn total * spacing;\n}\nvoid main() {\nfloat jitter = 0.5 + 0.5 * hash(gl_FragCoord.xy);\nvec3 glare = vec3(0.0);\nfor (int axis = 0; axis < MAX_AXES; axis++) {\nif (float(axis) >= uAxes) break;\nfloat angle = uAngle + float(axis) * PI / uAxes;\nglare += streakAlong(vUv, vec2(cos(angle), sin(angle)), jitter);\n}\nfragColor = screenLight(texture(uKept, vUv), clamp(glare * uGain, 0.0, 1.0), uIntensity, uSaturation);\n}\n`)(),setSampling=(e,t=e.CLAMP_TO_EDGE)=>{e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR)},hasPixels=e=>"naturalWidth"in e?e.complete&&e.naturalWidth>0:"readyState"in e?e.readyState>=e.HAVE_CURRENT_DATA:"displayWidth"in e?e.displayWidth>0:e.width>0,resizeCanvas=(e,t,a)=>{e.width===t&&e.height===a||(e.width=t,e.height=a)},compile=(e,t,a)=>{const r=e.createShader(t);if(!r)return"the context would not create a shader";if(e.shaderSource(r,a),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS))return r;const s=e.getShaderInfoLog(r)??"";return e.deleteShader(r),`failed to compile: ${s}`},ScreenPassRenderer=class{canvas;context;programs=/* @__PURE__ */new Map;failures=/* @__PURE__ */new Set;sourceTexture;stageTextures=/* @__PURE__ */new Map;lookupTextures=[];targets=[];targetSize={width:0,height:0};lost=!1;constructor(){this.canvas=document.createElement("canvas"),this.canvas.addEventListener("webglcontextlost",e=>{e.preventDefault(),this.lost=!0}),this.canvas.addEventListener("webglcontextrestored",()=>{this.programs.clear(),this.failures.clear(),this.sourceTexture=void 0,this.stageTextures.clear(),this.lookupTextures=[],this.targets=[],this.targetSize={width:0,height:0},this.lost=!1})}get gl(){return void 0===this.context&&(this.context=this.canvas.getContext("webgl2",{alpha:!0,antialias:!1,premultipliedAlpha:!1})),this.context??void 0}render(e,t){if(!t.length||!e.width||!e.height)return;const a=this.gl;if(!a||this.lost)return;resizeCanvas(this.canvas,e.width,e.height);let r=this.uploadSource(e,t[0].mipmapped);const s=t.slice(1).some(e=>e.keepsInput)?3:2;if(!r||t.length>1&&!this.holdTargets(a,s))return;let n;for(let i=0;i<t.length;i++){const e=t[i];e.keepsInput&&(n=r);const s=i===t.length-1?void 0:this.targets.find(({texture:e})=>e!==r&&e!==n);this.runStage(a,e,r,s,n),s&&(r=s.texture,t[i+1].mipmapped&&this.mipmap(a,r))}return this.canvas}get hasContext(){return void 0!==this.gl}compileError(e){const t=this.gl;if(!t||this.lost)return;const a=this.cachedProgram(t,e);return"string"==typeof a?a:void 0}release(e){const t=this.programs.get(e);t&&this.context?.deleteProgram(t),this.programs.delete(e),this.failures.delete(e)}releaseTextures(e){for(const t of e){const e=this.stageTextures.get(t);e&&this.context?.deleteTexture(e),this.stageTextures.delete(t)}}remove(){this.context?.getExtension("WEBGL_lose_context")?.loseContext(),this.context=null,this.stageTextures.clear()}holdTargets(e,t){const{width:a,height:r}=this.canvas,s=this.targetSize.width!==a||this.targetSize.height!==r;for(this.releaseTargets(e,s?0:t);this.targets.length<t;){const t=e.createTexture(),s=e.createFramebuffer();if(!t||!s)return!1;e.bindTexture(e.TEXTURE_2D,t),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,a,r,0,e.RGBA,e.UNSIGNED_BYTE,null),setSampling(e),e.bindFramebuffer(e.FRAMEBUFFER,s),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,t,0),this.targets.push({texture:t,framebuffer:s})}return this.targetSize={width:a,height:r},!0}releaseTargets(e,t=0){for(const{texture:a,framebuffer:r}of this.targets.splice(t))e.deleteTexture(a),e.deleteFramebuffer(r)}bindStageTextures(e,t,a){Object.entries(a.textures??{}).forEach(([a,r],s)=>{const n=s+1;e.activeTexture(e.TEXTURE0+n);const i=this.stageTextures.get(r),o=i??e.createTexture();o&&(e.bindTexture(e.TEXTURE_2D,o),i&&!(e=>"readyState"in e||"getContext"in e)(r)||(e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r)),i||(setSampling(e,e.REPEAT),this.stageTextures.set(r,o)),e.uniform1i(e.getUniformLocation(t,a),n))})}mipmap(e,t){e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.generateMipmap(e.TEXTURE_2D)}uploadSource(e,t=!1){const a=this.gl;if(a&&(this.sourceTexture??=a.createTexture()??void 0,this.sourceTexture))return a.bindTexture(a.TEXTURE_2D,this.sourceTexture),a.pixelStorei(a.UNPACK_FLIP_Y_WEBGL,!0),a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,e),setSampling(a),t&&this.mipmap(a,this.sourceTexture),this.sourceTexture}lookupTextureOf(e,t){const a=this.lookupTextures.find(e=>e.cube===t);if(a)return this.lookupTextures=[a,...this.lookupTextures.filter(e=>e!==a)],a.texture;const r=(this.lookupTextures.length>=2?this.lookupTextures.pop():void 0)?.texture??e.createTexture();if(!r)return;e.bindTexture(e.TEXTURE_3D,r),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!1),e.pixelStorei(e.UNPACK_ALIGNMENT,1);const{side:s,texels:n}=t;return e.texImage3D(e.TEXTURE_3D,0,e.R8UI,s,s,s,0,e.RED_INTEGER,e.UNSIGNED_BYTE,n),e.texParameteri(e.TEXTURE_3D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_3D,e.TEXTURE_MAG_FILTER,e.NEAREST),this.lookupTextures.unshift({cube:t,texture:r}),r}runStage(e,t,a,r,s){const n=this.programOf(e,t.fragment);if(!n)return;const i=t.lookup&&this.lookupTextureOf(e,t.lookup);if(!t.lookup||i){e.bindFramebuffer(e.FRAMEBUFFER,r?.framebuffer??null),e.viewport(0,0,this.canvas.width,this.canvas.height),e.useProgram(n),i&&(e.activeTexture(e.TEXTURE0+2),e.bindTexture(e.TEXTURE_3D,i),e.uniform1i(e.getUniformLocation(n,"uLookup"),2)),s&&(e.activeTexture(e.TEXTURE0+1),e.bindTexture(e.TEXTURE_2D,s),e.uniform1i(e.getUniformLocation(n,"uKept"),1)),this.bindStageTextures(e,n,t),e.activeTexture(e.TEXTURE0+0),e.bindTexture(e.TEXTURE_2D,a),e.uniform1i(e.getUniformLocation(n,"uSource"),0);for(const[a,r]of Object.entries(t.uniforms)){const t=e.getUniformLocation(n,a);t&&("number"==typeof r?e.uniform1f(t,r):2===r.length?e.uniform2f(t,r[0],r[1]):3===r.length?e.uniform3f(t,r[0],r[1],r[2]):e.uniform4f(t,r[0],r[1],r[2],r[3]))}e.drawArrays(e.TRIANGLES,0,3)}}programOf(e,t){if(this.failures.has(t))return;const a=this.cachedProgram(e,t);if("string"!=typeof a)return a;this.failures.add(t),console.warn(`[MapEffects] shader ${a}`)}cachedProgram(e,t){const a=this.programs.get(t);if(a)return a;const r=this.build(e,t);return"string"!=typeof r&&this.programs.set(t,r),r}build(e,t){const a=[compile(e,e.VERTEX_SHADER,"#version 300 es\nout vec2 vUv;\nvoid main() {\nvec2 corner = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\nvUv = corner;\ngl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);\n}\n"),compile(e,e.FRAGMENT_SHADER,`#version 300 es\nprecision highp float;\nprecision highp sampler2D;\nuniform sampler2D uSource;\nin vec2 vUv;\nout vec4 fragColor;\n${t}`)],r=a.find(e=>"string"==typeof e),s=a.filter(e=>"string"!=typeof e),n=r?null:e.createProgram();if(n){for(const t of s)e.attachShader(n,t);e.linkProgram(n)}for(const o of s)e.deleteShader(o);if(r)return r;if(!n)return"the context would not create a program";if(e.getProgramParameter(n,e.LINK_STATUS))return n;const i=e.getProgramInfoLog(n)??"";return e.deleteProgram(n),`failed to link: ${i}`}},PASS_PLACEMENTS={"before-depth-of-field":!0,"after-depth-of-field":!0},layerPlacementOf=e=>`after-${e}`,backdropFilterStyle=e=>({backdropFilter:e,webkitBackdropFilter:e}),maskStyle=e=>({maskImage:e,webkitMaskImage:e}),LAYER_STYLE={position:"absolute",inset:"0",width:"100%",height:"100%",pointerEvents:"none",display:"none"},rasterScaleOf=e=>e.width/Math.max(e.clientWidth,1),MapEffects=class{map;config={};passes=[];layers=[];container;overlays;bloomCanvas;artworkCanvas;chain=new ScreenPassRenderer;scopeMasks={};styleGeneration=0;warnedScopes=/* @__PURE__ */new Set;warnedNoWebGL2=!1;warnedFrame=!1;warnedThrows=/* @__PURE__ */new WeakSet;passSources=/* @__PURE__ */new Map;onStyleData=()=>{this.styleGeneration++,this.map.mapLibreMap.triggerRepaint(),this.showFog(withDefaults(this.config))};onRender=()=>{try{this.drawChain(withDefaults(this.config)),"block"===this.bloomCanvas.style.display&&this.drawBloom(),this.layers.length&&this.drawLayers()}catch(e){if(this.artworkCanvas.style.display="none",this.warnedFrame)return;this.warnedFrame=!0,console.warn("[MapEffects] the effects are skipped for each frame they fail to draw:",e)}};constructor(e,t={}){this.map=e,this.container=document.createElement("div"),this.container.className="tomtom-map-effects",Object.assign(this.container.style,{position:"absolute",inset:"0",pointerEvents:"none",overflow:"hidden"}),this.map.mapLibreMap.getCanvas().insertAdjacentElement("afterend",this.container),this.bloomCanvas=document.createElement("canvas"),this.overlays=Object.fromEntries(OVERLAYS.map(e=>{const t="bloom"===e?this.bloomCanvas:document.createElement("div");return t.dataset.effect=e,Object.assign(t.style,LAYER_STYLE),this.container.appendChild(t),[e,t]})),this.bloomCanvas.style.mixBlendMode="screen",this.artworkCanvas=this.chain.canvas,this.artworkCanvas.dataset.effect="artwork",Object.assign(this.artworkCanvas.style,LAYER_STYLE),this.container.insertAdjacentElement("afterbegin",this.artworkCanvas),this.map.mapLibreMap.on("render",this.onRender),this.map.mapLibreMap.on("styledata",this.onStyleData),this.applyConfig(t)}cameraAngles(){const e=this.map.mapLibreMap;return{pitch:e.getPitch(),verticalFieldOfView:e.getVerticalFieldOfView()}}getConfig(){return structuredClone(this.config)}applyConfig(e){this.config=e,this.render()}updateConfig(e){this.applyConfig({...this.config,...e})}resetConfig(){this.applyConfig({})}_knobValue(e){return knobValueIn(this.config,e)}_setKnobValues(e){this.applyConfig(configWithKnobs(this.config,e))}_resetKnobValue(e){this.applyConfig(configWithoutKnob(this.config,e))}addPass(e){if(!e.id)throw new Error("A pass needs an id, to be replaced or removed by.");if(void 0!==e.placement&&!Object.hasOwn(PASS_PLACEMENTS,e.placement))throw new RangeError(`Pass '${e.id}' expects a placement of ${Object.keys(PASS_PLACEMENTS).join(" or ")}; got ${String(e.placement)}.`);const t=Object.keys(e.textures??{});if(t.length>15||t.includes("uSource"))throw new RangeError(`Pass '${e.id}' takes up to 15 textures, none named uSource; got ${t.join(", ")}.`);this.chain.hasContext||this.warnNoWebGL2();const a=this.chain.compileError(e.fragment);if(a)throw new Error(`Pass '${e.id}' ${a}`);const r={...e},s=this.passes.find(t=>t.id===e.id);this.passes=s?this.passes.map(e=>e===s?r:e):[...this.passes,r],s&&this.releaseUnused(s),this.render()}removePass(e){const t=this.passes.find(t=>t.id===e);t&&(this.passes=this.passes.filter(e=>e!==t),this.releaseUnused(t),this.render())}addLayer(e){if(!e.id)throw new Error("A layer needs an id, to be replaced or removed by.");if(t=e.placement,!OVERLAYS.some(e=>t===layerPlacementOf(e)))throw new RangeError(`Layer '${e.id}' expects a placement of ${OVERLAYS.map(layerPlacementOf).join(", ")}; got ${String(e.placement)}.`);var t;const a=this.layers.find(t=>t.layer.id===e.id),r={layer:{...e},canvas:a?.canvas??this.layerCanvas(e.id)};this.layers=a?this.layers.map(e=>e===a?r:e):[...this.layers,r],this.mountLayers(),this.paintLayer(r.layer,r.canvas,this.rasterOf())}removeLayer(e){const t=this.layers.find(({layer:t})=>t.id===e);t&&(this.layers=this.layers.filter(e=>e!==t),t.canvas.remove())}async capture(e={}){const t=this.map.mapLibreMap,a=t,r=t.getPixelRatio(),s=e.pixelRatio??r,n=r!==(globalThis.devicePixelRatio||1),i=Math.abs(s-r)>.01;i&&(a.setPixelRatio(s),await this.settled());try{return await this.readBack()}finally{i&&a.setPixelRatio(n?r:null)}}remove(){this.map.mapLibreMap.off("render",this.onRender),this.map.mapLibreMap.off("styledata",this.onStyleData),this.chain.remove(),this.passSources.clear(),this.container.remove()}show(e,t,a={}){const r=this.overlays[e];r.style.display=t?"block":"none",Object.assign(r.style,a)}render(){const e=withDefaults(this.config),t=gradeFilter(e["grade.brightness"],e["grade.contrast"],e["grade.saturation"]);var a;this.show("grade",""!==t,backdropFilterStyle(t)),this.showFog(e),this.show("edgeBlur",e["edgeBlur.intensity"]>0,{...backdropFilterStyle((a=e["edgeBlur.intensity"],`blur(${a}px) saturate(1.08)`)),...maskStyle(rimMask(e["edgeBlur.reach"]))}),e["glare.intensity"]>0&&!this.chain.hasContext&&this.warnNoWebGL2(),this.show("tint",e["tint.opacity"]>0,{backgroundColor:e["tint.color"],opacity:String(e["tint.opacity"])}),this.show("vignette",0!==e["vignette.intensity"],{backgroundImage:vignetteGradient(e["vignette.intensity"],e["vignette.reach"])}),("block"===this.artworkCanvas.style.display||"block"===this.bloomCanvas.style.display||this.readsFrame(e))&&this.map.mapLibreMap.triggerRepaint()}readsFrame(e){return this.passes.length>0||e["bloom.intensity"]>0||e["glare.intensity"]>0||e["depthOfField.intensity"]>0}bloomInChain(e){return e["bloom.intensity"]>0&&void 0!==this.currentScopeMask("bloom")}routeBloom(e){const t=e["bloom.intensity"]>0,a=this.bloomInChain(e);this.show("bloom",t&&!a,{opacity:String(e["bloom.intensity"])}),a&&!this.chain.hasContext&&this.warnNoWebGL2()}showFog(e){const t=fogAir(e["fog.color"],this.map.styleLightDarkTheme);this.show("fog",e["fog.intensity"]>0,{...backdropFilterStyle(fogFilter(e["fog.intensity"],t)),...maskStyle(rimMask(e["fog.reach"])),backgroundImage:fogVeil(e["fog.intensity"],e["fog.reach"],t)})}chainStages(e){return[...this.passStages("before-depth-of-field"),...this.depthOfFieldStages(e),...this.passStages("after-depth-of-field"),...this.glareStages(e),...this.bloomStages(e)]}glareStages(e){if(e["glare.intensity"]<=0)return[];const t=((e,t)=>({...lightUniforms(e,t),uLength:Math.max(e.lengthCssPx*t.scale,1),uAxes:clamp(Math.ceil(e.rays/2),1,4),uAngle:toRadians(e.angleDegrees),uGain:1/(4*t.scale)}))({intensity:e["glare.intensity"],lengthCssPx:e["glare.length"],rays:e["glare.rays"],angleDegrees:e["glare.angle"],threshold:e["glare.threshold"]},this.rasterOf()),a=this.currentScopeMask("glare")??(everyColor??={texels:new Uint8Array(32768).fill(1),side:32},everyColor);return[{fragment:BLOOM_KEY_SHADER,uniforms:t,keepsInput:!0,lookup:a},{fragment:GLARE_SHADER,uniforms:t,mipmapped:!0}]}bloomStages(e){const t=e["bloom.intensity"]>0?this.currentScopeMask("bloom"):void 0;if(!t)return[];const a=((e,t)=>({...lightUniforms(e,t),uSigma:Math.max(e.radiusCssPx*t.scale,1)}))({intensity:e["bloom.intensity"],radiusCssPx:e["bloom.radius"],threshold:e["bloom.threshold"]},this.rasterOf());return[{fragment:BLOOM_KEY_SHADER,uniforms:a,keepsInput:!0,lookup:t},{fragment:BLOOM_BLUR_SHADER,uniforms:{...a,uDirection:[1,0]},mipmapped:!0},{fragment:BLOOM_COMPOSITE_SHADER,uniforms:{...a,uDirection:[0,1]},mipmapped:!0}]}rasterOf(){const e=this.map.mapLibreMap.getCanvas();return{width:e.width,height:e.height,scale:this.map.mapLibreMap.getPixelRatio()}}passStages(e){return this.passes.filter(t=>(t.placement??"before-depth-of-field")===e).flatMap(e=>{const t=this.guarded(e,`pass '${e.id}' is skipped while its uniforms or textures throw`,()=>this.inputsOf(e));return t?[{fragment:e.fragment,mipmapped:e.mipmapped,...t}]:[]})}inputsOf(e){const t={};for(const[r,s]of Object.entries(e.textures??{})){const a="function"==typeof s?s():s;if(!a||!hasPixels(a))return;this.holdSource(e,r,a),t[r]=a}const{uniforms:a={}}=e;return{uniforms:"function"==typeof a?a():a,textures:t}}guarded(e,t,a){try{return a()}catch(r){return void(this.warnedThrows.has(e)||(this.warnedThrows.add(e),console.warn(`[MapEffects] ${t}:`,r)))}}layerCanvas(e){const t=document.createElement("canvas");return t.dataset.layer=e,Object.assign(t.style,LAYER_STYLE,{display:"block"}),t}mountLayers(){for(const e of OVERLAYS){const t=layerPlacementOf(e),a=this.layers.filter(({layer:e})=>e.placement===t);this.overlays[e].after(...a.map(({canvas:e})=>e))}}drawLayers(){const e=this.rasterOf();for(const{layer:t,canvas:a}of this.layers)this.paintLayer(t,a,e)}paintLayer(e,t,{width:a,height:r,scale:s}){resizeCanvas(t,a,r);const n=t.getContext("2d");n&&(n.clearRect(0,0,a,r),n.save(),this.guarded(e,`layer '${e.id}' is left empty while its draw() throws`,()=>e.draw(n,{width:a,height:r,pixelRatio:s})),n.restore())}captureLayersAfter(e,t,a){const r=layerPlacementOf(e);for(const{layer:s}of this.layers.filter(e=>e.layer.placement===r)){const e=document.createElement("canvas");this.paintLayer(s,e,a),t.drawImage(e,0,0)}}releaseUnused(e){this.passes.some(t=>t.fragment===e.fragment)||this.chain.release(e.fragment);const t=this.passSources.get(e);this.passSources.delete(e),t&&this.releaseUnsampled([...t.values()])}releaseUnsampled(e){const t=new Set([...this.passSources.values()].flatMap(e=>[...e.values()]));this.chain.releaseTextures(e.filter(e=>!t.has(e)))}holdSource(e,t,a){const r=this.passSources.get(e)??/* @__PURE__ */new Map;this.passSources.set(e,r);const s=r.get(t);r.set(t,a),s&&s!==a&&this.releaseUnsampled([s])}depthOfFieldStages(e){const t=this.cameraAngles();if(e["depthOfField.intensity"]<=0||depthRamp(t)<=0)return[];const a=this.map.mapLibreMap.getCanvas(),r=((e,t,a)=>{const r=depthRamp(t);if(!(r<=0||e.blurCssPx<=0))return{uDepth:r,uFocus:Math.min(asFraction(e.focus)*r,1),uBand:.5*asFraction(e.band),uBlur:Math.max(e.blurCssPx,0)*a.scale,uBokeh:asFraction(e.bokeh),uTexel:texelOf(a)}})({focus:e["depthOfField.focus"],band:e["depthOfField.band"],blurCssPx:e["depthOfField.intensity"],bokeh:e["depthOfField.bokeh"]},t,{width:a.width,height:a.height,scale:rasterScaleOf(a)});return r?[{fragment:DEFOCUS_SHADER,uniforms:r,mipmapped:!0}]:[]}drawChain(e){const t=this.chainStages(e),a=t.length?this.chain.render(this.map.mapLibreMap.getCanvas(),t):void 0;this.artworkCanvas.style.display=a?"block":"none",this.routeBloom(e)}drawBloom(){const e="block"===this.artworkCanvas.style.display?this.artworkCanvas:this.map.mapLibreMap.getCanvas(),t=Math.max(1,Math.round(.5*e.width)),a=Math.max(1,Math.round(.5*e.height));resizeCanvas(this.bloomCanvas,t,a);const r=this.bloomCanvas.getContext("2d");r&&(r.filter=bloomFilter(effectValueOf(this.config,"bloom.radius"),effectValueOf(this.config,"bloom.threshold"),.5*this.rasterOf().scale),r.clearRect(0,0,t,a),r.drawImage(e,0,0,t,a))}currentScopeMask(e){const t=effectValueOf(this.config,`${e}.only`);if(0===t.length)return;const a=t.join("\n"),r=effectValueOf(this.config,`${e}.onlyTolerance`),s=this.scopeMasks[e],n=s?.key===a&&s.tolerance===r,i=(e=>e.some(e=>NAMED_KNOBS.has(e)))(t);if(i&&!this.map.mapReady)return n?s.mask:void 0;if(n&&(!i||s.styleGeneration===this.styleGeneration))return s.mask;const o=this.buildScopeMaskFor(e,t,r);return this.scopeMasks[e]={key:a,tolerance:r,styleGeneration:this.styleGeneration,mask:o},o}buildScopeMaskFor(e,t,a){const r=((e,t)=>t.flatMap(t=>{const a=NAMED_KNOBS.get(t);return(a?a.map(t=>getStyleColor(e,t)):[t]).map(e=>"string"==typeof e?parseCssColor(e):void 0).filter(e=>void 0!==e)}))(this.map,t);return 0===r.length&&this.warnScopeEmpty(e,t),((e,t)=>{const a=e.map(chromaticity).filter(e=>void 0!==e);if(0===a.length)return;const r=t*t,s=new Uint8Array(32768);for(let n=0;n<256;n+=8)for(let e=0;e<256;e+=8)for(let t=0;t<256;t+=8){const i=chromaticity([n,e,t]);i&&a.some(e=>{const t=i[0]-e[0],a=i[1]-e[1],s=i[2]-e[2];return t*t+a*a+s*s<=r})&&(s[cubeIndex(n,e,t)]=1)}return{texels:s,side:32}})(r,a)}warnScopeEmpty(e,t){this.warnedScopes.has(e)||(this.warnedScopes.add(e),console.warn(`[MapEffects] '${e}.only: ${t.join(", ")}' found no colour to key on in the loaded style, so ${e} is lighting the whole frame.`))}readBack(){const e=this.map.mapLibreMap;return new Promise((t,a)=>{e.once("render",()=>{try{t(this.compositeFrame(e.getCanvas()))}catch(r){a(r)}}),e.triggerRepaint()})}compositeFrame(e){const t=withDefaults(this.config),a=document.createElement("canvas");a.width=e.width,a.height=e.height;const r=a.getContext("2d");if(!r)return a;const s=this.chainStages(t);r.drawImage(this.chain.render(e,s)??e,0,0);const n={width:e.width,height:e.height,scale:rasterScaleOf(e)};return((e,t,a,r,s,n,i,o=()=>{})=>{const l=e=>r=>fillEllipticalRadial(r,t,a,e,"rgba(0,0,0,0)","rgba(0,0,0,1)"),h={bloom:()=>{s["bloom.intensity"]>0&&!i&&((e,t,a,r,s)=>{const n=filteredCopy(e,t,a,bloomFilter(s["bloom.radius"],s["bloom.threshold"],r));n&&drawOver(e,n,"screen",s["bloom.intensity"])})(e,t,a,r,s)},grade:()=>{const r=gradeFilter(s["grade.brightness"],s["grade.contrast"],s["grade.saturation"]);r&&((e,t,a,r)=>{const s=filteredCopy(e,t,a,r);s&&drawOver(e,s,"copy")})(e,t,a,r)},fog:()=>{if(s["fog.intensity"]<=0)return;const i=s["fog.intensity"],o=s["fog.reach"],h=fogAir(s["fog.color"],n),{inner:c,outer:u}=fogVeilStops(i,h),d=fogBlurPx(i);applyMaskedBlur(e,t,a,r,d,h.grade,l(o)),fillEllipticalRadial(e,t,a,o,c,u)},edgeBlur:()=>{if(s["edgeBlur.intensity"]<=0)return;const n=s["edgeBlur.intensity"];applyMaskedBlur(e,t,a,r,n,"saturate(1.08)",l(s["edgeBlur.reach"]))},tint:()=>{s["tint.opacity"]<=0||(e.save(),e.globalAlpha=s["tint.opacity"],e.fillStyle=s["tint.color"],e.fillRect(0,0,t,a),e.restore())},vignette:()=>{if(0===s["vignette.intensity"])return;const{inner:r,outer:n}=vignetteStops(s["vignette.intensity"]);fillEllipticalRadial(e,t,a,s["vignette.reach"],r,n)}};for(const c of OVERLAYS)h[c](),o(c)})(r,n.width,n.height,n.scale,t,this.map.styleLightDarkTheme,this.bloomInChain(t),e=>this.captureLayersAfter(e,r,n)),a}settled(e=15e3){const t=this.map.mapLibreMap;return new Promise(a=>{const r=()=>{clearTimeout(s),t.off("idle",r),a()},s=setTimeout(r,e);t.once("idle",r)})}warnNoWebGL2(){this.warnedNoWebGL2||(this.warnedNoWebGL2=!0,console.warn("[MapEffects] this browser gives no WebGL2 context, so passes from addPass neither compile nor run, the glare draws no streaks, and a bloom.only scope draws no glow."))}};export{MapEffects,effectKnobCatalogue,effectKnobIds};
|
|
1
|
+
import{configWithKnobs,configWithoutKnob,getStyleColor,knobValueIn,parseCssColor,styleColorKnobIds,toHsl}from"@tomtom-org/maps-sdk/map";var everyColor,groupsOf=e=>e.split(".").slice(0,-1).map((e,t,a)=>a.slice(0,t+1).join(".")),NAMED_KNOBS=/* @__PURE__ */(()=>{const e=/* @__PURE__ */new Map;for(const t of styleColorKnobIds)for(const a of[...groupsOf(t),t])e.set(a,[...e.get(a)??[],t]);return e})(),bloomScopeNames=/* @__PURE__ */(()=>[...NAMED_KNOBS.keys()])(),cubeIndex=(e,t,a)=>32*(32*(e>>3)+(t>>3))+(a>>3),chromaticity=([e,t,a])=>{const r=Math.max(e,t,a);if(0!==r)return[e/r,t/r,a/r]},isKeyed=(e,t,a,r,s)=>{const i=Math.max(a,r,s);if(0===i)return!1;const o=a/i,n=r/i,l=s/i;for(let h=0;h<e.length;h+=3){const a=o-e[h],r=n-e[h+1],s=l-e[h+2];if(a*a+r*r+s*s<=t)return!0}return!1},FRACTION={min:0,max:1,step:.05,bounds:"hard"},TOLERANCE={min:0,max:1,step:.05,bounds:"hard-min"},GRADE_TERM={min:.5,max:1.5,step:.05,bounds:"hard-min"},BLUR_CSS_PX={min:0,max:30,step:1,bounds:"hard-min"},USE_CASE_bloom="Emphasis on dark and driving styles.",USE_CASE_glare="Lights that read as lights on dark styles: lit roads, icons and routes cast streaks, as lamps do through a lens.",USE_CASE_grade="Muting the base map under a data overlay.",USE_CASE_fog="Depth cue: the far context recedes as if seen through air.",USE_CASE_edgeBlur="Focus: the centre stays sharp and the edges lose their detail.",USE_CASE_depthOfField="Depth on a tilted map: one band of distance stays sharp, the rest defocuses with distance.",USE_CASE_tint="Muting the base map under a data overlay; matching a brand.",effectKnobDefinitions={"bloom.intensity":{kind:"number",description:"How strongly bright features (lit roads, traffic tubes, route lines) glow. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_bloom},"bloom.radius":{kind:"number",description:"How far the glow spreads, in CSS pixels.",default:12,range:{min:2,max:40,step:1,bounds:"hard-min"},useCase:USE_CASE_bloom},"bloom.threshold":{kind:"number",description:"Brightness below which nothing glows. Raise it so only the brightest features light up.",default:.55,range:FRACTION,useCase:USE_CASE_bloom},"bloom.only":{kind:"colors",description:"The colours bloom may light: CSS colours, style colour knobs or groups of them (`traffic`, `traffic.incidents.colors.major`), read from the loaded style. Only the pixels wearing one of them glow; empty lights the whole frame.",default:[],options:bloomScopeNames,useCase:USE_CASE_bloom},"bloom.onlyTolerance":{kind:"number",description:"How far a pixel may sit from one of the scope's colours and still glow. Raise it when antialiasing has mixed the colour with the map underneath; lower it when a neighbouring severity is glowing too.",default:.15,range:TOLERANCE,useCase:USE_CASE_bloom},"glare.intensity":{kind:"number",description:"How strongly the lights cast streaks, as a star filter or an anamorphic lens draws them from a lamp. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_glare},"glare.length":{kind:"number",description:"How far each streak reaches from its light, in CSS pixels.",default:32,range:{min:4,max:120,step:1,bounds:"hard-min"},useCase:USE_CASE_glare},"glare.rays":{kind:"number",description:"How many streaks each light casts: 2 is an anamorphic lens’s line, 4 a cross, 6 and 8 a star. An odd count rounds up.",default:4,range:{min:2,max:8,step:2,bounds:"hard"},useCase:USE_CASE_glare},"glare.angle":{kind:"number",description:"The first streak’s direction, in degrees anticlockwise from the horizontal; the others share the turn evenly.",default:45,range:{min:0,max:180,step:5,bounds:"hard"},useCase:USE_CASE_glare},"glare.threshold":{kind:"number",description:"Brightness below which a light casts nothing.",default:.3,range:FRACTION,useCase:USE_CASE_glare},"glare.only":{kind:"colors",description:"The colours that count as lights, as `bloom.only` names them. By default the major roads, read from the loaded style; empty lets every bright pixel streak, labels included.",default:["colors.roadMajor"],options:bloomScopeNames,useCase:USE_CASE_glare},"glare.onlyTolerance":{kind:"number",description:"How far a pixel may sit from a light's colour and still streak.",default:.15,range:TOLERANCE,useCase:USE_CASE_glare},"grade.brightness":{kind:"number",description:"Brightness of the whole base map (1 = unchanged).",default:1,range:GRADE_TERM,useCase:USE_CASE_grade},"grade.contrast":{kind:"number",description:"Contrast of the whole base map (1 = unchanged).",default:1,range:GRADE_TERM,useCase:USE_CASE_grade},"grade.saturation":{kind:"number",description:"Colour saturation of the whole base map (1 = unchanged, 0 = greyscale).",default:1,range:{min:0,max:1.5,step:.05,bounds:"hard-min"},useCase:USE_CASE_grade},"fog.intensity":{kind:"number",description:"Haze over the edges of the view: blur, plus a wash towards the colour of the air (`fog.color`). Reads as distance. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_fog},"fog.reach":{kind:"number",description:"How far the fog reaches in from the edges (1 = to the centre).",default:.4,range:FRACTION,useCase:USE_CASE_fog},"fog.color":{kind:"color",description:"The colour of the air the fog washes towards; its lightness brightens or darkens the rim with it, and a translucent colour does both less. Unset, it follows the map's light/dark theme: pale on a light map, dark on a dark one.",useCase:USE_CASE_fog},"edgeBlur.intensity":{kind:"number",description:"Blur over the edges of the view, in CSS pixels, leaving their colour untouched. Reads as a lens, not as distance — `fog` is the effect that also washes the rim out. 0 = off.",default:0,range:BLUR_CSS_PX,useCase:USE_CASE_edgeBlur},"edgeBlur.reach":{kind:"number",description:"How far the edge blur reaches in from the edges (1 = to the centre).",default:.5,range:FRACTION,useCase:USE_CASE_edgeBlur},"depthOfField.intensity":{kind:"number",description:"The widest circle of confusion, in CSS pixels — the blur the most out-of-focus distance gets. Needs a tilted camera: a map seen from straight above has no depth, and the effect ends there on its own. 0 = off.",default:0,range:BLUR_CSS_PX,useCase:USE_CASE_depthOfField},"depthOfField.focus":{kind:"number",description:"Which distance is sharp, across what the frame shows: 0 the nearest ground at the bottom of the view, 1 the farthest at the top.",default:0,range:FRACTION,useCase:USE_CASE_depthOfField},"depthOfField.band":{kind:"number",description:"How much of that depth stays sharp — the field itself. Thin is the miniature; wide holds a whole run of the map. A gentle tilt has little depth, so the same value covers more of the view.",default:.3,range:FRACTION,useCase:USE_CASE_depthOfField},"depthOfField.bokeh":{kind:"number",description:"Whether an out-of-focus highlight fades or comes back as a disc of light. 0 averages the light away, as a blur does; turned up, a lit road or a traffic tube keeps its brightness across the whole circle it is spread over.",default:.5,range:FRACTION,useCase:USE_CASE_depthOfField},"tint.color":{kind:"color",description:"A flat colour laid over the whole map.",default:"#1f2937",useCase:USE_CASE_tint},"tint.opacity":{kind:"number",description:"Opacity of the tint. 0 = off.",default:0,range:FRACTION,useCase:USE_CASE_tint},"vignette.intensity":{kind:"number",description:"Darkening (negative) or lightening (positive) towards the edges. 0 = off.",default:0,range:{min:-1,max:1,step:.05,bounds:"hard"},useCase:"Focus on the centre of the view."},"vignette.reach":{kind:"number",description:"How far the vignette reaches in from the edges (1 = to the centre).",default:.45,range:FRACTION,useCase:"Focus on the centre of the view."}},effectKnobIds=/* @__PURE__ */Object.keys(effectKnobDefinitions),effectKnobCatalogue=/* @__PURE__ */effectKnobIds.map(e=>({id:e,...effectKnobDefinitions[e]})),clamp=(e,t,a)=>Math.min(Math.max(e,t),a),asFraction=e=>clamp(e,0,1),bloomThreshold=e=>{const t=.92*asFraction(e);return{brightness:1/(1+t),contrast:(1+t)/(1-t)}},bloomFilter=(e,t,a=1)=>{const{brightness:r,contrast:s}=bloomThreshold(t);return`brightness(${r.toFixed(4)}) contrast(${s.toFixed(3)}) blur(${Math.max(e,0)*a}px) saturate(1.35)`},texelOf=e=>[1/Math.max(e.width,1),1/Math.max(e.height,1)],lightUniforms=(e,t)=>{const{brightness:a,contrast:r}=bloomThreshold(e.threshold);return{uBrightness:a,uContrast:r,uTexel:texelOf(t),uIntensity:asFraction(e.intensity),uSaturation:1.35}},toRadians=e=>e*Math.PI/180,gradeFilter=(e,t,a)=>[1===e?"":`brightness(${Math.max(e,0)})`,1===t?"":`contrast(${Math.max(t,0)})`,1===a?"":`saturate(${Math.max(a,0)})`].filter(Boolean).join(" "),rimStart=e=>1-asFraction(e),rimGradient=(e,t,a)=>`radial-gradient(ellipse farthest-corner at center, ${e} ${Math.round(100*rimStart(a))}%, ${t} 100%)`,vignetteGradient=(e,t)=>{const{inner:a,outer:r}=vignetteStops(e);return rimGradient(a,r,t)},vignetteStops=e=>{const t=e<0?"0, 0, 0":"255, 255, 255";return{inner:`rgba(${t}, 0)`,outer:`rgba(${t}, ${Math.min(Math.abs(e),1)})`}},rimMask=e=>rimGradient("transparent","black",e),depthRamp=({pitch:e,verticalFieldOfView:t})=>{const a=Math.tan(toRadians(clamp(e,0,89))),r=2*Math.tan(toRadians(clamp(t,1,175))/2)*a;return r/(1+r/2)},THEME_AIR={light:{rgb:[238,240,244],brightness:1.08},dark:{rgb:[16,22,33],brightness:.92}},lightnessOf=e=>toHsl(`rgb(${e.join(", ")})`)?.l??0,fogAir=(e,t)=>{const a=THEME_AIR[t],r=void 0===e?void 0:parseCssColor(e),s=r?[Math.round(255*r[0]),Math.round(255*r[1]),Math.round(255*r[2])]:a.rgb,i=r?.[3]??1,o=a.brightness+i*((e=>{const t=lightnessOf(THEME_AIR.dark.rgb),a=(lightnessOf(e)-t)/(lightnessOf(THEME_AIR.light.rgb)-t);return THEME_AIR.dark.brightness+a*(THEME_AIR.light.brightness-THEME_AIR.dark.brightness)})(s)-a.brightness);return{rgb:s.join(", "),alpha:i,grade:`brightness(${Math.round(1e3*o)/1e3}) contrast(0.82) saturate(0.7)`}},fogFilter=(e,t,a=1)=>`blur(${(e=>2+6*asFraction(e))(e)*a}px) ${t.grade}`,fogVeilStops=(e,t)=>({inner:`rgba(${t.rgb}, 0)`,outer:`rgba(${t.rgb}, ${.65*asFraction(e)*t.alpha})`}),fogVeil=(e,t,a)=>{const{inner:r,outer:s}=fogVeilStops(e,a);return rimGradient(r,s,t)},defocusFilter=(e,t=1)=>`blur(${e*t}px) saturate(1.08)`,filteredCopy=(e,t,a,r)=>{const s=document.createElement("canvas");s.width=t,s.height=a;const i=s.getContext("2d");if(i)return i.filter=r,i.drawImage(e.canvas,0,0),i.filter="none",i},drawOver=(e,t,a,r=1)=>{e.save(),e.globalCompositeOperation=a,e.globalAlpha=r,e.drawImage(t.canvas,0,0),e.restore()},fillEllipticalRadial=(e,t,a,r,s,i)=>{const o=t/Math.SQRT2,n=a/Math.SQRT2,l=e.createRadialGradient(0,0,rimStart(r),0,0,1);l.addColorStop(0,s),l.addColorStop(1,i),e.save(),e.translate(t/2,a/2),e.scale(o,n),e.fillStyle=l,e.fillRect(-t/2/o,-a/2/n,t/o,a/n),e.restore()},applyMaskedBlur=(e,t,a,r,s)=>{const i=filteredCopy(e,t,a,r);i&&(i.globalCompositeOperation="destination-in",s(i),e.drawImage(i.canvas,0,0))},OVERLAYS=["bloom","grade","fog","edgeBlur","tint","vignette"],effectValueOf=(e,t)=>{const a=effectKnobDefinitions[t];return knobValueIn(e,t)??a.default},withDefaults=e=>Object.fromEntries(effectKnobIds.map(t=>[t,effectValueOf(e,t)])),bloomBlur_default="uniform float uSigma;uniform vec2 uTexel;uniform vec2 uDirection;const int BLUR_TAPS=12;vec3 blurAlong(vec2 uv){float spacing=max(3.*uSigma/float(BLUR_TAPS),1.);float level=log2(spacing);vec3 total=vec3(0.);float weights=0.;for(int index=-BLUR_TAPS;index<=BLUR_TAPS;index++){float offset=float(index)*spacing;float weight=exp(-.5*offset*offset/(uSigma*uSigma));vec2 at=uv+uDirection*offset*uTexel;float inside=float(all(greaterThanEqual(at,vec2(0.)))&&all(lessThanEqual(at,vec2(1.))));total+=textureLod(uSource,at,level).rgb*weight*inside;weights+=weight;}return total/weights;}\n",shared_default="float luma(vec3 colour){return dot(colour,vec3(.2126,.7152,.0722));}vec4 screenLight(vec4 picture,vec3 light,float intensity,float saturation){light=clamp(mix(vec3(luma(light)),light,saturation),0.,1.);return vec4(picture.rgb+intensity*light*(1.-picture.rgb),picture.a);}float hash(vec2 at){vec3 spread=fract(vec3(at.xyx)*.1031);spread+=dot(spread,spread.yzx+33.33);return fract((spread.x+spread.y)*spread.z);}\n",BLOOM_KEY_SHADER="uniform highp usampler3D uLookup;uniform float uBrightness;uniform float uContrast;void main(){vec4 colour=texture(uSource,vUv);ivec3 cell=ivec3(colour.bgr*255.+.5)>>3;float lit=float(texelFetch(uLookup,cell,0).r);vec3 cut=clamp((colour.rgb*lit*uBrightness-.5)*uContrast+.5,0.,1.);fragColor=vec4(cut,1.);}\n",BLOOM_BLUR_SHADER=/* @__PURE__ */(()=>`${bloomBlur_default}void main(){fragColor=vec4(blurAlong(vUv),1.);}\n`)(),BLOOM_COMPOSITE_SHADER=/* @__PURE__ */(()=>`${shared_default}${bloomBlur_default}uniform sampler2D uKept;uniform float uIntensity;uniform float uSaturation;void main(){fragColor=screenLight(texture(uKept,vUv),blurAlong(vUv),uIntensity,uSaturation);}\n`)(),DEFOCUS_SHADER=/* @__PURE__ */(()=>`${shared_default}uniform float uDepth;uniform float uFocus;uniform float uBand;uniform float uBlur;uniform float uBokeh;uniform vec2 uTexel;const int MAX_TAPS=48;const float GOLDEN_ANGLE=2.39996323;float depthAt(float v){return uDepth*v;}float circleOfConfusion(float v){float defocus=abs(depthAt(v)-uFocus)-uBand;return uBlur*clamp(defocus/max(1.-uBand,.001),0.,1.);}float bokehWeight(vec3 colour){float bright=luma(colour);float fourth=bright*bright*bright*bright;return 1.+uBokeh*8.*fourth;}vec4 defocus(vec2 uv,float coc){float radius=coc*.5;if(radius<.75)return textureLod(uSource,uv,0.);int taps=int(clamp(radius*1.5,12.,float(MAX_TAPS)));float spacing=radius*sqrt(3.14159265/float(taps));float level=max(log2(spacing),0.);float spin=hash(gl_FragCoord.xy)*6.28318531;vec4 centre=textureLod(uSource,uv,level);float weight=bokehWeight(centre.rgb);vec4 total=vec4(centre.rgb*centre.a,centre.a)*weight;float carried=weight;for(int index=0;index<MAX_TAPS;index++){if(index>=taps)break;float fraction=(float(index)+.5)/float(taps);float distance=radius*sqrt(fraction);float angle=float(index)*GOLDEN_ANGLE+spin;vec2 offset=vec2(cos(angle),sin(angle))*distance;vec4 sampled=textureLod(uSource,uv+offset*uTexel,level);float tapWeight=bokehWeight(sampled.rgb);total+=vec4(sampled.rgb*sampled.a,sampled.a)*tapWeight;carried+=tapWeight;}return vec4(total.rgb/max(total.a,.0001),total.a/carried);}void main(){fragColor=defocus(vUv,circleOfConfusion(vUv.y));}\n`)(),GLARE_SHADER=/* @__PURE__ */(()=>`${shared_default}uniform sampler2D uKept;uniform float uIntensity;uniform float uLength;uniform float uAxes;uniform float uAngle;uniform float uGain;uniform float uSaturation;uniform vec2 uTexel;const int MAX_AXES=4;const int RAY_TAPS=16;const float PI=3.14159265;float inFrame(vec2 at){vec2 inside=step(vec2(0.),at)*step(at,vec2(1.));return inside.x*inside.y;}vec3 streakAlong(vec2 uv,vec2 direction,float jitter){float spacing=max(uLength/float(RAY_TAPS),1.);float level=max(log2(spacing),0.);vec3 total=vec3(0.);for(int index=0;index<RAY_TAPS;index++){float distance=(float(index)+jitter)*spacing;float remaining=max(1.-distance/uLength,0.);vec2 offset=direction*distance*uTexel;vec3 both=textureLod(uSource,uv+offset,level).rgb*inFrame(uv+offset)+textureLod(uSource,uv-offset,level).rgb*inFrame(uv-offset);total+=both*remaining*remaining;}return total*spacing;}void main(){float jitter=.5+.5*hash(gl_FragCoord.xy);vec3 glare=vec3(0.);for(int axis=0;axis<MAX_AXES;axis++){if(float(axis)>=uAxes)break;float angle=uAngle+float(axis)*PI/uAxes;glare+=streakAlong(vUv,vec2(cos(angle),sin(angle)),jitter);}fragColor=screenLight(texture(uKept,vUv),clamp(glare*uGain,0.,1.),uIntensity,uSaturation);}\n`)(),setSampling=(e,t=e.CLAMP_TO_EDGE)=>{e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_S,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_WRAP_T,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MAG_FILTER,e.LINEAR),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR)},hasPixels=e=>"naturalWidth"in e?e.complete&&e.naturalWidth>0:"readyState"in e?e.readyState>=e.HAVE_CURRENT_DATA:"displayWidth"in e?e.displayWidth>0:e.width>0,resizeCanvas=(e,t,a)=>{e.width===t&&e.height===a||(e.width=t,e.height=a)},compile=(e,t,a)=>{const r=e.createShader(t);if(!r)return"the context would not create a shader";if(e.shaderSource(r,a),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS))return r;const s=e.getShaderInfoLog(r)??"";return e.deleteShader(r),`failed to compile: ${s}`},ScreenPassRenderer=class{canvas;context;programs=/* @__PURE__ */new Map;failures=/* @__PURE__ */new Set;sourceTexture;stageTextures=/* @__PURE__ */new Map;lookupTextures=[];targets=[];targetSize={width:0,height:0};lost=!1;constructor(){this.canvas=document.createElement("canvas"),this.canvas.addEventListener("webglcontextlost",e=>{e.preventDefault(),this.lost=!0}),this.canvas.addEventListener("webglcontextrestored",()=>{this.programs.clear(),this.failures.clear(),this.sourceTexture=void 0,this.stageTextures.clear(),this.lookupTextures=[],this.targets=[],this.targetSize={width:0,height:0},this.lost=!1})}get gl(){return void 0===this.context&&(this.context=this.canvas.getContext("webgl2",{alpha:!0,antialias:!1,premultipliedAlpha:!1})),this.context??void 0}render(e,t){if(t.length<2&&this.context&&this.releaseTargets(this.context),!t.length||!e.width||!e.height)return;const a=this.gl;if(!a||this.lost)return;resizeCanvas(this.canvas,e.width,e.height);const r=this.uploadSource(e,t[0].mipmapped),s=t.slice(1,-2).some(e=>e.keepsInput)?3:2;return!r||t.length>1&&!this.holdTargets(a,s)?void 0:(this.runStages(a,t,r),this.canvas)}runStages(e,t,a){let r,s=a;for(let i=0;i<t.length;i++){const a=t[i];a.keepsInput&&(r=s);const o=i===t.length-1?void 0:this.targets.find(({texture:e})=>e!==s&&e!==r);this.runStage(e,a,s,o,r),o&&(s=o.texture,t[i+1].mipmapped?this.mipmap(e,s):(e.bindTexture(e.TEXTURE_2D,s),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR)))}}get hasContext(){return void 0!==this.gl}compileError(e){const t=this.gl;if(!t||this.lost)return;const a=this.cachedProgram(t,e);return"string"==typeof a?a:void 0}release(e){const t=this.programs.get(e);t&&this.context?.deleteProgram(t),this.programs.delete(e),this.failures.delete(e)}releaseTextures(e){for(const t of e){const e=this.stageTextures.get(t);e&&this.context?.deleteTexture(e),this.stageTextures.delete(t)}}remove(){this.context?.getExtension("WEBGL_lose_context")?.loseContext(),this.context=null,this.stageTextures.clear()}holdTargets(e,t){const{width:a,height:r}=this.canvas,s=this.targetSize.width!==a||this.targetSize.height!==r;for(this.releaseTargets(e,s?0:t);this.targets.length<t;){const t=e.createTexture(),s=e.createFramebuffer();if(!t||!s)return!1;e.bindTexture(e.TEXTURE_2D,t),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,a,r,0,e.RGBA,e.UNSIGNED_BYTE,null),setSampling(e),e.bindFramebuffer(e.FRAMEBUFFER,s),e.framebufferTexture2D(e.FRAMEBUFFER,e.COLOR_ATTACHMENT0,e.TEXTURE_2D,t,0),this.targets.push({texture:t,framebuffer:s})}return this.targetSize={width:a,height:r},!0}releaseTargets(e,t=0){for(const{texture:a,framebuffer:r}of this.targets.splice(t))e.deleteTexture(a),e.deleteFramebuffer(r)}bindStageTextures(e,t,a){Object.entries(a.textures??{}).forEach(([a,r],s)=>{const i=s+1;e.activeTexture(e.TEXTURE0+i);const o=this.stageTextures.get(r),n=o??e.createTexture();n&&(e.bindTexture(e.TEXTURE_2D,n),o&&!(e=>"readyState"in e||"getContext"in e)(r)||(e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!0),e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,r)),o||(setSampling(e,e.REPEAT),this.stageTextures.set(r,n)),e.uniform1i(e.getUniformLocation(t,a),i))})}mipmap(e,t){e.bindTexture(e.TEXTURE_2D,t),e.texParameteri(e.TEXTURE_2D,e.TEXTURE_MIN_FILTER,e.LINEAR_MIPMAP_LINEAR),e.generateMipmap(e.TEXTURE_2D)}uploadSource(e,t=!1){const a=this.gl;if(a&&(this.sourceTexture??=a.createTexture()??void 0,this.sourceTexture))return a.bindTexture(a.TEXTURE_2D,this.sourceTexture),a.pixelStorei(a.UNPACK_FLIP_Y_WEBGL,!0),a.texImage2D(a.TEXTURE_2D,0,a.RGBA,a.RGBA,a.UNSIGNED_BYTE,e),setSampling(a),t&&this.mipmap(a,this.sourceTexture),this.sourceTexture}lookupTextureOf(e,t){const a=this.lookupTextures.find(e=>e.cube===t);if(a)return this.lookupTextures=[a,...this.lookupTextures.filter(e=>e!==a)],a.texture;const r=(this.lookupTextures.length>=2?this.lookupTextures.pop():void 0)?.texture??e.createTexture();if(!r)return;e.bindTexture(e.TEXTURE_3D,r),e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL,!1),e.pixelStorei(e.UNPACK_ALIGNMENT,1);const{side:s,texels:i}=t;return e.texImage3D(e.TEXTURE_3D,0,e.R8UI,s,s,s,0,e.RED_INTEGER,e.UNSIGNED_BYTE,i),e.texParameteri(e.TEXTURE_3D,e.TEXTURE_MIN_FILTER,e.NEAREST),e.texParameteri(e.TEXTURE_3D,e.TEXTURE_MAG_FILTER,e.NEAREST),this.lookupTextures.unshift({cube:t,texture:r}),r}runStage(e,t,a,r,s){const i=this.programOf(e,t.fragment);if(!i)return;const o=t.lookup&&this.lookupTextureOf(e,t.lookup);if(!t.lookup||o){e.bindFramebuffer(e.FRAMEBUFFER,r?.framebuffer??null),e.viewport(0,0,this.canvas.width,this.canvas.height),e.useProgram(i),o&&(e.activeTexture(e.TEXTURE0+2),e.bindTexture(e.TEXTURE_3D,o),e.uniform1i(e.getUniformLocation(i,"uLookup"),2)),s&&(e.activeTexture(e.TEXTURE0+1),e.bindTexture(e.TEXTURE_2D,s),e.uniform1i(e.getUniformLocation(i,"uKept"),1)),this.bindStageTextures(e,i,t),e.activeTexture(e.TEXTURE0+0),e.bindTexture(e.TEXTURE_2D,a),e.uniform1i(e.getUniformLocation(i,"uSource"),0);for(const[a,r]of Object.entries(t.uniforms)){const t=e.getUniformLocation(i,a);t&&("number"==typeof r?e.uniform1f(t,r):2===r.length?e.uniform2f(t,r[0],r[1]):3===r.length?e.uniform3f(t,r[0],r[1],r[2]):e.uniform4f(t,r[0],r[1],r[2],r[3]))}e.drawArrays(e.TRIANGLES,0,3)}}programOf(e,t){if(this.failures.has(t))return;const a=this.cachedProgram(e,t);if("string"!=typeof a)return a;this.failures.add(t),console.warn(`[MapEffects] shader ${a}`)}cachedProgram(e,t){const a=this.programs.get(t);if(a)return a;const r=this.build(e,t);return"string"!=typeof r&&this.programs.set(t,r),r}build(e,t){const a=[compile(e,e.VERTEX_SHADER,"#version 300 es\nout vec2 vUv;void main(){vec2 corner=vec2((gl_VertexID<<1)&2,gl_VertexID&2);vUv=corner;gl_Position=vec4(corner*2.-1.,0.,1.);}\n"),compile(e,e.FRAGMENT_SHADER,`#version 300 es\nprecision highp float;precision highp sampler2D;uniform sampler2D uSource;in vec2 vUv;out vec4 fragColor;\n${t}`)],r=a.find(e=>"string"==typeof e),s=a.filter(e=>"string"!=typeof e),i=r?null:e.createProgram();if(i){for(const t of s)e.attachShader(i,t);e.linkProgram(i)}for(const n of s)e.deleteShader(n);if(r)return r;if(!i)return"the context would not create a program";if(e.getProgramParameter(i,e.LINK_STATUS))return i;const o=e.getProgramInfoLog(i)??"";return e.deleteProgram(i),`failed to link: ${o}`}},PASS_PLACEMENTS={"before-depth-of-field":!0,"after-depth-of-field":!0},layerPlacementOf=e=>`after-${e}`,backdropFilterStyle=e=>({backdropFilter:e,webkitBackdropFilter:e}),maskStyle=e=>({maskImage:e,webkitMaskImage:e}),LAYER_STYLE={position:"absolute",inset:"0",width:"100%",height:"100%",pointerEvents:"none",display:"none"},rasterScaleOf=e=>e.width/Math.max(e.clientWidth,1),MapEffects=class{map;config={};values=withDefaults({});passes=[];layers=[];container;overlays;bloomCanvas;artworkCanvas;chain=new ScreenPassRenderer;scopeMasks={};styleGeneration=0;warnedScopes=/* @__PURE__ */new Set;warnedNoWebGL2=!1;warnedFrame=!1;capturing;warnedThrows=/* @__PURE__ */new WeakSet;passSources=/* @__PURE__ */new Map;onStyleData=()=>{this.styleGeneration++,this.map.mapLibreMap.triggerRepaint(),this.showFog(this.values)};onRender=()=>{try{this.drawChain(this.values),"block"===this.bloomCanvas.style.display&&this.drawBloom()}catch(e){this.skipFrame(e)}this.layers.length&&this.drawLayers()};constructor(e,t={}){this.map=e,this.container=document.createElement("div"),this.container.className="tomtom-map-effects",Object.assign(this.container.style,{position:"absolute",inset:"0",pointerEvents:"none",overflow:"hidden"}),this.map.mapLibreMap.getCanvas().insertAdjacentElement("afterend",this.container),this.bloomCanvas=document.createElement("canvas"),this.overlays=Object.fromEntries(OVERLAYS.map(e=>{const t="bloom"===e?this.bloomCanvas:document.createElement("div");return t.dataset.effect=e,Object.assign(t.style,LAYER_STYLE),this.container.appendChild(t),[e,t]})),this.bloomCanvas.style.mixBlendMode="screen",this.artworkCanvas=this.chain.canvas,this.artworkCanvas.dataset.effect="artwork",Object.assign(this.artworkCanvas.style,LAYER_STYLE),this.container.insertAdjacentElement("afterbegin",this.artworkCanvas),this.map.mapLibreMap.on("render",this.onRender),this.map.mapLibreMap.on("styledata",this.onStyleData),this.applyConfig(t)}cameraAngles(){const e=this.map.mapLibreMap;return{pitch:e.getPitch(),verticalFieldOfView:e.getVerticalFieldOfView()}}getConfig(){return structuredClone(this.config)}applyConfig(e){this.config=e,this.values=withDefaults(e),this.render()}updateConfig(e){this.applyConfig({...this.config,...e})}resetConfig(){this.applyConfig({})}_knobValue(e){return knobValueIn(this.config,e)}_setKnobValues(e){this.applyConfig(configWithKnobs(this.config,e))}_resetKnobValue(e){this.applyConfig(configWithoutKnob(this.config,e))}addPass(e){if(!e.id)throw new Error("A pass needs an id, to be replaced or removed by.");if(void 0!==e.placement&&!Object.hasOwn(PASS_PLACEMENTS,e.placement))throw new RangeError(`Pass '${e.id}' expects a placement of ${Object.keys(PASS_PLACEMENTS).join(" or ")}; got ${String(e.placement)}.`);const t=Object.keys(e.textures??{});if(t.length>15||t.includes("uSource"))throw new RangeError(`Pass '${e.id}' takes up to 15 textures, none named uSource; got ${t.join(", ")}.`);this.chain.hasContext||this.warnNoWebGL2();const a=this.chain.compileError(e.fragment);if(a)throw new Error(`Pass '${e.id}' ${a}`);const r={...e},s=this.passes.find(t=>t.id===e.id);this.passes=s?this.passes.map(e=>e===s?r:e):[...this.passes,r],s&&this.releaseUnused(s),this.render()}removePass(e){const t=this.passes.find(t=>t.id===e);t&&(this.passes=this.passes.filter(e=>e!==t),this.releaseUnused(t),this.render())}addLayer(e){if(!e.id)throw new Error("A layer needs an id, to be replaced or removed by.");if(t=e.placement,!OVERLAYS.some(e=>t===layerPlacementOf(e)))throw new RangeError(`Layer '${e.id}' expects a placement of ${OVERLAYS.map(layerPlacementOf).join(", ")}; got ${String(e.placement)}.`);var t;const a=this.layers.find(t=>t.layer.id===e.id),r={layer:{...e},canvas:a?.canvas??this.layerCanvas(e.id)};this.layers=a?this.layers.map(e=>e===a?r:e):[...this.layers,r],this.mountLayers(),this.paintLayer(r.layer,r.canvas,this.rasterOf())}removeLayer(e){const t=this.layers.find(({layer:t})=>t.id===e);t&&(this.layers=this.layers.filter(e=>e!==t),t.canvas.remove())}async capture(e={}){const{pixelRatio:t}=e;if(void 0!==t&&!(Number.isFinite(t)&&t>0))throw new RangeError(`A capture's pixelRatio must be a positive number, not ${t}.`);for(;this.capturing;)await this.capturing.catch(()=>{});const a=this.captureAtRatio(e);this.capturing=a;try{return await a}finally{this.capturing=void 0}}async captureAtRatio(e){const t=this.map.mapLibreMap,a=t,r=t.getPixelRatio(),s=e.pixelRatio??r,i=r!==(globalThis.devicePixelRatio||1),o=Math.abs(s-r)>.01;o&&a.setPixelRatio(s),!o&&t.loaded()||await this.settled();try{return await this.readBack()}finally{o&&a.setPixelRatio(i?r:null)}}remove(){this.map.mapLibreMap.off("render",this.onRender),this.map.mapLibreMap.off("styledata",this.onStyleData),this.chain.remove(),this.passSources.clear(),this.container.remove()}show(e,t,a={}){const r=this.overlays[e];r.style.display=t?"block":"none",Object.assign(r.style,a)}render(){const{values:e}=this,t=gradeFilter(e["grade.brightness"],e["grade.contrast"],e["grade.saturation"]);this.show("grade",""!==t,backdropFilterStyle(t)),this.showFog(e),this.show("edgeBlur",e["edgeBlur.intensity"]>0,{...backdropFilterStyle(defocusFilter(e["edgeBlur.intensity"])),...maskStyle(rimMask(e["edgeBlur.reach"]))}),(e["glare.intensity"]>0||e["depthOfField.intensity"]>0)&&!this.chain.hasContext&&this.warnNoWebGL2(),this.show("tint",e["tint.opacity"]>0,{backgroundColor:e["tint.color"],opacity:String(e["tint.opacity"])}),this.show("vignette",0!==e["vignette.intensity"],{backgroundImage:vignetteGradient(e["vignette.intensity"],e["vignette.reach"])}),("block"===this.artworkCanvas.style.display||"block"===this.bloomCanvas.style.display||this.readsFrame(e))&&this.map.mapLibreMap.triggerRepaint()}readsFrame(e){return this.passes.length>0||e["bloom.intensity"]>0||e["glare.intensity"]>0||e["depthOfField.intensity"]>0}bloomInChain(e){return e["bloom.intensity"]>0&&void 0!==this.currentScopeMask("bloom")}routeBloom(e){const t=e["bloom.intensity"]>0,a=this.bloomInChain(e);this.show("bloom",t&&!a,{opacity:String(e["bloom.intensity"])}),a&&!this.chain.hasContext&&this.warnNoWebGL2()}showFog(e){const t=fogAir(e["fog.color"],this.map.styleLightDarkTheme);this.show("fog",e["fog.intensity"]>0,{...backdropFilterStyle(fogFilter(e["fog.intensity"],t)),...maskStyle(rimMask(e["fog.reach"])),backgroundImage:fogVeil(e["fog.intensity"],e["fog.reach"],t)})}chainStages(e){return[...this.passStages("before-depth-of-field"),...this.depthOfFieldStages(e),...this.passStages("after-depth-of-field"),...this.glareStages(e),...this.bloomStages(e)]}glareStages(e){if(e["glare.intensity"]<=0)return[];const t=((e,t)=>({...lightUniforms(e,t),uLength:Math.max(e.lengthCssPx*t.scale,1),uAxes:clamp(Math.ceil(e.rays/2),1,4),uAngle:toRadians(e.angleDegrees),uGain:1/(4*t.scale)}))({intensity:e["glare.intensity"],lengthCssPx:e["glare.length"],rays:e["glare.rays"],angleDegrees:e["glare.angle"],threshold:e["glare.threshold"]},this.rasterOf()),a=this.currentScopeMask("glare")??(everyColor??={texels:new Uint8Array(32768).fill(1),side:32},everyColor);return[{fragment:BLOOM_KEY_SHADER,uniforms:t,keepsInput:!0,lookup:a},{fragment:GLARE_SHADER,uniforms:t,mipmapped:!0}]}bloomStages(e){const t=e["bloom.intensity"]>0?this.currentScopeMask("bloom"):void 0;if(!t)return[];const a=((e,t)=>({...lightUniforms(e,t),uSigma:Math.max(e.radiusCssPx*t.scale,1)}))({intensity:e["bloom.intensity"],radiusCssPx:e["bloom.radius"],threshold:e["bloom.threshold"]},this.rasterOf());return[{fragment:BLOOM_KEY_SHADER,uniforms:a,keepsInput:!0,lookup:t},{fragment:BLOOM_BLUR_SHADER,uniforms:{...a,uDirection:[1,0]},mipmapped:!0},{fragment:BLOOM_COMPOSITE_SHADER,uniforms:{...a,uDirection:[0,1]},mipmapped:!0}]}rasterOf(){const e=this.map.mapLibreMap.getCanvas();return{width:e.width,height:e.height,scale:this.map.mapLibreMap.getPixelRatio()}}passStages(e){return this.passes.filter(t=>(t.placement??"before-depth-of-field")===e).flatMap(e=>{const t=this.guarded(e,`pass '${e.id}' is skipped while its uniforms or textures throw`,()=>this.inputsOf(e));return t?[{fragment:e.fragment,mipmapped:e.mipmapped,...t}]:[]})}inputsOf(e){const t={};for(const[r,s]of Object.entries(e.textures??{})){const a="function"==typeof s?s():s;if(!a||!hasPixels(a))return;this.holdSource(e,r,a),t[r]=a}const{uniforms:a={}}=e;return{uniforms:"function"==typeof a?a():a,textures:t}}guarded(e,t,a){try{return a()}catch(r){return void(this.warnedThrows.has(e)||(this.warnedThrows.add(e),console.warn(`[MapEffects] ${t}:`,r)))}}layerCanvas(e){const t=document.createElement("canvas");return t.dataset.layer=e,Object.assign(t.style,LAYER_STYLE,{display:"block"}),t}mountLayers(){for(const e of OVERLAYS){const t=layerPlacementOf(e),a=this.layers.filter(({layer:e})=>e.placement===t);this.overlays[e].after(...a.map(({canvas:e})=>e))}}drawLayers(){const e=this.rasterOf();for(const{layer:t,canvas:a}of this.layers)this.paintLayer(t,a,e)}paintLayer(e,t,{width:a,height:r,scale:s}){resizeCanvas(t,a,r);const i=t.getContext("2d");if(!i)return;i.clearRect(0,0,a,r),i.save();const o=this.guarded(e,`layer '${e.id}' is left empty while its draw() throws`,()=>(e.draw(i,{width:a,height:r,pixelRatio:s}),!0));i.restore(),o||i.clearRect(0,0,a,r)}captureLayersAfter(e,t,a){const r=layerPlacementOf(e);for(const{layer:s}of this.layers.filter(e=>e.layer.placement===r)){const e=document.createElement("canvas");this.paintLayer(s,e,a),t.drawImage(e,0,0)}}releaseUnused(e){this.passes.some(t=>t.fragment===e.fragment)||this.chain.release(e.fragment);const t=this.passSources.get(e);this.passSources.delete(e),t&&this.releaseUnsampled([...t.values()])}releaseUnsampled(e){const t=new Set([...this.passSources.values()].flatMap(e=>[...e.values()]));this.chain.releaseTextures(e.filter(e=>!t.has(e)))}holdSource(e,t,a){const r=this.passSources.get(e)??/* @__PURE__ */new Map;this.passSources.set(e,r);const s=r.get(t);r.set(t,a),s&&s!==a&&this.releaseUnsampled([s])}depthOfFieldStages(e){const t=this.cameraAngles();if(e["depthOfField.intensity"]<=0||depthRamp(t)<=0)return[];const a=this.map.mapLibreMap.getCanvas(),r=((e,t,a)=>{const r=depthRamp(t);if(!(r<=0||e.blurCssPx<=0))return{uDepth:r,uFocus:Math.min(asFraction(e.focus)*r,1),uBand:.5*asFraction(e.band),uBlur:Math.max(e.blurCssPx,0)*a.scale,uBokeh:asFraction(e.bokeh),uTexel:texelOf(a)}})({focus:e["depthOfField.focus"],band:e["depthOfField.band"],blurCssPx:e["depthOfField.intensity"],bokeh:e["depthOfField.bokeh"]},t,{width:a.width,height:a.height,scale:rasterScaleOf(a)});return r?[{fragment:DEFOCUS_SHADER,uniforms:r,mipmapped:!0}]:[]}drawChain(e){const t=this.chainStages(e),a=this.chain.render(this.map.mapLibreMap.getCanvas(),t);this.artworkCanvas.style.display=a?"block":"none",this.routeBloom(e)}drawBloom(){const e="block"===this.artworkCanvas.style.display?this.artworkCanvas:this.map.mapLibreMap.getCanvas(),t=Math.max(1,Math.round(.5*e.width)),a=Math.max(1,Math.round(.5*e.height));resizeCanvas(this.bloomCanvas,t,a);const r=this.bloomCanvas.getContext("2d");r&&(r.filter=bloomFilter(this.values["bloom.radius"],this.values["bloom.threshold"],.5*this.rasterOf().scale),r.clearRect(0,0,t,a),r.drawImage(e,0,0,t,a))}currentScopeMask(e){const t=this.values[`${e}.only`];if(0===t.length)return;const a=t.join("\n"),r=this.values[`${e}.onlyTolerance`],s=this.scopeMasks[e],i=s?.key===a&&s.tolerance===r,o=(e=>e.some(e=>NAMED_KNOBS.has(e)))(t);if(o&&!this.map.mapReady)return i?s.mask:void 0;if(i&&(!o||s.styleGeneration===this.styleGeneration))return s.mask;const n=((e,t)=>t.flatMap(t=>{const a=NAMED_KNOBS.get(t);return(a?a.map(t=>getStyleColor(e,t)):[t]).map(e=>"string"==typeof e?parseCssColor(e):void 0).filter(e=>void 0!==e)}))(this.map,t),l=n.join(";");if(i&&s.colorsKey===l)return s.styleGeneration=this.styleGeneration,s.mask;const h=((e,t)=>{const a=e.map(chromaticity).filter(e=>void 0!==e);if(0===a.length)return;const r=Float64Array.from(a.flat()),s=Math.max(t,0)**2,i=new Uint8Array(32768);for(let o=0;o<256;o+=8)for(let e=0;e<256;e+=8)for(let t=0;t<256;t+=8)isKeyed(r,s,o,e,t)&&(i[cubeIndex(o,e,t)]=1);for(const[o,n,l]of e)Math.max(o,n,l)>0&&(i[cubeIndex(Math.round(255*o),Math.round(255*n),Math.round(255*l))]=1);return{texels:i,side:32}})(n,r);return h||this.warnScopeEmpty(e,t),this.scopeMasks[e]={key:a,tolerance:r,styleGeneration:this.styleGeneration,colorsKey:l,mask:h},h}warnScopeEmpty(e,t){this.warnedScopes.has(e)||(this.warnedScopes.add(e),console.warn(`[MapEffects] '${e}.only: ${t.join(", ")}' found no colour to key on in the loaded style, so ${e} is lighting the whole frame.`))}readBack(){const e=this.map.mapLibreMap;return new Promise((t,a)=>{e.once("render",()=>{try{t(this.compositeFrame(e.getCanvas()))}catch(r){a(r)}}),e.triggerRepaint()})}compositeFrame(e){const{values:t}=this,a=document.createElement("canvas");a.width=e.width,a.height=e.height;const r=a.getContext("2d");if(!r)return a;const s=this.chainStages(t);r.drawImage(this.chain.render(e,s)??e,0,0);const i={width:e.width,height:e.height,scale:rasterScaleOf(e)};return((e,t,a,r,s,i,o,n=()=>{})=>{const l=e=>r=>fillEllipticalRadial(r,t,a,e,"rgba(0,0,0,0)","rgba(0,0,0,1)"),h={bloom:()=>{s["bloom.intensity"]>0&&!o&&((e,t,a,r,s)=>{const i=filteredCopy(e,t,a,bloomFilter(s["bloom.radius"],s["bloom.threshold"],r));i&&drawOver(e,i,"screen",s["bloom.intensity"])})(e,t,a,r,s)},grade:()=>{const r=gradeFilter(s["grade.brightness"],s["grade.contrast"],s["grade.saturation"]);r&&((e,t,a,r)=>{const s=filteredCopy(e,t,a,r);s&&drawOver(e,s,"copy")})(e,t,a,r)},fog:()=>{if(s["fog.intensity"]<=0)return;const o=s["fog.intensity"],n=s["fog.reach"],h=fogAir(s["fog.color"],i),{inner:c,outer:u}=fogVeilStops(o,h);applyMaskedBlur(e,t,a,fogFilter(o,h,r),l(n)),fillEllipticalRadial(e,t,a,n,c,u)},edgeBlur:()=>{if(s["edgeBlur.intensity"]<=0)return;const i=defocusFilter(s["edgeBlur.intensity"],r);applyMaskedBlur(e,t,a,i,l(s["edgeBlur.reach"]))},tint:()=>{var r;s["tint.opacity"]<=0||(r=s["tint.color"],!("undefined"!=typeof CSS&&"function"==typeof CSS.supports?CSS.supports("color",r):void 0!==parseCssColor(r)))||(e.save(),e.globalAlpha=s["tint.opacity"],e.fillStyle=s["tint.color"],e.fillRect(0,0,t,a),e.restore())},vignette:()=>{if(0===s["vignette.intensity"])return;const{inner:r,outer:i}=vignetteStops(s["vignette.intensity"]);fillEllipticalRadial(e,t,a,s["vignette.reach"],r,i)}};for(const c of OVERLAYS)h[c](),n(c)})(r,i.width,i.height,i.scale,t,this.map.styleLightDarkTheme,this.bloomInChain(t),e=>this.captureLayersAfter(e,r,i)),a}settled(e=15e3){const t=this.map.mapLibreMap;return new Promise(a=>{const r=()=>{clearTimeout(s),t.off("idle",r),a()},s=setTimeout(r,e);t.once("idle",r)})}skipFrame(e){this.artworkCanvas.style.display="none",this.bloomCanvas.style.display="none",this.warnedFrame||(this.warnedFrame=!0,console.warn("[MapEffects] the effects are skipped for each frame they fail to draw:",e))}warnNoWebGL2(){this.warnedNoWebGL2||(this.warnedNoWebGL2=!0,console.warn("[MapEffects] this browser gives no WebGL2 context, so passes from addPass neither compile nor run, the depth of field blurs nothing, the glare draws no streaks, and a bloom.only scope draws no glow."))}};export{MapEffects,effectKnobCatalogue,effectKnobIds};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@tomtom-org/maps-sdk-plugin-map-effects",
|
|
3
3
|
"author": "TomTom International B.V.",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.2",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Practical post-processing for a TomTomMap: bloom for emphasis, glare that makes lights cast streaks, tint/grade/vignette to mute the base map under data, fog and edge blur for focus, a WebGL depth of field with bokeh for a tilted 3D view — plus high-DPI capture",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
"typescript": "^6.0.3",
|
|
22
22
|
"vite": "^8.3.4",
|
|
23
23
|
"vitest": "^5.0.5",
|
|
24
|
-
"@
|
|
24
|
+
"@testing/core-utils": "0.0.0",
|
|
25
|
+
"@tomtom-org/maps-sdk": "1.0.0-rc.6"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"dist",
|
|
28
29
|
"package.json",
|
|
29
30
|
"README.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
30
32
|
"LICENSE.txt",
|
|
31
33
|
"THIRD_PARTY.txt",
|
|
32
34
|
"!**/*.map"
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
"test:e2e:ui": "playwright test --config e2e-tests/playwright.config.ts --ui",
|
|
47
49
|
"test:e2e:update-snapshots": "playwright test --config e2e-tests/playwright.config.ts --update-snapshots",
|
|
48
50
|
"test:e2e:server": "vite --config e2e-tests/vite.config.ts",
|
|
49
|
-
"test:e2e:install": "playwright install chromium",
|
|
51
|
+
"test:e2e:install": "playwright install chromium firefox webkit",
|
|
50
52
|
"type-check": "tsc --noEmit"
|
|
51
53
|
}
|
|
52
54
|
}
|