@pibbl/core 0.0.3-alpha.85.1 → 0.0.3-alpha.90.1

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.
Files changed (59) hide show
  1. package/dist/features/viz/index.d.ts +13 -0
  2. package/dist/features/viz/index.js +57 -0
  3. package/dist/features/viz/index.js.map +2 -2
  4. package/dist/features/viz/lib/graph/bipartite.d.ts +14 -0
  5. package/dist/features/viz/lib/graph/bipartite.js +250 -0
  6. package/dist/features/viz/lib/graph/bipartite.js.map +7 -0
  7. package/dist/features/viz/lib/graph/chord.d.ts +44 -0
  8. package/dist/features/viz/lib/graph/chord.js +348 -0
  9. package/dist/features/viz/lib/graph/chord.js.map +7 -0
  10. package/dist/features/viz/lib/graph/dependency.d.ts +26 -0
  11. package/dist/features/viz/lib/graph/dependency.js +141 -0
  12. package/dist/features/viz/lib/graph/dependency.js.map +7 -0
  13. package/dist/features/viz/lib/graph/force.d.ts +28 -0
  14. package/dist/features/viz/lib/graph/force.js +580 -0
  15. package/dist/features/viz/lib/graph/force.js.map +7 -0
  16. package/dist/features/viz/lib/graph/hierarchy.d.ts +21 -0
  17. package/dist/features/viz/lib/graph/hierarchy.js +112 -0
  18. package/dist/features/viz/lib/graph/hierarchy.js.map +7 -0
  19. package/dist/features/viz/lib/graph/icicle.d.ts +16 -0
  20. package/dist/features/viz/lib/graph/icicle.js +153 -0
  21. package/dist/features/viz/lib/graph/icicle.js.map +7 -0
  22. package/dist/features/viz/lib/graph/matrix.d.ts +30 -0
  23. package/dist/features/viz/lib/graph/matrix.js +243 -0
  24. package/dist/features/viz/lib/graph/matrix.js.map +7 -0
  25. package/dist/features/viz/lib/graph/neighborhood.d.ts +11 -0
  26. package/dist/features/viz/lib/graph/neighborhood.js +281 -0
  27. package/dist/features/viz/lib/graph/neighborhood.js.map +7 -0
  28. package/dist/features/viz/lib/graph/routes.d.ts +35 -0
  29. package/dist/features/viz/lib/graph/routes.js +102 -0
  30. package/dist/features/viz/lib/graph/routes.js.map +7 -0
  31. package/dist/features/viz/lib/graph/sankey.d.ts +44 -0
  32. package/dist/features/viz/lib/graph/sankey.js +341 -0
  33. package/dist/features/viz/lib/graph/sankey.js.map +7 -0
  34. package/dist/features/viz/lib/graph/shared.d.ts +59 -0
  35. package/dist/features/viz/lib/graph/shared.js +296 -0
  36. package/dist/features/viz/lib/graph/shared.js.map +7 -0
  37. package/dist/features/viz/lib/graph/tree.d.ts +11 -0
  38. package/dist/features/viz/lib/graph/tree.js +232 -0
  39. package/dist/features/viz/lib/graph/tree.js.map +7 -0
  40. package/dist/features/viz/lib/graph/treemap.d.ts +20 -0
  41. package/dist/features/viz/lib/graph/treemap.js +227 -0
  42. package/dist/features/viz/lib/graph/treemap.js.map +7 -0
  43. package/dist/features/viz/lib/graph/types.d.ts +1097 -0
  44. package/dist/filters/backdrop-contrast-gpu.d.ts +5 -0
  45. package/dist/filters/backdrop-contrast-gpu.js +33 -0
  46. package/dist/filters/backdrop-contrast-gpu.js.map +7 -0
  47. package/dist/filters/backdrop-contrast.d.ts +21 -0
  48. package/dist/filters/backdrop-contrast.js +61 -0
  49. package/dist/filters/backdrop-contrast.js.map +7 -0
  50. package/dist/filters/filter-executor.d.ts +11 -4
  51. package/dist/filters/filter-executor.js +104 -49
  52. package/dist/filters/filter-executor.js.map +2 -2
  53. package/dist/filters/render-backdrop-contrast-boundary.d.ts +31 -0
  54. package/dist/filters/render-backdrop-contrast-boundary.js +116 -0
  55. package/dist/filters/render-backdrop-contrast-boundary.js.map +7 -0
  56. package/dist/filters.d.ts +9 -7
  57. package/dist/filters.js +2 -0
  58. package/dist/filters.js.map +2 -2
  59. package/package.json +2 -2
