ntk 8.5.0 → 8.6.0

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/lib/index.js CHANGED
@@ -48,6 +48,13 @@ import {
48
48
  setDefaultRasterizer
49
49
  } from './rasterize.js';
50
50
  import { DEFAULT_MASK_POLICY } from './maskcluster.js';
51
+ import {
52
+ DEFAULT_SHADOW_POLICY,
53
+ blurCoverage,
54
+ gaussianKernel1d,
55
+ shadowReach,
56
+ shadowSigma
57
+ } from './shadow.js';
51
58
  import { DEFAULT_SHAPE_POLICY } from './shapeglyphs.js';
52
59
  import { DEFAULT_SHARED_GLYPHS_POLICY } from './glyphdirectory.js';
53
60
  import { warmSharedGlyphs } from './text/glyphs.js';
@@ -275,6 +282,16 @@ export {
275
282
  DEFAULT_RASTER_POLICY,
276
283
  DEFAULT_MASK_POLICY,
277
284
  DEFAULT_SHAPE_POLICY,
285
+ // the shadow blur as a primitive (docs/surface.md#baking-a-blur), for a
286
+ // caller drawing shapes ntk's shadow properties never see. `blurCoverage`
287
+ // *bakes* the two separable passes into a surface's pixels — unlike
288
+ // `picture().setBlurFilter()`, whose kernel the server re-runs on every
289
+ // composite, which a cached shadow pays for once per frame
290
+ DEFAULT_SHADOW_POLICY,
291
+ blurCoverage,
292
+ gaussianKernel1d,
293
+ shadowReach,
294
+ shadowSigma,
278
295
  // cross-process shared glyphs (docs/shared-glyphs.md): the directory-side
279
296
  // budget, and the prewarm that makes a first paint of already-shared text
280
297
  // rasterize and upload nothing
package/lib/picture.js CHANGED
@@ -25,10 +25,35 @@ export default class Picture {
25
25
  registry.register(this, { Render: this.Render, X, id: this.id }, this);
26
26
  }
27
27
 
28
+ /**
29
+ * Set the picture's filter — a *property of the picture*, not an operation
30
+ * on its pixels. The server re-applies it every time the picture is
31
+ * sampled, so the cost is per composite and forever, not once.
32
+ *
33
+ * That is what makes it right for resampling (`'bilinear'` under a
34
+ * transform) and a trap for anything expensive: see `setBlurFilter`.
35
+ */
28
36
  setFilter(name, params) {
29
37
  this.Render.SetPictureFilter(this.id, name, params);
30
38
  }
31
39
 
40
+ /**
41
+ * Hang a k×k gaussian `convolution` on the picture.
42
+ *
43
+ * **This re-convolves on every composite** — it is a filter, so the server
44
+ * runs the whole kernel each time the picture is drawn, and the pixels
45
+ * never change on the client's side of the wire. A picture blurred once and
46
+ * then composited each frame pays k² multiply-accumulates per pixel per
47
+ * frame: at radius 61 over 489×134 that is 244M per draw, which is a 1.6s
48
+ * hover on XQuartz and a 9s window repaint (issue #335).
49
+ *
50
+ * Reach for it when the blur really is per-draw and small. To blur
51
+ * something *once* and composite the result cheaply afterwards — a drop
52
+ * shadow, a cached soft edge — bake it instead with `blurCoverage` from
53
+ * ntk's entry point: two separable 1d passes (2k multiplies per pixel, not
54
+ * k²), run once, leaving a surface with the blur in its pixels and no
55
+ * filter of its own. See docs/surface.md#baking-a-blur.
56
+ */
32
57
  setBlurFilter(radius, sigma) {
33
58
  if (radius === 0) {
34
59
  return this.setFilter('convolution', [1, 1, 1]);
package/lib/shadow.js CHANGED
@@ -32,6 +32,19 @@
32
32
  // second pass leaves a surface with the blur already in its pixels, so the
33
33
  // cached copy composites as a plain mask rather than re-running a kernel on
34
34
  // every frame.
35
+ //
36
+ // ## What of this is public
37
+ //
38
+ // The bake and the maths around it — `blurCoverage`, `shadowSigma`,
39
+ // `shadowReach`, `gaussianKernel1d` and `DEFAULT_SHADOW_POLICY` — are
40
+ // re-exported from `lib/index.js` (issue #335). A toolkit that draws its own
41
+ // shapes (react-x11 paints a `<box>`'s `boxShadow` itself, because the
42
+ // rounded rect is a path it already has and the result goes through its own
43
+ // paint cache) needs exactly this and nothing else: the alternative on the
44
+ // public surface, `picture().setBlurFilter()`, sets a k×k filter that the
45
+ // server re-runs on *every* composite, which is invisible until someone
46
+ // profiles a real display. The surfaces and the cache below stay private —
47
+ // they are the 2d context's bookkeeping, not a primitive.
35
48
  import { Surface } from './surface.js';
36
49
 
37
50
  /**
@@ -117,8 +130,31 @@ export function gaussianKernel1d(sigma, reach = shadowReach(sigma)) {
117
130
  * and leaving it alive would double what the cache is holding. The output
118
131
  * carries no filter of its own, so compositing it is an ordinary masked
119
132
  * composite no matter how wide the blur was.
133
+ *
134
+ * Public, and the reason is that half of it: a *filter* is re-applied by the
135
+ * server on every composite, so a caller who blurs a picture with
136
+ * `setBlurFilter` and then caches it re-runs the kernel per frame — 244M
137
+ * multiply-accumulates for one 489×134 shadow at σ 10 (issue #335). This
138
+ * bakes instead. `shape` must be a coverage surface with the blur's reach as
139
+ * padding on all four sides, or the result ends in a straight line where the
140
+ * kernel ran out of pixels; see docs/surface.md.
120
141
  */
121
142
  export function blurCoverage(shape, sigma) {
143
+ if (!(sigma > 0) || !Number.isFinite(sigma)) {
144
+ throw new Error(
145
+ `blurCoverage: sigma must be a finite number above 0, got ${sigma}. ` +
146
+ 'A canvas blur radius is a diameter, not a sigma — pass ' +
147
+ 'shadowSigma(blur), which halves it and applies the policy cap. ' +
148
+ 'σ = 0 is no blur at all: composite the coverage as it is.'
149
+ );
150
+ }
151
+ if (shape.format !== 'a8') {
152
+ throw new Error(
153
+ `blurCoverage: needs a coverage surface, got format ${JSON.stringify(shape.format)}. ` +
154
+ "Draw the shape into new Surface(app, { width, height, format: 'a8' }) — " +
155
+ 'the blur runs on alpha, so an argb32 surface would lose its colour.'
156
+ );
157
+ }
122
158
  const app = shape.app;
123
159
  const R = app.display.Render;
124
160
  const { width, height } = shape;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "8.5.0",
3
+ "version": "8.6.0",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",