@@ -0,0 +1,5 @@
1
+ import { ReflectionGpu, type ReflectionGpuCallbacks } from "./reflection-gpu.js";
2
+ /** Shares the existing private GPU resource/context-loss implementation. */
3
+ export declare class BackdropContrastGpu extends ReflectionGpu {
4
+ constructor(callbacks?: Readonly<ReflectionGpuCallbacks>);
5
+ }
@@ -0,0 +1,33 @@
1
+ import {
2
+ ReflectionGpu
3
+ } from "./reflection-gpu.js";
4
+ const fragment = `#version 300 es
5
+ precision highp float;
6
+ uniform sampler2D scene, map, lightMap;
7
+ in vec2 uv;
8
+ out vec4 outputColor;
9
+ vec3 linearRgb(vec3 c) {
10
+ return mix(c/12.92,pow((c+.055)/1.055,vec3(2.4)),step(vec3(.04045),c));
11
+ }
12
+ void main() {
13
+ float coverage=texture(scene,uv).a;
14
+ vec4 backdrop=texture(map,uv);
15
+ // A transparent current target is evaluated over white; DOM pixels are unavailable.
16
+ vec3 background=mix(vec3(1),backdrop.rgb,backdrop.a);
17
+ float luminance=dot(linearRgb(background),vec3(.2126,.7152,.0722));
18
+ // Equal black/white contrast occurs at sqrt(.05*1.05)-.05.
19
+ int choice=luminance<.179128784747792?0:1;
20
+ vec4 color=texelFetch(lightMap,ivec2(choice,0),0);
21
+ float alpha=coverage*color.a;
22
+ outputColor=vec4(color.rgb*alpha,alpha);
23
+ }
24
+ `;
25
+ class BackdropContrastGpu extends ReflectionGpu {
26
+ constructor(callbacks = {}) {
27
+ super(callbacks, fragment);
28
+ }
29
+ }
30
+ export {
31
+ BackdropContrastGpu
32
+ };
33
+ //# sourceMappingURL=backdrop-contrast-gpu.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/filters/backdrop-contrast-gpu.ts"],
4
+ "sourcesContent": ["import {\n ReflectionGpu,\n type ReflectionGpuCallbacks,\n} from \"./reflection-gpu.js\";\n\nconst fragment = `#version 300 es\nprecision highp float;\nuniform sampler2D scene, map, lightMap;\nin vec2 uv;\nout vec4 outputColor;\nvec3 linearRgb(vec3 c) {\n return mix(c/12.92,pow((c+.055)/1.055,vec3(2.4)),step(vec3(.04045),c));\n}\nvoid main() {\n float coverage=texture(scene,uv).a;\n vec4 backdrop=texture(map,uv);\n // A transparent current target is evaluated over white; DOM pixels are unavailable.\n vec3 background=mix(vec3(1),backdrop.rgb,backdrop.a);\n float luminance=dot(linearRgb(background),vec3(.2126,.7152,.0722));\n // Equal black/white contrast occurs at sqrt(.05*1.05)-.05.\n int choice=luminance<.179128784747792?0:1;\n vec4 color=texelFetch(lightMap,ivec2(choice,0),0);\n float alpha=coverage*color.a;\n outputColor=vec4(color.rgb*alpha,alpha);\n}\n`;\n/** Shares the existing private GPU resource/context-loss implementation. */\nexport class BackdropContrastGpu extends ReflectionGpu {\n constructor(callbacks: Readonly<ReflectionGpuCallbacks> = {}) {\n super(callbacks, fragment);\n }\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,OAEK;AAEP,MAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBV,MAAM,4BAA4B,cAAc;AAAA,EACrD,YAAY,YAA8C,CAAC,GAAG;AAC5D,UAAM,WAAW,QAAQ;AAAA,EAC3B;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ import type { SignalValue } from "../lib/signals/types.js";
2
+ import { type PibblFilter } from "../lib/style/filter-types.js";
3
+ /** Two foreground colors selected per backdrop pixel. See {@link backdropContrast}. */
4
+ export interface PibblBackdropContrastFilterOptions {
5
+ /** Color drawn over dark backdrop pixels; defaults to white. See {@link SignalValue}. */
6
+ readonly light?: SignalValue<string>;
7
+ /** Color drawn over light backdrop pixels; defaults to black. See {@link SignalValue}. */
8
+ readonly dark?: SignalValue<string>;
9
+ }
10
+ /**
11
+ * Recolors a receiving primitive and its descendants per pixel, using their combined alpha
12
+ * as a mask. Chooses light below relative backdrop luminance 0.179129 and dark otherwise.
13
+ * Samples already-painted content in the current Canvas target; transparent backdrop is
14
+ * evaluated over white. Isolated/cached layers see their own local backdrop, not outer paint.
15
+ * Requires OffscreenCanvas and WebGL2; performs no scene pixel readback. Geometry is unchanged.
16
+ *
17
+ * @param options - Optional signal-capable foreground colors; white and black by default.
18
+ * See {@link PibblBackdropContrastFilterOptions}.
19
+ * @returns An immutable descriptor for style.filter. See {@link PibblFilter}.
20
+ */
21
+ export declare function backdropContrast(options?: PibblBackdropContrastFilterOptions): PibblFilter;
@@ -0,0 +1,61 @@
1
+ import { isSignal } from "../lib/signals/graph.js";
2
+ import { layoutDiagnostic } from "../lib/style/diagnostics.js";
3
+ import {
4
+ PIBBL_FILTER_EXECUTOR
5
+ } from "../lib/style/filter-types.js";
6
+ import { concreteColor } from "./native-color.js";
7
+ import {
8
+ filterBoundaryExecutor
9
+ } from "./filter-executor.js";
10
+ import { processBackdropContrast } from "./render-backdrop-contrast-boundary.js";
11
+ const definitions = /* @__PURE__ */ new WeakMap();
12
+ const executor = {
13
+ prepare(value, property, context) {
14
+ const options = definitions.get(value);
15
+ if (!options)
16
+ throw new Error(
17
+ "A backdropContrast filter was created by an incompatible Pibbl runtime."
18
+ );
19
+ const color = (key, fallback) => {
20
+ const input = options[key];
21
+ const value2 = input === void 0 ? fallback : isSignal(input) ? input.get() : input;
22
+ const result = concreteColor(value2, `${property}.${key}`, context);
23
+ if (typeof CSS === "undefined" || !CSS.supports("color", result))
24
+ throw layoutDiagnostic({
25
+ component: context.component,
26
+ property: `${property}.${key}`,
27
+ value: value2,
28
+ algorithm: context.algorithm,
29
+ constraints: context.constraints,
30
+ reason: "must be a supported concrete CSS color"
31
+ });
32
+ return result;
33
+ };
34
+ const light = color("light", "white"), dark = color("dark", "black");
35
+ return Object.freeze({
36
+ executor: filterBoundaryExecutor,
37
+ needsBackdrop: true,
38
+ reverseInfluence: (output) => ({ ...output }),
39
+ process: (source, windows, backdrop) => {
40
+ if (!backdrop)
41
+ throw new Error(
42
+ "backdropContrast requires the current Canvas backdrop."
43
+ );
44
+ return processBackdropContrast(source, windows, backdrop, {
45
+ light,
46
+ dark
47
+ });
48
+ }
49
+ });
50
+ },
51
+ render: filterBoundaryExecutor.render
52
+ };
53
+ function backdropContrast(options = {}) {
54
+ const value = Object.freeze({ [PIBBL_FILTER_EXECUTOR]: executor });
55
+ definitions.set(value, Object.freeze({ ...options }));
56
+ return value;
57
+ }
58
+ export {
59
+ backdropContrast
60
+ };
61
+ //# sourceMappingURL=backdrop-contrast.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/filters/backdrop-contrast.ts"],
4
+ "sourcesContent": ["import { isSignal } from \"../lib/signals/graph.js\";\nimport type { SignalValue } from \"../lib/signals/types.js\";\nimport { layoutDiagnostic } from \"../lib/style/diagnostics.js\";\nimport {\n PIBBL_FILTER_EXECUTOR,\n type PibblFilter,\n type PibblFilterExecutor,\n} from \"../lib/style/filter-types.js\";\nimport { concreteColor } from \"./native-color.js\";\nimport {\n filterBoundaryExecutor,\n type CustomPreparedFilter,\n} from \"./filter-executor.js\";\nimport { processBackdropContrast } from \"./render-backdrop-contrast-boundary.js\";\n\n/** Two foreground colors selected per backdrop pixel. See {@link backdropContrast}. */\nexport interface PibblBackdropContrastFilterOptions {\n /** Color drawn over dark backdrop pixels; defaults to white. See {@link SignalValue}. */\n readonly light?: SignalValue<string>;\n /** Color drawn over light backdrop pixels; defaults to black. See {@link SignalValue}. */\n readonly dark?: SignalValue<string>;\n}\nconst definitions = new WeakMap<\n PibblFilter,\n Readonly<PibblBackdropContrastFilterOptions>\n>();\nconst executor: PibblFilterExecutor = {\n prepare(value, property, context) {\n const options = definitions.get(value);\n if (!options)\n throw new Error(\n \"A backdropContrast filter was created by an incompatible Pibbl runtime.\",\n );\n const color = (key: \"light\" | \"dark\", fallback: string) => {\n const input = options[key];\n const value =\n input === undefined ? fallback : isSignal(input) ? input.get() : input;\n const result = concreteColor(value, `${property}.${key}`, context);\n if (typeof CSS === \"undefined\" || !CSS.supports(\"color\", result))\n throw layoutDiagnostic({\n component: context.component,\n property: `${property}.${key}`,\n value,\n algorithm: context.algorithm,\n constraints: context.constraints,\n reason: \"must be a supported concrete CSS color\",\n });\n return result;\n };\n const light = color(\"light\", \"white\"),\n dark = color(\"dark\", \"black\");\n return Object.freeze({\n executor: filterBoundaryExecutor,\n needsBackdrop: true,\n reverseInfluence: (output) => ({ ...output }),\n process: (source, windows, backdrop) => {\n if (!backdrop)\n throw new Error(\n \"backdropContrast requires the current Canvas backdrop.\",\n );\n return processBackdropContrast(source, windows, backdrop, {\n light,\n dark,\n });\n },\n } satisfies CustomPreparedFilter);\n },\n render: filterBoundaryExecutor.render,\n};\n/**\n * Recolors a receiving primitive and its descendants per pixel, using their combined alpha\n * as a mask. Chooses light below relative backdrop luminance 0.179129 and dark otherwise.\n * Samples already-painted content in the current Canvas target; transparent backdrop is\n * evaluated over white. Isolated/cached layers see their own local backdrop, not outer paint.\n * Requires OffscreenCanvas and WebGL2; performs no scene pixel readback. Geometry is unchanged.\n *\n * @param options - Optional signal-capable foreground colors; white and black by default.\n * See {@link PibblBackdropContrastFilterOptions}.\n * @returns An immutable descriptor for style.filter. See {@link PibblFilter}.\n */\nexport function backdropContrast(\n options: PibblBackdropContrastFilterOptions = {},\n): PibblFilter {\n const value = Object.freeze({ [PIBBL_FILTER_EXECUTOR]: executor });\n definitions.set(value, Object.freeze({ ...options }));\n return value;\n}\n"],
5
+ "mappings": "AAAA,SAAS,gBAAgB;AAEzB,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAGK;AACP,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,OAEK;AACP,SAAS,+BAA+B;AASxC,MAAM,cAAc,oBAAI,QAGtB;AACF,MAAM,WAAgC;AAAA,EACpC,QAAQ,OAAO,UAAU,SAAS;AAChC,UAAM,UAAU,YAAY,IAAI,KAAK;AACrC,QAAI,CAAC;AACH,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AACF,UAAM,QAAQ,CAAC,KAAuB,aAAqB;AACzD,YAAM,QAAQ,QAAQ,GAAG;AACzB,YAAMA,SACJ,UAAU,SAAY,WAAW,SAAS,KAAK,IAAI,MAAM,IAAI,IAAI;AACnE,YAAM,SAAS,cAAcA,QAAO,GAAG,QAAQ,IAAI,GAAG,IAAI,OAAO;AACjE,UAAI,OAAO,QAAQ,eAAe,CAAC,IAAI,SAAS,SAAS,MAAM;AAC7D,cAAM,iBAAiB;AAAA,UACrB,WAAW,QAAQ;AAAA,UACnB,UAAU,GAAG,QAAQ,IAAI,GAAG;AAAA,UAC5B,OAAAA;AAAA,UACA,WAAW,QAAQ;AAAA,UACnB,aAAa,QAAQ;AAAA,UACrB,QAAQ;AAAA,QACV,CAAC;AACH,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,MAAM,SAAS,OAAO,GAClC,OAAO,MAAM,QAAQ,OAAO;AAC9B,WAAO,OAAO,OAAO;AAAA,MACnB,UAAU;AAAA,MACV,eAAe;AAAA,MACf,kBAAkB,CAAC,YAAY,EAAE,GAAG,OAAO;AAAA,MAC3C,SAAS,CAAC,QAAQ,SAAS,aAAa;AACtC,YAAI,CAAC;AACH,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AACF,eAAO,wBAAwB,QAAQ,SAAS,UAAU;AAAA,UACxD;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF,CAAgC;AAAA,EAClC;AAAA,EACA,QAAQ,uBAAuB;AACjC;AAYO,SAAS,iBACd,UAA8C,CAAC,GAClC;AACb,QAAM,QAAQ,OAAO,OAAO,EAAE,CAAC,qBAAqB,GAAG,SAAS,CAAC;AACjE,cAAY,IAAI,OAAO,OAAO,OAAO,EAAE,GAAG,QAAQ,CAAC,CAAC;AACpD,SAAO;AACT;",
6
+ "names": ["value"]
7
+ }
@@ -1,6 +1,12 @@
1
- import type { TexturePaint } from '../lib/textures/texture-protocol.js';
2
- import type { FilterWindows, PixelBounds } from '../lib/filter/filter-region.js';
3
- import type { PibblFilterBoundaryExecutor, PreparedPibblFilter } from '../lib/style/filter-types.js';
1
+ import type { TexturePaint } from "../lib/textures/texture-protocol.js";
2
+ import type { Canvas } from "../lib/types.js";
3
+ import type { FilterWindows, PixelBounds } from "../lib/filter/filter-region.js";
4
+ import type { PibblFilterBoundaryExecutor, PreparedPibblFilter } from "../lib/style/filter-types.js";
5
+ /** Backing-pixel projection of the already-painted current render target. */
6
+ export interface FilterBackdrop {
7
+ readonly canvas: Canvas;
8
+ readonly transform: DOMMatrix;
9
+ }
4
10
  /** An operation-owned pass. The shared executor knows nothing about its inputs. */
5
11
  export interface CustomPreparedFilter extends PreparedPibblFilter {
6
12
  readonly reverseInfluence: (output: Readonly<PixelBounds>, density: number) => PixelBounds;
@@ -16,6 +22,7 @@ export interface CustomPreparedFilter extends PreparedPibblFilter {
16
22
  }>;
17
23
  }>;
18
24
  readonly fuseWithPrevious?: (previous: CustomPreparedFilter) => CustomPreparedFilter | undefined;
19
- readonly process?: (source: OffscreenCanvas, windows: FilterWindows) => ImageBitmap;
25
+ readonly needsBackdrop?: boolean;
26
+ readonly process?: (source: OffscreenCanvas, windows: FilterWindows, backdrop?: FilterBackdrop) => ImageBitmap;
20
27
  }
21
28
  export declare const filterBoundaryExecutor: PibblFilterBoundaryExecutor;
@@ -1,8 +1,11 @@
1
1
  import { GLOBAL_STATE } from "../lib/global-state.js";
2
2
  import { getOptionalService } from "../lib/optional-services.js";
3
+ import { getCurrentPaintTransform } from "../lib/render-target.js";
3
4
  import { enterPrimitivePresentation } from "../lib/primitive-presentation.js";
4
5
  import { renderFilterBoundary } from "../lib/filter/render-filter-boundary.js";
5
- import { compileNativeFilters } from "./native-operations.js";
6
+ import {
7
+ compileNativeFilters
8
+ } from "./native-operations.js";
6
9
  import { assertNativeCanvasFilter } from "./native-canvas-filter.js";
7
10
  const SCRATCH = /* @__PURE__ */ Symbol("pibbl.filter-pass-scratch");
8
11
  class PassScratch {
@@ -24,11 +27,17 @@ class PassScratch {
24
27
  if (success || !this.committed) this.dispose();
25
28
  }
26
29
  target(index, width, height) {
27
- const canvas = this.surfaces[index] ??= new OffscreenCanvas(width, height);
30
+ const canvas = this.surfaces[index] ??= new OffscreenCanvas(
31
+ width,
32
+ height
33
+ );
28
34
  if (canvas.width !== width) canvas.width = width;
29
35
  if (canvas.height !== height) canvas.height = height;
30
36
  const context = canvas.getContext("2d");
31
- if (!context) throw new Error("Filter passes require an OffscreenCanvas 2D scratch target.");
37
+ if (!context)
38
+ throw new Error(
39
+ "Filter passes require an OffscreenCanvas 2D scratch target."
40
+ );
32
41
  context.reset();
33
42
  return context;
34
43
  }
@@ -60,58 +69,104 @@ const filterBoundaryExecutor = {
60
69
  }
61
70
  return;
62
71
  }
63
- if (operations.every((operation) => "css" in operation)) {
64
- renderFilterBoundary({ component: input.component, filters: operations, presentation: input.presentation }, input.renderSubtree);
72
+ if (operations.every(
73
+ (operation) => "css" in operation
74
+ )) {
75
+ renderFilterBoundary(
76
+ {
77
+ component: input.component,
78
+ filters: operations,
79
+ presentation: input.presentation
80
+ },
81
+ input.renderSubtree
82
+ );
65
83
  return;
66
84
  }
67
- const bounds = operations.map((operation) => "css" in operation ? operation : {
68
- executor: filterBoundaryExecutor,
69
- css: () => "none",
70
- reverseInfluence: operation.reverseInfluence
71
- });
72
- renderFilterBoundary({
73
- component: input.component,
74
- filters: bounds,
75
- presentation: input.presentation,
76
- process(source, result, windows) {
77
- const instance = GLOBAL_STATE.currentPibblInstance;
78
- if (!instance) throw new Error("Filter processing requires an active Pibbl instance.");
79
- const scratch = getOptionalService(instance, SCRATCH, () => new PassScratch(instance));
80
- scratch.used = true;
81
- let current = source, pass = 0;
82
- let native = [];
83
- const target = () => scratch.target(pass++ % 2, source.width, source.height);
84
- const applyNative = () => {
85
- if (!native.length) return;
86
- const context = target();
87
- assertNativeCanvasFilter(context, compileNativeFilters(native, windows.density), input.component, () => {
88
- for (const operation of native) if (operation.onRejected) {
89
- assertNativeCanvasFilter(context, operation.css(windows.density), input.component, operation.onRejected);
90
- }
91
- });
92
- context.drawImage(current, 0, 0);
93
- current = context.canvas;
94
- native = [];
95
- };
96
- for (const operation of operations) {
97
- if ("css" in operation) {
98
- native.push(operation);
99
- continue;
100
- }
101
- applyNative();
102
- const bitmap = operation.process(current, windows);
103
- try {
85
+ const parent = operations.some(
86
+ (operation) => "needsBackdrop" in operation && operation.needsBackdrop
87
+ ) ? GLOBAL_STATE.renderContext : void 0;
88
+ const parentTarget = parent ? GLOBAL_STATE.renderTarget : void 0;
89
+ const paint = parent ? getCurrentPaintTransform() : void 0;
90
+ const boundaryPaint = paint && input.presentation?.transform ? paint.multiply(input.presentation.transform) : paint;
91
+ const bounds = operations.map(
92
+ (operation) => "css" in operation ? operation : {
93
+ executor: filterBoundaryExecutor,
94
+ css: () => "none",
95
+ reverseInfluence: operation.reverseInfluence
96
+ }
97
+ );
98
+ renderFilterBoundary(
99
+ {
100
+ component: input.component,
101
+ filters: bounds,
102
+ presentation: input.presentation,
103
+ process(source, result, windows) {
104
+ const instance = GLOBAL_STATE.currentPibblInstance;
105
+ if (!instance)
106
+ throw new Error(
107
+ "Filter processing requires an active Pibbl instance."
108
+ );
109
+ const scratch = getOptionalService(
110
+ instance,
111
+ SCRATCH,
112
+ () => new PassScratch(instance)
113
+ );
114
+ scratch.used = true;
115
+ let current = source, pass = 0;
116
+ let native = [];
117
+ const target = () => scratch.target(pass++ % 2, source.width, source.height);
118
+ const applyNative = () => {
119
+ if (!native.length) return;
104
120
  const context = target();
105
- context.drawImage(bitmap, 0, 0);
121
+ assertNativeCanvasFilter(
122
+ context,
123
+ compileNativeFilters(native, windows.density),
124
+ input.component,
125
+ () => {
126
+ for (const operation of native)
127
+ if (operation.onRejected) {
128
+ assertNativeCanvasFilter(
129
+ context,
130
+ operation.css(windows.density),
131
+ input.component,
132
+ operation.onRejected
133
+ );
134
+ }
135
+ }
136
+ );
137
+ context.drawImage(current, 0, 0);
106
138
  current = context.canvas;
107
- } finally {
108
- bitmap.close();
139
+ native = [];
140
+ };
141
+ for (const operation of operations) {
142
+ if ("css" in operation) {
143
+ native.push(operation);
144
+ continue;
145
+ }
146
+ applyNative();
147
+ const backdrop = parent && parentTarget && boundaryPaint ? {
148
+ canvas: parent.canvas,
149
+ transform: windows.source.renderTransform.multiply(boundaryPaint.inverse()).multiply(parentTarget.inverseRenderTransform)
150
+ } : void 0;
151
+ const bitmap = operation.process(current, windows, backdrop);
152
+ try {
153
+ const context = target();
154
+ context.drawImage(bitmap, 0, 0);
155
+ current = context.canvas;
156
+ } finally {
157
+ bitmap.close();
158
+ }
109
159
  }
160
+ applyNative();
161
+ result.drawImage(
162
+ current,
163
+ windows.sourceToOutputX,
164
+ windows.sourceToOutputY
165
+ );
110
166
  }
111
- applyNative();
112
- result.drawImage(current, windows.sourceToOutputX, windows.sourceToOutputY);
113
- }
114
- }, input.renderSubtree);
167
+ },
168
+ input.renderSubtree
169
+ );
115
170
  }
116
171
  };
117
172
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/filters/filter-executor.ts"],
4
- "sourcesContent": ["import type { TexturePaint } from '../lib/textures/texture-protocol.js';\nimport { GLOBAL_STATE } from '../lib/global-state.js';\nimport { getOptionalService } from '../lib/optional-services.js';\nimport { enterPrimitivePresentation } from '../lib/primitive-presentation.js';\nimport { renderFilterBoundary } from '../lib/filter/render-filter-boundary.js';\nimport type { FilterWindows, PixelBounds } from '../lib/filter/filter-region.js';\nimport type { PibblFilterBoundaryExecutor, PreparedPibblFilter } from '../lib/style/filter-types.js';\nimport type { PibblInstance } from '../lib/types.js';\nimport { compileNativeFilters, type NativePreparedFilter } from './native-operations.js';\nimport { assertNativeCanvasFilter } from './native-canvas-filter.js';\n\n/** An operation-owned pass. The shared executor knows nothing about its inputs. */\nexport interface CustomPreparedFilter extends PreparedPibblFilter {\n readonly reverseInfluence: (output: Readonly<PixelBounds>, density: number) => PixelBounds;\n /** Private fusion capability; no concrete GPU implementation enters this module. */\n readonly textureWarp?: Readonly<{ map: TexturePaint; strength: number; region: Readonly<{x:number;y:number;width:number;height:number}> }>;\n readonly fuseWithPrevious?: (previous: CustomPreparedFilter) => CustomPreparedFilter | undefined;\n readonly process?: (source: OffscreenCanvas, windows: FilterWindows) => ImageBitmap;\n}\ntype Operation = NativePreparedFilter | CustomPreparedFilter;\nconst SCRATCH = Symbol('pibbl.filter-pass-scratch');\nclass PassScratch {\n private readonly surfaces: OffscreenCanvas[] = [];\n used = false;\n private committed = false;\n constructor(private readonly instance: PibblInstance) {}\n beginRender(): void { this.used = false; }\n endRender(success: boolean): void {\n if (success && this.used) { this.committed = true; return; }\n if (success || !this.committed) this.dispose();\n }\n target(index: number, width: number, height: number): OffscreenCanvasRenderingContext2D {\n const canvas = this.surfaces[index] ??= new OffscreenCanvas(width, height);\n if (canvas.width !== width) canvas.width = width;\n if (canvas.height !== height) canvas.height = height;\n const context = canvas.getContext('2d');\n if (!context) throw new Error('Filter passes require an OffscreenCanvas 2D scratch target.');\n context.reset();\n return context;\n }\n dispose(): void {\n for (const canvas of this.surfaces) { canvas.width = 0; canvas.height = 0; }\n this.surfaces.length = 0;\n this.instance.optionalServices.delete(SCRATCH);\n }\n}\n\nexport const filterBoundaryExecutor: PibblFilterBoundaryExecutor = {\n render(input) {\n const operations: Operation[] = [];\n for (const operation of input.filters as readonly Operation[]) {\n if (!('css' in operation) && !operation.process) continue;\n const previous = operations.at(-1);\n const fused = previous && !('css' in previous) && !('css' in operation)\n ? operation.fuseWithPrevious?.(previous) : undefined;\n if (fused) operations[operations.length - 1] = fused;\n else operations.push(operation);\n }\n if (!operations.length) {\n const release = enterPrimitivePresentation(input.presentation);\n try { input.renderSubtree(); } finally { release(); }\n return;\n }\n if (operations.every((operation): operation is NativePreparedFilter => 'css' in operation)) {\n renderFilterBoundary({ component: input.component, filters: operations, presentation: input.presentation }, input.renderSubtree);\n return;\n }\n const bounds = operations.map(operation => 'css' in operation ? operation : {\n executor: filterBoundaryExecutor, css: () => 'none', reverseInfluence: operation.reverseInfluence,\n });\n renderFilterBoundary({\n component: input.component, filters: bounds, presentation: input.presentation,\n process(source, result, windows) {\n const instance = GLOBAL_STATE.currentPibblInstance;\n if (!instance) throw new Error('Filter processing requires an active Pibbl instance.');\n const scratch = getOptionalService(instance, SCRATCH, () => new PassScratch(instance));\n scratch.used = true;\n let current = source, pass = 0;\n let native: NativePreparedFilter[] = [];\n const target = () => scratch.target(pass++ % 2, source.width, source.height);\n const applyNative = () => {\n if (!native.length) return;\n const context = target();\n assertNativeCanvasFilter(context, compileNativeFilters(native, windows.density), input.component, () => {\n for (const operation of native) if (operation.onRejected) {\n assertNativeCanvasFilter(context, operation.css(windows.density), input.component, operation.onRejected);\n }\n });\n context.drawImage(current, 0, 0);\n current = context.canvas;\n native = [];\n };\n for (const operation of operations) {\n if ('css' in operation) { native.push(operation); continue; }\n applyNative();\n const bitmap = operation.process!(current, windows);\n try {\n const context = target(); context.drawImage(bitmap, 0, 0); current = context.canvas;\n } finally { bitmap.close(); }\n }\n applyNative();\n result.drawImage(current, windows.sourceToOutputX, windows.sourceToOutputY);\n },\n }, input.renderSubtree);\n },\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { TexturePaint } from \"../lib/textures/texture-protocol.js\";\nimport { GLOBAL_STATE } from \"../lib/global-state.js\";\nimport { getOptionalService } from \"../lib/optional-services.js\";\nimport { getCurrentPaintTransform } from \"../lib/render-target.js\";\nimport type { Canvas } from \"../lib/types.js\";\nimport { enterPrimitivePresentation } from \"../lib/primitive-presentation.js\";\nimport { renderFilterBoundary } from \"../lib/filter/render-filter-boundary.js\";\nimport type {\n FilterWindows,\n PixelBounds,\n} from \"../lib/filter/filter-region.js\";\nimport type {\n PibblFilterBoundaryExecutor,\n PreparedPibblFilter,\n} from \"../lib/style/filter-types.js\";\nimport type { PibblInstance } from \"../lib/types.js\";\nimport {\n compileNativeFilters,\n type NativePreparedFilter,\n} from \"./native-operations.js\";\nimport { assertNativeCanvasFilter } from \"./native-canvas-filter.js\";\n\n/** Backing-pixel projection of the already-painted current render target. */\nexport interface FilterBackdrop {\n readonly canvas: Canvas;\n readonly transform: DOMMatrix;\n}\n\n/** An operation-owned pass. The shared executor knows nothing about its inputs. */\nexport interface CustomPreparedFilter extends PreparedPibblFilter {\n readonly reverseInfluence: (\n output: Readonly<PixelBounds>,\n density: number,\n ) => PixelBounds;\n /** Private fusion capability; no concrete GPU implementation enters this module. */\n readonly textureWarp?: Readonly<{\n map: TexturePaint;\n strength: number;\n region: Readonly<{ x: number; y: number; width: number; height: number }>;\n }>;\n readonly fuseWithPrevious?: (\n previous: CustomPreparedFilter,\n ) => CustomPreparedFilter | undefined;\n readonly needsBackdrop?: boolean;\n readonly process?: (\n source: OffscreenCanvas,\n windows: FilterWindows,\n backdrop?: FilterBackdrop,\n ) => ImageBitmap;\n}\ntype Operation = NativePreparedFilter | CustomPreparedFilter;\nconst SCRATCH = Symbol(\"pibbl.filter-pass-scratch\");\nclass PassScratch {\n private readonly surfaces: OffscreenCanvas[] = [];\n used = false;\n private committed = false;\n constructor(private readonly instance: PibblInstance) {}\n beginRender(): void {\n this.used = false;\n }\n endRender(success: boolean): void {\n if (success && this.used) {\n this.committed = true;\n return;\n }\n if (success || !this.committed) this.dispose();\n }\n target(\n index: number,\n width: number,\n height: number,\n ): OffscreenCanvasRenderingContext2D {\n const canvas = (this.surfaces[index] ??= new OffscreenCanvas(\n width,\n height,\n ));\n if (canvas.width !== width) canvas.width = width;\n if (canvas.height !== height) canvas.height = height;\n const context = canvas.getContext(\"2d\");\n if (!context)\n throw new Error(\n \"Filter passes require an OffscreenCanvas 2D scratch target.\",\n );\n context.reset();\n return context;\n }\n dispose(): void {\n for (const canvas of this.surfaces) {\n canvas.width = 0;\n canvas.height = 0;\n }\n this.surfaces.length = 0;\n this.instance.optionalServices.delete(SCRATCH);\n }\n}\n\nexport const filterBoundaryExecutor: PibblFilterBoundaryExecutor = {\n render(input) {\n const operations: Operation[] = [];\n for (const operation of input.filters as readonly Operation[]) {\n if (!(\"css\" in operation) && !operation.process) continue;\n const previous = operations.at(-1);\n const fused =\n previous && !(\"css\" in previous) && !(\"css\" in operation)\n ? operation.fuseWithPrevious?.(previous)\n : undefined;\n if (fused) operations[operations.length - 1] = fused;\n else operations.push(operation);\n }\n if (!operations.length) {\n const release = enterPrimitivePresentation(input.presentation);\n try {\n input.renderSubtree();\n } finally {\n release();\n }\n return;\n }\n if (\n operations.every(\n (operation): operation is NativePreparedFilter => \"css\" in operation,\n )\n ) {\n renderFilterBoundary(\n {\n component: input.component,\n filters: operations,\n presentation: input.presentation,\n },\n input.renderSubtree,\n );\n return;\n }\n // Snapshot the parent projection before capture switches the current render target.\n const parent = operations.some(\n (operation) => \"needsBackdrop\" in operation && operation.needsBackdrop,\n )\n ? GLOBAL_STATE.renderContext\n : undefined;\n const parentTarget = parent ? GLOBAL_STATE.renderTarget : undefined;\n const paint = parent ? getCurrentPaintTransform() : undefined;\n const boundaryPaint =\n paint && input.presentation?.transform\n ? paint.multiply(input.presentation.transform)\n : paint;\n const bounds = operations.map((operation) =>\n \"css\" in operation\n ? operation\n : {\n executor: filterBoundaryExecutor,\n css: () => \"none\",\n reverseInfluence: operation.reverseInfluence,\n },\n );\n renderFilterBoundary(\n {\n component: input.component,\n filters: bounds,\n presentation: input.presentation,\n process(source, result, windows) {\n const instance = GLOBAL_STATE.currentPibblInstance;\n if (!instance)\n throw new Error(\n \"Filter processing requires an active Pibbl instance.\",\n );\n const scratch = getOptionalService(\n instance,\n SCRATCH,\n () => new PassScratch(instance),\n );\n scratch.used = true;\n let current = source,\n pass = 0;\n let native: NativePreparedFilter[] = [];\n const target = () =>\n scratch.target(pass++ % 2, source.width, source.height);\n const applyNative = () => {\n if (!native.length) return;\n const context = target();\n assertNativeCanvasFilter(\n context,\n compileNativeFilters(native, windows.density),\n input.component,\n () => {\n for (const operation of native)\n if (operation.onRejected) {\n assertNativeCanvasFilter(\n context,\n operation.css(windows.density),\n input.component,\n operation.onRejected,\n );\n }\n },\n );\n context.drawImage(current, 0, 0);\n current = context.canvas;\n native = [];\n };\n for (const operation of operations) {\n if (\"css\" in operation) {\n native.push(operation);\n continue;\n }\n applyNative();\n const backdrop =\n parent && parentTarget && boundaryPaint\n ? {\n canvas: parent.canvas,\n transform: windows.source.renderTransform\n .multiply(boundaryPaint.inverse())\n .multiply(parentTarget.inverseRenderTransform),\n }\n : undefined;\n const bitmap = operation.process!(current, windows, backdrop);\n try {\n const context = target();\n context.drawImage(bitmap, 0, 0);\n current = context.canvas;\n } finally {\n bitmap.close();\n }\n }\n applyNative();\n result.drawImage(\n current,\n windows.sourceToOutputX,\n windows.sourceToOutputY,\n );\n },\n },\n input.renderSubtree,\n );\n },\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,31 @@
1
+ import type { PibblInstance } from "../lib/types.js";
2
+ import type { FilterWindows } from "../lib/filter/filter-region.js";
3
+ import type { FilterBackdrop } from "./filter-executor.js";
4
+ import { BackdropContrastGpu } from "./backdrop-contrast-gpu.js";
5
+ declare class BackdropContrastService {
6
+ private readonly instance;
7
+ readonly gpu: BackdropContrastGpu;
8
+ private readonly backdrop;
9
+ private readonly palette;
10
+ private revision;
11
+ private paletteRevision;
12
+ private colors;
13
+ private committed;
14
+ private disposed;
15
+ used: boolean;
16
+ constructor(instance: PibblInstance);
17
+ beginRender(): void;
18
+ endRender(success: boolean): void;
19
+ apply(source: OffscreenCanvas, windows: FilterWindows, input: FilterBackdrop, colors: Readonly<{
20
+ light: string;
21
+ dark: string;
22
+ }>): ImageBitmap;
23
+ dispose(): void;
24
+ }
25
+ /** @internal Read-only ownership evidence; never creates resources. */
26
+ export declare function peekBackdropContrastService(instance: PibblInstance): BackdropContrastService | undefined;
27
+ export declare function processBackdropContrast(source: OffscreenCanvas, windows: FilterWindows, backdrop: FilterBackdrop, colors: Readonly<{
28
+ light: string;
29
+ dark: string;
30
+ }>): ImageBitmap;
31
+ export {};
@@ -0,0 +1,116 @@
1
+ import { GLOBAL_STATE } from "../lib/global-state.js";
2
+ import { getOptionalService } from "../lib/optional-services.js";
3
+ import { update } from "../lib/update.js";
4
+ import { BackdropContrastGpu } from "./backdrop-contrast-gpu.js";
5
+ const SERVICE = /* @__PURE__ */ Symbol("pibbl.backdrop-contrast");
6
+ class BackdropContrastService {
7
+ constructor(instance) {
8
+ this.instance = instance;
9
+ this.gpu = new BackdropContrastGpu({
10
+ onContextRestored: () => {
11
+ if (!this.disposed && !instance.mainTeardowns.closed)
12
+ update(instance.canvas, void 0, true);
13
+ }
14
+ });
15
+ }
16
+ instance;
17
+ gpu;
18
+ backdrop = new OffscreenCanvas(1, 1);
19
+ palette = new OffscreenCanvas(2, 1);
20
+ revision = 0;
21
+ paletteRevision = 0;
22
+ colors = "";
23
+ committed = false;
24
+ disposed = false;
25
+ used = false;
26
+ beginRender() {
27
+ this.used = false;
28
+ }
29
+ endRender(success) {
30
+ if (success && this.used) {
31
+ this.committed = true;
32
+ return;
33
+ }
34
+ if (success || !this.committed) this.dispose();
35
+ }
36
+ apply(source, windows, input, colors) {
37
+ this.used = true;
38
+ if (this.backdrop.width !== source.width)
39
+ this.backdrop.width = source.width;
40
+ if (this.backdrop.height !== source.height)
41
+ this.backdrop.height = source.height;
42
+ const context = this.backdrop.getContext("2d");
43
+ if (!context)
44
+ throw new Error("backdropContrast requires OffscreenCanvas 2D.");
45
+ context.reset();
46
+ context.setTransform(input.transform);
47
+ context.drawImage(input.canvas, 0, 0);
48
+ const colorKey = JSON.stringify([colors.light, colors.dark]);
49
+ if (this.colors !== colorKey) {
50
+ const palette = this.palette.getContext("2d");
51
+ if (!palette)
52
+ throw new Error("backdropContrast colors require OffscreenCanvas 2D.");
53
+ palette.reset();
54
+ palette.fillStyle = colors.light;
55
+ palette.fillRect(0, 0, 1, 1);
56
+ palette.fillStyle = colors.dark;
57
+ palette.fillRect(1, 0, 1, 1);
58
+ this.colors = colorKey;
59
+ this.paletteRevision++;
60
+ }
61
+ const raster = (canvas, revision) => ({
62
+ source: canvas,
63
+ width: canvas.width,
64
+ height: canvas.height,
65
+ resource: canvas,
66
+ revision,
67
+ placement: { mode: "stretch" }
68
+ });
69
+ const region = windows.source.logicalBounds;
70
+ return this.gpu.apply(
71
+ source,
72
+ raster(this.backdrop, ++this.revision),
73
+ { raster: raster(this.palette, this.paletteRevision) },
74
+ { opacity: 1, threshold: 0, softness: 1, direction: [0, 1] },
75
+ {
76
+ originX: region.x,
77
+ originY: region.y,
78
+ density: windows.density,
79
+ region
80
+ }
81
+ );
82
+ }
83
+ dispose() {
84
+ if (this.disposed) return;
85
+ this.disposed = true;
86
+ this.gpu.dispose();
87
+ this.backdrop.width = this.backdrop.height = this.palette.width = this.palette.height = 0;
88
+ this.instance.optionalServices.delete(SERVICE);
89
+ }
90
+ }
91
+ function peekBackdropContrastService(instance) {
92
+ return instance.optionalServices.get(SERVICE);
93
+ }
94
+ function processBackdropContrast(source, windows, backdrop, colors) {
95
+ const instance = GLOBAL_STATE.currentPibblInstance;
96
+ if (!instance)
97
+ throw new Error("backdropContrast requires an active Pibbl instance.");
98
+ const service = getOptionalService(
99
+ instance,
100
+ SERVICE,
101
+ () => new BackdropContrastService(instance)
102
+ );
103
+ try {
104
+ return service.apply(source, windows, backdrop, colors);
105
+ } catch (error) {
106
+ throw new Error(
107
+ `backdropContrast processing failed: ${error instanceof Error ? error.message : String(error)}`,
108
+ { cause: error }
109
+ );
110
+ }
111
+ }
112
+ export {
113
+ peekBackdropContrastService,
114
+ processBackdropContrast
115
+ };
116
+ //# sourceMappingURL=render-backdrop-contrast-boundary.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/filters/render-backdrop-contrast-boundary.ts"],
4
+ "sourcesContent": ["import { GLOBAL_STATE } from \"../lib/global-state.js\";\nimport { getOptionalService } from \"../lib/optional-services.js\";\nimport { update } from \"../lib/update.js\";\nimport type { PibblInstance } from \"../lib/types.js\";\nimport type { FilterWindows } from \"../lib/filter/filter-region.js\";\nimport type { FilterBackdrop } from \"./filter-executor.js\";\nimport type { BorrowedTextureRaster } from \"../lib/textures/texture-protocol.js\";\nimport { BackdropContrastGpu } from \"./backdrop-contrast-gpu.js\";\n\nconst SERVICE = Symbol(\"pibbl.backdrop-contrast\");\nclass BackdropContrastService {\n readonly gpu: BackdropContrastGpu;\n private readonly backdrop = new OffscreenCanvas(1, 1);\n private readonly palette = new OffscreenCanvas(2, 1);\n private revision = 0;\n private paletteRevision = 0;\n private colors = \"\";\n private committed = false;\n private disposed = false;\n used = false;\n constructor(private readonly instance: PibblInstance) {\n this.gpu = new BackdropContrastGpu({\n onContextRestored: () => {\n if (!this.disposed && !instance.mainTeardowns.closed)\n update(instance.canvas, undefined, true);\n },\n });\n }\n beginRender(): void {\n this.used = false;\n }\n endRender(success: boolean): void {\n if (success && this.used) {\n this.committed = true;\n return;\n }\n if (success || !this.committed) this.dispose();\n }\n apply(\n source: OffscreenCanvas,\n windows: FilterWindows,\n input: FilterBackdrop,\n colors: Readonly<{ light: string; dark: string }>,\n ): ImageBitmap {\n this.used = true;\n if (this.backdrop.width !== source.width)\n this.backdrop.width = source.width;\n if (this.backdrop.height !== source.height)\n this.backdrop.height = source.height;\n const context = this.backdrop.getContext(\"2d\");\n if (!context)\n throw new Error(\"backdropContrast requires OffscreenCanvas 2D.\");\n context.reset();\n context.setTransform(input.transform);\n context.drawImage(input.canvas, 0, 0);\n const colorKey = JSON.stringify([colors.light, colors.dark]);\n if (this.colors !== colorKey) {\n const palette = this.palette.getContext(\"2d\");\n if (!palette)\n throw new Error(\"backdropContrast colors require OffscreenCanvas 2D.\");\n palette.reset();\n palette.fillStyle = colors.light;\n palette.fillRect(0, 0, 1, 1);\n palette.fillStyle = colors.dark;\n palette.fillRect(1, 0, 1, 1);\n this.colors = colorKey;\n this.paletteRevision++;\n }\n const raster = (\n canvas: OffscreenCanvas,\n revision: number,\n ): BorrowedTextureRaster => ({\n source: canvas,\n width: canvas.width,\n height: canvas.height,\n resource: canvas,\n revision,\n placement: { mode: \"stretch\" },\n });\n const region = windows.source.logicalBounds;\n return this.gpu.apply(\n source,\n raster(this.backdrop, ++this.revision),\n { raster: raster(this.palette, this.paletteRevision) },\n { opacity: 1, threshold: 0, softness: 1, direction: [0, 1] },\n {\n originX: region.x,\n originY: region.y,\n density: windows.density,\n region,\n },\n );\n }\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.gpu.dispose();\n this.backdrop.width =\n this.backdrop.height =\n this.palette.width =\n this.palette.height =\n 0;\n this.instance.optionalServices.delete(SERVICE);\n }\n}\n/** @internal Read-only ownership evidence; never creates resources. */\nexport function peekBackdropContrastService(\n instance: PibblInstance,\n): BackdropContrastService | undefined {\n return instance.optionalServices.get(SERVICE) as\n BackdropContrastService | undefined;\n}\nexport function processBackdropContrast(\n source: OffscreenCanvas,\n windows: FilterWindows,\n backdrop: FilterBackdrop,\n colors: Readonly<{ light: string; dark: string }>,\n): ImageBitmap {\n const instance = GLOBAL_STATE.currentPibblInstance;\n if (!instance)\n throw new Error(\"backdropContrast requires an active Pibbl instance.\");\n const service = getOptionalService(\n instance,\n SERVICE,\n () => new BackdropContrastService(instance),\n );\n try {\n return service.apply(source, windows, backdrop, colors);\n } catch (error) {\n throw new Error(\n `backdropContrast processing failed: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/dist/filters.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- export { blur, brightness, contrast, dropShadow, grayscale, hueRotate, invert, opacity, saturate, sepia, } from './filters/native.js';
2
- export { displacement } from './filters/displacement.js';
3
- export type { PibblFilter, PibblBlurFilterOptions, PibblBrightnessFilterOptions, PibblContrastFilterOptions, PibblDropShadowFilterOptions, PibblGrayscaleFilterOptions, PibblHueRotateFilterOptions, PibblInvertFilterOptions, PibblOpacityFilterOptions, PibblSaturateFilterOptions, PibblSepiaFilterOptions, PibblDisplacementFilterOptions, } from './lib/style/filter-types.js';
4
- export { reflectedLight } from './filters/reflected-light.js';
5
- export type { PibblReflectedLightFilterOptions, PibblReflectedLightSource } from './lib/style/filter-types.js';
6
- export { emboss } from './filters/emboss.js';
7
- export type { PibblEmbossFilterOptions } from './filters/emboss.js';
1
+ export { blur, brightness, contrast, dropShadow, grayscale, hueRotate, invert, opacity, saturate, sepia, } from "./filters/native.js";
2
+ export { displacement } from "./filters/displacement.js";
3
+ export type { PibblFilter, PibblBlurFilterOptions, PibblBrightnessFilterOptions, PibblContrastFilterOptions, PibblDropShadowFilterOptions, PibblGrayscaleFilterOptions, PibblHueRotateFilterOptions, PibblInvertFilterOptions, PibblOpacityFilterOptions, PibblSaturateFilterOptions, PibblSepiaFilterOptions, PibblDisplacementFilterOptions, } from "./lib/style/filter-types.js";
4
+ export { reflectedLight } from "./filters/reflected-light.js";
5
+ export type { PibblReflectedLightFilterOptions, PibblReflectedLightSource, } from "./lib/style/filter-types.js";
6
+ export { emboss } from "./filters/emboss.js";
7
+ export type { PibblEmbossFilterOptions } from "./filters/emboss.js";
8
+ export { backdropContrast } from "./filters/backdrop-contrast.js";
9
+ export type { PibblBackdropContrastFilterOptions } from "./filters/backdrop-contrast.js";
package/dist/filters.js CHANGED
@@ -13,7 +13,9 @@ import {
13
13
  import { displacement } from "./filters/displacement.js";
14
14
  import { reflectedLight } from "./filters/reflected-light.js";
15
15
  import { emboss } from "./filters/emboss.js";
16
+ import { backdropContrast } from "./filters/backdrop-contrast.js";
16
17
  export {
18
+ backdropContrast,
17
19
  blur,
18
20
  brightness,
19
21
  contrast,